Add to/from json methods to Credentials classes
This adds to_json and from_json methods to GoogleCredentials and _ServiceAccountCredentials classes. This allows them to be serialized by multistore_file. Resolves: #384
This commit is contained in:
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user