[mq]: oauth2
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/python2.4
|
||||
#
|
||||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Utilities for OAuth.
|
||||
@@ -10,10 +8,14 @@ Utilities for making it easier to work with OAuth.
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import httplib2
|
||||
import logging
|
||||
import oauth2 as oauth
|
||||
import urllib
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
from anyjson import simplejson
|
||||
|
||||
try:
|
||||
from urlparse import parse_qs, parse_qsl
|
||||
@@ -77,6 +79,9 @@ class Credentials(object):
|
||||
"""
|
||||
_abstract()
|
||||
|
||||
class Flow(object):
|
||||
"""Base class for all Flow objects."""
|
||||
pass
|
||||
|
||||
class OAuthCredentials(Credentials):
|
||||
"""Credentials object for OAuth 1.0a
|
||||
@@ -148,7 +153,7 @@ class OAuthCredentials(Credentials):
|
||||
return http
|
||||
|
||||
|
||||
class FlowThreeLegged(object):
|
||||
class FlowThreeLegged(Flow):
|
||||
"""Does the Three Legged Dance for OAuth 1.0a.
|
||||
"""
|
||||
|
||||
@@ -249,3 +254,4 @@ class FlowThreeLegged(object):
|
||||
oauth_params['oauth_token_secret'])
|
||||
|
||||
return OAuthCredentials(consumer, token, self.user_agent)
|
||||
|
||||
|
||||
0
oauth2client/__init__.py
Normal file
0
oauth2client/__init__.py
Normal file
135
oauth2client/appengine.py
Normal file
135
oauth2client/appengine.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# 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.
|
||||
|
||||
"""Utilities for Google App Engine
|
||||
|
||||
Utilities for making it easier to use OAuth 2.0
|
||||
on Google App Engine.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import pickle
|
||||
|
||||
from google.appengine.ext import db
|
||||
from client import Credentials
|
||||
from client import Flow
|
||||
|
||||
|
||||
class FlowProperty(db.Property):
|
||||
"""Utility property that allows easy
|
||||
storage and retreival of an
|
||||
oauth2client.Flow"""
|
||||
|
||||
# Tell what the user type is.
|
||||
data_type = Flow
|
||||
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
flow = super(FlowProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
return db.Blob(pickle.dumps(flow))
|
||||
|
||||
# For reading from datastore.
|
||||
def make_value_from_datastore(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return pickle.loads(value)
|
||||
|
||||
def validate(self, value):
|
||||
if value is not None and not isinstance(value, Flow):
|
||||
raise BadValueError('Property %s must be convertible '
|
||||
'to a FlowThreeLegged instance (%s)' %
|
||||
(self.name, value))
|
||||
return super(FlowProperty, self).validate(value)
|
||||
|
||||
def empty(self, value):
|
||||
return not value
|
||||
|
||||
|
||||
class CredentialsProperty(db.Property):
|
||||
"""Utility property that allows easy
|
||||
storage and retrieval of
|
||||
oath2client.Credentials
|
||||
"""
|
||||
|
||||
# Tell what the user type is.
|
||||
data_type = Credentials
|
||||
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
cred = super(CredentialsProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
return db.Blob(pickle.dumps(cred))
|
||||
|
||||
# For reading from datastore.
|
||||
def make_value_from_datastore(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return pickle.loads(value)
|
||||
|
||||
def validate(self, value):
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise BadValueError('Property %s must be convertible '
|
||||
'to an Credentials instance (%s)' %
|
||||
(self.name, value))
|
||||
return super(CredentialsProperty, self).validate(value)
|
||||
|
||||
def empty(self, value):
|
||||
return not value
|
||||
|
||||
|
||||
class StorageByKeyName(object):
|
||||
"""Store and retrieve a single credential to and from
|
||||
the App Engine datastore.
|
||||
|
||||
This Storage helper presumes the Credentials
|
||||
have been stored as a CredenialsProperty
|
||||
on a datastore model class, and that entities
|
||||
are stored by key_name.
|
||||
"""
|
||||
|
||||
def __init__(self, model, key_name, property_name):
|
||||
"""Constructor for Storage.
|
||||
|
||||
Args:
|
||||
model: db.Model, model class
|
||||
key_name: string, key name for the entity that has the credentials
|
||||
property_name: string, name of the property that is an CredentialsProperty
|
||||
"""
|
||||
self.model = model
|
||||
self.key_name = key_name
|
||||
self.property_name = property_name
|
||||
|
||||
def get(self):
|
||||
"""Retrieve Credential from datastore.
|
||||
|
||||
Returns:
|
||||
oauth2client.Credentials
|
||||
"""
|
||||
entity = self.model.get_or_insert(self.key_name)
|
||||
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.get_or_insert(self.key_name)
|
||||
setattr(entity, self.property_name, credentials)
|
||||
entity.put()
|
||||
326
oauth2client/client.py
Normal file
326
oauth2client/client.py
Normal file
@@ -0,0 +1,326 @@
|
||||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
|
||||
"""An OAuth 2.0 client
|
||||
|
||||
Tools for interacting with OAuth 2.0 protected
|
||||
resources.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import httplib2
|
||||
import logging
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
try: # pragma: no cover
|
||||
import simplejson
|
||||
except ImportError: # pragma: no cover
|
||||
try:
|
||||
# Try to import from django, should work on App Engine
|
||||
from django.utils import simplejson
|
||||
except ImportError:
|
||||
# Should work for Python2.6 and higher.
|
||||
import json as simplejson
|
||||
|
||||
try:
|
||||
from urlparse import parse_qsl
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base error for this module."""
|
||||
pass
|
||||
|
||||
|
||||
class RequestError(Error):
|
||||
"""Error occurred during request."""
|
||||
pass
|
||||
|
||||
|
||||
class MissingParameter(Error):
|
||||
pass
|
||||
|
||||
|
||||
def _abstract():
|
||||
raise NotImplementedError('You need to override this function')
|
||||
|
||||
|
||||
class Credentials(object):
|
||||
"""Base class for all Credentials objects.
|
||||
|
||||
Subclasses must define an authorize() method
|
||||
that applies the credentials to an HTTP transport.
|
||||
"""
|
||||
|
||||
def authorize(self, http):
|
||||
"""Take an httplib2.Http instance (or equivalent) and
|
||||
authorizes it for the set of credentials, usually by
|
||||
replacing http.request() with a method that adds in
|
||||
the appropriate headers and then delegates to the original
|
||||
Http.request() method.
|
||||
"""
|
||||
_abstract()
|
||||
|
||||
class Flow(object):
|
||||
"""Base class for all Flow objects."""
|
||||
pass
|
||||
|
||||
|
||||
class OAuth2Credentials(Credentials):
|
||||
"""Credentials object for OAuth 2.0
|
||||
|
||||
Credentials can be applied to an httplib2.Http object
|
||||
using the authorize() method, which then signs each
|
||||
request from that object with the OAuth 2.0 access token.
|
||||
|
||||
OAuth2Credentials objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent):
|
||||
"""Create an instance of OAuth2Credentials
|
||||
|
||||
This constructor is not usually called by the user, instead
|
||||
OAuth2Credentials objects are instantiated by
|
||||
the OAuth2WebServerFlow.
|
||||
|
||||
Args:
|
||||
token_uri: string, URI of token endpoint
|
||||
client_id: string, client identifier
|
||||
client_secret: string, client secret
|
||||
access_token: string, access token
|
||||
token_expiry: datetime, when the access_token expires
|
||||
refresh_token: string, refresh token
|
||||
user_agent: string, The HTTP User-Agent to provide for this application.
|
||||
|
||||
|
||||
Notes:
|
||||
store: callable, a callable that when passed a Credential
|
||||
will store the credential back to where it came from.
|
||||
This is needed to store the latest access_token if it
|
||||
has expired and been refreshed.
|
||||
"""
|
||||
self.access_token = access_token
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.refresh_token = refresh_token
|
||||
self.store = None
|
||||
self.token_expiry = token_expiry
|
||||
self.token_uri = token_uri
|
||||
self.user_agent = user_agent
|
||||
|
||||
def set_store(self, store):
|
||||
"""Set the storage for the credential.
|
||||
|
||||
Args:
|
||||
store: callable, a callable that when passed a Credential
|
||||
will store the credential back to where it came from.
|
||||
This is needed to store the latest access_token if it
|
||||
has expired and been refreshed.
|
||||
"""
|
||||
self.store = store
|
||||
|
||||
def __getstate__(self):
|
||||
"""Trim the state down to something that can be pickled.
|
||||
"""
|
||||
d = copy.copy(self.__dict__)
|
||||
del d['store']
|
||||
return d
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Reconstitute the state of the object from being pickled.
|
||||
"""
|
||||
self.__dict__.update(state)
|
||||
self.store = None
|
||||
|
||||
def _refresh(self, http_request):
|
||||
"""Refresh the access_token using the refresh_token.
|
||||
|
||||
Args:
|
||||
http: An instance of httplib2.Http.request
|
||||
or something that acts like it.
|
||||
"""
|
||||
body = urllib.urlencode({
|
||||
'grant_type': 'refresh_token',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'refresh_token' : self.refresh_token
|
||||
})
|
||||
headers = {
|
||||
'user-agent': self.user_agent,
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
resp, content = http_request(self.token_uri, method='POST', body=body, headers=headers)
|
||||
if resp.status == 200:
|
||||
# TODO(jcgregorio) Raise an error if loads fails?
|
||||
d = simplejson.loads(content)
|
||||
self.access_token = d['access_token']
|
||||
self.refresh_token = d.get('refresh_token', self.refresh_token)
|
||||
if 'expires_in' in d:
|
||||
self.token_expiry = datetime.timedelta(seconds = int(d['expires_in'])) + datetime.datetime.now()
|
||||
else:
|
||||
self.token_expiry = None
|
||||
if self.store is not None:
|
||||
self.store(self)
|
||||
else:
|
||||
logging.error('Failed to retrieve access token: %s' % content)
|
||||
raise RequestError('Invalid response %s.' % resp['status'])
|
||||
|
||||
def authorize(self, http):
|
||||
"""
|
||||
Args:
|
||||
http: An instance of httplib2.Http
|
||||
or something that acts like it.
|
||||
|
||||
Returns:
|
||||
A modified instance of http that was passed in.
|
||||
|
||||
Example:
|
||||
|
||||
h = httplib2.Http()
|
||||
h = credentials.authorize(h)
|
||||
|
||||
You can't create a new OAuth
|
||||
subclass of httplib2.Authenication because
|
||||
it never gets passed the absolute URI, which is
|
||||
needed for signing. So instead we have to overload
|
||||
'request' with a closure that adds in the
|
||||
Authorization header and then calls the original version
|
||||
of 'request()'.
|
||||
"""
|
||||
request_orig = http.request
|
||||
|
||||
# The closure that will replace 'httplib2.Http.request'.
|
||||
def new_request(uri, method='GET', body=None, headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
|
||||
connection_type=None):
|
||||
"""Modify the request headers to add the appropriate
|
||||
Authorization header."""
|
||||
if headers == None:
|
||||
headers = {}
|
||||
if ((self.token_expiry is not None) and (self.token_expiry <= datetime.datetime.now())):
|
||||
logging.info("Refreshing because %s <= %s" %(self.token_expiry, datetime.datetime.now()))
|
||||
self._refresh(request_orig)
|
||||
headers['authorization'] = 'WRAP access_token=' + self.access_token
|
||||
if 'user-agent' in headers:
|
||||
headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
|
||||
else:
|
||||
headers['user-agent'] = self.user_agent
|
||||
resp, content = request_orig(uri, method, body, headers,
|
||||
redirections, connection_type)
|
||||
if resp.status == 401 and 'invalid_token' in resp.get('www-authenticate', ''):
|
||||
logging.info("Refreshing because we got a 401")
|
||||
self._refresh(request_orig)
|
||||
return request_orig(uri, method, body, headers,
|
||||
redirections, connection_type)
|
||||
else:
|
||||
return (resp, content)
|
||||
|
||||
http.request = new_request
|
||||
return http
|
||||
|
||||
|
||||
class OAuth2WebServerFlow(Flow):
|
||||
"""Does the Web Server Flow for OAuth 2.0.
|
||||
|
||||
OAuth2Credentials objects may be safely pickled and unpickled.
|
||||
"""
|
||||
|
||||
def __init__(self, client_id, client_secret, scope, user_agent,
|
||||
authorization_uri='https://www.google.com/accounts/o8/oauth2/authorization',
|
||||
token_uri='https://www.google.com/accounts/o8/oauth2/token',
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2WebServerFlow
|
||||
|
||||
Args:
|
||||
client_id: string, client identifier
|
||||
client_secret: string client secret
|
||||
scope: string, scope of the credentials being requested
|
||||
user_agent: string, HTTP User-Agent to provide for this application.
|
||||
authorization_uri: string, URI for authorization endpoint
|
||||
token_uri: string, URI for token endpoint
|
||||
**kwargs: dict, The keyword arguments are all optional and required
|
||||
parameters for the OAuth calls.
|
||||
"""
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.scope = scope
|
||||
self.user_agent = user_agent
|
||||
self.authorization_uri = authorization_uri
|
||||
self.token_uri = token_uri
|
||||
self.params = kwargs
|
||||
self.redirect_uri = None
|
||||
|
||||
def step1_get_authorize_url(self, redirect_uri='oob'):
|
||||
"""Returns a URI to redirect to the provider.
|
||||
|
||||
Args:
|
||||
redirect_uri: string, Either the string 'oob' for a non-web-based
|
||||
application, or a URI that handles the callback from
|
||||
the authorization server.
|
||||
|
||||
If redirect_uri is 'oob' then pass in the
|
||||
generated verification code to step2_exchange,
|
||||
otherwise pass in the query parameters received
|
||||
at the callback uri to step2_exchange.
|
||||
"""
|
||||
|
||||
self.redirect_uri = redirect_uri
|
||||
query = {
|
||||
'response_type': 'code',
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': redirect_uri,
|
||||
'scope': self.scope,
|
||||
}
|
||||
query.update(self.params)
|
||||
parts = list(urlparse.urlparse(self.authorization_uri))
|
||||
query.update(dict(parse_qsl(parts[4]))) # 4 is the index of the query part
|
||||
parts[4] = urllib.urlencode(query)
|
||||
return urlparse.urlunparse(parts)
|
||||
|
||||
def step2_exchange(self, code):
|
||||
"""Exhanges a code for OAuth2Credentials.
|
||||
|
||||
Args:
|
||||
code: string or dict, either the code as a string, or a dictionary
|
||||
of the query parameters to the redirect_uri, which contains
|
||||
the code.
|
||||
"""
|
||||
|
||||
if not (isinstance(code, str) or isinstance(code, unicode)):
|
||||
code = code['code']
|
||||
|
||||
body = urllib.urlencode({
|
||||
'grant_type': 'authorization_code',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'code': code,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'scope': self.scope
|
||||
})
|
||||
headers = {
|
||||
'user-agent': self.user_agent,
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
h = httplib2.Http()
|
||||
resp, content = h.request(self.token_uri, method='POST', body=body, headers=headers)
|
||||
if resp.status == 200:
|
||||
# TODO(jcgregorio) Raise an error if simplejson.loads fails?
|
||||
d = simplejson.loads(content)
|
||||
access_token = d['access_token']
|
||||
refresh_token = d.get('refresh_token', None)
|
||||
token_expiry = None
|
||||
if 'expires_in' in d:
|
||||
token_expiry = datetime.datetime.now() + datetime.timedelta(seconds = int(d['expires_in']))
|
||||
|
||||
logging.info('Successfully retrieved access token: %s' % content)
|
||||
return OAuth2Credentials(access_token, self.client_id, self.client_secret,
|
||||
refresh_token, token_expiry, self.token_uri,
|
||||
self.user_agent)
|
||||
else:
|
||||
logging.error('Failed to retrieve access token: %s' % content)
|
||||
raise RequestError('Invalid response %s.' % resp['status'])
|
||||
38
oauth2client/django_orm.py
Normal file
38
oauth2client/django_orm.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CredentialsField(models.Field):
|
||||
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def db_type(self):
|
||||
return 'VARCHAR'
|
||||
|
||||
def to_python(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, oauth2client.Credentials):
|
||||
return value
|
||||
return pickle.loads(base64.b64decode(value))
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
return base64.b64encode(pickle.dumps(value))
|
||||
|
||||
|
||||
class FlowField(models.Field):
|
||||
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def db_type(self):
|
||||
return 'VARCHAR'
|
||||
|
||||
def to_python(self, value):
|
||||
print "In to_python", value
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, oauth2client.Flow):
|
||||
return value
|
||||
return pickle.loads(base64.b64decode(value))
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
return base64.b64encode(pickle.dumps(value))
|
||||
42
oauth2client/file.py
Normal file
42
oauth2client/file.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Utilities for OAuth.
|
||||
|
||||
Utilities for making it easier to work with OAuth 2.0
|
||||
credentials.
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import pickle
|
||||
|
||||
|
||||
class Storage(object):
|
||||
"""Store and retrieve a single credential to and from a file."""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
|
||||
def get(self):
|
||||
"""Retrieve Credential from file.
|
||||
|
||||
Returns:
|
||||
apiclient.oauth.Credentials
|
||||
"""
|
||||
f = open(self.filename, 'r')
|
||||
credentials = pickle.loads(f.read())
|
||||
f.close()
|
||||
credentials.set_store(self.put)
|
||||
return credentials
|
||||
|
||||
def put(self, credentials):
|
||||
"""Write a pickled Credentials to file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
f = open(self.filename, 'w')
|
||||
f.write(pickle.dumps(credentials))
|
||||
f.close()
|
||||
|
||||
|
||||
136
oauth2client/tools.py
Normal file
136
oauth2client/tools.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# 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.
|
||||
|
||||
"""Command-line tools for authenticating via OAuth 2.0
|
||||
|
||||
Do the OAuth 2.0 Web Server dance for
|
||||
a command line application. Stores the generated
|
||||
credentials in a common file that is used by
|
||||
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
|
||||
# - oob when implemented
|
||||
|
||||
|
||||
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):
|
||||
"""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")
|
||||
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")
|
||||
parser.add_option("-w", "--local_web_server", dest="localhost",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Run a web server on localhost to handle redirect URIs")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
host_name = 'localhost'
|
||||
port_numbers = [8080, 8090]
|
||||
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
|
||||
|
||||
authorize_url = flow.step1_get_authorize_url('http://%s:%s/' % (host_name, port_number))
|
||||
|
||||
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) ')
|
||||
code = raw_input('What is the verification code? ').strip()
|
||||
|
||||
credentials = flow.step2_exchange(code)
|
||||
|
||||
Storage(options.filename).put(credentials)
|
||||
print "You have successfully authenticated."
|
||||
1
samples/oauth2/appengine/apiclient
Symbolic link
1
samples/oauth2/appengine/apiclient
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../apiclient/
|
||||
16
samples/oauth2/appengine/app.yaml
Normal file
16
samples/oauth2/appengine/app.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
application: m-buzz
|
||||
version: 1
|
||||
runtime: python
|
||||
api_version: 1
|
||||
|
||||
handlers:
|
||||
- url: /static
|
||||
static_dir: static
|
||||
|
||||
- url: /google8f1adb368b7bd14c.html
|
||||
upload: google8f1adb368b7bd14c.html
|
||||
static_files: static/google8f1adb368b7bd14c.html
|
||||
|
||||
- url: .*
|
||||
script: main.py
|
||||
|
||||
1
samples/oauth2/appengine/httplib2
Symbolic link
1
samples/oauth2/appengine/httplib2
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../httplib2/
|
||||
11
samples/oauth2/appengine/index.yaml
Normal file
11
samples/oauth2/appengine/index.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
indexes:
|
||||
|
||||
# AUTOGENERATED
|
||||
|
||||
# This index.yaml is automatically updated whenever the dev_appserver
|
||||
# detects that a new type of query is run. If you want to manage the
|
||||
# index.yaml file manually, remove the above marker line (the line
|
||||
# saying "# AUTOGENERATED"). If you want to manage some indexes
|
||||
# manually, move them above the marker line. The index.yaml file is
|
||||
# automatically uploaded to the admin console when you next deploy
|
||||
# your application using appcfg.py.
|
||||
104
samples/oauth2/appengine/main.py
Normal file
104
samples/oauth2/appengine/main.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2007 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.
|
||||
#
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
|
||||
import httplib2
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
|
||||
from apiclient.discovery import build
|
||||
from oauth2client.appengine import CredentialsProperty
|
||||
from oauth2client.appengine import StorageByKeyName
|
||||
from oauth2client.client import OAuth2WebServerFlow
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.api import users
|
||||
from google.appengine.ext import db
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp import template
|
||||
from google.appengine.ext.webapp import util
|
||||
from google.appengine.ext.webapp.util import login_required
|
||||
|
||||
|
||||
class Credentials(db.Model):
|
||||
credentials = CredentialsProperty()
|
||||
|
||||
|
||||
class MainHandler(webapp.RequestHandler):
|
||||
|
||||
@login_required
|
||||
def get(self):
|
||||
user = users.get_current_user()
|
||||
credentials = StorageByKeyName(Credentials, user.user_id(), 'credentials').get()
|
||||
|
||||
if credentials:
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
p = build("buzz", "v1", http=http)
|
||||
activities = p.activities()
|
||||
activitylist = activities.list(scope='@consumption',
|
||||
userId='@me').execute()
|
||||
path = os.path.join(os.path.dirname(__file__), 'welcome.html')
|
||||
logout = users.create_logout_url('/')
|
||||
self.response.out.write(
|
||||
template.render(
|
||||
path, {'activitylist': activitylist,
|
||||
'logout': logout
|
||||
}))
|
||||
else:
|
||||
flow = OAuth2WebServerFlow(
|
||||
client_id='anonymous',
|
||||
client_secret='anonymous',
|
||||
scope='https://www.googleapis.com/auth/buzz',
|
||||
user_agent='buzz-cmdline-sample/1.0',
|
||||
domain='anonymous',
|
||||
xoauth_displayname='Google App Engine Example App')
|
||||
|
||||
callback = self.request.relative_url('/auth_return')
|
||||
authorize_url = flow.step1_get_authorize_url(callback)
|
||||
memcache.set(user.user_id(), pickle.dumps(flow))
|
||||
self.redirect(authorize_url)
|
||||
|
||||
|
||||
class OAuthHandler(webapp.RequestHandler):
|
||||
|
||||
@login_required
|
||||
def get(self):
|
||||
user = users.get_current_user()
|
||||
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)
|
||||
self.redirect("/")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
application = webapp.WSGIApplication(
|
||||
[
|
||||
('/', MainHandler),
|
||||
('/auth_return', OAuthHandler)
|
||||
],
|
||||
debug=True)
|
||||
util.run_wsgi_app(application)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
samples/oauth2/appengine/oauth2client
Symbolic link
1
samples/oauth2/appengine/oauth2client
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../oauth2client/
|
||||
1
samples/oauth2/appengine/simplejson
Symbolic link
1
samples/oauth2/appengine/simplejson
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../simplejson/
|
||||
1
samples/oauth2/appengine/uritemplate
Symbolic link
1
samples/oauth2/appengine/uritemplate
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../uritemplate/
|
||||
29
samples/oauth2/appengine/welcome.html
Normal file
29
samples/oauth2/appengine/welcome.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Buzz Stuff</title>
|
||||
<style type=text/css>
|
||||
td { vertical-align: top; padding: 0.5em }
|
||||
img { border:0 }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="{{ logout }}">Logout</a></p>
|
||||
<table border=0>
|
||||
{% for item in activitylist.items %}
|
||||
<tr valign=top>
|
||||
<td>
|
||||
<a href="{{ item.actor.profileUrl }}"><img
|
||||
src="{{ item.actor.thumbnailUrl }}"></a><br>
|
||||
<a href="{{ item.actor.profileUrl }}">{{ item.actor.name }}</a></td>
|
||||
<td>
|
||||
{{ item.object.content }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ item.object.links.alternate.0.href }}"><img
|
||||
src="/static/go.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
71
samples/oauth2/buzz/buzz.py
Normal file
71
samples/oauth2/buzz/buzz.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/python2.4
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Simple command-line example for Buzz.
|
||||
|
||||
Command-line application that retrieves the users
|
||||
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
|
||||
|
||||
# Uncomment the next line to get very detailed logging
|
||||
#httplib2.debuglevel = 4
|
||||
|
||||
|
||||
def main():
|
||||
credentials = Storage('buzz.dat').get()
|
||||
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
|
||||
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()
|
||||
print "Retrieved the first two activities"
|
||||
|
||||
# Retrieve the next two activities
|
||||
if activitylist:
|
||||
activitylist = activities.list_next(activitylist).execute()
|
||||
print "Retrieved the next two activities"
|
||||
|
||||
# Add a new activity
|
||||
new_activity_body = {
|
||||
"data": {
|
||||
'title': 'Testing insert',
|
||||
'object': {
|
||||
'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()
|
||||
|
||||
# Add a comment to that activity
|
||||
comment_body = {
|
||||
"data": {
|
||||
"content": "This is a comment"
|
||||
}
|
||||
}
|
||||
item = activitylist['items'][0]
|
||||
comment = p.comments().insert(
|
||||
userId=item['actor']['id'], postId=item['id'], body=comment_body
|
||||
).execute()
|
||||
print 'Added a comment to the new activity'
|
||||
pprint.pprint(comment)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
37
samples/oauth2/buzz/web_server_dance.py
Normal file
37
samples/oauth2/buzz/web_server_dance.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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='anonymous',
|
||||
client_secret='anonymous',
|
||||
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')
|
||||
83
samples/oauth2/moderator/moderator.py
Normal file
83
samples/oauth2/moderator/moderator.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python2.4
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
|
||||
"""Simple command-line example for Buzz.
|
||||
|
||||
Command-line application that retrieves the users
|
||||
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
|
||||
|
||||
# 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()
|
||||
|
||||
http = httplib2.Http()
|
||||
http = credentials.authorize(http)
|
||||
|
||||
p = build("moderator", "v1", http=http)
|
||||
|
||||
series_body = {
|
||||
"data": {
|
||||
"description": "Share and rank tips for eating healthy and cheap!",
|
||||
"name": "Eating Healthy & Cheap",
|
||||
"videoSubmissionAllowed": False
|
||||
}
|
||||
}
|
||||
series = p.series().insert(body=series_body).execute()
|
||||
print "Created a new series"
|
||||
|
||||
topic_body = {
|
||||
"data": {
|
||||
"description": "Share your ideas on eating healthy!",
|
||||
"name": "Ideas",
|
||||
"presenter": "liz"
|
||||
}
|
||||
}
|
||||
topic = p.topics().insert(seriesId=series['id']['seriesId'],
|
||||
body=topic_body).execute()
|
||||
print "Created a new topic"
|
||||
|
||||
submission_body = {
|
||||
"data": {
|
||||
"attachmentUrl": "http://www.youtube.com/watch?v=1a1wyc5Xxpg",
|
||||
"attribution": {
|
||||
"displayName": "Bashan",
|
||||
"location": "Bainbridge Island, WA"
|
||||
},
|
||||
"text": "Charlie Ayers @ Google"
|
||||
}
|
||||
}
|
||||
submission = p.submissions().insert(seriesId=topic['id']['seriesId'],
|
||||
topicId=topic['id']['topicId'], body=submission_body).execute()
|
||||
print "Inserted a new submisson on the topic"
|
||||
|
||||
vote_body = {
|
||||
"data": {
|
||||
"vote": "PLUS"
|
||||
}
|
||||
}
|
||||
p.votes().insert(seriesId=topic['id']['seriesId'],
|
||||
submissionId=submission['id']['submissionId'],
|
||||
body=vote_body)
|
||||
print "Voted on the submission"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
35
samples/oauth2/moderator/web_server_dance.py
Normal file
35
samples/oauth2/moderator/web_server_dance.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# 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='anonymous',
|
||||
client_secret='anonymous',
|
||||
scope='https://www.googleapis.com/auth/moderator',
|
||||
user_agent='moderator-cmdline-sample/1.0',
|
||||
xoauth_displayname='Moderator Client Example App')
|
||||
|
||||
run(flow, 'moderator.dat')
|
||||
Reference in New Issue
Block a user