Skip to content
This repository was archived by the owner on Jan 18, 2025. It is now read-only.
Closed
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
18 changes: 9 additions & 9 deletions oauth2client/_openssl_crypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def from_string(key_pem, is_x509_cert):
Raises:
OpenSSL.crypto.Error: if the key_pem can't be parsed.
"""
key_pem = _to_bytes(key_pem)
if is_x509_cert:
pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
else:
Expand Down Expand Up @@ -112,7 +113,8 @@ def from_string(key, password=b'notasecret'):
Raises:
OpenSSL.crypto.Error if the key can't be parsed.
"""
parsed_pem_key = _parse_pem_key(_to_bytes(key))
key = _to_bytes(key)
parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
else:
Expand All @@ -121,19 +123,17 @@ def from_string(key, password=b'notasecret'):
return OpenSSLSigner(pkey)


def pkcs12_key_as_pem(private_key_text, private_key_password):
"""Convert the contents of a PKCS12 key to PEM using OpenSSL.
def pkcs12_key_as_pem(private_key_bytes, private_key_password):
"""Convert the contents of a PKCS#12 key to PEM using pyOpenSSL.

Args:
private_key_text: String. Private key.
private_key_password: String. Password for PKCS12.
private_key_bytes: Bytes. PKCS#12 key in DER format.
private_key_password: String. Password for PKCS#12 key.

Returns:
String. PEM contents of ``private_key_text``.
String. PEM contents of ``private_key_bytes``.
"""
decoded_body = base64.b64decode(private_key_text)
private_key_password = _to_bytes(private_key_password)

pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
pkcs12 = crypto.load_pkcs12(private_key_bytes, private_key_password)
return crypto.dump_privatekey(crypto.FILETYPE_PEM,
pkcs12.get_privatekey())
2 changes: 1 addition & 1 deletion oauth2client/_pycrypto_crypt.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def from_string(key, password='notasecret'):
Raises:
NotImplementedError if the key isn't in PEM format.
"""
parsed_pem_key = _parse_pem_key(key)
parsed_pem_key = _parse_pem_key(_to_bytes(key))
if parsed_pem_key:
pkey = RSA.importKey(parsed_pem_key)
else:
Expand Down
176 changes: 39 additions & 137 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,32 +256,37 @@ def apply(self, headers):
"""
_abstract()

def _to_json(self, strip):
def _to_json(self, strip, to_serialize=None):
"""Utility function that creates JSON repr. of a Credentials object.

Args:
strip: array, An array of names of members to not include in the
strip: array, An array of names of members to exclude from the
JSON.
to_serialize: dict, (Optional) The properties for this object
that will be serialized. This allows callers to modify
before serializing.

Returns:
string, a JSON representation of this instance, suitable to pass to
from_json().
"""
t = type(self)
d = copy.copy(self.__dict__)
curr_type = self.__class__
if to_serialize is None:
to_serialize = copy.copy(self.__dict__)
for member in strip:
if member in d:
del d[member]
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__
for key, val in d.items():
if member in to_serialize:
del to_serialize[member]
to_serialize['token_expiry'] = _parse_expiry(
to_serialize.get('token_expiry'))
# Add in information we will need later to reconstitute this instance.
to_serialize['_class'] = curr_type.__name__
to_serialize['_module'] = curr_type.__module__
for key, val in to_serialize.items():
if isinstance(val, bytes):
d[key] = val.decode('utf-8')
to_serialize[key] = val.decode('utf-8')
if isinstance(val, set):
d[key] = list(val)
return json.dumps(d)
to_serialize[key] = list(val)
return json.dumps(to_serialize)

def to_json(self):
"""Creating a JSON representation of an instance of Credentials.
Expand All @@ -293,19 +298,19 @@ def to_json(self):
return self._to_json(self.NON_SERIALIZED_MEMBERS)

@classmethod
def new_from_json(cls, s):
def new_from_json(cls, json_str):
"""Utility class method to instantiate a Credentials subclass from JSON.

Expects the JSON string to have been produced by to_json().

Args:
s: string or bytes, JSON from to_json().
json_str: string or bytes, JSON from to_json().

Returns:
An instance of the subclass of Credentials that was serialized with
to_json().
"""
json_string_as_unicode = _from_bytes(s)
json_string_as_unicode = _from_bytes(json_str)
data = json.loads(json_string_as_unicode)
# Find and call the right classmethod from_json() to restore
# the object.
Expand All @@ -321,8 +326,7 @@ def new_from_json(cls, s):
module_obj = __import__(module_name,
fromlist=module_name.split('.')[:-1])
kls = getattr(module_obj, data['_class'])
from_json = getattr(kls, 'from_json')
return from_json(json_string_as_unicode)
return kls.from_json(json_string_as_unicode)

@classmethod
def from_json(cls, unused_data):
Expand Down Expand Up @@ -705,19 +709,18 @@ def retrieve_scopes(self, http):
return self.scopes

@classmethod
def from_json(cls, s):
def from_json(cls, json_str):
"""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.
json_str: str, The JSON to deserialize.

Returns:
An instance of a Credentials subclass.
"""
s = _from_bytes(s)
data = json.loads(s)
data = json.loads(_from_bytes(json_str))
if (data.get('token_expiry') and
not isinstance(data['token_expiry'], datetime.datetime)):
try:
Expand Down Expand Up @@ -1065,8 +1068,8 @@ def __init__(self, access_token, user_agent, revoke_uri=None):
revoke_uri=revoke_uri)

