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