Merge pull request #385 from dlorenc/master

Add to/from json methods to Credentials classes.
This commit is contained in:
Nathaniel Manista
2016-01-27 10:51:47 -08:00
3 changed files with 104 additions and 8 deletions

View File

@@ -198,6 +198,13 @@ class MemoryCache(object):
self.cache.pop(key, None)
def _parse_expiry(expiry):
if expiry and isinstance(expiry, datetime.datetime):
return expiry.strftime(EXPIRY_FORMAT)
else:
return None
class Credentials(object):
"""Base class for all Credentials objects.
@@ -208,7 +215,7 @@ class Credentials(object):
JSON string as input and returns an instantiated Credentials object.
"""
NON_SERIALIZED_MEMBERS = ['store']
NON_SERIALIZED_MEMBERS = frozenset(['store'])
def authorize(self, http):
"""Take an httplib2.Http instance (or equivalent) and authorizes it.
@@ -265,9 +272,7 @@ class Credentials(object):
for member in strip:
if member in d:
del d[member]
if (d.get('token_expiry') and
isinstance(d['token_expiry'], datetime.datetime)):
d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
d['token_expiry'] = _parse_expiry(d.get('token_expiry'))
# Add in information we will need later to reconsistitue this instance.
d['_class'] = t.__name__
d['_module'] = t.__module__
@@ -285,7 +290,7 @@ class Credentials(object):
string, a JSON representation of this instance, suitable to pass to
from_json().
"""
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
return self._to_json(self.NON_SERIALIZED_MEMBERS)
@classmethod
def new_from_json(cls, s):
@@ -699,9 +704,6 @@ class OAuth2Credentials(Credentials):
self._retrieve_scopes(http.request)
return self.scopes
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.
@@ -1180,6 +1182,11 @@ class GoogleCredentials(OAuth2Credentials):
print(response)
"""
NON_SERIALIZED_MEMBERS = (
frozenset(['_private_key']) |
OAuth2Credentials.NON_SERIALIZED_MEMBERS)
def __init__(self, access_token, client_id, client_secret, refresh_token,
token_expiry, token_uri, user_agent,
revoke_uri=GOOGLE_REVOKE_URI):
@@ -1222,6 +1229,32 @@ class GoogleCredentials(OAuth2Credentials):
"""
return self
@classmethod
def from_json(cls, s):
# TODO(issue 388): eliminate the circularity that is the reason for
# this non-top-level import.
from oauth2client.service_account import _ServiceAccountCredentials
data = json.loads(_from_bytes(s))
# We handle service_account._ServiceAccountCredentials since it is a
# possible return type of GoogleCredentials.get_application_default()
if (data['_module'] == 'oauth2client.service_account' and
data['_class'] == '_ServiceAccountCredentials'):
return _ServiceAccountCredentials.from_json(s)
token_expiry = _parse_expiry(data.get('token_expiry'))
google_credentials = cls(
data['access_token'],
data['client_id'],
data['client_secret'],
data['refresh_token'],
token_expiry,
data['token_uri'],
data['user_agent'],
revoke_uri=data.get('revoke_uri', None))
google_credentials.invalid = data['invalid']
return google_credentials
@property
def serialization_data(self):
"""Get the fields and values identifying the current credentials."""

View File

@@ -18,6 +18,8 @@ This credentials class is implemented on top of rsa library.
"""
import base64
import datetime
import json
import time
from pyasn1.codec.ber import decoder
@@ -27,10 +29,12 @@ import rsa
from oauth2client import GOOGLE_REVOKE_URI
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client._helpers import _json_encode
from oauth2client._helpers import _from_bytes
from oauth2client._helpers import _to_bytes
from oauth2client._helpers import _urlsafe_b64encode
from oauth2client import util
from oauth2client.client import AssertionCredentials
from oauth2client.client import EXPIRY_FORMAT
class _ServiceAccountCredentials(AssertionCredentials):
@@ -38,6 +42,11 @@ class _ServiceAccountCredentials(AssertionCredentials):
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
NON_SERIALIZED_MEMBERS = (
frozenset(['_private_key']) |
AssertionCredentials.NON_SERIALIZED_MEMBERS)
def __init__(self, service_account_id, service_account_email,
private_key_id, private_key_pkcs8_text, scopes,
user_agent=None, token_uri=GOOGLE_TOKEN_URI,
@@ -108,6 +117,25 @@ class _ServiceAccountCredentials(AssertionCredentials):
'private_key': self._private_key_pkcs8_text
}
@classmethod
def from_json(cls, s):
data = json.loads(_from_bytes(s))
credentials = cls(
service_account_id=data['_service_account_id'],
service_account_email=data['_service_account_email'],
private_key_id=data['_private_key_id'],
private_key_pkcs8_text=data['_private_key_pkcs8_text'],
scopes=[],
user_agent=data['_user_agent'])
credentials.invalid = data['invalid']
credentials.access_token = data['access_token']
token_expiry = data.get('token_expiry', None)
if token_expiry is not None:
credentials.token_expiry = datetime.datetime.strptime(
token_expiry, EXPIRY_FORMAT)
return credentials
def create_scoped_required(self):
return not self._scopes

View File

@@ -618,6 +618,35 @@ class GoogleCredentialsTests(unittest2.TestCase):
self.get_a_google_credentials_object().from_stream,
credentials_file)
def test_to_from_json_authorized_user(self):
credentials_file = datafile(
os.path.join('gcloud', 'application_default_credentials_authorized_user.json'))
creds = GoogleCredentials.from_stream(credentials_file)
json = creds.to_json()
creds2 = GoogleCredentials.from_json(json)
self.assertEqual(creds.__dict__, creds2.__dict__)
def test_to_from_json_service_account(self):
self.maxDiff=None
credentials_file = datafile(
os.path.join('gcloud', _WELL_KNOWN_CREDENTIALS_FILE))
creds = GoogleCredentials.from_stream(credentials_file)
json = creds.to_json()
creds2 = GoogleCredentials.from_json(json)
self.assertEqual(creds.__dict__, creds2.__dict__)
def test_parse_expiry(self):
dt = datetime.datetime(2016, 1, 1)
parsed_expiry = client._parse_expiry(dt)
self.assertEqual('2016-01-01T00:00:00Z', parsed_expiry)
def test_bad_expiry(self):
dt = object()
parsed_expiry = client._parse_expiry(dt)
self.assertEqual(None, parsed_expiry)
class DummyDeleteStorage(Storage):
delete_called = False
@@ -774,6 +803,12 @@ class BasicCredentialsTests(unittest2.TestCase):
instance = OAuth2Credentials.from_json(json.dumps(data))
self.assertTrue(isinstance(instance, OAuth2Credentials))
def test_from_json_bad_token_expiry(self):
data = json.loads(self.credentials.to_json())
data['token_expiry'] = 'foobar'
instance = OAuth2Credentials.from_json(json.dumps(data))
self.assertTrue(isinstance(instance, OAuth2Credentials))
def test_unicode_header_checks(self):
access_token = u'foo'
client_id = u'some_client_id'