@classmethod
def from_json(cls, s):
data = json.loads(_from_bytes(s))
def from_json(cls, json_str):
data = json.loads(_from_bytes(json_str))
retval = AccessTokenCredentials(
data['access_token'],
data['user_agent'])
Expand Down Expand Up @@ -1185,7 +1188,7 @@ class GoogleCredentials(OAuth2Credentials):
NON_SERIALIZED_MEMBERS = (
frozenset(['_private_key']) |
OAuth2Credentials.NON_SERIALIZED_MEMBERS)

"""Members that aren't serialized when object is converted to JSON."""

def __init__(self, access_token, client_id, client_secret, refresh_token,
token_expiry, token_uri, user_agent,
Expand Down Expand Up @@ -1230,17 +1233,17 @@ def create_scoped(self, scopes):
return self

@classmethod
def from_json(cls, s):
def from_json(cls, json_data):
# 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))
# this non-top-level import.
from oauth2client.service_account import ServiceAccountCredentials
data = json.loads(_from_bytes(json_data))

# We handle service_account._ServiceAccountCredentials since it is a
# 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)
data['_class'] == 'ServiceAccountCredentials'):
return ServiceAccountCredentials.from_json(data)

token_expiry = _parse_expiry(data.get('token_expiry'))
google_credentials = cls(
Expand Down Expand Up @@ -1490,9 +1493,6 @@ def _get_well_known_file():

def _get_application_default_credential_from_file(filename):
"""Build the Application Default Credentials from file."""

from oauth2client import service_account

# read the credentials from the file
with open(filename) as file_obj:
client_credentials = json.load(file_obj)
Expand Down Expand Up @@ -1523,12 +1523,9 @@ def _get_application_default_credential_from_file(filename):
token_uri=GOOGLE_TOKEN_URI,
user_agent='Python client library')
else: # client_credentials['type'] == SERVICE_ACCOUNT
return service_account._ServiceAccountCredentials(
service_account_id=client_credentials['client_id'],
service_account_email=client_credentials['client_email'],
private_key_id=client_credentials['private_key_id'],
private_key_pkcs8_text=client_credentials['private_key'],
scopes=[])
from oauth2client.service_account import ServiceAccountCredentials
return ServiceAccountCredentials.from_json_keyfile_dict(
client_credentials)


def _raise_exception_for_missing_fields(missing_fields):
Expand Down Expand Up @@ -1631,101 +1628,6 @@ def _RequireCryptoOrDie():
raise CryptoUnavailableError('No crypto library available')


class SignedJwtAssertionCredentials(AssertionCredentials):
"""Credentials object used for OAuth 2.0 Signed JWT assertion grants.

This credential does not require a flow to instantiate because it
represents a two legged flow, and therefore has all of the required
information to generate and refresh its own access tokens.

SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto
2.6 or later. For App Engine you may also consider using
AppAssertionCredentials.
"""

MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds

@util.positional(4)
def __init__(self,
service_account_name,
private_key,
scope,
private_key_password='notasecret',
user_agent=None,
token_uri=GOOGLE_TOKEN_URI,
revoke_uri=GOOGLE_REVOKE_URI,
**kwargs):
"""Constructor for SignedJwtAssertionCredentials.

Args:
service_account_name: string, id for account, usually an email
address.
private_key: string or bytes, private key in PKCS12 or PEM format.
scope: string or iterable of strings, scope(s) of the credentials
being requested.
private_key_password: string, password for private_key, unused if
private_key is in PEM format.
user_agent: string, HTTP User-Agent to provide for this
application.
token_uri: string, URI for token endpoint. For convenience defaults
to Google's endpoints but any OAuth 2.0 provider can be
used.
revoke_uri: string, URI for revoke endpoint.
kwargs: kwargs, Additional parameters to add to the JWT token, for
example sub=joe@xample.org.

Raises:
CryptoUnavailableError if no crypto library is available.
"""
_RequireCryptoOrDie()
super(SignedJwtAssertionCredentials, self).__init__(
None,
user_agent=user_agent,
token_uri=token_uri,
revoke_uri=revoke_uri,
)

self.scope = util.scopes_to_string(scope)

# Keep base64 encoded so it can be stored in JSON.
self.private_key = base64.b64encode(_to_bytes(private_key))
self.private_key_password = private_key_password
self.service_account_name = service_account_name
self.kwargs = kwargs

@classmethod
def from_json(cls, s):
data = json.loads(_from_bytes(s))
retval = SignedJwtAssertionCredentials(
data['service_account_name'],
base64.b64decode(data['private_key']),
data['scope'],
private_key_password=data['private_key_password'],
user_agent=data['user_agent'],
token_uri=data['token_uri'],
**data['kwargs']
)
retval.invalid = data['invalid']
retval.access_token = data['access_token']
return retval

def _generate_assertion(self):
"""Generate the assertion that will be used in the request."""
now = int(time.time())
payload = {
'aud': self.token_uri,
'scope': self.scope,
'iat': now,
'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
'iss': self.service_account_name
}
payload.update(self.kwargs)
logger.debug(str(payload))

private_key = base64.b64decode(self.private_key)
return crypt.make_signed_jwt(crypt.Signer.from_string(
private_key, self.private_key_password), payload)

# Only used in verify_id_token(), which is always calling to the same URI
# for the certs.
_cached_http = httplib2.Http(MemoryCache())
Expand Down
Loading