Cleaned up OAuth 2.0 support fully using Storage() and updating samples.

This commit is contained in:
Joe Gregorio
2011-02-15 14:49:57 -05:00
parent cb8103dbd3
commit deeb020445
16 changed files with 272 additions and 202 deletions

View File

@@ -84,9 +84,11 @@ def build(serviceName, version,
parameters {api} and {apiVersion} that when filled in
produce an absolute URI to the discovery document for
that service.
developerKey: string, key obtained from https://code.google.com/apis/console
developerKey: string, key obtained
from https://code.google.com/apis/console
model: apiclient.Model, converts to and from the wire format
requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP request
requestBuilder: apiclient.http.HttpRequest, encapsulator for
an HTTP request
Returns:
A Resource object with methods for interacting with

View File

@@ -22,7 +22,10 @@ class HttpRequest(object):
"""Encapsulates a single HTTP request.
"""
def __init__(self, http, postproc, uri, method="GET", body=None, headers=None,
def __init__(self, http, postproc, uri,
method="GET",
body=None,
headers=None,
methodId=None):
"""Constructor for an HttpRequest.
@@ -150,6 +153,7 @@ class RequestMockBuilder(object):
model = JsonModel()
return HttpRequestMock(None, '{}', model.response)
class HttpMock(object):
"""Mock of httplib2.Http"""
@@ -164,5 +168,10 @@ class HttpMock(object):
f.close()
self.headers = headers
def request(self, uri, method="GET", body=None, headers=None, redirections=1, connection_type=None):
def request(self, uri,
method="GET",
body=None,
headers=None,
redirections=1,
connection_type=None):
return httplib2.Response(self.headers), self.data

View File

@@ -18,6 +18,7 @@ import urllib
from anyjson import simplejson
from errors import HttpError
def _abstract():
raise NotImplementedError('You need to override this function')

View File

@@ -25,6 +25,7 @@ import pickle
from google.appengine.ext import db
from client import Credentials
from client import Flow
from client import Storage
class FlowProperty(db.Property):
@@ -90,7 +91,7 @@ class CredentialsProperty(db.Property):
return not value
class StorageByKeyName(object):
class StorageByKeyName(Storage):
"""Store and retrieve a single credential to and from
the App Engine datastore.

View File

@@ -70,6 +70,30 @@ class Flow(object):
pass
class Storage(object):
"""Base class for all Storage objects.
Store and retrieve a single credential.
"""
def get(self):
"""Retrieve credential.
Returns:
apiclient.oauth.Credentials
"""
_abstract()
def put(self, credentials):
"""Write a credential.
Args:
credentials: Credentials, the credentials to store.
"""
_abstract()
class OAuth2Credentials(Credentials):
"""Credentials object for OAuth 2.0

View File

@@ -1,5 +1,5 @@
from django.db import models
from oauth2client.client import Storage as BaseStorage
class CredentialsField(models.Field):
@@ -36,3 +36,50 @@ class FlowField(models.Field):
def get_db_prep_value(self, value):
return base64.b64encode(pickle.dumps(value))
class Storage(BaseStorage):
"""Store and retrieve a single credential to and from
the datastore.
This Storage helper presumes the Credentials
have been stored as a CredenialsField
on a db model class.
"""
def __init__(self, model_class, key_name, key_value, property_name):
"""Constructor for Storage.
Args:
model: db.Model, model class
key_name: string, key name for the entity that has the credentials
key_value: string, key value for the entity that has the credentials
property_name: string, name of the property that is an CredentialsProperty
"""
self.model_class = model_class
self.key_name = key_name
self.key_value = key_value
self.property_name = property_name
def get(self):
"""Retrieve Credential from datastore.
Returns:
oauth2client.Credentials
"""
query = {self.key_name: self.key_value}
entity = self.model_class.objects.filter(*query)[0]
credential = getattr(entity, self.property_name)
if credential and hasattr(credential, 'set_store'):
credential.set_store(self.put)
return credential
def put(self, credentials):
"""Write a Credentials to the datastore.
Args:
credentials: Credentials, the credentials to store.
"""
entity = self.model_class(self.key_name=self.key_value)
setattr(entity, self.property_name, credentials)
entity.save()

View File

@@ -10,8 +10,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import pickle
from client import Storage as BaseStorage
class Storage(object):
class Storage(BaseStorage):
"""Store and retrieve a single credential to and from a file."""
def __init__(self, filename):
@@ -23,10 +25,13 @@ class Storage(object):
Returns:
apiclient.oauth.Credentials
"""
f = open(self.filename, 'r')
credentials = pickle.loads(f.read())
f.close()
credentials.set_store(self.put)
try:
f = open(self.filename, 'r')
credentials = pickle.loads(f.read())
f.close()
credentials.set_store(self.put)
except:
credentials = None
return credentials
def put(self, credentials):
@@ -38,5 +43,3 @@ class Storage(object):
f = open(self.filename, 'w')
f.write(pickle.dumps(credentials))
f.close()

View File

@@ -23,111 +23,16 @@ other example apps in the same directory.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
__all__ = ["run"]
import socket
import sys
import BaseHTTPServer
import logging
from optparse import OptionParser
from oauth2client.file import Storage
try:
from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
# TODO(jcgregorio)
# - docs
# - error handling
HOST_NAME = 'localhost'
PORT_NUMBERS = [8080, 8090]
class ClientRedirectServer(BaseHTTPServer.HTTPServer):
"""A server to handle OAuth 2.0 redirects back to localhost.
Waits for a single request and parses the query parameters
into query_params and then stops serving.
"""
query_params = {}
class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""A handler for OAuth 2.0 redirects back to localhost.
Waits for a single request and parses the query parameters
into the servers query_params and then stops serving.
"""
def do_GET(s):
"""Handle a GET request
Checks the query parameters and if an error
occurred print a message of failure, otherwise
indicate success.
"""
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
query = s.path.split('?', 1)[-1]
query = dict(parse_qsl(query))
s.server.query_params = query
s.wfile.write("<html><head><title>Authentication Status</title></head>")
if 'error' in query:
s.wfile.write("<body><p>The authentication request failed.</p>")
else:
s.wfile.write("<body><p>You have successfully authenticated</p>")
s.wfile.write("</body></html>")
def log_message(self, format, *args):
"""Do not log messages to stdout while running as a command line program."""
pass
def run(flow, filename):
def run(flow, storage):
"""Core code for a command-line application.
"""
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
default=filename, help="write credentials to FILE", metavar="FILE")
# The support for localhost is a work in progress and does not
# work at this point.
#parser.add_option("-p", "--no_local_web_server", dest="localhost",
# action="store_false",
# default=True,
# help="Do not run a web server on localhost to handle redirect URIs")
(options, args) = parser.parse_args()
#if options.localhost:
# server_class = BaseHTTPServer.HTTPServer
# try:
# port_number = PORT_NUMBERS[0]
# httpd = server_class((HOST_NAME, port_number), ClientRedirectHandler)
# except socket.error:
# port_number = PORT_NUMBERS[1]
# try:
# httpd = server_class((HOST_NAME, port_number), ClientRedirectHandler)
# except socket.error:
# options.localhost = False
# redirect_uri = 'http://%s:%s/' % (HOST_NAME, port_number)
#else:
# redirect_uri = 'oob'
redirect_uri = 'oob'
authorize_url = flow.step1_get_authorize_url(redirect_uri)
authorize_url = flow.step1_get_authorize_url('oob')
print 'Go to the following link in your browser:'
print authorize_url
print
#if options.localhost:
# httpd.handle_request()
# if 'error' in httpd.query_params:
# sys.exit('Authentication request was rejected.')
# if 'code' in httpd.query_params:
# code = httpd.query_params['code']
#else:
accepted = 'n'
while accepted.lower() == 'n':
accepted = raw_input('Have you authorized me? (y/n) ')
@@ -135,5 +40,9 @@ def run(flow, filename):
credentials = flow.step2_exchange(code)
Storage(options.filename).put(credentials)
print "You have successfully authenticated."
storage.put(credentials)
credentials.set_store(storage.put)
print 'You have successfully authenticated.'
return credentials

View File

@@ -4,7 +4,11 @@ try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.stderr.write("""Error: Can't find the file 'settings.py' in the
directory containing %r. It appears you've customized things. You'll
have to run django-admin.py, passing it your settings module.
(If the file settings.py does indeed exist, it's causing an ImportError
somehow.)\n""" % __file__)
sys.exit(1)
if __name__ == "__main__":

View File

@@ -45,7 +45,8 @@ class MainHandler(webapp.RequestHandler):
@login_required
def get(self):
user = users.get_current_user()
credentials = StorageByKeyName(Credentials, user.user_id(), 'credentials').get()
credentials = StorageByKeyName(
Credentials, user.user_id(), 'credentials').get()
if credentials:
http = httplib2.Http()
@@ -87,7 +88,8 @@ class OAuthHandler(webapp.RequestHandler):
flow = pickle.loads(memcache.get(user.user_id()))
if flow:
credentials = flow.step2_exchange(self.request.params)
StorageByKeyName(Credentials, user.user_id(), 'credentials').put(credentials)
StorageByKeyName(
Credentials, user.user_id(), 'credentials').put(credentials)
self.redirect("/")
else:
pass

View File

@@ -11,28 +11,43 @@ latest content and then adds a new entry.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
from apiclient.discovery import build
from oauth2client.file import Storage
import httplib2
import pickle
import pprint
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
# Uncomment the next line to get very detailed logging
#httplib2.debuglevel = 4
def main():
credentials = Storage('buzz.dat').get()
storage = Storage('buzz.dat')
credentials = storage.get()
if not credentials:
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/buzz',
user_agent='buzz-cmdline-sample/1.0',
domain='anonymous',
xoauth_displayname='Buzz Client Example App'
)
credentials = run(flow, storage)
http = httplib2.Http()
http = credentials.authorize(http)
p = build("buzz", "v1", http=http, developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0")
p = build("buzz", "v1", http=http,
developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0")
activities = p.activities()
# Retrieve the first two activities
activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute()
activitylist = activities.list(
max_results='2', scope='@self', userId='@me').execute()
print "Retrieved the first two activities"
# Retrieve the next two activities
@@ -45,14 +60,16 @@ def main():
"data": {
'title': 'Testing insert',
'object': {
'content': u'Just a short note to show that insert is working. ☄',
'content':
u'Just a short note to show that insert is working. ☄',
'type': 'note'}
}
}
activity = activities.insert(userId='@me', body=new_activity_body).execute()
print "Added a new activity"
activitylist = activities.list(max_results='2', scope='@self', userId='@me').execute()
activitylist = activities.list(
max_results='2', scope='@self', userId='@me').execute()
# Add a comment to that activity
comment_body = {

View File

@@ -1,37 +0,0 @@
# Copyright (C) 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Do the OAuth 2.0 Web Server dance.
Do the OAuth 2.0 Web Server dance for
a command line application. Store the generated
credentials in a common file that is used by
other example apps in the same directory.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/buzz',
user_agent='buzz-cmdline-sample/1.0',
domain='anonymous',
xoauth_displayname='Buzz Client Example App'
)
run(flow, 'buzz.dat')

View File

@@ -0,0 +1,56 @@
#!/usr/bin/python2.4
# -*- coding: utf-8 -*-
#
# Copyright 2010 Google Inc. All Rights Reserved.
"""Simple command-line example for Latitude.
Command-line application that sets the users
current location.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import httplib2
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
# Uncomment to get detailed logging
#httplib2.debuglevel = 4
def main():
storage = Storage('latitude.dat')
credentials = storage.get()
if not credentials:
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/latitude',
user_agent='latitude-cmdline-sample/1.0',
xoauth_displayname='Latitude Client Example App')
credentials = run(flow, storage)
http = httplib2.Http()
http = credentials.authorize(http)
p = build("latitude", "v1", http=http)
body = {
"data": {
"kind": "latitude#location",
"latitude": 37.420352,
"longitude": -122.083389,
"accuracy": 130,
"altitude": 35
}
}
print p.currentLocation().insert(body=body).execute()
if __name__ == '__main__':
main()

View File

@@ -11,22 +11,30 @@ latest content and then adds a new entry.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import httplib2
from apiclient.discovery import build
from oauth2client.file import Storage
import httplib2
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
# Uncomment to get low level HTTP logging
#httplib2.debuglevel = 4
# Uncomment to get logging
#import logging
#logging.basicConfig(level=logging.DEBUG)
def main():
credentials = Storage('moderator.dat').get()
storage = Storage('moderator.dat')
credentials = storage.get()
if not credentials:
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/moderator',
user_agent='moderator-cmdline-sample/1.0',
xoauth_displayname='Moderator Client Example App')
credentials = run(flow, storage)
http = httplib2.Http()
http = credentials.authorize(http)

