Making oauth2client/ files pass PEP8.
This commit is contained in:
@@ -63,11 +63,11 @@ def _to_bytes(value, encoding='ascii'):
|
||||
ValueError if the value could not be converted to bytes.
|
||||
"""
|
||||
result = (value.encode(encoding)
|
||||
if isinstance(value, six.text_type) else value)
|
||||
if isinstance(value, six.text_type) else value)
|
||||
if isinstance(result, six.binary_type):
|
||||
return result
|
||||
else:
|
||||
raise ValueError('%r could not be converted to bytes' % (value, ))
|
||||
raise ValueError('%r could not be converted to bytes' % (value,))
|
||||
|
||||
|
||||
def _from_bytes(value):
|
||||
@@ -84,11 +84,11 @@ def _from_bytes(value):
|
||||
ValueError if the value could not be converted to unicode.
|
||||
"""
|
||||
result = (value.decode('utf-8')
|
||||
if isinstance(value, six.binary_type) else value)
|
||||
if isinstance(value, six.binary_type) else value)
|
||||
if isinstance(result, six.text_type):
|
||||
return result
|
||||
else:
|
||||
raise ValueError('%r could not be converted to unicode' % (value, ))
|
||||
raise ValueError('%r could not be converted to unicode' % (value,))
|
||||
|
||||
|
||||
def _urlsafe_b64encode(raw_bytes):
|
||||
|
||||
@@ -54,7 +54,7 @@ class OpenSSLVerifier(object):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def from_string(key_pem, is_x509_cert):
|
||||
def from_string(key_pem, is_x509_cert):
|
||||
"""Construct a Verified instance from a string.
|
||||
|
||||
Args:
|
||||
@@ -136,4 +136,4 @@ def pkcs12_key_as_pem(private_key_text, private_key_password):
|
||||
|
||||
pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM,
|
||||
pkcs12.get_privatekey())
|
||||
pkcs12.get_privatekey())
|
||||
|
||||
@@ -50,7 +50,7 @@ class PyCryptoVerifier(object):
|
||||
"""
|
||||
message = _to_bytes(message, encoding='utf-8')
|
||||
return PKCS1_v1_5.new(self._pubkey).verify(
|
||||
SHA256.new(message), signature)
|
||||
SHA256.new(message), signature)
|
||||
|
||||
@staticmethod
|
||||
def from_string(key_pem, is_x509_cert):
|
||||
@@ -58,8 +58,8 @@ class PyCryptoVerifier(object):
|
||||
|
||||
Args:
|
||||
key_pem: string, public key in PEM format.
|
||||
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
|
||||
expected to be an RSA key in PEM format.
|
||||
is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
|
||||
is expected to be an RSA key in PEM format.
|
||||
|
||||
Returns:
|
||||
Verifier instance.
|
||||
@@ -121,8 +121,9 @@ class PyCryptoSigner(object):
|
||||
pkey = RSA.importKey(parsed_pem_key)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'PKCS12 format is not supported by the PyCrypto library. '
|
||||
'Try converting to a "PEM" '
|
||||
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
|
||||
'or using PyOpenSSL if native code is an option.')
|
||||
'PKCS12 format is not supported by the PyCrypto library. '
|
||||
'Try converting to a "PEM" '
|
||||
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > '
|
||||
'privatekey.pem) '
|
||||
'or using PyOpenSSL if native code is an option.')
|
||||
return PyCryptoSigner(pkey)
|
||||
|
||||
@@ -132,7 +132,8 @@ def xsrf_secret_key():
|
||||
model.secret = _generate_new_xsrf_secret_key()
|
||||
model.put()
|
||||
secret = model.secret
|
||||
memcache.add(XSRF_MEMCACHE_ID, secret, namespace=OAUTH2CLIENT_NAMESPACE)
|
||||
memcache.add(XSRF_MEMCACHE_ID, secret,
|
||||
namespace=OAUTH2CLIENT_NAMESPACE)
|
||||
|
||||
return str(secret)
|
||||
|
||||
@@ -166,7 +167,8 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
self._kwargs = kwargs
|
||||
self.service_account_id = kwargs.get('service_account_id', None)
|
||||
|
||||
# Assertion type is no longer used, but still in the parent class signature.
|
||||
# Assertion type is no longer used, but still in the
|
||||
# parent class signature.
|
||||
super(AppAssertionCredentials, self).__init__(None)
|
||||
|
||||
@classmethod
|
||||
@@ -192,14 +194,15 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
try:
|
||||
scopes = self.scope.split()
|
||||
(token, _) = app_identity.get_access_token(
|
||||
scopes, service_account_id=self.service_account_id)
|
||||
scopes, service_account_id=self.service_account_id)
|
||||
except app_identity.Error as e:
|
||||
raise AccessTokenRefreshError(str(e))
|
||||
self.access_token = token
|
||||
|
||||
@property
|
||||
def serialization_data(self):
|
||||
raise NotImplementedError('Cannot serialize credentials for AppEngine.')
|
||||
raise NotImplementedError('Cannot serialize credentials '
|
||||
'for Google App Engine.')
|
||||
|
||||
def create_scoped_required(self):
|
||||
return not self.scope
|
||||
@@ -220,8 +223,8 @@ class FlowProperty(db.Property):
|
||||
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
flow = super(FlowProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
flow = super(FlowProperty, self).get_value_for_datastore(
|
||||
model_instance)
|
||||
return db.Blob(pickle.dumps(flow))
|
||||
|
||||
# For reading from datastore.
|
||||
@@ -233,8 +236,8 @@ class FlowProperty(db.Property):
|
||||
def validate(self, value):
|
||||
if value is not None and not isinstance(value, Flow):
|
||||
raise db.BadValueError('Property %s must be convertible '
|
||||
'to a FlowThreeLegged instance (%s)' %
|
||||
(self.name, value))
|
||||
'to a FlowThreeLegged instance (%s)' %
|
||||
(self.name, value))
|
||||
return super(FlowProperty, self).validate(value)
|
||||
|
||||
def empty(self, value):
|
||||
@@ -266,7 +269,8 @@ if ndb is not None:
|
||||
logger.info('validate: Got type %s', type(value))
|
||||
if value is not None and not isinstance(value, Flow):
|
||||
raise TypeError('Property %s must be convertible to a flow '
|
||||
'instance; received: %s.' % (self._name, value))
|
||||
'instance; received: %s.' % (self._name,
|
||||
value))
|
||||
|
||||
|
||||
class CredentialsProperty(db.Property):
|
||||
@@ -282,8 +286,8 @@ class CredentialsProperty(db.Property):
|
||||
# For writing to datastore.
|
||||
def get_value_for_datastore(self, model_instance):
|
||||
logger.info("get: Got type " + str(type(model_instance)))
|
||||
cred = super(CredentialsProperty,
|
||||
self).get_value_for_datastore(model_instance)
|
||||
cred = super(CredentialsProperty, self).get_value_for_datastore(
|
||||
model_instance)
|
||||
if cred is None:
|
||||
cred = ''
|
||||
else:
|
||||
@@ -308,16 +312,12 @@ class CredentialsProperty(db.Property):
|
||||
logger.info("validate: Got type " + str(type(value)))
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise db.BadValueError('Property %s must be convertible '
|
||||
'to a Credentials instance (%s)' %
|
||||
(self.name, value))
|
||||
#if value is not None and not isinstance(value, Credentials):
|
||||
# return None
|
||||
'to a Credentials instance (%s)' %
|
||||
(self.name, value))
|
||||
return value
|
||||
|
||||
|
||||
if ndb is not None:
|
||||
|
||||
|
||||
# TODO(dhermes): Turn this into a JsonProperty and overhaul the Credentials
|
||||
# and subclass mechanics to use new_from_dict, to_dict,
|
||||
# from_dict, etc.
|
||||
@@ -344,8 +344,9 @@ if ndb is not None:
|
||||
"""
|
||||
logger.info('validate: Got type %s', type(value))
|
||||
if value is not None and not isinstance(value, Credentials):
|
||||
raise TypeError('Property %s must be convertible to a credentials '
|
||||
'instance; received: %s.' % (self._name, value))
|
||||
raise TypeError('Property %s must be convertible to a '
|
||||
'credentials instance; received: %s.' %
|
||||
(self._name, value))
|
||||
|
||||
def _to_base_type(self, value):
|
||||
"""Converts our validated value to a JSON serialized string.
|
||||
@@ -409,7 +410,8 @@ class StorageByKeyName(Storage):
|
||||
"""
|
||||
if key_name is None:
|
||||
if user is None:
|
||||
raise ValueError('StorageByKeyName called with no key name or user.')
|
||||
raise ValueError('StorageByKeyName called with no '
|
||||
'key name or user.')
|
||||
key_name = user.user_id()
|
||||
|
||||
self._model = model
|
||||
@@ -423,15 +425,17 @@ class StorageByKeyName(Storage):
|
||||
Returns:
|
||||
Boolean indicating whether or not the model is an NDB or DB model.
|
||||
"""
|
||||
# issubclass will fail if one of the arguments is not a class, only need
|
||||
# worry about new-style classes since ndb and db models are new-style
|
||||
# issubclass will fail if one of the arguments is not a class, only
|
||||
# need worry about new-style classes since ndb and db models are
|
||||
# new-style
|
||||
if isinstance(self._model, type):
|
||||
if ndb is not None and issubclass(self._model, ndb.Model):
|
||||
return True
|
||||
elif issubclass(self._model, db.Model):
|
||||
return False
|
||||
|
||||
raise TypeError('Model class not an NDB or DB model: %s.' % (self._model, ))
|
||||
raise TypeError('Model class not an NDB or DB model: %s.' %
|
||||
(self._model,))
|
||||
|
||||
def _get_entity(self):
|
||||
"""Retrieve entity from datastore.
|
||||
@@ -460,7 +464,7 @@ class StorageByKeyName(Storage):
|
||||
db.delete(entity_key)
|
||||
|
||||
@db.non_transactional(allow_existing=True)
|
||||
def locked_get(self):
|
||||
def locked_get(self):
|
||||
"""Retrieve Credential from datastore.
|
||||
|
||||
Returns:
|
||||
@@ -496,7 +500,7 @@ class StorageByKeyName(Storage):
|
||||
self._cache.set(self._key_name, credentials.to_json())
|
||||
|
||||
@db.non_transactional(allow_existing=True)
|
||||
def locked_delete(self):
|
||||
def locked_delete(self):
|
||||
"""Delete Credential from datastore."""
|
||||
|
||||
if self._cache:
|
||||
@@ -548,8 +552,8 @@ def _build_state_value(request_handler, user):
|
||||
"""
|
||||
uri = request_handler.request.url
|
||||
token = xsrfutil.generate_token(xsrf_secret_key(), user.user_id(),
|
||||
action_id=str(uri))
|
||||
return uri + ':' + token
|
||||
action_id=str(uri))
|
||||
return uri + ':' + token
|
||||
|
||||
|
||||
def _parse_state_value(state, user):
|
||||
@@ -569,7 +573,7 @@ def _parse_state_value(state, user):
|
||||
"""
|
||||
uri, token = state.rsplit(':', 1)
|
||||
if not xsrfutil.validate_token(xsrf_secret_key(), token, user.user_id(),
|
||||
action_id=uri):
|
||||
action_id=uri):
|
||||
raise InvalidXsrfTokenError()
|
||||
|
||||
return uri
|
||||
@@ -629,18 +633,17 @@ class OAuth2Decorator(object):
|
||||
|
||||
@util.positional(4)
|
||||
def __init__(self, client_id, client_secret, scope,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
user_agent=None,
|
||||
message=None,
|
||||
callback_path='/oauth2callback',
|
||||
token_response_param=None,
|
||||
_storage_class=StorageByKeyName,
|
||||
_credentials_class=CredentialsModel,
|
||||
_credentials_property_name='credentials',
|
||||
**kwargs):
|
||||
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
user_agent=None,
|
||||
message=None,
|
||||
callback_path='/oauth2callback',
|
||||
token_response_param=None,
|
||||
_storage_class=StorageByKeyName,
|
||||
_credentials_class=CredentialsModel,
|
||||
_credentials_property_name='credentials',
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2Decorator
|
||||
|
||||
Args:
|
||||
@@ -659,13 +662,14 @@ class OAuth2Decorator(object):
|
||||
provider can be used.
|
||||
user_agent: string, User agent of your application, default to
|
||||
None.
|
||||
message: Message to display if there are problems with the OAuth 2.0
|
||||
configuration. The message may contain HTML and will be
|
||||
presented on the web interface for any method that uses
|
||||
the decorator.
|
||||
message: Message to display if there are problems with the
|
||||
OAuth 2.0 configuration. The message may contain HTML and
|
||||
will be presented on the web interface for any method that
|
||||
uses the decorator.
|
||||
callback_path: string, The absolute path to use as the callback
|
||||
URI. Note that this must match up with the URI given
|
||||
when registering the application in the APIs Console.
|
||||
when registering the application in the APIs
|
||||
Console.
|
||||
token_response_param: string. If provided, the full JSON response
|
||||
to the access token request will be encoded
|
||||
and included in this query parameter in the
|
||||
@@ -730,19 +734,21 @@ class OAuth2Decorator(object):
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
# Don't use @login_decorator as this could be used in a
|
||||
# POST request.
|
||||
if not user:
|
||||
request_handler.redirect(users.create_login_url(
|
||||
request_handler.request.uri))
|
||||
request_handler.request.uri))
|
||||
return
|
||||
|
||||
self._create_flow(request_handler)
|
||||
|
||||
# Store the request URI in 'state' so we can use it later
|
||||
self.flow.params['state'] = _build_state_value(request_handler, user)
|
||||
self.flow.params['state'] = _build_state_value(
|
||||
request_handler, user)
|
||||
self.credentials = self._storage_class(
|
||||
self._credentials_class, None,
|
||||
self._credentials_property_name, user=user).get()
|
||||
self._credentials_class, None,
|
||||
self._credentials_property_name, user=user).get()
|
||||
|
||||
if not self.has_credentials():
|
||||
return request_handler.redirect(self.authorize_url())
|
||||
@@ -768,14 +774,12 @@ class OAuth2Decorator(object):
|
||||
"""
|
||||
if self.flow is None:
|
||||
redirect_uri = request_handler.request.relative_url(
|
||||
self._callback_path) # Usually /oauth2callback
|
||||
self.flow = OAuth2WebServerFlow(self._client_id, self._client_secret,
|
||||
self._scope, redirect_uri=redirect_uri,
|
||||
user_agent=self._user_agent,
|
||||
auth_uri=self._auth_uri,
|
||||
token_uri=self._token_uri,
|
||||
revoke_uri=self._revoke_uri,
|
||||
**self._kwargs)
|
||||
self._callback_path) # Usually /oauth2callback
|
||||
self.flow = OAuth2WebServerFlow(
|
||||
self._client_id, self._client_secret, self._scope,
|
||||
redirect_uri=redirect_uri, user_agent=self._user_agent,
|
||||
auth_uri=self._auth_uri, token_uri=self._token_uri,
|
||||
revoke_uri=self._revoke_uri, **self._kwargs)
|
||||
|
||||
def oauth_aware(self, method):
|
||||
"""Decorator that sets up for OAuth 2.0 dance, but doesn't do it.
|
||||
@@ -797,18 +801,20 @@ class OAuth2Decorator(object):
|
||||
return
|
||||
|
||||
user = users.get_current_user()
|
||||
# Don't use @login_decorator as this could be used in a POST request.
|
||||
# Don't use @login_decorator as this could be used in a
|
||||
# POST request.
|
||||
if not user:
|
||||
request_handler.redirect(users.create_login_url(
|
||||
request_handler.request.uri))
|
||||
request_handler.request.uri))
|
||||
return
|
||||
|
||||
self._create_flow(request_handler)
|
||||
|
||||
self.flow.params['state'] = _build_state_value(request_handler, user)
|
||||
self.flow.params['state'] = _build_state_value(request_handler,
|
||||
user)
|
||||
self.credentials = self._storage_class(
|
||||
self._credentials_class, None,
|
||||
self._credentials_property_name, user=user).get()
|
||||
self._credentials_class, None,
|
||||
self._credentials_property_name, user=user).get()
|
||||
try:
|
||||
resp = method(request_handler, *args, **kwargs)
|
||||
finally:
|
||||
@@ -885,21 +891,26 @@ class OAuth2Decorator(object):
|
||||
if error:
|
||||
errormsg = self.request.get('error_description', error)
|
||||
self.response.out.write(
|
||||
'The authorization request failed: %s' % _safe_html(errormsg))
|
||||
'The authorization request failed: %s' %
|
||||
_safe_html(errormsg))
|
||||
else:
|
||||
user = users.get_current_user()
|
||||
decorator._create_flow(self)
|
||||
credentials = decorator.flow.step2_exchange(self.request.params)
|
||||
credentials = decorator.flow.step2_exchange(
|
||||
self.request.params)
|
||||
decorator._storage_class(
|
||||
decorator._credentials_class, None,
|
||||
decorator._credentials_property_name, user=user).put(credentials)
|
||||
redirect_uri = _parse_state_value(str(self.request.get('state')),
|
||||
user)
|
||||
decorator._credentials_class, None,
|
||||
decorator._credentials_property_name,
|
||||
user=user).put(credentials)
|
||||
redirect_uri = _parse_state_value(
|
||||
str(self.request.get('state')), user)
|
||||
|
||||
if decorator._token_response_param and credentials.token_response:
|
||||
if (decorator._token_response_param and
|
||||
credentials.token_response):
|
||||
resp_json = json.dumps(credentials.token_response)
|
||||
redirect_uri = util._add_query_parameter(
|
||||
redirect_uri, decorator._token_response_param, resp_json)
|
||||
redirect_uri, decorator._token_response_param,
|
||||
resp_json)
|
||||
|
||||
self.redirect(redirect_uri)
|
||||
|
||||
@@ -916,7 +927,7 @@ class OAuth2Decorator(object):
|
||||
server during the OAuth 2.0 dance.
|
||||
"""
|
||||
return webapp.WSGIApplication([
|
||||
(self.callback_path, self.callback_handler())
|
||||
(self.callback_path, self.callback_handler())
|
||||
])
|
||||
|
||||
|
||||
@@ -959,23 +970,25 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
|
||||
**kwargs: dict, Keyword arguments are passed along as kwargs to
|
||||
the OAuth2WebServerFlow constructor.
|
||||
"""
|
||||
client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
|
||||
if client_type not in [
|
||||
clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
|
||||
client_type, client_info = clientsecrets.loadfile(filename,
|
||||
cache=cache)
|
||||
if client_type not in (clientsecrets.TYPE_WEB,
|
||||
clientsecrets.TYPE_INSTALLED):
|
||||
raise InvalidClientSecretsError(
|
||||
"OAuth2Decorator doesn't support this OAuth 2.0 flow.")
|
||||
"OAuth2Decorator doesn't support this OAuth 2.0 flow.")
|
||||
|
||||
constructor_kwargs = dict(kwargs)
|
||||
constructor_kwargs.update({
|
||||
'auth_uri': client_info['auth_uri'],
|
||||
'token_uri': client_info['token_uri'],
|
||||
'message': message,
|
||||
'auth_uri': client_info['auth_uri'],
|
||||
'token_uri': client_info['token_uri'],
|
||||
'message': message,
|
||||
})
|
||||
revoke_uri = client_info.get('revoke_uri')
|
||||
if revoke_uri is not None:
|
||||
constructor_kwargs['revoke_uri'] = revoke_uri
|
||||
super(OAuth2DecoratorFromClientSecrets, self).__init__(
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
if message is not None:
|
||||
self._message = message
|
||||
else:
|
||||
@@ -1001,4 +1014,4 @@ def oauth2decorator_from_clientsecrets(filename, scope,
|
||||
Returns: An OAuth2Decorator
|
||||
"""
|
||||
return OAuth2DecoratorFromClientSecrets(filename, scope,
|
||||
message=message, cache=cache)
|
||||
message=message, cache=cache)
|
||||
|
||||
@@ -93,12 +93,13 @@ _CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG'
|
||||
# The error message we show users when we can't find the Application
|
||||
# Default Credentials.
|
||||
ADC_HELP_MSG = (
|
||||
'The Application Default Credentials are not available. They are available '
|
||||
'if running in Google Compute Engine. Otherwise, the environment variable '
|
||||
+ GOOGLE_APPLICATION_CREDENTIALS +
|
||||
'The Application Default Credentials are not available. They are '
|
||||
'available if running in Google Compute Engine. Otherwise, the '
|
||||
'environment variable ' +
|
||||
GOOGLE_APPLICATION_CREDENTIALS +
|
||||
' must be defined pointing to a file defining the credentials. See '
|
||||
'https://developers.google.com/accounts/docs/application-default-credentials' # pylint:disable=line-too-long
|
||||
' for more information.')
|
||||
'https://developers.google.com/accounts/docs/'
|
||||
'application-default-credentials for more information.')
|
||||
|
||||
# The access token along with the seconds in which it expires.
|
||||
AccessTokenInfo = collections.namedtuple(
|
||||
@@ -132,7 +133,7 @@ class TokenRevokeError(Error):
|
||||
|
||||
|
||||
class UnknownClientSecretsFlowError(Error):
|
||||
"""The client secrets file called for an unknown type of OAuth 2.0 flow. """
|
||||
"""The client secrets file called for an unknown type of OAuth 2.0 flow."""
|
||||
|
||||
|
||||
class AccessTokenCredentialsError(Error):
|
||||
@@ -247,7 +248,7 @@ class Credentials(object):
|
||||
if member in d:
|
||||
del d[member]
|
||||
if (d.get('token_expiry') and
|
||||
isinstance(d['token_expiry'], datetime.datetime)):
|
||||
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.
|
||||
d['_class'] = t.__name__
|
||||
@@ -283,16 +284,19 @@ class Credentials(object):
|
||||
"""
|
||||
json_string_as_unicode = _from_bytes(s)
|
||||
data = json.loads(json_string_as_unicode)
|
||||
# Find and call the right classmethod from_json() to restore the object.
|
||||
# Find and call the right classmethod from_json() to restore
|
||||
# the object.
|
||||
module_name = data['_module']
|
||||
try:
|
||||
module_obj = __import__(module_name)
|
||||
except ImportError:
|
||||
# In case there's an object from the old package structure, update it
|
||||
# In case there's an object from the old package structure,
|
||||
# update it
|
||||
module_name = module_name.replace('.googleapiclient', '')
|
||||
module_obj = __import__(module_name)
|
||||
|
||||
module_obj = __import__(module_name, fromlist=module_name.split('.')[:-1])
|
||||
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)
|
||||
@@ -465,9 +469,9 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
@util.positional(8)
|
||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent, revoke_uri=None,
|
||||
id_token=None, token_response=None, scopes=None,
|
||||
token_info_uri=None):
|
||||
token_expiry, token_uri, user_agent, revoke_uri=None,
|
||||
id_token=None, token_response=None, scopes=None,
|
||||
token_info_uri=None):
|
||||
"""Create an instance of OAuth2Credentials.
|
||||
|
||||
This constructor is not usually called by the user, instead
|
||||
@@ -522,10 +526,9 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
The modified http.request method will add authentication headers to
|
||||
each request and will refresh access_tokens when a 401 is received on a
|
||||
request. In addition the http.request method has a credentials property,
|
||||
http.request.credentials, which is the Credentials object that
|
||||
authorized it.
|
||||
|
||||
request. In addition the http.request method has a credentials
|
||||
property, http.request.credentials, which is the Credentials object
|
||||
that authorized it.
|
||||
|
||||
Args:
|
||||
http: An instance of ``httplib2.Http`` or something that acts
|
||||
@@ -549,10 +552,11 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
# The closure that will replace 'httplib2.Http.request'.
|
||||
def new_request(uri, method='GET', body=None, headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
|
||||
connection_type=None):
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
|
||||
connection_type=None):
|
||||
if not self.access_token:
|
||||
logger.info('Attempting refresh to obtain initial access_token')
|
||||
logger.info('Attempting refresh to obtain '
|
||||
'initial access_token')
|
||||
self._refresh(request_orig)
|
||||
|
||||
# Clone and modify the request headers to add the appropriate
|
||||
@@ -565,33 +569,37 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
if self.user_agent is not None:
|
||||
if 'user-agent' in headers:
|
||||
headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
|
||||
headers['user-agent'] = (self.user_agent + ' ' +
|
||||
headers['user-agent'])
|
||||
else:
|
||||
headers['user-agent'] = self.user_agent
|
||||
|
||||
body_stream_position = None
|
||||
if all(getattr(body, stream_prop, None) for stream_prop in
|
||||
('read', 'seek', 'tell')):
|
||||
if all(getattr(body, stream_prop, None) for stream_prop in
|
||||
('read', 'seek', 'tell')):
|
||||
body_stream_position = body.tell()
|
||||
|
||||
resp, content = request_orig(uri, method, body, clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
resp, content = request_orig(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
# A stored token may expire between the time it is retrieved and the time
|
||||
# the request is made, so we may need to try twice.
|
||||
# A stored token may expire between the time it is retrieved and
|
||||
# the time the request is made, so we may need to try twice.
|
||||
max_refresh_attempts = 2
|
||||
for refresh_attempt in range(max_refresh_attempts):
|
||||
if resp.status not in REFRESH_STATUS_CODES:
|
||||
break
|
||||
logger.info('Refreshing due to a %s (attempt %s/%s)', resp.status,
|
||||
refresh_attempt + 1, max_refresh_attempts)
|
||||
logger.info('Refreshing due to a %s (attempt %s/%s)',
|
||||
resp.status, refresh_attempt + 1,
|
||||
max_refresh_attempts)
|
||||
self._refresh(request_orig)
|
||||
self.apply(headers)
|
||||
if body_stream_position is not None:
|
||||
body.seek(body_stream_position)
|
||||
|
||||
resp, content = request_orig(uri, method, body, clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
resp, content = request_orig(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
return (resp, content)
|
||||
|
||||
@@ -681,25 +689,25 @@ class OAuth2Credentials(Credentials):
|
||||
s = _from_bytes(s)
|
||||
data = json.loads(s)
|
||||
if (data.get('token_expiry') and
|
||||
not isinstance(data['token_expiry'], datetime.datetime)):
|
||||
not isinstance(data['token_expiry'], datetime.datetime)):
|
||||
try:
|
||||
data['token_expiry'] = datetime.datetime.strptime(
|
||||
data['token_expiry'], EXPIRY_FORMAT)
|
||||
data['token_expiry'], EXPIRY_FORMAT)
|
||||
except ValueError:
|
||||
data['token_expiry'] = None
|
||||
retval = cls(
|
||||
data['access_token'],
|
||||
data['client_id'],
|
||||
data['client_secret'],
|
||||
data['refresh_token'],
|
||||
data['token_expiry'],
|
||||
data['token_uri'],
|
||||
data['user_agent'],
|
||||
revoke_uri=data.get('revoke_uri', None),
|
||||
id_token=data.get('id_token', None),
|
||||
token_response=data.get('token_response', None),
|
||||
scopes=data.get('scopes', None),
|
||||
token_info_uri=data.get('token_info_uri', None))
|
||||
data['access_token'],
|
||||
data['client_id'],
|
||||
data['client_secret'],
|
||||
data['refresh_token'],
|
||||
data['token_expiry'],
|
||||
data['token_uri'],
|
||||
data['user_agent'],
|
||||
revoke_uri=data.get('revoke_uri', None),
|
||||
id_token=data.get('id_token', None),
|
||||
token_response=data.get('token_response', None),
|
||||
scopes=data.get('scopes', None),
|
||||
token_info_uri=data.get('token_info_uri', None))
|
||||
retval.invalid = data['invalid']
|
||||
return retval
|
||||
|
||||
@@ -718,7 +726,7 @@ class OAuth2Credentials(Credentials):
|
||||
now = datetime.datetime.utcnow()
|
||||
if now >= self.token_expiry:
|
||||
logger.info('access_token is expired. Now: %s, token_expiry: %s',
|
||||
now, self.token_expiry)
|
||||
now, self.token_expiry)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -733,7 +741,7 @@ class OAuth2Credentials(Credentials):
|
||||
http = httplib2.Http()
|
||||
self.refresh(http)
|
||||
return AccessTokenInfo(access_token=self.access_token,
|
||||
expires_in=self._expires_in())
|
||||
expires_in=self._expires_in())
|
||||
|
||||
def set_store(self, store):
|
||||
"""Set the Storage for the credential.
|
||||
@@ -785,17 +793,17 @@ class OAuth2Credentials(Credentials):
|
||||
def _generate_refresh_request_body(self):
|
||||
"""Generate the body that will be used in the refresh request."""
|
||||
body = urllib.parse.urlencode({
|
||||
'grant_type': 'refresh_token',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'refresh_token': self.refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'refresh_token': self.refresh_token,
|
||||
})
|
||||
return body
|
||||
|
||||
def _generate_refresh_request_headers(self):
|
||||
"""Generate the headers that will be used in the refresh request."""
|
||||
headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
if self.user_agent is not None:
|
||||
@@ -826,8 +834,8 @@ class OAuth2Credentials(Credentials):
|
||||
new_cred = self.store.locked_get()
|
||||
|
||||
if (new_cred and not new_cred.invalid and
|
||||
new_cred.access_token != self.access_token and
|
||||
not new_cred.access_token_expired):
|
||||
new_cred.access_token != self.access_token and
|
||||
not new_cred.access_token_expired):
|
||||
logger.info('Updated access_token read from Storage')
|
||||
self._updateFromCredential(new_cred)
|
||||
else:
|
||||
@@ -851,7 +859,7 @@ class OAuth2Credentials(Credentials):
|
||||
|
||||
logger.info('Refreshing access_token')
|
||||
resp, content = http_request(
|
||||
self.token_uri, method='POST', body=body, headers=headers)
|
||||
self.token_uri, method='POST', body=body, headers=headers)
|
||||
content = _from_bytes(content)
|
||||
if resp.status == 200:
|
||||
d = json.loads(content)
|
||||
@@ -860,7 +868,7 @@ class OAuth2Credentials(Credentials):
|
||||
self.refresh_token = d.get('refresh_token', self.refresh_token)
|
||||
if 'expires_in' in d:
|
||||
self.token_expiry = datetime.timedelta(
|
||||
seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
|
||||
seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
|
||||
else:
|
||||
self.token_expiry = None
|
||||
# On temporary refresh errors, the user does not actually have to
|
||||
@@ -869,8 +877,8 @@ class OAuth2Credentials(Credentials):
|
||||
if self.store:
|
||||
self.store.locked_put(self)
|
||||
else:
|
||||
# An {'error':...} response body means the token is expired or revoked,
|
||||
# so we flag the credentials as such.
|
||||
# An {'error':...} response body means the token is expired or
|
||||
# revoked, so we flag the credentials as such.
|
||||
logger.info('Failed to retrieve access token: %s', content)
|
||||
error_msg = 'Invalid response %s.' % resp['status']
|
||||
try:
|
||||
@@ -955,14 +963,15 @@ class OAuth2Credentials(Credentials):
|
||||
"""
|
||||
logger.info('Refreshing scopes')
|
||||
query_params = {'access_token': token, 'fields': 'scope'}
|
||||
token_info_uri = _update_query_params(self.token_info_uri, query_params)
|
||||
token_info_uri = _update_query_params(self.token_info_uri,
|
||||
query_params)
|
||||
resp, content = http_request(token_info_uri)
|
||||
content = _from_bytes(content)
|
||||
if resp.status == 200:
|
||||
d = json.loads(content)
|
||||
self.scopes = set(util.string_to_scopes(d.get('scope', '')))
|
||||
else:
|
||||
error_msg = 'Invalid response %s.' % (resp.status, )
|
||||
error_msg = 'Invalid response %s.' % (resp.status,)
|
||||
try:
|
||||
d = json.loads(content)
|
||||
if 'error_description' in d:
|
||||
@@ -1012,26 +1021,26 @@ class AccessTokenCredentials(OAuth2Credentials):
|
||||
token can't be revoked if this is None.
|
||||
"""
|
||||
super(AccessTokenCredentials, self).__init__(
|
||||
access_token,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user_agent,
|
||||
revoke_uri=revoke_uri)
|
||||
access_token,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
user_agent,
|
||||
revoke_uri=revoke_uri)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, s):
|
||||
data = json.loads(_from_bytes(s))
|
||||
retval = AccessTokenCredentials(
|
||||
data['access_token'],
|
||||
data['user_agent'])
|
||||
data['access_token'],
|
||||
data['user_agent'])
|
||||
return retval
|
||||
|
||||
def _refresh(self, http_request):
|
||||
raise AccessTokenCredentialsError(
|
||||
'The access_token is expired or invalid and can\'t be refreshed.')
|
||||
'The access_token is expired or invalid and can\'t be refreshed.')
|
||||
|
||||
def _revoke(self, http_request):
|
||||
"""Revokes the access_token and deletes the store if available.
|
||||
@@ -1143,8 +1152,8 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
"""
|
||||
|
||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent,
|
||||
revoke_uri=GOOGLE_REVOKE_URI):
|
||||
token_expiry, token_uri, user_agent,
|
||||
revoke_uri=GOOGLE_REVOKE_URI):
|
||||
"""Create an instance of GoogleCredentials.
|
||||
|
||||
This constructor is not usually called by the user, instead
|
||||
@@ -1166,8 +1175,8 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
is None.
|
||||
"""
|
||||
super(GoogleCredentials, self).__init__(
|
||||
access_token, client_id, client_secret, refresh_token, token_expiry,
|
||||
token_uri, user_agent, revoke_uri=revoke_uri)
|
||||
access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent, revoke_uri=revoke_uri)
|
||||
|
||||
def create_scoped_required(self):
|
||||
"""Whether this Credentials object is scopeless.
|
||||
@@ -1188,10 +1197,10 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
def serialization_data(self):
|
||||
"""Get the fields and values identifying the current credentials."""
|
||||
return {
|
||||
'type': 'authorized_user',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'refresh_token': self.refresh_token
|
||||
'type': 'authorized_user',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'refresh_token': self.refresh_token
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -1247,27 +1256,29 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
credentials_filename = _get_well_known_file()
|
||||
if os.path.isfile(credentials_filename):
|
||||
extra_help = (' (produced automatically when running'
|
||||
' "gcloud auth login" command)')
|
||||
' "gcloud auth login" command)')
|
||||
else:
|
||||
credentials_filename = None
|
||||
else:
|
||||
extra_help = (' (pointed to by ' + GOOGLE_APPLICATION_CREDENTIALS +
|
||||
' environment variable)')
|
||||
' environment variable)')
|
||||
|
||||
if not credentials_filename:
|
||||
return
|
||||
|
||||
# If we can read the credentials from a file, we don't need to know what
|
||||
# environment we are in.
|
||||
# If we can read the credentials from a file, we don't need to know
|
||||
# what environment we are in.
|
||||
SETTINGS.env_name = DEFAULT_ENV_NAME
|
||||
|
||||
try:
|
||||
return _get_application_default_credential_from_file(credentials_filename)
|
||||
return _get_application_default_credential_from_file(
|
||||
credentials_filename)
|
||||
except (ApplicationDefaultCredentialsError, ValueError) as error:
|
||||
_raise_exception_for_reading_json(credentials_filename, extra_help, error)
|
||||
_raise_exception_for_reading_json(credentials_filename,
|
||||
extra_help, error)
|
||||
|
||||
@classmethod
|
||||
def _get_implicit_credentials(cls):
|
||||
def _get_implicit_credentials(cls):
|
||||
"""Gets credentials implicitly from the environment.
|
||||
|
||||
Checks environment in order of precedence:
|
||||
@@ -1283,9 +1294,9 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
"""
|
||||
# Environ checks (in order).
|
||||
environ_checkers = [
|
||||
cls._implicit_credentials_from_gae,
|
||||
cls._implicit_credentials_from_files,
|
||||
cls._implicit_credentials_from_gce,
|
||||
cls._implicit_credentials_from_gae,
|
||||
cls._implicit_credentials_from_files,
|
||||
cls._implicit_credentials_from_gce,
|
||||
]
|
||||
|
||||
for checker in environ_checkers:
|
||||
@@ -1323,16 +1334,17 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
if credential_filename and os.path.isfile(credential_filename):
|
||||
try:
|
||||
return _get_application_default_credential_from_file(
|
||||
credential_filename)
|
||||
credential_filename)
|
||||
except (ApplicationDefaultCredentialsError, ValueError) as error:
|
||||
extra_help = ' (provided as parameter to the from_stream() method)'
|
||||
extra_help = (' (provided as parameter to the '
|
||||
'from_stream() method)')
|
||||
_raise_exception_for_reading_json(credential_filename,
|
||||
extra_help,
|
||||
error)
|
||||
extra_help,
|
||||
error)
|
||||
else:
|
||||
raise ApplicationDefaultCredentialsError(
|
||||
'The parameter passed to the from_stream() '
|
||||
'method should point to a file.')
|
||||
'The parameter passed to the from_stream() '
|
||||
'method should point to a file.')
|
||||
|
||||
|
||||
def _save_private_file(filename, json_contents):
|
||||
@@ -1346,7 +1358,7 @@ def _save_private_file(filename, json_contents):
|
||||
file_desc = os.open(temp_filename, os.O_WRONLY | os.O_CREAT, 0o600)
|
||||
with os.fdopen(file_desc, 'w') as file_handle:
|
||||
json.dump(json_contents, file_handle, sort_keys=True,
|
||||
indent=2, separators=(',', ': '))
|
||||
indent=2, separators=(',', ': '))
|
||||
shutil.move(temp_filename, filename)
|
||||
|
||||
|
||||
@@ -1384,9 +1396,10 @@ def _get_environment_variable_file():
|
||||
return application_default_credential_filename
|
||||
else:
|
||||
raise ApplicationDefaultCredentialsError(
|
||||
'File ' + application_default_credential_filename + ' (pointed by ' +
|
||||
GOOGLE_APPLICATION_CREDENTIALS +
|
||||
' environment variable) does not exist!')
|
||||
'File ' + application_default_credential_filename +
|
||||
' (pointed by ' +
|
||||
GOOGLE_APPLICATION_CREDENTIALS +
|
||||
' environment variable) does not exist!')
|
||||
|
||||
|
||||
def _get_well_known_file():
|
||||
@@ -1401,16 +1414,17 @@ def _get_well_known_file():
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
default_config_dir = os.path.join(os.environ['APPDATA'],
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
except KeyError:
|
||||
# This should never happen unless someone is really messing with things.
|
||||
# This should never happen unless someone is really
|
||||
# messing with things.
|
||||
drive = os.environ.get('SystemDrive', 'C:')
|
||||
default_config_dir = os.path.join(drive, '\\',
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
else:
|
||||
default_config_dir = os.path.join(os.path.expanduser('~'),
|
||||
'.config',
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
'.config',
|
||||
_CLOUDSDK_CONFIG_DIRECTORY)
|
||||
|
||||
return os.path.join(default_config_dir, WELL_KNOWN_CREDENTIALS_FILE)
|
||||
|
||||
@@ -1429,11 +1443,11 @@ def _get_application_default_credential_from_file(filename):
|
||||
required_fields = set(['client_id', 'client_secret', 'refresh_token'])
|
||||
elif credentials_type == SERVICE_ACCOUNT:
|
||||
required_fields = set(['client_id', 'client_email', 'private_key_id',
|
||||
'private_key'])
|
||||
'private_key'])
|
||||
else:
|
||||
raise ApplicationDefaultCredentialsError(
|
||||
"'type' field should be defined (and have one of the '" +
|
||||
AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
|
||||
"'type' field should be defined (and have one of the '" +
|
||||
AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
|
||||
|
||||
missing_fields = required_fields.difference(client_credentials.keys())
|
||||
|
||||
@@ -1442,25 +1456,25 @@ def _get_application_default_credential_from_file(filename):
|
||||
|
||||
if client_credentials['type'] == AUTHORIZED_USER:
|
||||
return GoogleCredentials(
|
||||
access_token=None,
|
||||
client_id=client_credentials['client_id'],
|
||||
client_secret=client_credentials['client_secret'],
|
||||
refresh_token=client_credentials['refresh_token'],
|
||||
token_expiry=None,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
user_agent='Python client library')
|
||||
access_token=None,
|
||||
client_id=client_credentials['client_id'],
|
||||
client_secret=client_credentials['client_secret'],
|
||||
refresh_token=client_credentials['refresh_token'],
|
||||
token_expiry=None,
|
||||
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=[])
|
||||
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=[])
|
||||
|
||||
|
||||
def _raise_exception_for_missing_fields(missing_fields):
|
||||
raise ApplicationDefaultCredentialsError(
|
||||
'The following field(s) must be defined: ' + ', '.join(missing_fields))
|
||||
'The following field(s) must be defined: ' + ', '.join(missing_fields))
|
||||
|
||||
|
||||
def _raise_exception_for_reading_json(credential_file,
|
||||
@@ -1496,9 +1510,9 @@ class AssertionCredentials(GoogleCredentials):
|
||||
|
||||
@util.positional(2)
|
||||
def __init__(self, assertion_type, user_agent=None,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
**unused_kwargs):
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
**unused_kwargs):
|
||||
"""Constructor for AssertionFlowCredentials.
|
||||
|
||||
Args:
|
||||
@@ -1512,22 +1526,22 @@ class AssertionCredentials(GoogleCredentials):
|
||||
revoke_uri: string, URI for revoke endpoint.
|
||||
"""
|
||||
super(AssertionCredentials, self).__init__(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
token_uri,
|
||||
user_agent,
|
||||
revoke_uri=revoke_uri)
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
token_uri,
|
||||
user_agent,
|
||||
revoke_uri=revoke_uri)
|
||||
self.assertion_type = assertion_type
|
||||
|
||||
def _generate_refresh_request_body(self):
|
||||
assertion = self._generate_assertion()
|
||||
|
||||
body = urllib.parse.urlencode({
|
||||
'assertion': assertion,
|
||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
'assertion': assertion,
|
||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
})
|
||||
|
||||
return body
|
||||
@@ -1574,14 +1588,14 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
|
||||
|
||||
@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):
|
||||
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:
|
||||
@@ -1606,10 +1620,10 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
|
||||
"""
|
||||
_RequireCryptoOrDie()
|
||||
super(SignedJwtAssertionCredentials, self).__init__(
|
||||
None,
|
||||
user_agent=user_agent,
|
||||
token_uri=token_uri,
|
||||
revoke_uri=revoke_uri,
|
||||
None,
|
||||
user_agent=user_agent,
|
||||
token_uri=token_uri,
|
||||
revoke_uri=revoke_uri,
|
||||
)
|
||||
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
@@ -1625,13 +1639,13 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
|
||||
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']
|
||||
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']
|
||||
@@ -1641,18 +1655,18 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
|
||||
"""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
|
||||
'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)
|
||||
private_key, self.private_key_password), payload)
|
||||
|
||||
# Only used in verify_id_token(), which is always calling to the same URI
|
||||
# for the certs.
|
||||
@@ -1712,7 +1726,7 @@ def _extract_id_token(id_token):
|
||||
|
||||
if len(segments) != 3:
|
||||
raise VerifyJwtTokenError(
|
||||
'Wrong number of segments in token: %s' % id_token)
|
||||
'Wrong number of segments in token: %s' % id_token)
|
||||
|
||||
return json.loads(_from_bytes(_urlsafe_b64decode(segments[1])))
|
||||
|
||||
@@ -1786,10 +1800,11 @@ def credentials_from_code(client_id, client_secret, scope, code,
|
||||
access token
|
||||
"""
|
||||
flow = OAuth2WebServerFlow(client_id, client_secret, scope,
|
||||
redirect_uri=redirect_uri, user_agent=user_agent,
|
||||
auth_uri=auth_uri, token_uri=token_uri,
|
||||
revoke_uri=revoke_uri, device_uri=device_uri,
|
||||
token_info_uri=token_info_uri)
|
||||
redirect_uri=redirect_uri,
|
||||
user_agent=user_agent, auth_uri=auth_uri,
|
||||
token_uri=token_uri, revoke_uri=revoke_uri,
|
||||
device_uri=device_uri,
|
||||
token_info_uri=token_info_uri)
|
||||
|
||||
credentials = flow.step2_exchange(code, http=http)
|
||||
return credentials
|
||||
@@ -1836,16 +1851,16 @@ def credentials_from_clientsecrets_and_code(filename, scope, code,
|
||||
clientsecrets.InvalidClientSecretsError: if the clientsecrets file is
|
||||
invalid.
|
||||
"""
|
||||
flow = flow_from_clientsecrets(filename, scope, message=message, cache=cache,
|
||||
redirect_uri=redirect_uri,
|
||||
device_uri=device_uri)
|
||||
flow = flow_from_clientsecrets(filename, scope, message=message,
|
||||
cache=cache, redirect_uri=redirect_uri,
|
||||
device_uri=device_uri)
|
||||
credentials = flow.step2_exchange(code, http=http)
|
||||
return credentials
|
||||
|
||||
|
||||
class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
|
||||
'device_code', 'user_code', 'interval', 'verification_url',
|
||||
'user_code_expiry'))):
|
||||
'device_code', 'user_code', 'interval', 'verification_url',
|
||||
'user_code_expiry'))):
|
||||
"""Intermediate information the OAuth2 for devices flow."""
|
||||
|
||||
@classmethod
|
||||
@@ -1858,26 +1873,26 @@ class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
|
||||
"""
|
||||
# device_code, user_code, and verification_url are required.
|
||||
kwargs = {
|
||||
'device_code': response['device_code'],
|
||||
'user_code': response['user_code'],
|
||||
'device_code': response['device_code'],
|
||||
'user_code': response['user_code'],
|
||||
}
|
||||
# The response may list the verification address as either
|
||||
# verification_url or verification_uri, so we check for both.
|
||||
verification_url = response.get(
|
||||
'verification_url', response.get('verification_uri'))
|
||||
'verification_url', response.get('verification_uri'))
|
||||
if verification_url is None:
|
||||
raise OAuth2DeviceCodeError(
|
||||
'No verification_url provided in server response')
|
||||
'No verification_url provided in server response')
|
||||
kwargs['verification_url'] = verification_url
|
||||
# expires_in and interval are optional.
|
||||
kwargs.update({
|
||||
'interval': response.get('interval'),
|
||||
'user_code_expiry': None,
|
||||
'interval': response.get('interval'),
|
||||
'user_code_expiry': None,
|
||||
})
|
||||
if 'expires_in' in response:
|
||||
kwargs['user_code_expiry'] = datetime.datetime.now() + datetime.timedelta(
|
||||
seconds=int(response['expires_in']))
|
||||
|
||||
kwargs['user_code_expiry'] = (
|
||||
datetime.datetime.now() +
|
||||
datetime.timedelta(seconds=int(response['expires_in'])))
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
@@ -1889,18 +1904,18 @@ class OAuth2WebServerFlow(Flow):
|
||||
|
||||
@util.positional(4)
|
||||
def __init__(self, client_id,
|
||||
client_secret=None,
|
||||
scope=None,
|
||||
redirect_uri=None,
|
||||
user_agent=None,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
login_hint=None,
|
||||
device_uri=GOOGLE_DEVICE_URI,
|
||||
token_info_uri=GOOGLE_TOKEN_INFO_URI,
|
||||
authorization_header=None,
|
||||
**kwargs):
|
||||
client_secret=None,
|
||||
scope=None,
|
||||
redirect_uri=None,
|
||||
user_agent=None,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
login_hint=None,
|
||||
device_uri=GOOGLE_DEVICE_URI,
|
||||
token_info_uri=GOOGLE_TOKEN_INFO_URI,
|
||||
authorization_header=None,
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2WebServerFlow.
|
||||
|
||||
The kwargs argument is used to set extra query parameters on the
|
||||
@@ -1915,7 +1930,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob'
|
||||
for a non-web-based application, or a URI that
|
||||
handles the callback from the authorization server.
|
||||
user_agent: string, HTTP User-Agent to provide for this application.
|
||||
user_agent: string, HTTP User-Agent to provide for this
|
||||
application.
|
||||
auth_uri: string, URI for authorization endpoint. For convenience
|
||||
defaults to Google's endpoints but any OAuth 2.0 provider
|
||||
can be used.
|
||||
@@ -1956,8 +1972,8 @@ class OAuth2WebServerFlow(Flow):
|
||||
self.token_info_uri = token_info_uri
|
||||
self.authorization_header = authorization_header
|
||||
self.params = {
|
||||
'access_type': 'offline',
|
||||
'response_type': 'code',
|
||||
'access_type': 'offline',
|
||||
'response_type': 'code',
|
||||
}
|
||||
self.params.update(kwargs)
|
||||
|
||||
@@ -1981,18 +1997,19 @@ class OAuth2WebServerFlow(Flow):
|
||||
"""
|
||||
if redirect_uri is not None:
|
||||
logger.warning((
|
||||
'The redirect_uri parameter for '
|
||||
'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. Please '
|
||||
'move to passing the redirect_uri in via the constructor.'))
|
||||
'The redirect_uri parameter for '
|
||||
'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. '
|
||||
'Please move to passing the redirect_uri in via the '
|
||||
'constructor.'))
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
if self.redirect_uri is None:
|
||||
raise ValueError('The value of redirect_uri must not be None.')
|
||||
|
||||
query_params = {
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'scope': self.scope,
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'scope': self.scope,
|
||||
}
|
||||
if state is not None:
|
||||
query_params['state'] = state
|
||||
@@ -2013,11 +2030,11 @@ class OAuth2WebServerFlow(Flow):
|
||||
raise ValueError('The value of device_uri must not be None.')
|
||||
|
||||
body = urllib.parse.urlencode({
|
||||
'client_id': self.client_id,
|
||||
'scope': self.scope,
|
||||
'client_id': self.client_id,
|
||||
'scope': self.scope,
|
||||
})
|
||||
headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
if self.user_agent is not None:
|
||||
@@ -2027,15 +2044,15 @@ class OAuth2WebServerFlow(Flow):
|
||||
http = httplib2.Http()
|
||||
|
||||
resp, content = http.request(self.device_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
headers=headers)
|
||||
content = _from_bytes(content)
|
||||
if resp.status == 200:
|
||||
try:
|
||||
flow_info = json.loads(content)
|
||||
except ValueError as e:
|
||||
raise OAuth2DeviceCodeError(
|
||||
'Could not parse server response as JSON: "%s", error: "%s"' % (
|
||||
content, e))
|
||||
'Could not parse server response as JSON: "%s", '
|
||||
'error: "%s"' % (content, e))
|
||||
return DeviceFlowInfo.FromResponse(flow_info)
|
||||
else:
|
||||
error_msg = 'Invalid response %s.' % resp.status
|
||||
@@ -2044,12 +2061,13 @@ class OAuth2WebServerFlow(Flow):
|
||||
if 'error' in d:
|
||||
error_msg += ' Error: %s' % d['error']
|
||||
except ValueError:
|
||||
# Couldn't decode a JSON response, stick with the default message.
|
||||
# Couldn't decode a JSON response, stick with the
|
||||
# default message.
|
||||
pass
|
||||
raise OAuth2DeviceCodeError(error_msg)
|
||||
|
||||
@util.positional(2)
|
||||
def step2_exchange(self, code=None, http=None, device_flow_info=None):
|
||||
def step2_exchange(self, code=None, http=None, device_flow_info=None):
|
||||
"""Exchanges a code for OAuth2Credentials.
|
||||
|
||||
Args:
|
||||
@@ -2081,13 +2099,13 @@ class OAuth2WebServerFlow(Flow):
|
||||
elif not isinstance(code, six.string_types):
|
||||
if 'code' not in code:
|
||||
raise FlowExchangeError(code.get(
|
||||
'error', 'No code was supplied in the query parameters.'))
|
||||
'error', 'No code was supplied in the query parameters.'))
|
||||
code = code['code']
|
||||
|
||||
post_data = {
|
||||
'client_id': self.client_id,
|
||||
'code': code,
|
||||
'scope': self.scope,
|
||||
'client_id': self.client_id,
|
||||
'code': code,
|
||||
'scope': self.scope,
|
||||
}
|
||||
if self.client_secret is not None:
|
||||
post_data['client_secret'] = self.client_secret
|
||||
@@ -2098,7 +2116,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
post_data['redirect_uri'] = self.redirect_uri
|
||||
body = urllib.parse.urlencode(post_data)
|
||||
headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
if self.authorization_header is not None:
|
||||
headers['Authorization'] = self.authorization_header
|
||||
@@ -2109,38 +2127,38 @@ class OAuth2WebServerFlow(Flow):
|
||||
http = httplib2.Http()
|
||||
|
||||
resp, content = http.request(self.token_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
headers=headers)
|
||||
d = _parse_exchange_token_response(content)
|
||||
if resp.status == 200 and 'access_token' in d:
|
||||
access_token = d['access_token']
|
||||
refresh_token = d.get('refresh_token', None)
|
||||
if not refresh_token:
|
||||
logger.info(
|
||||
'Received token response with no refresh_token. Consider '
|
||||
"reauthenticating with approval_prompt='force'.")
|
||||
'Received token response with no refresh_token. Consider '
|
||||
"reauthenticating with approval_prompt='force'.")
|
||||
token_expiry = None
|
||||
if 'expires_in' in d:
|
||||
token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
|
||||
seconds=int(d['expires_in']))
|
||||
token_expiry = (
|
||||
datetime.datetime.utcnow() +
|
||||
datetime.timedelta(seconds=int(d['expires_in'])))
|
||||
|
||||
extracted_id_token = None
|
||||
if 'id_token' in d:
|
||||
extracted_id_token = _extract_id_token(d['id_token'])
|
||||
|
||||
logger.info('Successfully retrieved access token')
|
||||
return OAuth2Credentials(access_token, self.client_id,
|
||||
self.client_secret, refresh_token, token_expiry,
|
||||
self.token_uri, self.user_agent,
|
||||
revoke_uri=self.revoke_uri,
|
||||
id_token=extracted_id_token,
|
||||
token_response=d,
|
||||
scopes=self.scope,
|
||||
token_info_uri=self.token_info_uri)
|
||||
return OAuth2Credentials(
|
||||
access_token, self.client_id, self.client_secret,
|
||||
refresh_token, token_expiry, self.token_uri, self.user_agent,
|
||||
revoke_uri=self.revoke_uri, id_token=extracted_id_token,
|
||||
token_response=d, scopes=self.scope,
|
||||
token_info_uri=self.token_info_uri)
|
||||
else:
|
||||
logger.info('Failed to retrieve access token: %s', content)
|
||||
if 'error' in d:
|
||||
# you never know what those providers got to say
|
||||
error_msg = str(d['error']) + str(d.get('error_description', ''))
|
||||
error_msg = (str(d['error']) +
|
||||
str(d.get('error_description', '')))
|
||||
else:
|
||||
error_msg = 'Invalid response: %s.' % str(resp.status)
|
||||
raise FlowExchangeError(error_msg)
|
||||
@@ -2187,13 +2205,15 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
invalid.
|
||||
"""
|
||||
try:
|
||||
client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
|
||||
if client_type in (clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED):
|
||||
client_type, client_info = clientsecrets.loadfile(filename,
|
||||
cache=cache)
|
||||
if client_type in (clientsecrets.TYPE_WEB,
|
||||
clientsecrets.TYPE_INSTALLED):
|
||||
constructor_kwargs = {
|
||||
'redirect_uri': redirect_uri,
|
||||
'auth_uri': client_info['auth_uri'],
|
||||
'token_uri': client_info['token_uri'],
|
||||
'login_hint': login_hint,
|
||||
'redirect_uri': redirect_uri,
|
||||
'auth_uri': client_info['auth_uri'],
|
||||
'token_uri': client_info['token_uri'],
|
||||
'login_hint': login_hint,
|
||||
}
|
||||
revoke_uri = client_info.get('revoke_uri')
|
||||
if revoke_uri is not None:
|
||||
@@ -2201,8 +2221,8 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
if device_uri is not None:
|
||||
constructor_kwargs['device_uri'] = device_uri
|
||||
return OAuth2WebServerFlow(
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
|
||||
except clientsecrets.InvalidClientSecretsError:
|
||||
if message:
|
||||
@@ -2211,4 +2231,4 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
raise
|
||||
else:
|
||||
raise UnknownClientSecretsFlowError(
|
||||
'This OAuth 2.0 flow is unsupported: %r' % client_type)
|
||||
'This OAuth 2.0 flow is unsupported: %r' % client_type)
|
||||
|
||||
@@ -70,30 +70,31 @@ class InvalidClientSecretsError(Error):
|
||||
|
||||
def _validate_clientsecrets(obj):
|
||||
_INVALID_FILE_FORMAT_MSG = (
|
||||
'Invalid file format. See '
|
||||
'https://developers.google.com/api-client-library/'
|
||||
'python/guide/aaa_client_secrets')
|
||||
'Invalid file format. See '
|
||||
'https://developers.google.com/api-client-library/'
|
||||
'python/guide/aaa_client_secrets')
|
||||
|
||||
if obj is None:
|
||||
raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG)
|
||||
if len(obj) != 1:
|
||||
raise InvalidClientSecretsError(
|
||||
_INVALID_FILE_FORMAT_MSG + ' '
|
||||
'Expected a JSON object with a single property for a "web" or '
|
||||
'"installed" application')
|
||||
_INVALID_FILE_FORMAT_MSG + ' '
|
||||
'Expected a JSON object with a single property for a "web" or '
|
||||
'"installed" application')
|
||||
client_type = tuple(obj)[0]
|
||||
if client_type not in VALID_CLIENT:
|
||||
raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type, ))
|
||||
raise InvalidClientSecretsError(
|
||||
'Unknown client type: %s.' % (client_type,))
|
||||
client_info = obj[client_type]
|
||||
for prop_name in VALID_CLIENT[client_type]['required']:
|
||||
if prop_name not in client_info:
|
||||
raise InvalidClientSecretsError(
|
||||
'Missing property "%s" in a client type of "%s".' % (prop_name,
|
||||
client_type))
|
||||
'Missing property "%s" in a client type of "%s".' %
|
||||
(prop_name, client_type))
|
||||
for prop_name in VALID_CLIENT[client_type]['string']:
|
||||
if client_info[prop_name].startswith('[['):
|
||||
raise InvalidClientSecretsError(
|
||||
'Property "%s" is not configured.' % prop_name)
|
||||
'Property "%s" is not configured.' % prop_name)
|
||||
return client_type, client_info
|
||||
|
||||
|
||||
|
||||
@@ -45,11 +45,9 @@ except ImportError:
|
||||
OpenSSLVerifier = None
|
||||
OpenSSLSigner = None
|
||||
|
||||
|
||||
def pkcs12_key_as_pem(*args, **kwargs):
|
||||
raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
|
||||
|
||||
|
||||
try:
|
||||
from oauth2client._pycrypto_crypt import PyCryptoVerifier
|
||||
from oauth2client._pycrypto_crypt import PyCryptoSigner
|
||||
@@ -66,7 +64,7 @@ elif PyCryptoSigner:
|
||||
Verifier = PyCryptoVerifier
|
||||
else:
|
||||
raise ImportError('No encryption library found. Please install either '
|
||||
'PyOpenSSL, or PyCrypto 2.6 or later')
|
||||
'PyOpenSSL, or PyCrypto 2.6 or later')
|
||||
|
||||
|
||||
def make_signed_jwt(signer, payload):
|
||||
@@ -157,10 +155,10 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
|
||||
|
||||
if now < earliest:
|
||||
raise AppIdentityError('Token used too early, %d < %d: %s' %
|
||||
(now, earliest, json_body))
|
||||
(now, earliest, json_body))
|
||||
if now > latest:
|
||||
raise AppIdentityError('Token used too late, %d > %d: %s' %
|
||||
(now, latest, json_body))
|
||||
(now, latest, json_body))
|
||||
|
||||
# Check audience.
|
||||
if audience is not None:
|
||||
@@ -169,6 +167,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
|
||||
raise AppIdentityError('No aud field in token: %s' % json_body)
|
||||
if aud != audience:
|
||||
raise AppIdentityError('Wrong recipient, %s != %s: %s' %
|
||||
(aud, audience, json_body))
|
||||
(aud, audience, json_body))
|
||||
|
||||
return parsed
|
||||
|
||||
@@ -35,8 +35,9 @@ class CommunicationError(Error):
|
||||
class NoDevshellServer(Error):
|
||||
"""Error when no Developer Shell server can be contacted."""
|
||||
|
||||
# The request for credential information to the Developer Shell client socket is
|
||||
# always an empty PBLite-formatted JSON object, so just define it as a constant.
|
||||
# The request for credential information to the Developer Shell client socket
|
||||
# is always an empty PBLite-formatted JSON object, so just define it as a
|
||||
# constant.
|
||||
CREDENTIAL_INFO_REQUEST_JSON = '[]'
|
||||
|
||||
|
||||
@@ -44,7 +45,9 @@ class CredentialInfoResponse(object):
|
||||
"""Credential information response from Developer Shell server.
|
||||
|
||||
The credential information response from Developer Shell socket is a
|
||||
PBLite-formatted JSON array with fields encoded by their index in the array:
|
||||
PBLite-formatted JSON array with fields encoded by their index in the
|
||||
array:
|
||||
|
||||
* Index 0 - user email
|
||||
* Index 1 - default project ID. None if the project context is not known.
|
||||
* Index 2 - OAuth2 access token. None if there is no valid auth context.
|
||||
@@ -103,13 +106,13 @@ class DevshellCredentials(client.GoogleCredentials):
|
||||
|
||||
def __init__(self, user_agent=None):
|
||||
super(DevshellCredentials, self).__init__(
|
||||
None, # access_token, initialized below
|
||||
None, # client_id
|
||||
None, # client_secret
|
||||
None, # refresh_token
|
||||
None, # token_expiry
|
||||
None, # token_uri
|
||||
user_agent)
|
||||
None, # access_token, initialized below
|
||||
None, # client_id
|
||||
None, # client_secret
|
||||
None, # refresh_token
|
||||
None, # token_expiry
|
||||
None, # token_uri
|
||||
user_agent)
|
||||
self._refresh(None)
|
||||
|
||||
def _refresh(self, http_request):
|
||||
@@ -127,9 +130,9 @@ class DevshellCredentials(client.GoogleCredentials):
|
||||
@classmethod
|
||||
def from_json(cls, json_data):
|
||||
raise NotImplementedError(
|
||||
'Cannot load Developer Shell credentials from JSON.')
|
||||
'Cannot load Developer Shell credentials from JSON.')
|
||||
|
||||
@property
|
||||
def serialization_data(self):
|
||||
raise NotImplementedError(
|
||||
'Cannot serialize Developer Shell credentials.')
|
||||
'Cannot serialize Developer Shell credentials.')
|
||||
|
||||
@@ -93,7 +93,8 @@ class Storage(BaseStorage):
|
||||
Args:
|
||||
model: db.Model, model class
|
||||
key_name: string, key name for the entity that has the credentials
|
||||
key_value: string, key value for the entity that has the credentials
|
||||
key_value: string, key value for the entity that has the
|
||||
credentials
|
||||
property_name: string, name of the property that is an
|
||||
CredentialsProperty
|
||||
"""
|
||||
@@ -124,12 +125,14 @@ class Storage(BaseStorage):
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
overwrite: Boolean, indicates whether you would like these
|
||||
credentials to overwrite any existing stored credentials.
|
||||
credentials to overwrite any existing stored
|
||||
credentials.
|
||||
"""
|
||||
args = {self.key_name: self.key_value}
|
||||
|
||||
if overwrite:
|
||||
entity, unused_is_new = self.model_class.objects.get_or_create(**args)
|
||||
(entity,
|
||||
unused_is_new) = self.model_class.objects.get_or_create(**args)
|
||||
else:
|
||||
entity = self.model_class(**args)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class Storage(BaseStorage):
|
||||
def _validate_file(self):
|
||||
if os.path.islink(self._filename):
|
||||
raise CredentialsFileSymbolicLinkError(
|
||||
'File: %s is a symbolic link.' % self._filename)
|
||||
'File: %s is a symbolic link.' % self._filename)
|
||||
|
||||
def acquire_lock(self):
|
||||
"""Acquires any lock necessary to access this Storage.
|
||||
|
||||
@@ -46,8 +46,8 @@ apiui/credential>`__.
|
||||
Usage
|
||||
=====
|
||||
|
||||
Once configured, you can use the :meth:`UserOAuth2.required` decorator to ensure
|
||||
that credentials are available within a view.
|
||||
Once configured, you can use the :meth:`UserOAuth2.required` decorator to
|
||||
ensure that credentials are available within a view.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3,7,10
|
||||
@@ -190,7 +190,7 @@ from oauth2client import util
|
||||
|
||||
__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
|
||||
|
||||
DEFAULT_SCOPES = ('email', )
|
||||
DEFAULT_SCOPES = ('email',)
|
||||
|
||||
|
||||
class UserOAuth2(object):
|
||||
@@ -291,8 +291,9 @@ class UserOAuth2(object):
|
||||
raise ValueError(
|
||||
'OAuth2 configuration could not be found. Either specify the '
|
||||
'client_secrets_file or client_id and client_secret or set the'
|
||||
'app configuration variables GOOGLE_OAUTH2_CLIENT_SECRETS_FILE '
|
||||
'or GOOGLE_OAUTH2_CLIENT_ID and GOOGLE_OAUTH2_CLIENT_SECRET.')
|
||||
'app configuration variables '
|
||||
'GOOGLE_OAUTH2_CLIENT_SECRETS_FILE or '
|
||||
'GOOGLE_OAUTH2_CLIENT_ID and GOOGLE_OAUTH2_CLIENT_SECRET.')
|
||||
|
||||
def _load_client_secrets(self, filename):
|
||||
"""Loads client secrets from the given filename."""
|
||||
@@ -392,7 +393,8 @@ class UserOAuth2(object):
|
||||
credentials = flow.step2_exchange(code)
|
||||
except FlowExchangeError as exchange_error:
|
||||
current_app.logger.exception(exchange_error)
|
||||
return 'An error occurred: %s' % exchange_error, httplib.BAD_REQUEST
|
||||
content = 'An error occurred: %s' % (exchange_error,)
|
||||
return content, httplib.BAD_REQUEST
|
||||
|
||||
# Save the credentials to the storage.
|
||||
self.storage.put(credentials)
|
||||
@@ -420,9 +422,9 @@ class UserOAuth2(object):
|
||||
def email(self):
|
||||
"""Returns the user's email address or None if there are no credentials.
|
||||
|
||||
The email address is provided by the current credentials' id_token. This
|
||||
should not be used as unique identifier as the user can change their
|
||||
email. If you need a unique identifier, use user_id.
|
||||
The email address is provided by the current credentials' id_token.
|
||||
This should not be used as unique identifier as the user can change
|
||||
their email. If you need a unique identifier, use user_id.
|
||||
"""
|
||||
if not self.credentials:
|
||||
return None
|
||||
@@ -451,8 +453,9 @@ class UserOAuth2(object):
|
||||
def authorize_url(self, return_url, **kwargs):
|
||||
"""Creates a URL that can be used to start the authorization flow.
|
||||
|
||||
When the user is directed to the URL, the authorization flow will begin.
|
||||
Once complete, the user will be redirected to the specified return URL.
|
||||
When the user is directed to the URL, the authorization flow will
|
||||
begin. Once complete, the user will be redirected to the specified
|
||||
return URL.
|
||||
|
||||
Any kwargs are passed into the flow constructor.
|
||||
"""
|
||||
|
||||
@@ -44,9 +44,9 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
used for the purpose of accessing data stored under an account assigned to
|
||||
the Compute Engine instance itself.
|
||||
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
|
||||
@util.positional(2)
|
||||
@@ -60,7 +60,8 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
self.scope = util.scopes_to_string(scope)
|
||||
self.kwargs = kwargs
|
||||
|
||||
# Assertion type is no longer used, but still in the parent class signature.
|
||||
# Assertion type is no longer used, but still in the
|
||||
# parent class signature.
|
||||
super(AppAssertionCredentials, self).__init__(None)
|
||||
|
||||
@classmethod
|
||||
@@ -74,9 +75,9 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
Skip all the storage hoops and just refresh using the API.
|
||||
|
||||
Args:
|
||||
http_request: callable, a callable that matches the method signature
|
||||
of httplib2.Http.request, used to make the refresh
|
||||
request.
|
||||
http_request: callable, a callable that matches the method
|
||||
signature of httplib2.Http.request, used to make
|
||||
the refresh request.
|
||||
|
||||
Raises:
|
||||
AccessTokenRefreshError: When the refresh fails.
|
||||
@@ -94,13 +95,13 @@ class AppAssertionCredentials(AssertionCredentials):
|
||||
else:
|
||||
if response.status == 404:
|
||||
content += (' This can occur if a VM was created'
|
||||
' with no service account or scopes.')
|
||||
' with no service account or scopes.')
|
||||
raise AccessTokenRefreshError(content)
|
||||
|
||||
@property
|
||||
def serialization_data(self):
|
||||
def serialization_data(self):
|
||||
raise NotImplementedError(
|
||||
'Cannot serialize credentials for GCE service accounts.')
|
||||
'Cannot serialize credentials for GCE service accounts.')
|
||||
|
||||
def create_scoped_required(self):
|
||||
return not self.scope
|
||||
|
||||
@@ -103,7 +103,7 @@ class Storage(BaseStorage):
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
keyring.set_password(self._service_name, self._user_name,
|
||||
credentials.to_json())
|
||||
credentials.to_json())
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete Credentials file.
|
||||
|
||||
@@ -57,7 +57,7 @@ class AlreadyLockedException(Exception):
|
||||
def validate_file(filename):
|
||||
if os.path.islink(filename):
|
||||
raise CredentialsFileSymbolicLinkError(
|
||||
'File: %s is a symbolic link.' % filename)
|
||||
'File: %s is a symbolic link.' % filename)
|
||||
|
||||
|
||||
class _Opener(object):
|
||||
@@ -123,7 +123,7 @@ class _PosixOpener(_Opener):
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
self._filename)
|
||||
self._locked = False
|
||||
|
||||
validate_file(self._filename)
|
||||
@@ -140,7 +140,7 @@ class _PosixOpener(_Opener):
|
||||
while True:
|
||||
try:
|
||||
self._lock_fd = os.open(lock_filename,
|
||||
os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
self._locked = True
|
||||
break
|
||||
|
||||
@@ -149,7 +149,7 @@ class _PosixOpener(_Opener):
|
||||
raise
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not acquire lock %s in %s seconds',
|
||||
lock_filename, timeout)
|
||||
lock_filename, timeout)
|
||||
# Close the file and open in fallback_mode.
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
@@ -176,7 +176,6 @@ class _PosixOpener(_Opener):
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
|
||||
class _FcntlOpener(_Opener):
|
||||
"""Open, lock, and unlock a file using fcntl.lockf."""
|
||||
|
||||
@@ -190,18 +189,20 @@ try:
|
||||
Raises:
|
||||
AlreadyLockedException: if the lock is already acquired.
|
||||
IOError: if the open fails.
|
||||
CredentialsFileSymbolicLinkError if the file is a symbolic link.
|
||||
CredentialsFileSymbolicLinkError: if the file is a symbolic
|
||||
link.
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
self._filename)
|
||||
start_time = time.time()
|
||||
|
||||
validate_file(self._filename)
|
||||
try:
|
||||
self._fh = open(self._filename, self._mode)
|
||||
except IOError as e:
|
||||
# If we can't access with _mode, try _fallback_mode and don't lock.
|
||||
# If we can't access with _mode, try _fallback_mode and
|
||||
# don't lock.
|
||||
if e.errno in (errno.EPERM, errno.EACCES):
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
@@ -221,7 +222,7 @@ try:
|
||||
# We could not acquire the lock. Try again.
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not lock %s in %s seconds',
|
||||
self._filename, timeout)
|
||||
self._filename, timeout)
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
@@ -244,7 +245,6 @@ try:
|
||||
import win32con
|
||||
import win32file
|
||||
|
||||
|
||||
class _Win32Opener(_Opener):
|
||||
"""Open, lock, and unlock a file using windows primitives."""
|
||||
|
||||
@@ -271,14 +271,15 @@ try:
|
||||
"""
|
||||
if self._locked:
|
||||
raise AlreadyLockedException('File %s is already locked' %
|
||||
self._filename)
|
||||
self._filename)
|
||||
start_time = time.time()
|
||||
|
||||
validate_file(self._filename)
|
||||
try:
|
||||
self._fh = open(self._filename, self._mode)
|
||||
except IOError as e:
|
||||
# If we can't access with _mode, try _fallback_mode and don't lock.
|
||||
# If we can't access with _mode, try _fallback_mode
|
||||
# and don't lock.
|
||||
if e.errno == errno.EACCES:
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
return
|
||||
@@ -288,24 +289,25 @@ try:
|
||||
try:
|
||||
hfile = win32file._get_osfhandle(self._fh.fileno())
|
||||
win32file.LockFileEx(
|
||||
hfile,
|
||||
(win32con.LOCKFILE_FAIL_IMMEDIATELY |
|
||||
win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
|
||||
pywintypes.OVERLAPPED())
|
||||
hfile,
|
||||
(win32con.LOCKFILE_FAIL_IMMEDIATELY |
|
||||
win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
|
||||
pywintypes.OVERLAPPED())
|
||||
self._locked = True
|
||||
return
|
||||
except pywintypes.error as e:
|
||||
if timeout == 0:
|
||||
raise
|
||||
|
||||
# If the error is not that the file is already in use, raise.
|
||||
# If the error is not that the file is already
|
||||
# in use, raise.
|
||||
if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
|
||||
raise
|
||||
|
||||
# We could not acquire the lock. Try again.
|
||||
if (time.time() - start_time) >= timeout:
|
||||
logger.warn('Could not lock %s in %s seconds' % (
|
||||
self._filename, timeout))
|
||||
self._filename, timeout))
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
self._fh = open(self._filename, self._fallback_mode)
|
||||
@@ -317,12 +319,13 @@ try:
|
||||
if self._locked:
|
||||
try:
|
||||
hfile = win32file._get_osfhandle(self._fh.fileno())
|
||||
win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
|
||||
win32file.UnlockFileEx(hfile, 0, -0x10000,
|
||||
pywintypes.OVERLAPPED())
|
||||
except pywintypes.error as e:
|
||||
if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
|
||||
raise
|
||||
self._locked = False
|
||||
if self._fh:
|
||||
if self._fh:
|
||||
self._fh.close()
|
||||
except ImportError:
|
||||
_Win32Opener = None
|
||||
|
||||
@@ -91,14 +91,14 @@ def get_credential_storage(filename, client_id, user_agent, scope,
|
||||
"""
|
||||
# Recreate the legacy key with these specific parameters
|
||||
key = {'clientId': client_id, 'userAgent': user_agent,
|
||||
'scope': util.scopes_to_string(scope)}
|
||||
'scope': util.scopes_to_string(scope)}
|
||||
return get_credential_storage_custom_key(
|
||||
filename, key, warn_on_readonly=warn_on_readonly)
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def get_credential_storage_custom_string_key(
|
||||
filename, key_string, warn_on_readonly=True):
|
||||
def get_credential_storage_custom_string_key(filename, key_string,
|
||||
warn_on_readonly=True):
|
||||
"""Get a Storage instance for a credential using a single string as a key.
|
||||
|
||||
Allows you to provide a string as a custom key that will be used for
|
||||
@@ -120,8 +120,8 @@ def get_credential_storage_custom_string_key(
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def get_credential_storage_custom_key(
|
||||
filename, key_dict, warn_on_readonly=True):
|
||||
def get_credential_storage_custom_key(filename, key_dict,
|
||||
warn_on_readonly=True):
|
||||
"""Get a Storage instance for a credential using a dictionary as a key.
|
||||
|
||||
Allows you to provide a dictionary as a custom key that will be used for
|
||||
@@ -179,7 +179,7 @@ def _get_multistore(filename, warn_on_readonly=True):
|
||||
_multistores_lock.acquire()
|
||||
try:
|
||||
multistore = _multistores.setdefault(
|
||||
filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
|
||||
filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
|
||||
finally:
|
||||
_multistores_lock.release()
|
||||
return multistore
|
||||
@@ -211,7 +211,7 @@ class _MultiStore(object):
|
||||
self._data = None
|
||||
|
||||
class _Storage(BaseStorage):
|
||||
"""A Storage object that knows how to read/write a single credential."""
|
||||
"""A Storage object that can read/write a single credential."""
|
||||
|
||||
def __init__(self, multistore, key):
|
||||
self._multistore = multistore
|
||||
@@ -285,19 +285,20 @@ class _MultiStore(object):
|
||||
self._file.open_and_lock()
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOSYS:
|
||||
logger.warn('File system does not support locking the credentials '
|
||||
'file.')
|
||||
logger.warn('File system does not support locking the '
|
||||
'credentials file.')
|
||||
elif e.errno == errno.ENOLCK:
|
||||
logger.warn('File system is out of resources for writing the '
|
||||
'credentials file (is your disk full?).')
|
||||
'credentials file (is your disk full?).')
|
||||
else:
|
||||
raise
|
||||
if not self._file.is_locked():
|
||||
self._read_only = True
|
||||
if self._warn_on_readonly:
|
||||
logger.warn('The credentials file (%s) is not writable. Opening in '
|
||||
'read-only mode. Any refreshed credentials will only be '
|
||||
'valid for this run.', self._file.filename())
|
||||
logger.warn('The credentials file (%s) is not writable. '
|
||||
'Opening in read-only mode. Any refreshed '
|
||||
'credentials will only be '
|
||||
'valid for this run.', self._file.filename())
|
||||
if os.path.getsize(self._file.filename()) == 0:
|
||||
logger.debug('Initializing empty multistore file')
|
||||
# The multistore is empty so write out an empty file.
|
||||
@@ -340,7 +341,8 @@ class _MultiStore(object):
|
||||
if self._read_only:
|
||||
return
|
||||
self._file.file_handle().seek(0)
|
||||
json.dump(data, self._file.file_handle(), sort_keys=True, indent=2, separators=(',', ': '))
|
||||
json.dump(data, self._file.file_handle(),
|
||||
sort_keys=True, indent=2, separators=(',', ': '))
|
||||
self._file.file_handle().truncate()
|
||||
|
||||
def _refresh_data_cache(self):
|
||||
@@ -357,7 +359,7 @@ class _MultiStore(object):
|
||||
raw_data = self._locked_json_read()
|
||||
except Exception:
|
||||
logger.warn('Credential data store could not be loaded. '
|
||||
'Will ignore and overwrite.')
|
||||
'Will ignore and overwrite.')
|
||||
return
|
||||
|
||||
version = 0
|
||||
@@ -365,11 +367,11 @@ class _MultiStore(object):
|
||||
version = raw_data['file_version']
|
||||
except Exception:
|
||||
logger.warn('Missing version for credential data store. It may be '
|
||||
'corrupt or an old version. Overwriting.')
|
||||
'corrupt or an old version. Overwriting.')
|
||||
if version > 1:
|
||||
raise NewerCredentialStoreError(
|
||||
'Credential file has file_version of %d. '
|
||||
'Only file_version of 1 is supported.' % version)
|
||||
'Credential file has file_version of %d. '
|
||||
'Only file_version of 1 is supported.' % version)
|
||||
|
||||
credentials = []
|
||||
try:
|
||||
@@ -379,11 +381,12 @@ class _MultiStore(object):
|
||||
|
||||
for cred_entry in credentials:
|
||||
try:
|
||||
(key, credential) = self._decode_credential_from_json(cred_entry)
|
||||
key, credential = self._decode_credential_from_json(cred_entry)
|
||||
self._data[key] = credential
|
||||
except:
|
||||
# If something goes wrong loading a credential, just ignore it
|
||||
logger.info('Error decoding credential, skipping', exc_info=True)
|
||||
logger.info('Error decoding credential, skipping',
|
||||
exc_info=True)
|
||||
|
||||
def _decode_credential_from_json(self, cred_entry):
|
||||
"""Load a credential from our JSON serialization.
|
||||
@@ -398,7 +401,8 @@ class _MultiStore(object):
|
||||
raw_key = cred_entry['key']
|
||||
key = util.dict_to_tuple_key(raw_key)
|
||||
credential = None
|
||||
credential = Credentials.new_from_json(json.dumps(cred_entry['credential']))
|
||||
credential = Credentials.new_from_json(
|
||||
json.dumps(cred_entry['credential']))
|
||||
return (key, credential)
|
||||
|
||||
def _write(self):
|
||||
|
||||
@@ -72,7 +72,8 @@ def run(flow, storage, http=None):
|
||||
of values.
|
||||
|
||||
``--[no]auth_local_webserver`` (boolean, default: ``True``)
|
||||
Run a local web server to handle redirects during OAuth authorization.
|
||||
Run a local web server to handle redirects during OAuth
|
||||
authorization.
|
||||
|
||||
Since it uses flags make sure to initialize the ``gflags`` module before
|
||||
calling ``run()``.
|
||||
@@ -87,8 +88,8 @@ def run(flow, storage, http=None):
|
||||
Credentials, the obtained credential.
|
||||
"""
|
||||
logging.warning('This function, oauth2client.tools.run(), and the use of '
|
||||
'the gflags library are deprecated and will be removed in a future '
|
||||
'version of the library.')
|
||||
'the gflags library are deprecated and will be removed in '
|
||||
'a future version of the library.')
|
||||
if FLAGS.auth_local_webserver:
|
||||
success = False
|
||||
port_number = 0
|
||||
@@ -96,7 +97,7 @@ def run(flow, storage, http=None):
|
||||
port_number = port
|
||||
try:
|
||||
httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
|
||||
ClientRedirectHandler)
|
||||
ClientRedirectHandler)
|
||||
except socket.error as e:
|
||||
pass
|
||||
else:
|
||||
@@ -104,13 +105,14 @@ def run(flow, storage, http=None):
|
||||
break
|
||||
FLAGS.auth_local_webserver = success
|
||||
if not success:
|
||||
print('Failed to start a local webserver listening on either port 8080')
|
||||
print('or port 9090. Please check your firewall settings and locally')
|
||||
print('running programs that may be blocking or using those ports.')
|
||||
print()
|
||||
print('Falling back to --noauth_local_webserver and continuing with')
|
||||
print('authorization.')
|
||||
print()
|
||||
print('Failed to start a local webserver listening on '
|
||||
'either port 8080')
|
||||
print('or port 9090. Please check your firewall settings and locally')
|
||||
print('running programs that may be blocking or using those ports.')
|
||||
print()
|
||||
print('Falling back to --noauth_local_webserver and continuing with')
|
||||
print('authorization.')
|
||||
print()
|
||||
|
||||
if FLAGS.auth_local_webserver:
|
||||
oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
|
||||
@@ -144,7 +146,8 @@ def run(flow, storage, http=None):
|
||||
if 'code' in httpd.query_params:
|
||||
code = httpd.query_params['code']
|
||||
else:
|
||||
print('Failed to find "code" in the query parameters of the redirect.')
|
||||
print('Failed to find "code" in the query '
|
||||
'parameters of the redirect.')
|
||||
sys.exit('Try running with --noauth_local_webserver.')
|
||||
else:
|
||||
code = input('Enter verification code: ').strip()
|
||||
|
||||
@@ -38,13 +38,14 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
|
||||
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
|
||||
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, revoke_uri=GOOGLE_REVOKE_URI,
|
||||
**kwargs):
|
||||
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,
|
||||
revoke_uri=GOOGLE_REVOKE_URI, **kwargs):
|
||||
|
||||
super(_ServiceAccountCredentials, self).__init__(
|
||||
None, user_agent=user_agent, token_uri=token_uri, revoke_uri=revoke_uri)
|
||||
None, user_agent=user_agent, token_uri=token_uri,
|
||||
revoke_uri=revoke_uri)
|
||||
|
||||
self._service_account_id = service_account_id
|
||||
self._service_account_email = service_account_email
|
||||
@@ -61,18 +62,18 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
"""Generate the assertion that will be used in the request."""
|
||||
|
||||
header = {
|
||||
'alg': 'RS256',
|
||||
'typ': 'JWT',
|
||||
'kid': self._private_key_id
|
||||
'alg': 'RS256',
|
||||
'typ': 'JWT',
|
||||
'kid': self._private_key_id
|
||||
}
|
||||
|
||||
now = int(time.time())
|
||||
payload = {
|
||||
'aud': self._token_uri,
|
||||
'scope': self._scopes,
|
||||
'iat': now,
|
||||
'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
|
||||
'iss': self._service_account_email
|
||||
'aud': self._token_uri,
|
||||
'scope': self._scopes,
|
||||
'iat': now,
|
||||
'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
|
||||
'iss': self._service_account_email
|
||||
}
|
||||
payload.update(self._kwargs)
|
||||
|
||||
@@ -81,7 +82,8 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
assertion_input = first_segment + b'.' + second_segment
|
||||
|
||||
# Sign the assertion.
|
||||
rsa_bytes = rsa.pkcs1.sign(assertion_input, self._private_key, 'SHA-256')
|
||||
rsa_bytes = rsa.pkcs1.sign(assertion_input, self._private_key,
|
||||
'SHA-256')
|
||||
signature = base64.urlsafe_b64encode(rsa_bytes).rstrip(b'=')
|
||||
|
||||
return assertion_input + b'.' + signature
|
||||
@@ -90,7 +92,7 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
# Ensure that it is bytes
|
||||
blob = _to_bytes(blob, encoding='utf-8')
|
||||
return (self._private_key_id,
|
||||
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
|
||||
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
@@ -99,11 +101,11 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
@property
|
||||
def serialization_data(self):
|
||||
return {
|
||||
'type': 'service_account',
|
||||
'client_id': self._service_account_id,
|
||||
'client_email': self._service_account_email,
|
||||
'private_key_id': self._private_key_id,
|
||||
'private_key': self._private_key_pkcs8_text
|
||||
'type': 'service_account',
|
||||
'client_id': self._service_account_id,
|
||||
'client_email': self._service_account_email,
|
||||
'private_key_id': self._private_key_id,
|
||||
'private_key': self._private_key_pkcs8_text
|
||||
}
|
||||
|
||||
def create_scoped_required(self):
|
||||
@@ -111,14 +113,14 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
||||
|
||||
def create_scoped(self, scopes):
|
||||
return _ServiceAccountCredentials(self._service_account_id,
|
||||
self._service_account_email,
|
||||
self._private_key_id,
|
||||
self._private_key_pkcs8_text,
|
||||
scopes,
|
||||
user_agent=self._user_agent,
|
||||
token_uri=self._token_uri,
|
||||
revoke_uri=self._revoke_uri,
|
||||
**self._kwargs)
|
||||
self._service_account_email,
|
||||
self._private_key_id,
|
||||
self._private_key_pkcs8_text,
|
||||
scopes,
|
||||
user_agent=self._user_agent,
|
||||
token_uri=self._token_uri,
|
||||
revoke_uri=self._revoke_uri,
|
||||
**self._kwargs)
|
||||
|
||||
|
||||
def _get_private_key(private_key_pkcs8_text):
|
||||
@@ -127,5 +129,5 @@ def _get_private_key(private_key_pkcs8_text):
|
||||
der = rsa.pem.load_pem(private_key_pkcs8_text, 'PRIVATE KEY')
|
||||
asn1_private_key, _ = decoder.decode(der, asn1Spec=PrivateKeyInfo())
|
||||
return rsa.PrivateKey.load_pkcs1(
|
||||
asn1_private_key.getComponentByName('privateKey').asOctets(),
|
||||
format='DER')
|
||||
asn1_private_key.getComponentByName('privateKey').asOctets(),
|
||||
format='DER')
|
||||
|
||||
@@ -55,14 +55,15 @@ def _CreateArgumentParser():
|
||||
return None
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--auth_host_name', default='localhost',
|
||||
help='Hostname when running a local web server.')
|
||||
help='Hostname when running a local web server.')
|
||||
parser.add_argument('--noauth_local_webserver', action='store_true',
|
||||
default=False, help='Do not run a local web server.')
|
||||
default=False, help='Do not run a local web server.')
|
||||
parser.add_argument('--auth_host_port', default=[8080, 8090], type=int,
|
||||
nargs='*', help='Port web server should listen on.')
|
||||
parser.add_argument('--logging_level', default='ERROR',
|
||||
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
|
||||
help='Set the logging level of detail.')
|
||||
nargs='*', help='Port web server should listen on.')
|
||||
parser.add_argument(
|
||||
'--logging_level', default='ERROR',
|
||||
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
|
||||
help='Set the logging level of detail.')
|
||||
return parser
|
||||
|
||||
# argparser is an ArgumentParser that contains command-line options expected
|
||||
@@ -100,12 +101,14 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
query = self.path.split('?', 1)[-1]
|
||||
query = dict(urllib.parse.parse_qsl(query))
|
||||
self.server.query_params = query
|
||||
self.wfile.write(b"<html><head><title>Authentication Status</title></head>")
|
||||
self.wfile.write(b"<body><p>The authentication flow has completed.</p>")
|
||||
self.wfile.write(
|
||||
b"<html><head><title>Authentication Status</title></head>")
|
||||
self.wfile.write(
|
||||
b"<body><p>The authentication flow has completed.</p>")
|
||||
self.wfile.write(b"</body></html>")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Do not log messages to stdout while running as command line program."""
|
||||
"""Do not log messages to stdout while running as cmd. line program."""
|
||||
|
||||
|
||||
@util.positional(3)
|
||||
@@ -137,9 +140,9 @@ def run_flow(flow, storage, flags, http=None):
|
||||
Run a local web server to handle redirects during OAuth
|
||||
authorization.
|
||||
|
||||
The tools module defines an ``ArgumentParser`` the already contains the flag
|
||||
definitions that ``run()`` requires. You can pass that ``ArgumentParser`` to
|
||||
your ``ArgumentParser`` constructor::
|
||||
The tools module defines an ``ArgumentParser`` the already contains the
|
||||
flag definitions that ``run()`` requires. You can pass that
|
||||
``ArgumentParser`` to your ``ArgumentParser`` constructor::
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
@@ -167,7 +170,7 @@ def run_flow(flow, storage, flags, http=None):
|
||||
port_number = port
|
||||
try:
|
||||
httpd = ClientRedirectServer((flags.auth_host_name, port),
|
||||
ClientRedirectHandler)
|
||||
ClientRedirectHandler)
|
||||
except socket.error:
|
||||
pass
|
||||
else:
|
||||
@@ -175,13 +178,14 @@ def run_flow(flow, storage, flags, http=None):
|
||||
break
|
||||
flags.noauth_local_webserver = not success
|
||||
if not success:
|
||||
print('Failed to start a local webserver listening on either port 8080')
|
||||
print('or port 9090. Please check your firewall settings and locally')
|
||||
print('running programs that may be blocking or using those ports.')
|
||||
print()
|
||||
print('Falling back to --noauth_local_webserver and continuing with')
|
||||
print('authorization.')
|
||||
print()
|
||||
print('Failed to start a local webserver listening '
|
||||
'on either port 8080')
|
||||
print('or port 9090. Please check your firewall settings and locally')
|
||||
print('running programs that may be blocking or using those ports.')
|
||||
print()
|
||||
print('Falling back to --noauth_local_webserver and continuing with')
|
||||
print('authorization.')
|
||||
print()
|
||||
|
||||
if not flags.noauth_local_webserver:
|
||||
oauth_callback = 'http://%s:%s/' % (flags.auth_host_name, port_number)
|
||||
@@ -197,7 +201,8 @@ def run_flow(flow, storage, flags, http=None):
|
||||
print()
|
||||
print(' ' + authorize_url)
|
||||
print()
|
||||
print('If your browser is on a different machine then exit and re-run this')
|
||||
print('If your browser is on a different machine then '
|
||||
'exit and re-run this')
|
||||
print('application with the command-line parameter ')
|
||||
print()
|
||||
print(' --noauth_local_webserver')
|
||||
@@ -216,7 +221,8 @@ def run_flow(flow, storage, flags, http=None):
|
||||
if 'code' in httpd.query_params:
|
||||
code = httpd.query_params['code']
|
||||
else:
|
||||
print('Failed to find "code" in the query parameters of the redirect.')
|
||||
print('Failed to find "code" in the query parameters '
|
||||
'of the redirect.')
|
||||
sys.exit('Try running with --noauth_local_webserver.')
|
||||
else:
|
||||
code = input('Enter verification code: ').strip()
|
||||
@@ -235,15 +241,15 @@ def run_flow(flow, storage, flags, http=None):
|
||||
|
||||
def message_if_missing(filename):
|
||||
"""Helpful message to display if the CLIENT_SECRETS file is missing."""
|
||||
|
||||
return _CLIENT_SECRETS_MESSAGE % filename
|
||||
|
||||
|
||||
try:
|
||||
from oauth2client.old_run import run
|
||||
from oauth2client.old_run import FLAGS
|
||||
except ImportError:
|
||||
def run(*args, **kwargs):
|
||||
raise NotImplementedError(
|
||||
'The gflags library must be installed to use tools.run(). '
|
||||
'Please install gflags or preferrably switch to using '
|
||||
'tools.run_flow().')
|
||||
'The gflags library must be installed to use tools.run(). '
|
||||
'Please install gflags or preferrably switch to using '
|
||||
'tools.run_flow().')
|
||||
|
||||
@@ -129,8 +129,10 @@ def positional(max_positional_args):
|
||||
plural_s = ''
|
||||
if max_positional_args != 1:
|
||||
plural_s = 's'
|
||||
message = '%s() takes at most %d positional argument%s (%d given)' % (
|
||||
wrapped.__name__, max_positional_args, plural_s, len(args))
|
||||
message = ('%s() takes at most %d positional '
|
||||
'argument%s (%d given)' % (
|
||||
wrapped.__name__, max_positional_args,
|
||||
plural_s, len(args)))
|
||||
if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
|
||||
raise TypeError(message)
|
||||
elif positional_parameters_enforcement == POSITIONAL_WARNING:
|
||||
|
||||
@@ -104,7 +104,7 @@ def validate_token(key, token, user_id, action_id="", current_time=None):
|
||||
|
||||
# The given token should match the generated one with the same time.
|
||||
expected_token = generate_token(key, user_id, action_id=action_id,
|
||||
when=token_time)
|
||||
when=token_time)
|
||||
if len(token) != len(expected_token):
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user