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)
|
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):
|
class Credentials(object):
|
||||||
"""Base class for all Credentials objects.
|
"""Base class for all Credentials objects.
|
||||||
|
|
||||||
@@ -208,7 +215,7 @@ class Credentials(object):
|
|||||||
JSON string as input and returns an instantiated 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):
|
def authorize(self, http):
|
||||||
"""Take an httplib2.Http instance (or equivalent) and authorizes it.
|
"""Take an httplib2.Http instance (or equivalent) and authorizes it.
|
||||||
@@ -265,9 +272,7 @@ class Credentials(object):
|
|||||||
for member in strip:
|
for member in strip:
|
||||||
if member in d:
|
if member in d:
|
||||||
del d[member]
|
del d[member]
|
||||||
if (d.get('token_expiry') and
|
d['token_expiry'] = _parse_expiry(d.get('token_expiry'))
|
||||||
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.
|
# Add in information we will need later to reconsistitue this instance.
|
||||||
d['_class'] = t.__name__
|
d['_class'] = t.__name__
|
||||||
d['_module'] = t.__module__
|
d['_module'] = t.__module__
|
||||||
@@ -285,7 +290,7 @@ class Credentials(object):
|
|||||||
string, a JSON representation of this instance, suitable to pass to
|
string, a JSON representation of this instance, suitable to pass to
|
||||||
from_json().
|
from_json().
|
||||||
"""
|
"""
|
||||||
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
|
return self._to_json(self.NON_SERIALIZED_MEMBERS)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_from_json(cls, s):
|
def new_from_json(cls, s):
|
||||||
@@ -699,9 +704,6 @@ class OAuth2Credentials(Credentials):
|
|||||||
self._retrieve_scopes(http.request)
|
self._retrieve_scopes(http.request)
|
||||||
return self.scopes
|
return self.scopes
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, s):
|
def from_json(cls, s):
|
||||||
"""Instantiate a Credentials object from a JSON description of it.
|
"""Instantiate a Credentials object from a JSON description of it.
|
||||||
@@ -1180,6 +1182,11 @@ class GoogleCredentials(OAuth2Credentials):
|
|||||||
print(response)
|
print(response)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
NON_SERIALIZED_MEMBERS = (
|
||||||
|
frozenset(['_private_key']) |
|
||||||
|
OAuth2Credentials.NON_SERIALIZED_MEMBERS)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||||
token_expiry, token_uri, user_agent,
|
token_expiry, token_uri, user_agent,
|
||||||
revoke_uri=GOOGLE_REVOKE_URI):
|
revoke_uri=GOOGLE_REVOKE_URI):
|
||||||
@@ -1222,6 +1229,32 @@ class GoogleCredentials(OAuth2Credentials):
|
|||||||
"""
|
"""
|
||||||
return self
|
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
|
@property
|
||||||
def serialization_data(self):
|
def serialization_data(self):
|
||||||
"""Get the fields and values identifying the current credentials."""
|
"""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 base64
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pyasn1.codec.ber import decoder
|
from pyasn1.codec.ber import decoder
|
||||||
@@ -27,10 +29,12 @@ import rsa
|
|||||||
from oauth2client import GOOGLE_REVOKE_URI
|
from oauth2client import GOOGLE_REVOKE_URI
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
from oauth2client._helpers import _json_encode
|
from oauth2client._helpers import _json_encode
|
||||||
|
from oauth2client._helpers import _from_bytes
|
||||||
from oauth2client._helpers import _to_bytes
|
from oauth2client._helpers import _to_bytes
|
||||||
from oauth2client._helpers import _urlsafe_b64encode
|
from oauth2client._helpers import _urlsafe_b64encode
|
||||||
from oauth2client import util
|
from oauth2client import util
|
||||||
from oauth2client.client import AssertionCredentials
|
from oauth2client.client import AssertionCredentials
|
||||||
|
from oauth2client.client import EXPIRY_FORMAT
|
||||||
|
|
||||||
|
|
||||||
class _ServiceAccountCredentials(AssertionCredentials):
|
class _ServiceAccountCredentials(AssertionCredentials):
|
||||||
@@ -38,6 +42,11 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
|||||||
|
|
||||||
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
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,
|
def __init__(self, service_account_id, service_account_email,
|
||||||
private_key_id, private_key_pkcs8_text, scopes,
|
private_key_id, private_key_pkcs8_text, scopes,
|
||||||
user_agent=None, token_uri=GOOGLE_TOKEN_URI,
|
user_agent=None, token_uri=GOOGLE_TOKEN_URI,
|
||||||
@@ -108,6 +117,25 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
|||||||
'private_key': self._private_key_pkcs8_text
|
'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):
|
def create_scoped_required(self):
|
||||||
return not self._scopes
|
return not self._scopes
|
||||||
|
|
||||||
|
|||||||
@@ -618,6 +618,35 @@ class GoogleCredentialsTests(unittest2.TestCase):
|
|||||||
self.get_a_google_credentials_object().from_stream,
|
self.get_a_google_credentials_object().from_stream,
|
||||||
credentials_file)
|
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):
|
class DummyDeleteStorage(Storage):
|
||||||
delete_called = False
|
delete_called = False
|
||||||
@@ -774,6 +803,12 @@ class BasicCredentialsTests(unittest2.TestCase):
|
|||||||
instance = OAuth2Credentials.from_json(json.dumps(data))
|
instance = OAuth2Credentials.from_json(json.dumps(data))
|
||||||
self.assertTrue(isinstance(instance, OAuth2Credentials))
|
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):
|
def test_unicode_header_checks(self):
|
||||||
access_token = u'foo'
|
access_token = u'foo'
|
||||||
client_id = u'some_client_id'
|
client_id = u'some_client_id'
|
||||||
|
|||||||
Reference in New Issue
Block a user