Stage 1 conversion to JSON for storing Credentials.
Reviewed in http://codereview.appspot.com/4972065/
This commit is contained in:
@@ -59,11 +59,12 @@ DEFAULT_METHOD_DOC = 'A description of how to use this function'
|
||||
STACK_QUERY_PARAMETERS = ['trace', 'fields', 'pp', 'prettyPrint', 'userIp',
|
||||
'userip', 'strict']
|
||||
|
||||
RESERVED_WORDS = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del',
|
||||
RESERVED_WORDS = ['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
|
||||
'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
|
||||
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
|
||||
'pass', 'print', 'raise', 'return', 'try', 'while' ]
|
||||
|
||||
|
||||
def _fix_method_name(name):
|
||||
if name in RESERVED_WORDS:
|
||||
return name + '_'
|
||||
@@ -242,10 +243,10 @@ def _cast(value, schema_type):
|
||||
return str(value)
|
||||
|
||||
MULTIPLIERS = {
|
||||
"KB": 2**10,
|
||||
"MB": 2**20,
|
||||
"GB": 2**30,
|
||||
"TB": 2**40,
|
||||
"KB": 2 ** 10,
|
||||
"MB": 2 ** 20,
|
||||
"GB": 2 ** 30,
|
||||
"TB": 2 ** 40,
|
||||
}
|
||||
|
||||
def _media_size_to_long(maxSize):
|
||||
@@ -255,7 +256,7 @@ def _media_size_to_long(maxSize):
|
||||
units = maxSize[-2:].upper()
|
||||
multiplier = MULTIPLIERS.get(units, 0)
|
||||
if multiplier:
|
||||
return int(maxSize[:-2])*multiplier
|
||||
return int(maxSize[:-2]) * multiplier
|
||||
else:
|
||||
return int(maxSize)
|
||||
|
||||
|
||||
@@ -222,7 +222,8 @@ class BaseModel(Model):
|
||||
_abstract()
|
||||
|
||||
def deserialize(self, content):
|
||||
"""Perform the actual deserialization from response string to Python object.
|
||||
"""Perform the actual deserialization from response string to Python
|
||||
object.
|
||||
|
||||
Args:
|
||||
content: string, the body of the HTTP response
|
||||
@@ -285,8 +286,8 @@ class ProtocolBufferModel(BaseModel):
|
||||
de-serialized using the given protocol buffer class.
|
||||
|
||||
Args:
|
||||
protocol_buffer: The protocol buffer class used to de-serialize a response
|
||||
from the API.
|
||||
protocol_buffer: The protocol buffer class used to de-serialize a
|
||||
response from the API.
|
||||
"""
|
||||
self._protocol_buffer = protocol_buffer
|
||||
|
||||
|
||||
@@ -377,7 +377,6 @@ class TwoLeggedOAuthCredentials(Credentials):
|
||||
return http
|
||||
|
||||
|
||||
|
||||
class FlowThreeLegged(Flow):
|
||||
"""Does the Three Legged Dance for OAuth 1.0a.
|
||||
"""
|
||||
|
||||
@@ -95,6 +95,16 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
None,
|
||||
token_uri)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
data = simplejson.loads(json)
|
||||
retval = AccessTokenCredentials(
|
||||
data['scope'],
|
||||
data['audience'],
|
||||
data['assertion_type'],
|
||||
data['token_uri'])
|
||||
return retval
|
||||
|
||||
def _generate_assertion(self):
|
||||
header = {
|
||||
'typ': 'JWT',
|
||||
@@ -165,17 +175,28 @@ class CredentialsProperty(db.Property):
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
cred = super(CredentialsProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
return db.Blob(pickle.dumps(cred))
|
||||
if cred is None:
|
||||
cred = ''
|
||||
else:
|
||||
cred = cred.to_json()
|
||||
return db.Blob(cred)
|
||||
|
||||
# For reading from datastore.
|
||||
def make_value_from_datastore(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return pickle.loads(value)
|
||||
if len(value) == 0:
|
||||
return None
|
||||
credentials = None
|
||||
try:
|
||||
credentials = Credentials.new_from_json(value)
|
||||
except ValueError:
|
||||
credentials = pickle.loads(value)
|
||||
return credentials
|
||||
|
||||
def validate(self, value):
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise BadValueError('Property %s must be convertible '
|
||||
raise db.BadValueError('Property %s must be convertible '
|
||||
'to an Credentials instance (%s)' %
|
||||
(self.name, value))
|
||||
return super(CredentialsProperty, self).validate(value)
|
||||
@@ -215,15 +236,15 @@ class StorageByKeyName(Storage):
|
||||
oauth2client.Credentials
|
||||
"""
|
||||
if self._cache:
|
||||
credential = self._cache.get(self._key_name)
|
||||
if credential:
|
||||
return pickle.loads(credential)
|
||||
json = self._cache.get(self._key_name)
|
||||
if json:
|
||||
return Credentials.new_from_json(json)
|
||||
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)
|
||||
if self._cache:
|
||||
self._cache.set(self._key_name, pickle.dumps(credentials))
|
||||
self._cache.set(self._key_name, credentials.to_json())
|
||||
|
||||
return credential
|
||||
|
||||
@@ -237,7 +258,7 @@ class StorageByKeyName(Storage):
|
||||
setattr(entity, self._property_name, credentials)
|
||||
entity.put()
|
||||
if self._cache:
|
||||
self._cache.set(self._key_name, pickle.dumps(credentials))
|
||||
self._cache.set(self._key_name, credentials.to_json())
|
||||
|
||||
|
||||
class CredentialsModel(db.Model):
|
||||
|
||||
@@ -43,6 +43,9 @@ except ImportError:
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Expiry is stored in RFC3339 UTC format
|
||||
EXPIRY_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base error for this module."""
|
||||
@@ -71,10 +74,15 @@ def _abstract():
|
||||
class Credentials(object):
|
||||
"""Base class for all Credentials objects.
|
||||
|
||||
Subclasses must define an authorize() method
|
||||
that applies the credentials to an HTTP transport.
|
||||
Subclasses must define an authorize() method that applies the credentials to
|
||||
an HTTP transport.
|
||||
|
||||
Subclasses must also specify a classmethod named 'from_json' that takes a JSON
|
||||
string as input and returns an instaniated Crentials object.
|
||||
"""
|
||||
|
||||
NON_SERIALIZED_MEMBERS = ['store']
|
||||
|
||||
def authorize(self, http):
|
||||
"""Take an httplib2.Http instance (or equivalent) and
|
||||
authorizes it for the set of credentials, usually by
|
||||
@@ -84,6 +92,58 @@ class Credentials(object):
|
||||
"""
|
||||
_abstract()
|
||||
|
||||
def _to_json(self, strip):
|
||||
"""Utility function for creating a JSON representation of an instance of Credentials.
|
||||
|
||||
Args:
|
||||
strip: array, An array of names of members to not include in the JSON.
|
||||
|
||||
Returns:
|
||||
string, a JSON representation of this instance, suitable to pass to
|
||||
from_json().
|
||||
"""
|
||||
t = type(self)
|
||||
d = copy.copy(self.__dict__)
|
||||
for member in strip:
|
||||
del d[member]
|
||||
if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
|
||||
d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
|
||||
# Add in information we will need later to reconsistitue this instance.
|
||||
d['_class'] = t.__name__
|
||||
d['_module'] = t.__module__
|
||||
return simplejson.dumps(d)
|
||||
|
||||
def to_json(self):
|
||||
"""Creating a JSON representation of an instance of Credentials.
|
||||
|
||||
Returns:
|
||||
string, a JSON representation of this instance, suitable to pass to
|
||||
from_json().
|
||||
"""
|
||||
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
|
||||
|
||||
@classmethod
|
||||
def new_from_json(cls, s):
|
||||
"""Utility class method to instantiate a Credentials subclass from a JSON
|
||||
representation produced by to_json().
|
||||
|
||||
Args:
|
||||
s: string, JSON from to_json().
|
||||
|
||||
Returns:
|
||||
An instance of the subclass of Credentials that was serialized with
|
||||
to_json().
|
||||
"""
|
||||
data = simplejson.loads(s)
|
||||
# Find and call the right classmethod from_json() to restore the object.
|
||||
module = data['_module']
|
||||
m = __import__(module)
|
||||
for sub_module in module.split('.')[1:]:
|
||||
m = getattr(m, sub_module)
|
||||
kls = getattr(m, data['_class'])
|
||||
from_json = getattr(kls, 'from_json')
|
||||
return from_json(s)
|
||||
|
||||
|
||||
class Flow(object):
|
||||
"""Base class for all Flow objects."""
|
||||
@@ -206,6 +266,36 @@ class OAuth2Credentials(Credentials):
|
||||
# refreshed.
|
||||
self.invalid = False
|
||||
|
||||
def to_json(self):
|
||||
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, s):
|
||||
"""Instantiate a Credentials object from a JSON description of it. The JSON
|
||||
should have been produced by calling .to_json() on the object.
|
||||
|
||||
Args:
|
||||
data: dict, A deserialized JSON object.
|
||||
|
||||
Returns:
|
||||
An instance of a Credentials subclass.
|
||||
"""
|
||||
data = simplejson.loads(s)
|
||||
if 'token_expiry' in data and not isinstance(data['token_expiry'],
|
||||
datetime.datetime):
|
||||
data['token_expiry'] = datetime.datetime.strptime(
|
||||
data['token_expiry'], EXPIRY_FORMAT)
|
||||
retval = OAuth2Credentials(
|
||||
data['access_token'],
|
||||
data['client_id'],
|
||||
data['client_secret'],
|
||||
data['refresh_token'],
|
||||
data['token_expiry'],
|
||||
data['token_uri'],
|
||||
data['user_agent'])
|
||||
retval.invalid = data['invalid']
|
||||
return retval
|
||||
|
||||
@property
|
||||
def access_token_expired(self):
|
||||
"""True if the credential is expired or invalid.
|
||||
@@ -218,7 +308,7 @@ class OAuth2Credentials(Credentials):
|
||||
if not self.token_expiry:
|
||||
return False
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = datetime.datetime.utcnow()
|
||||
if now >= self.token_expiry:
|
||||
logger.info('access_token is expired. Now: %s, token_expiry: %s',
|
||||
now, self.token_expiry)
|
||||
@@ -318,7 +408,7 @@ class OAuth2Credentials(Credentials):
|
||||
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()
|
||||
seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
|
||||
else:
|
||||
self.token_expiry = None
|
||||
if self.store:
|
||||
@@ -446,6 +536,15 @@ class AccessTokenCredentials(OAuth2Credentials):
|
||||
None,
|
||||
user_agent)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, s):
|
||||
data = simplejson.loads(s)
|
||||
retval = AccessTokenCredentials(
|
||||
data['access_token'],
|
||||
data['user_agent'])
|
||||
return retval
|
||||
|
||||
def _refresh(self, http_request):
|
||||
raise AccessTokenCredentialsError(
|
||||
"The access_token is expired or invalid and can't be refreshed.")
|
||||
@@ -601,7 +700,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
refresh_token = d.get('refresh_token', None)
|
||||
token_expiry = None
|
||||
if 'expires_in' in d:
|
||||
token_expiry = datetime.datetime.now() + datetime.timedelta(
|
||||
token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
|
||||
seconds=int(d['expires_in']))
|
||||
|
||||
logger.info('Successfully retrieved access token: %s' % content)
|
||||
|
||||
@@ -23,7 +23,20 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
import pickle
|
||||
import threading
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
from client import Storage as BaseStorage
|
||||
from client import Credentials
|
||||
|
||||
|
||||
class Storage(BaseStorage):
|
||||
@@ -40,25 +53,40 @@ class Storage(BaseStorage):
|
||||
oauth2client.client.Credentials
|
||||
"""
|
||||
self._lock.acquire()
|
||||
credentials = None
|
||||
try:
|
||||
f = open(self._filename, 'r')
|
||||
credentials = pickle.loads(f.read())
|
||||
content = f.read()
|
||||
f.close()
|
||||
except IOError:
|
||||
self._lock.release()
|
||||
return credentials
|
||||
|
||||
# First try reading as JSON, and if that fails fall back to pickle.
|
||||
try:
|
||||
credentials = Credentials.new_from_json(content)
|
||||
credentials.set_store(self)
|
||||
except:
|
||||
credentials = None
|
||||
self._lock.release()
|
||||
except ValueError:
|
||||
# TODO(jcgregorio) On a future release remove this path to finally remove
|
||||
# all pickle support.
|
||||
try:
|
||||
credentials = pickle.loads(content)
|
||||
credentials.set_store(self)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
return credentials
|
||||
|
||||
def put(self, credentials):
|
||||
"""Write a pickled Credentials to file.
|
||||
"""Write Credentials to file.
|
||||
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
f = open(self._filename, 'w')
|
||||
f.write(pickle.dumps(credentials))
|
||||
f.write(credentials.to_json())
|
||||
f.close()
|
||||
self._lock.release()
|
||||
|
||||
@@ -21,7 +21,9 @@ The format of the stored data is like so:
|
||||
'userAgent': '<user agent>',
|
||||
'scope': '<scope>'
|
||||
},
|
||||
'credential': '<base64 encoding of pickeled Credential object>'
|
||||
'credential': {
|
||||
# JSON serialized Credentials.
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -47,6 +49,7 @@ except ImportError: # pragma: no cover
|
||||
import json as simplejson
|
||||
|
||||
from client import Storage as BaseStorage
|
||||
from client import Credentials
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -295,7 +298,8 @@ class _MultiStore(object):
|
||||
user_agent = raw_key['userAgent']
|
||||
scope = raw_key['scope']
|
||||
key = (client_id, user_agent, scope)
|
||||
credential = pickle.loads(base64.b64decode(cred_entry['credential']))
|
||||
credential = None
|
||||
credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
|
||||
return (key, credential)
|
||||
|
||||
def _write(self):
|
||||
@@ -312,7 +316,7 @@ class _MultiStore(object):
|
||||
'userAgent': cred_key[1],
|
||||
'scope': cred_key[2]
|
||||
}
|
||||
raw_cred = base64.b64encode(pickle.dumps(cred))
|
||||
raw_cred = simplejson.loads(cred.to_json())
|
||||
raw_creds.append({'key': raw_key, 'credential': raw_cred})
|
||||
self._locked_json_write(raw_data)
|
||||
|
||||
@@ -330,6 +334,7 @@ class _MultiStore(object):
|
||||
The credential specified or None if not present
|
||||
"""
|
||||
key = (client_id, user_agent, scope)
|
||||
|
||||
return self._data.get(key, None)
|
||||
|
||||
def _update_credential(self, cred, scope):
|
||||
|
||||
@@ -129,12 +129,16 @@ def run(flow, storage):
|
||||
print '--noauth_local_webserver.'
|
||||
print
|
||||
|
||||
code = None
|
||||
if FLAGS.auth_local_webserver:
|
||||
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:
|
||||
print 'Failed to find "code" in the query parameters of the redirect.'
|
||||
sys.exit('Try running with --noauth_local_webserver.')
|
||||
else:
|
||||
code = raw_input('Enter verification code: ').strip()
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@ from google.appengine.ext.webapp.util import run_wsgi_app
|
||||
decorator = OAuth2Decorator(
|
||||
client_id='837647042410-75ifgipj95q4agpm0cs452mg7i2pn17c.apps.googleusercontent.com',
|
||||
client_secret='QhxYsjM__u4vy5N0DXUFRwwI',
|
||||
scope='https://www.googleapis.com/auth/buzz',
|
||||
user_agent='my-sample-app/1.0')
|
||||
scope='https://www.googleapis.com/auth/buzz')
|
||||
|
||||
http = httplib2.Http(memcache)
|
||||
service = build("buzz", "v1", http=http)
|
||||
|
||||
@@ -88,6 +88,7 @@ def main(argv):
|
||||
# Credentials will get written back to a file.
|
||||
storage = Storage('buzz.dat')
|
||||
credentials = storage.get()
|
||||
|
||||
if credentials is None or credentials.invalid:
|
||||
credentials = run(FLOW, storage)
|
||||
|
||||
@@ -112,31 +113,10 @@ def main(argv):
|
||||
activitylist = activities.list_next(activitylist).execute()
|
||||
print "Retrieved the next two activities"
|
||||
|
||||
# Add a new activity
|
||||
new_activity_body = {
|
||||
'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 = {
|
||||
"content": "This is a comment"
|
||||
}
|
||||
item = activitylist['items'][0]
|
||||
comment = service.comments().insert(
|
||||
userId=item['actor']['id'], postId=item['id'], body=comment_body
|
||||
).execute()
|
||||
print 'Added a comment to the new activity'
|
||||
pprint.pprint(comment)
|
||||
|
||||
# List the number of followers
|
||||
followers = service.people().list(
|
||||
userId='@me', groupId='@followers').execute(http)
|
||||
print 'Hello, you have %s followers!' % followers['totalResults']
|
||||
|
||||
except AccessTokenRefreshError:
|
||||
print ("The credentials have been revoked or expired, please re-run"
|
||||
|
||||
@@ -140,8 +140,6 @@ def main(argv):
|
||||
body=vote_body)
|
||||
print "Voted on the submission"
|
||||
|
||||
|
||||
|
||||
except AccessTokenRefreshError:
|
||||
print ("The credentials have been revoked or expired, please re-run"
|
||||
"the application to re-authorize")
|
||||
|
||||
@@ -109,7 +109,7 @@ def main(argv):
|
||||
|
||||
# Start training on a data set
|
||||
train = service.training()
|
||||
body = {'id' : FLAGS.object_name}
|
||||
body = {'id': FLAGS.object_name}
|
||||
start = train.insert(body=body).execute()
|
||||
|
||||
print 'Started training'
|
||||
|
||||
@@ -43,4 +43,3 @@
|
||||
submissionId=submission['id']['submissionId'],
|
||||
body=vote_body)
|
||||
print "Voted on the submission"
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Unit tests for oauth2client.
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import datetime
|
||||
import httplib2
|
||||
import unittest
|
||||
import urlparse
|
||||
@@ -31,6 +32,16 @@ try:
|
||||
except ImportError:
|
||||
from cgi import parse_qs
|
||||
|
||||
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
|
||||
|
||||
from apiclient.http import HttpMockSequence
|
||||
from oauth2client.client import AccessTokenCredentials
|
||||
from oauth2client.client import AccessTokenCredentialsError
|
||||
@@ -48,7 +59,7 @@ class OAuth2CredentialsTests(unittest.TestCase):
|
||||
client_id = "some_client_id"
|
||||
client_secret = "cOuDdkfjxxnv+"
|
||||
refresh_token = "1/0/a.df219fjls0"
|
||||
token_expiry = "ignored"
|
||||
token_expiry = datetime.datetime.utcnow()
|
||||
token_uri = "https://www.google.com/accounts/o8/oauth2/token"
|
||||
user_agent = "refresh_checker/1.0"
|
||||
self.credentials = OAuth2Credentials(
|
||||
@@ -86,6 +97,12 @@ class OAuth2CredentialsTests(unittest.TestCase):
|
||||
resp, content = http.request("http://example.com")
|
||||
self.assertEqual(400, resp.status)
|
||||
|
||||
def test_to_from_json(self):
|
||||
json = self.credentials.to_json()
|
||||
instance = OAuth2Credentials.from_json(json)
|
||||
self.assertEquals(type(instance), OAuth2Credentials)
|
||||
self.assertEquals(self.credentials.__dict__, instance.__dict__)
|
||||
|
||||
|
||||
class AccessTokenCredentialsTests(unittest.TestCase):
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ class DecoratorTests(unittest.TestCase):
|
||||
self.assertEqual('code', q['response_type'][0])
|
||||
self.assertEqual(False, self.decorator.has_credentials())
|
||||
|
||||
# Now simulate the callback to /oauth2callback
|
||||
# Now simulate the callback to /oauth2callback.
|
||||
response = self.app.get('/oauth2callback', {
|
||||
'code': 'foo_access_code',
|
||||
'state': 'foo_path',
|
||||
@@ -181,7 +181,7 @@ class DecoratorTests(unittest.TestCase):
|
||||
self.assertEqual('http://localhost/foo_path', response.headers['Location'])
|
||||
self.assertEqual(None, self.decorator.credentials)
|
||||
|
||||
# Now requesting the decorated path should work
|
||||
# Now requesting the decorated path should work.
|
||||
response = self.app.get('/foo_path')
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual(True, self.decorator.has_credentials())
|
||||
@@ -190,18 +190,18 @@ class DecoratorTests(unittest.TestCase):
|
||||
self.assertEqual('foo_access_token',
|
||||
self.decorator.credentials.access_token)
|
||||
|
||||
# Invalidate the stored Credentials
|
||||
# Invalidate the stored Credentials.
|
||||
self.decorator.credentials.invalid = True
|
||||
self.decorator.credentials.store.put(self.decorator.credentials)
|
||||
|
||||
# Invalid Credentials should start the OAuth dance again
|
||||
# Invalid Credentials should start the OAuth dance again.
|
||||
response = self.app.get('/foo_path')
|
||||
self.assertTrue(response.status.startswith('302'))
|
||||
q = parse_qs(response.headers['Location'].split('?', 1)[1])
|
||||
self.assertEqual('http://localhost/oauth2callback', q['redirect_uri'][0])
|
||||
|
||||
def test_aware(self):
|
||||
# An initial request to an oauth_aware decorated path should not redirect
|
||||
# An initial request to an oauth_aware decorated path should not redirect.
|
||||
response = self.app.get('/bar_path')
|
||||
self.assertEqual('Hello World!', response.body)
|
||||
self.assertEqual('200 OK', response.status)
|
||||
@@ -214,7 +214,7 @@ class DecoratorTests(unittest.TestCase):
|
||||
self.assertEqual('http://localhost/bar_path', q['state'][0])
|
||||
self.assertEqual('code', q['response_type'][0])
|
||||
|
||||
# Now simulate the callback to /oauth2callback
|
||||
# Now simulate the callback to /oauth2callback.
|
||||
url = self.decorator.authorize_url()
|
||||
response = self.app.get('/oauth2callback', {
|
||||
'code': 'foo_access_code',
|
||||
@@ -223,7 +223,7 @@ class DecoratorTests(unittest.TestCase):
|
||||
self.assertEqual('http://localhost/bar_path', response.headers['Location'])
|
||||
self.assertEqual(False, self.decorator.has_credentials())
|
||||
|
||||
# Now requesting the decorated path will have credentials
|
||||
# Now requesting the decorated path will have credentials.
|
||||
response = self.app.get('/bar_path')
|
||||
self.assertEqual('200 OK', response.status)
|
||||
self.assertEqual('Hello World!', response.body)
|
||||
|
||||
157
tests/test_oauth2client_file.py
Normal file
157
tests/test_oauth2client_file.py
Normal file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/python2.4
|
||||
#
|
||||
# Copyright 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.
|
||||
|
||||
|
||||
"""Oauth2client.file tests
|
||||
|
||||
Unit tests for oauth2client.file
|
||||
"""
|
||||
|
||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import unittest
|
||||
import datetime
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
from oauth2client.client import OAuth2Credentials
|
||||
from oauth2client.client import AccessTokenCredentials
|
||||
from oauth2client.client import AssertionCredentials
|
||||
from oauth2client.file import Storage
|
||||
from oauth2client import multistore_file
|
||||
|
||||
|
||||
FILENAME = os.path.join(os.path.dirname(__file__), 'test_file_storage.data')
|
||||
|
||||
|
||||
class OAuth2ClientFileTests(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.unlink(FILENAME)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
try:
|
||||
os.unlink(FILENAME)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_non_existent_file_storage(self):
|
||||
s = Storage(FILENAME)
|
||||
credentials = s.get()
|
||||
self.assertEquals(None, credentials)
|
||||
|
||||
def test_pickle_and_json_interop(self):
|
||||
# Write a file with a pickled OAuth2Credentials.
|
||||
access_token = 'foo'
|
||||
client_id = 'some_client_id'
|
||||
client_secret = 'cOuDdkfjxxnv+'
|
||||
refresh_token = '1/0/a.df219fjls0'
|
||||
token_expiry = datetime.datetime.utcnow()
|
||||
token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
|
||||
user_agent = 'refresh_checker/1.0'
|
||||
|
||||
credentials = OAuth2Credentials(
|
||||
access_token, client_id, client_secret,
|
||||
refresh_token, token_expiry, token_uri,
|
||||
user_agent)
|
||||
|
||||
f = open(FILENAME, 'w')
|
||||
pickle.dump(credentials, f)
|
||||
f.close()
|
||||
|
||||
# Storage should be able to read that object.
|
||||
# TODO(jcgregorio) This should fail once pickle support is removed.
|
||||
s = Storage(FILENAME)
|
||||
credentials = s.get()
|
||||
self.assertNotEquals(None, credentials)
|
||||
self.assertEquals('foo', credentials.access_token)
|
||||
|
||||
# Now write it back out and confirm it has been rewritten as JSON
|
||||
s.put(credentials)
|
||||
f = file(FILENAME)
|
||||
data = simplejson.load(f)
|
||||
f.close()
|
||||
|
||||
self.assertEquals(data['access_token'], 'foo')
|
||||
self.assertEquals(data['_class'], 'OAuth2Credentials')
|
||||
self.assertEquals(data['_module'], 'oauth2client.client')
|
||||
|
||||
def test_access_token_credentials(self):
|
||||
access_token = 'foo'
|
||||
user_agent = 'refresh_checker/1.0'
|
||||
|
||||
credentials = AccessTokenCredentials(access_token, user_agent)
|
||||
|
||||
s = Storage(FILENAME)
|
||||
credentials = s.put(credentials)
|
||||
credentials = s.get()
|
||||
|
||||
self.assertNotEquals(None, credentials)
|
||||
self.assertEquals('foo', credentials.access_token)
|
||||
|
||||
def test_multistore_non_existent_file(self):
|
||||
store = multistore_file.get_credential_storage(
|
||||
FILENAME,
|
||||
'some_client_id',
|
||||
'user-agent/1.0',
|
||||
'some-scope')
|
||||
|
||||
credentials = store.get()
|
||||
self.assertEquals(None, credentials)
|
||||
|
||||
def test_multistore_file(self):
|
||||
access_token = 'foo'
|
||||
client_secret = 'cOuDdkfjxxnv+'
|
||||
refresh_token = '1/0/a.df219fjls0'
|
||||
token_expiry = datetime.datetime.utcnow()
|
||||
token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
|
||||
user_agent = 'refresh_checker/1.0'
|
||||
client_id = 'some_client_id'
|
||||
|
||||
credentials = OAuth2Credentials(
|
||||
access_token, client_id, client_secret,
|
||||
refresh_token, token_expiry, token_uri,
|
||||
user_agent)
|
||||
|
||||
store = multistore_file.get_credential_storage(
|
||||
FILENAME,
|
||||
credentials.client_id,
|
||||
credentials.user_agent,
|
||||
'some-scope')
|
||||
|
||||
store.put(credentials)
|
||||
credentials = store.get()
|
||||
|
||||
self.assertNotEquals(None, credentials)
|
||||
self.assertEquals('foo', credentials.access_token)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user