View File

@@ -1,35 +0,0 @@
# Copyright (C) 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Do the OAuth 2.0 Web Server dance.
Do the OAuth 2.0 Web Server dance for
a command line application. Store the generated
credentials in a common file that is used by
other example apps in the same directory.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/moderator',
user_agent='moderator-cmdline-sample/1.0',
xoauth_displayname='Moderator Client Example App')
run(flow, 'moderator.dat')

View File

@@ -0,0 +1,59 @@
#!/usr/bin/python2.4
# -*- coding: utf-8 -*-
#
# Copyright 2010 Google Inc. All Rights Reserved.
"""Simple command-line example for Google URL Shortener API.
Command-line application that shortens a URL.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import httplib2
import pprint
from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
# Uncomment to get detailed logging
#httplib2.debuglevel = 4
def main():
storage = Storage('urlshortener.dat')
credentials = storage.get()
if not credentials:
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
client_secret='jigtZpMApkRxncxikFpR+SFg',
scope='https://www.googleapis.com/auth/urlshortener',
user_agent='urlshortener-cmdline-sample/1.0',
xoauth_displayname='URL Shortener Client Example App')
credentials = run(flow, storage)
http = httplib2.Http()
http = credentials.authorize(http)
# Build the url shortener service
service = build("urlshortener", "v1", http=http,
developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0")
url = service.url()
# Create a shortened URL by inserting the URL into the url collection.
body = {"longUrl": "http://code.google.com/apis/urlshortener/" }
resp = url.insert(body=body).execute()
pprint.pprint(resp)
shortUrl = resp['id']
# Convert the shortened URL back into a long URL
resp = url.get(shortUrl=shortUrl).execute()
pprint.pprint(resp)
if __name__ == '__main__':
main()