Skip to content
This repository was archived by the owner on Jan 18, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ def delete(self, key):
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.

Expand All @@ -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.
Expand Down Expand Up @@ -265,9 +272,7 @@ def _to_json(self, strip):
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__
Expand All @@ -285,7 +290,7 @@ def to_json(self):
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):
Expand Down Expand Up @@ -699,9 +704,6 @@ def retrieve_scopes(self, http):
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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -1222,6 +1229,32 @@ def create_scoped(self, scopes):
"""
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

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


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."""
Expand Down
28 changes: 28 additions & 0 deletions oauth2client/service_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"""

import base64
import datetime
import json
import time

from pyasn1.codec.ber import decoder
Expand All @@ -27,17 +29,24 @@
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):
"""Class representing a service account (signed JWT) credential."""

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,
Expand Down Expand Up @@ -108,6 +117,25 @@ def serialization_data(self):
'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

Expand Down
35 changes: 35 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,35 @@ def test_from_stream_malformed_file_3(self):
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
Expand Down Expand Up @@ -774,6 +803,12 @@ def test_from_json_token_expiry(self):
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'
Expand Down