Docstring pass after pep8ify in oauth2client/

This commit is contained in:
Danny Hermes
2015-08-21 08:05:44 -07:00
parent 34c1ff543d
commit 119f5b57fe
19 changed files with 1579 additions and 1504 deletions

View File

@@ -21,15 +21,17 @@ import six
def _parse_pem_key(raw_key_input): def _parse_pem_key(raw_key_input):
"""Identify and extract PEM keys. """Identify and extract PEM keys.
Determines whether the given key is in the format of PEM key, and extracts Determines whether the given key is in the format of PEM key, and extracts
the relevant part of the key if it is. the relevant part of the key if it is.
Args: Args:
raw_key_input: The contents of a private key file (either PEM or PKCS12). raw_key_input: The contents of a private key file (either PEM or
PKCS12).
Returns: Returns:
string, The actual key if the contents are from a PEM file, or else None. string, The actual key if the contents are from a PEM file, or
""" else None.
"""
offset = raw_key_input.find(b'-----BEGIN ') offset = raw_key_input.find(b'-----BEGIN ')
if offset != -1: if offset != -1:
return raw_key_input[offset:] return raw_key_input[offset:]
@@ -42,24 +44,24 @@ def _json_encode(data):
def _to_bytes(value, encoding='ascii'): def _to_bytes(value, encoding='ascii'):
"""Converts a string value to bytes, if necessary. """Converts a string value to bytes, if necessary.
Unfortunately, ``six.b`` is insufficient for this task since in Unfortunately, ``six.b`` is insufficient for this task since in
Python2 it does not modify ``unicode`` objects. Python2 it does not modify ``unicode`` objects.
Args: Args:
value: The string/bytes value to be converted. value: The string/bytes value to be converted.
encoding: The encoding to use to convert unicode to bytes. Defaults encoding: The encoding to use to convert unicode to bytes. Defaults
to "ascii", which will not allow any characters from ordinals to "ascii", which will not allow any characters from ordinals
larger than 127. Other useful values are "latin-1", which larger than 127. Other useful values are "latin-1", which
which will only allows byte ordinals (up to 255) and "utf-8", which will only allows byte ordinals (up to 255) and "utf-8",
which will encode any unicode that needs to be. which will encode any unicode that needs to be.
Returns: Returns:
The original value converted to bytes (if unicode) or as passed in The original value converted to bytes (if unicode) or as passed in
if it started out as bytes. if it started out as bytes.
Raises: Raises:
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):
@@ -71,16 +73,16 @@ def _to_bytes(value, encoding='ascii'):
def _from_bytes(value): def _from_bytes(value):
"""Converts bytes to a string value, if necessary. """Converts bytes to a string value, if necessary.
Args: Args:
value: The string/bytes value to be converted. value: The string/bytes value to be converted.
Returns: Returns:
The original value converted to unicode (if bytes) or as passed in The original value converted to unicode (if bytes) or as passed in
if it started out as unicode. if it started out as unicode.
Raises: Raises:
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):

View File

@@ -27,24 +27,24 @@ class OpenSSLVerifier(object):
def __init__(self, pubkey): def __init__(self, pubkey):
"""Constructor. """Constructor.
Args: Args:
pubkey, OpenSSL.crypto.PKey, The public key to verify with. pubkey: OpenSSL.crypto.PKey, The public key to verify with.
""" """
self._pubkey = pubkey self._pubkey = pubkey
def verify(self, message, signature): def verify(self, message, signature):
"""Verifies a message against a signature. """Verifies a message against a signature.
Args: Args:
message: string or bytes, The message to verify. If string, will be message: string or bytes, The message to verify. If string, will be
encoded to bytes as utf-8. encoded to bytes as utf-8.
signature: string or bytes, The signature on the message. If string, signature: string or bytes, The signature on the message. If string,
will be encoded to bytes as utf-8. will be encoded to bytes as utf-8.
Returns: Returns:
True if message was signed by the private key associated with the public True if message was signed by the private key associated with the
key that this object was constructed with. public key that this object was constructed with.
""" """
message = _to_bytes(message, encoding='utf-8') message = _to_bytes(message, encoding='utf-8')
signature = _to_bytes(signature, encoding='utf-8') signature = _to_bytes(signature, encoding='utf-8')
try: try:
@@ -57,17 +57,17 @@ class OpenSSLVerifier(object):
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:
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.
Raises: Raises:
OpenSSL.crypto.Error if the key_pem can't be parsed. OpenSSL.crypto.Error: if the key_pem can't be parsed.
""" """
if is_x509_cert: if is_x509_cert:
pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem) pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
else: else:
@@ -81,20 +81,20 @@ class OpenSSLSigner(object):
def __init__(self, pkey): def __init__(self, pkey):
"""Constructor. """Constructor.
Args: Args:
pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with. pkey: OpenSSL.crypto.PKey (or equiv), The private key to sign with.
""" """
self._key = pkey self._key = pkey
def sign(self, message): def sign(self, message):
"""Signs a message. """Signs a message.
Args: Args:
message: bytes, Message to be signed. message: bytes, Message to be signed.
Returns: Returns:
string, The signature of the message for the given key. string, The signature of the message for the given key.
""" """
message = _to_bytes(message, encoding='utf-8') message = _to_bytes(message, encoding='utf-8')
return crypto.sign(self._key, message, 'sha256') return crypto.sign(self._key, message, 'sha256')
@@ -102,16 +102,16 @@ class OpenSSLSigner(object):
def from_string(key, password=b'notasecret'): def from_string(key, password=b'notasecret'):
"""Construct a Signer instance from a string. """Construct a Signer instance from a string.
Args: Args:
key: string, private key in PKCS12 or PEM format. key: string, private key in PKCS12 or PEM format.
password: string, password for the private key file. password: string, password for the private key file.
Returns: Returns:
Signer instance. Signer instance.
Raises: Raises:
OpenSSL.crypto.Error if the key can't be parsed. OpenSSL.crypto.Error if the key can't be parsed.
""" """
parsed_pem_key = _parse_pem_key(key) parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key: if parsed_pem_key:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key) pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
@@ -124,13 +124,13 @@ class OpenSSLSigner(object):
def pkcs12_key_as_pem(private_key_text, private_key_password): def pkcs12_key_as_pem(private_key_text, private_key_password):
"""Convert the contents of a PKCS12 key to PEM using OpenSSL. """Convert the contents of a PKCS12 key to PEM using OpenSSL.
Args: Args:
private_key_text: String. Private key. private_key_text: String. Private key.
private_key_password: String. Password for PKCS12. private_key_password: String. Password for PKCS12.
Returns: Returns:
String. PEM contents of ``private_key_text``. String. PEM contents of ``private_key_text``.
""" """
decoded_body = base64.b64decode(private_key_text) decoded_body = base64.b64decode(private_key_text)
private_key_password = _to_bytes(private_key_password) private_key_password = _to_bytes(private_key_password)

View File

@@ -30,23 +30,24 @@ class PyCryptoVerifier(object):
def __init__(self, pubkey): def __init__(self, pubkey):
"""Constructor. """Constructor.
Args: Args:
pubkey, OpenSSL.crypto.PKey (or equiv), The public key to verify with. pubkey: OpenSSL.crypto.PKey (or equiv), The public key to verify
""" with.
"""
self._pubkey = pubkey self._pubkey = pubkey
def verify(self, message, signature): def verify(self, message, signature):
"""Verifies a message against a signature. """Verifies a message against a signature.
Args: Args:
message: string or bytes, The message to verify. If string, will be message: string or bytes, The message to verify. If string, will be
encoded to bytes as utf-8. encoded to bytes as utf-8.
signature: string or bytes, The signature on the message. signature: string or bytes, The signature on the message.
Returns: Returns:
True if message was signed by the private key associated with the public True if message was signed by the private key associated with the
key that this object was constructed with. public key that this object was constructed with.
""" """
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)
@@ -55,14 +56,14 @@ class PyCryptoVerifier(object):
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:
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 is
expected to be an RSA key in PEM format. expected to be an RSA key in PEM format.
Returns: Returns:
Verifier instance. Verifier instance.
""" """
if is_x509_cert: if is_x509_cert:
key_pem = _to_bytes(key_pem) key_pem = _to_bytes(key_pem)
pemLines = key_pem.replace(b' ', b'').split() pemLines = key_pem.replace(b' ', b'').split()
@@ -83,20 +84,20 @@ class PyCryptoSigner(object):
def __init__(self, pkey): def __init__(self, pkey):
"""Constructor. """Constructor.
Args: Args:
pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with. pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
""" """
self._key = pkey self._key = pkey
def sign(self, message): def sign(self, message):
"""Signs a message. """Signs a message.
Args: Args:
message: string, Message to be signed. message: string, Message to be signed.
Returns: Returns:
string, The signature of the message for the given key. string, The signature of the message for the given key.
""" """
message = _to_bytes(message, encoding='utf-8') message = _to_bytes(message, encoding='utf-8')
return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
@@ -104,16 +105,17 @@ class PyCryptoSigner(object):
def from_string(key, password='notasecret'): def from_string(key, password='notasecret'):
"""Construct a Signer instance from a string. """Construct a Signer instance from a string.
Args: Args:
key: string, private key in PEM format. key: string, private key in PEM format.
password: string, password for private key file. Unused for PEM files. password: string, password for private key file. Unused for PEM
files.
Returns: Returns:
Signer instance. Signer instance.
Raises: Raises:
NotImplementedError if the key isn't in PEM format. NotImplementedError if the key isn't in PEM format.
""" """
parsed_pem_key = _parse_pem_key(key) parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key: if parsed_pem_key:
pkey = RSA.importKey(parsed_pem_key) pkey = RSA.importKey(parsed_pem_key)

View File

@@ -65,12 +65,12 @@ XSRF_MEMCACHE_ID = 'xsrf_secret_key'
def _safe_html(s): def _safe_html(s):
"""Escape text to make it safe to display. """Escape text to make it safe to display.
Args: Args:
s: string, The text to escape. s: string, The text to escape.
Returns: Returns:
The escaped text as a string. The escaped text as a string.
""" """
return cgi.escape(s, quote=1).replace("'", ''') return cgi.escape(s, quote=1).replace("'", ''')
@@ -85,22 +85,22 @@ class InvalidXsrfTokenError(Exception):
class SiteXsrfSecretKey(db.Model): class SiteXsrfSecretKey(db.Model):
"""Storage for the sites XSRF secret key. """Storage for the sites XSRF secret key.
There will only be one instance stored of this model, the one used for the There will only be one instance stored of this model, the one used for the
site. site.
""" """
secret = db.StringProperty() secret = db.StringProperty()
if ndb is not None: if ndb is not None:
class SiteXsrfSecretKeyNDB(ndb.Model): class SiteXsrfSecretKeyNDB(ndb.Model):
"""NDB Model for storage for the sites XSRF secret key. """NDB Model for storage for the sites XSRF secret key.
Since this model uses the same kind as SiteXsrfSecretKey, it can be used Since this model uses the same kind as SiteXsrfSecretKey, it can be
interchangeably. This simply provides an NDB model for interacting with the used interchangeably. This simply provides an NDB model for interacting
same data the DB model interacts with. with the same data the DB model interacts with.
There should only be one instance stored of this model, the one used for the There should only be one instance stored of this model, the one used
site. for the site.
""" """
secret = ndb.StringProperty() secret = ndb.StringProperty()
@classmethod @classmethod
@@ -110,20 +110,19 @@ if ndb is not None:
def _generate_new_xsrf_secret_key(): def _generate_new_xsrf_secret_key():
"""Returns a random XSRF secret key. """Returns a random XSRF secret key."""
"""
return os.urandom(16).encode("hex") return os.urandom(16).encode("hex")
def xsrf_secret_key(): def xsrf_secret_key():
"""Return the secret key for use for XSRF protection. """Return the secret key for use for XSRF protection.
If the Site entity does not have a secret key, this method will also create If the Site entity does not have a secret key, this method will also create
one and persist it. one and persist it.
Returns: Returns:
The secret key. The secret key.
""" """
secret = memcache.get(XSRF_MEMCACHE_ID, namespace=OAUTH2CLIENT_NAMESPACE) secret = memcache.get(XSRF_MEMCACHE_ID, namespace=OAUTH2CLIENT_NAMESPACE)
if not secret: if not secret:
# Load the one and only instance of SiteXsrfSecretKey. # Load the one and only instance of SiteXsrfSecretKey.
@@ -140,27 +139,28 @@ def xsrf_secret_key():
class AppAssertionCredentials(AssertionCredentials): class AppAssertionCredentials(AssertionCredentials):
"""Credentials object for App Engine Assertion Grants """Credentials object for App Engine Assertion Grants
This object will allow an App Engine application to identify itself to Google This object will allow an App Engine application to identify itself to
and other OAuth 2.0 servers that can verify assertions. It can be used for the Google and other OAuth 2.0 servers that can verify assertions. It can be
purpose of accessing data stored under an account assigned to the App Engine used for the purpose of accessing data stored under an account assigned to
application itself. the App Engine application 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)
def __init__(self, scope, **kwargs): def __init__(self, scope, **kwargs):
"""Constructor for AppAssertionCredentials """Constructor for AppAssertionCredentials
Args: Args:
scope: string or iterable of strings, scope(s) of the credentials being scope: string or iterable of strings, scope(s) of the credentials
requested. being requested.
**kwargs: optional keyword args, including: **kwargs: optional keyword args, including:
service_account_id: service account id of the application. If None or service_account_id: service account id of the application. If None
unspecified, the default service account for the app is used. or unspecified, the default service account for
""" the app is used.
"""
self.scope = util.scopes_to_string(scope) self.scope = util.scopes_to_string(scope)
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)
@@ -176,17 +176,18 @@ class AppAssertionCredentials(AssertionCredentials):
def _refresh(self, http_request): def _refresh(self, http_request):
"""Refreshes the access_token. """Refreshes the access_token.
Since the underlying App Engine app_identity implementation does its own Since the underlying App Engine app_identity implementation does its
caching we can skip all the storage hoops and just to a refresh using the own caching we can skip all the storage hoops and just to a refresh
API. using the API.
Args: Args:
http_request: callable, a callable that matches the method signature of http_request: callable, a callable that matches the method
httplib2.Http.request, used to make the refresh request. signature of httplib2.Http.request, used to make the
refresh request.
Raises: Raises:
AccessTokenRefreshError: When the refresh fails. AccessTokenRefreshError: When the refresh fails.
""" """
try: try:
scopes = self.scope.split() scopes = self.scope.split()
(token, _) = app_identity.get_access_token( (token, _) = app_identity.get_access_token(
@@ -209,8 +210,9 @@ class AppAssertionCredentials(AssertionCredentials):
class FlowProperty(db.Property): class FlowProperty(db.Property):
"""App Engine datastore Property for Flow. """App Engine datastore Property for Flow.
Utility property that allows easy storage and retrieval of an Utility property that allows easy storage and retrieval of an
oauth2client.Flow""" oauth2client.Flow
"""
# Tell what the user type is. # Tell what the user type is.
data_type = Flow data_type = Flow
@@ -242,23 +244,24 @@ if ndb is not None:
class FlowNDBProperty(ndb.PickleProperty): class FlowNDBProperty(ndb.PickleProperty):
"""App Engine NDB datastore Property for Flow. """App Engine NDB datastore Property for Flow.
Serves the same purpose as the DB FlowProperty, but for NDB models. Since Serves the same purpose as the DB FlowProperty, but for NDB models.
PickleProperty inherits from BlobProperty, the underlying representation of Since PickleProperty inherits from BlobProperty, the underlying
the data in the datastore will be the same as in the DB case. representation of the data in the datastore will be the same as in the
DB case.
Utility property that allows easy storage and retrieval of an Utility property that allows easy storage and retrieval of an
oauth2client.Flow oauth2client.Flow
""" """
def _validate(self, value): def _validate(self, value):
"""Validates a value as a proper Flow object. """Validates a value as a proper Flow object.
Args: Args:
value: A value to be set on the property. value: A value to be set on the property.
Raises: Raises:
TypeError if the value is not an instance of Flow. TypeError if the value is not an instance of Flow.
""" """
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 '
@@ -268,9 +271,9 @@ if ndb is not None:
class CredentialsProperty(db.Property): class CredentialsProperty(db.Property):
"""App Engine datastore Property for Credentials. """App Engine datastore Property for Credentials.
Utility property that allows easy storage and retrieval of Utility property that allows easy storage and retrieval of
oath2client.Credentials oath2client.Credentials
""" """
# Tell what the user type is. # Tell what the user type is.
data_type = Credentials data_type = Credentials
@@ -320,23 +323,24 @@ if ndb is not None:
class CredentialsNDBProperty(ndb.BlobProperty): class CredentialsNDBProperty(ndb.BlobProperty):
"""App Engine NDB datastore Property for Credentials. """App Engine NDB datastore Property for Credentials.
Serves the same purpose as the DB CredentialsProperty, but for NDB models. Serves the same purpose as the DB CredentialsProperty, but for NDB
Since CredentialsProperty stores data as a blob and this inherits from models. Since CredentialsProperty stores data as a blob and this
BlobProperty, the data in the datastore will be the same as in the DB case. inherits from BlobProperty, the data in the datastore will be the same
as in the DB case.
Utility property that allows easy storage and retrieval of Credentials and Utility property that allows easy storage and retrieval of Credentials
subclasses. and subclasses.
""" """
def _validate(self, value): def _validate(self, value):
"""Validates a value as a proper credentials object. """Validates a value as a proper credentials object.
Args: Args:
value: A value to be set on the property. value: A value to be set on the property.
Raises: Raises:
TypeError if the value is not an instance of Credentials. TypeError if the value is not an instance of Credentials.
""" """
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 credentials '
@@ -345,12 +349,13 @@ if ndb is not None:
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.
Args: Args:
value: A value to be set in the datastore. value: A value to be set in the datastore.
Returns: Returns:
A JSON serialized version of the credential, else '' if value is None. A JSON serialized version of the credential, else '' if value
""" is None.
"""
if value is None: if value is None:
return '' return ''
else: else:
@@ -359,13 +364,14 @@ if ndb is not None:
def _from_base_type(self, value): def _from_base_type(self, value):
"""Converts our stored JSON string back to the desired type. """Converts our stored JSON string back to the desired type.
Args: Args:
value: A value from the datastore to be converted to the desired type. value: A value from the datastore to be converted to the
desired type.
Returns: Returns:
A deserialized Credentials (or subclass) object, else None if the A deserialized Credentials (or subclass) object, else None if
value can't be parsed. the value can't be parsed.
""" """
if not value: if not value:
return None return None
try: try:
@@ -379,26 +385,27 @@ if ndb is not None:
class StorageByKeyName(Storage): class StorageByKeyName(Storage):
"""Store and retrieve a credential to and from the App Engine datastore. """Store and retrieve a credential to and from the App Engine datastore.
This Storage helper presumes the Credentials have been stored as a This Storage helper presumes the Credentials have been stored as a
CredentialsProperty or CredentialsNDBProperty on a datastore model class, and CredentialsProperty or CredentialsNDBProperty on a datastore model class,
that entities are stored by key_name. and that entities are stored by key_name.
""" """
@util.positional(4) @util.positional(4)
def __init__(self, model, key_name, property_name, cache=None, user=None): def __init__(self, model, key_name, property_name, cache=None, user=None):
"""Constructor for Storage. """Constructor for Storage.
Args: Args:
model: db.Model or ndb.Model, model class model: db.Model or ndb.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
property_name: string, name of the property that is a CredentialsProperty property_name: string, name of the property that is a
or CredentialsNDBProperty. CredentialsProperty or CredentialsNDBProperty.
cache: memcache, a write-through cache to put in front of the datastore. cache: memcache, a write-through cache to put in front of the
If the model you are using is an NDB model, using a cache will be datastore. If the model you are using is an NDB model, using
redundant since the model uses an instance cache and memcache for you. a cache will be redundant since the model uses an instance
user: users.User object, optional. Can be used to grab user ID as a cache and memcache for you.
key_name if no key name is specified. user: users.User object, optional. Can be used to grab user ID as a
""" key_name if no key name is specified.
"""
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.')
@@ -412,9 +419,9 @@ class StorageByKeyName(Storage):
def _is_ndb(self): def _is_ndb(self):
"""Determine whether the model of the instance is an NDB model. """Determine whether the model of the instance is an NDB model.
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 need
# worry about new-style classes since ndb and db models are new-style # worry about new-style classes since ndb and db models are new-style
if isinstance(self._model, type): if isinstance(self._model, type):
@@ -428,12 +435,12 @@ class StorageByKeyName(Storage):
def _get_entity(self): def _get_entity(self):
"""Retrieve entity from datastore. """Retrieve entity from datastore.
Uses a different model method for db or ndb models. Uses a different model method for db or ndb models.
Returns: Returns:
Instance of the model corresponding to the current storage object Instance of the model corresponding to the current storage object
and stored using the key name of the storage object. and stored using the key name of the storage object.
""" """
if self._is_ndb(): if self._is_ndb():
return self._model.get_by_id(self._key_name) return self._model.get_by_id(self._key_name)
else: else:
@@ -442,9 +449,9 @@ class StorageByKeyName(Storage):
def _delete_entity(self): def _delete_entity(self):
"""Delete entity from datastore. """Delete entity from datastore.
Attempts to delete using the key_name stored on the object, whether or not Attempts to delete using the key_name stored on the object, whether or
the given key is in the datastore. not the given key is in the datastore.
""" """
if self._is_ndb(): if self._is_ndb():
ndb.Key(self._model, self._key_name).delete() ndb.Key(self._model, self._key_name).delete()
else: else:
@@ -455,9 +462,9 @@ class StorageByKeyName(Storage):
def locked_get(self): def locked_get(self):
"""Retrieve Credential from datastore. """Retrieve Credential from datastore.
Returns: Returns:
oauth2client.Credentials oauth2client.Credentials
""" """
credentials = None credentials = None
if self._cache: if self._cache:
json = self._cache.get(self._key_name) json = self._cache.get(self._key_name)
@@ -478,9 +485,9 @@ class StorageByKeyName(Storage):
def locked_put(self, credentials): def locked_put(self, credentials):
"""Write a Credentials to the datastore. """Write a Credentials to the datastore.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
entity = self._model.get_or_insert(self._key_name) entity = self._model.get_or_insert(self._key_name)
setattr(entity, self._property_name, credentials) setattr(entity, self._property_name, credentials)
entity.put() entity.put()
@@ -500,8 +507,8 @@ class StorageByKeyName(Storage):
class CredentialsModel(db.Model): class CredentialsModel(db.Model):
"""Storage for OAuth 2.0 Credentials """Storage for OAuth 2.0 Credentials
Storage of the model is keyed by the user.user_id(). Storage of the model is keyed by the user.user_id().
""" """
credentials = CredentialsProperty() credentials = CredentialsProperty()
@@ -509,14 +516,14 @@ if ndb is not None:
class CredentialsNDBModel(ndb.Model): class CredentialsNDBModel(ndb.Model):
"""NDB Model for storage of OAuth 2.0 Credentials """NDB Model for storage of OAuth 2.0 Credentials
Since this model uses the same kind as CredentialsModel and has a property Since this model uses the same kind as CredentialsModel and has a
which can serialize and deserialize Credentials correctly, it can be used property which can serialize and deserialize Credentials correctly, it
interchangeably with a CredentialsModel to access, insert and delete the can be used interchangeably with a CredentialsModel to access, insert
same entities. This simply provides an NDB model for interacting with the and delete the same entities. This simply provides an NDB model for
same data the DB model interacts with. interacting with the same data the DB model interacts with.
Storage of the model is keyed by the user.user_id(). Storage of the model is keyed by the user.user_id().
""" """
credentials = CredentialsNDBProperty() credentials = CredentialsNDBProperty()
@classmethod @classmethod
@@ -528,16 +535,16 @@ if ndb is not None:
def _build_state_value(request_handler, user): def _build_state_value(request_handler, user):
"""Composes the value for the 'state' parameter. """Composes the value for the 'state' parameter.
Packs the current request URI and an XSRF token into an opaque string that Packs the current request URI and an XSRF token into an opaque string that
can be passed to the authentication server via the 'state' parameter. can be passed to the authentication server via the 'state' parameter.
Args: Args:
request_handler: webapp.RequestHandler, The request. request_handler: webapp.RequestHandler, The request.
user: google.appengine.api.users.User, The current user. user: google.appengine.api.users.User, The current user.
Returns: Returns:
The state value as a string. The state value as a string.
""" """
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))
@@ -547,18 +554,18 @@ def _build_state_value(request_handler, user):
def _parse_state_value(state, user): def _parse_state_value(state, user):
"""Parse the value of the 'state' parameter. """Parse the value of the 'state' parameter.
Parses the value and validates the XSRF token in the state parameter. Parses the value and validates the XSRF token in the state parameter.
Args: Args:
state: string, The value of the state parameter. state: string, The value of the state parameter.
user: google.appengine.api.users.User, The current user. user: google.appengine.api.users.User, The current user.
Raises: Raises:
InvalidXsrfTokenError: if the XSRF token is invalid. InvalidXsrfTokenError: if the XSRF token is invalid.
Returns: Returns:
The redirect URI. The redirect URI.
""" """
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):
@@ -570,24 +577,24 @@ def _parse_state_value(state, user):
class OAuth2Decorator(object): class OAuth2Decorator(object):
"""Utility for making OAuth 2.0 easier. """Utility for making OAuth 2.0 easier.
Instantiate and then use with oauth_required or oauth_aware Instantiate and then use with oauth_required or oauth_aware
as decorators on webapp.RequestHandler methods. as decorators on webapp.RequestHandler methods.
:: ::
decorator = OAuth2Decorator( decorator = OAuth2Decorator(
client_id='837...ent.com', client_id='837...ent.com',
client_secret='Qh...wwI', client_secret='Qh...wwI',
scope='https://www.googleapis.com/auth/plus') scope='https://www.googleapis.com/auth/plus')
class MainHandler(webapp.RequestHandler): class MainHandler(webapp.RequestHandler):
@decorator.oauth_required @decorator.oauth_required
def get(self): def get(self):
http = decorator.http() http = decorator.http()
# http is authorized with the user's Credentials and can be used # http is authorized with the user's Credentials and can be
# in API calls # used in API calls
""" """
def set_credentials(self, credentials): def set_credentials(self, credentials):
self._tls.credentials = credentials self._tls.credentials = credentials
@@ -595,11 +602,11 @@ class OAuth2Decorator(object):
def get_credentials(self): def get_credentials(self):
"""A thread local Credentials object. """A thread local Credentials object.
Returns: Returns:
A client.Credentials object, or None if credentials hasn't been set in A client.Credentials object, or None if credentials hasn't been set
this thread yet, which may happen when calling has_credentials inside in this thread yet, which may happen when calling has_credentials
oauth_aware. inside oauth_aware.
""" """
return getattr(self._tls, 'credentials', None) return getattr(self._tls, 'credentials', None)
credentials = property(get_credentials, set_credentials) credentials = property(get_credentials, set_credentials)
@@ -610,11 +617,11 @@ class OAuth2Decorator(object):
def get_flow(self): def get_flow(self):
"""A thread local Flow object. """A thread local Flow object.
Returns: Returns:
A credentials.Flow object, or None if the flow hasn't been set in this A credentials.Flow object, or None if the flow hasn't been set in
thread yet, which happens in _create_flow() since Flows are created this thread yet, which happens in _create_flow() since Flows are
lazily. created lazily.
""" """
return getattr(self._tls, 'flow', None) return getattr(self._tls, 'flow', None)
flow = property(get_flow, set_flow) flow = property(get_flow, set_flow)
@@ -635,42 +642,52 @@ class OAuth2Decorator(object):
"""Constructor for OAuth2Decorator """Constructor for OAuth2Decorator
Args: Args:
client_id: string, client identifier. client_id: string, client identifier.
client_secret: string client secret. client_secret: string client secret.
scope: string or iterable of strings, scope(s) of the credentials being scope: string or iterable of strings, scope(s) of the credentials
requested. being requested.
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 can be used. defaults to Google's endpoints but any OAuth 2.0 provider
token_uri: string, URI for token endpoint. For convenience can be used.
defaults to Google's endpoints but any OAuth 2.0 provider can be used. token_uri: string, URI for token endpoint. For convenience defaults
revoke_uri: string, URI for revoke endpoint. For convenience to Google's endpoints but any OAuth 2.0 provider can be
defaults to Google's endpoints but any OAuth 2.0 provider can be used. used.
user_agent: string, User agent of your application, default to None. revoke_uri: string, URI for revoke endpoint. For convenience
message: Message to display if there are problems with the OAuth 2.0 defaults to Google's endpoints but any OAuth 2.0
configuration. The message may contain HTML and will be presented on the provider can be used.
web interface for any method that uses the decorator. user_agent: string, User agent of your application, default to
callback_path: string, The absolute path to use as the callback URI. Note None.
that this must match up with the URI given when registering the message: Message to display if there are problems with the OAuth 2.0
application in the APIs Console. configuration. The message may contain HTML and will be
token_response_param: string. If provided, the full JSON response presented on the web interface for any method that uses
to the access token request will be encoded and included in this query the decorator.
parameter in the callback URI. This is useful with providers (e.g. callback_path: string, The absolute path to use as the callback
wordpress.com) that include extra fields that the client may want. URI. Note that this must match up with the URI given
_storage_class: "Protected" keyword argument not typically provided to when registering the application in the APIs Console.
this constructor. A storage class to aid in storing a Credentials object token_response_param: string. If provided, the full JSON response
for a user in the datastore. Defaults to StorageByKeyName. to the access token request will be encoded
_credentials_class: "Protected" keyword argument not typically provided to and included in this query parameter in the
this constructor. A db or ndb Model class to hold credentials. Defaults callback URI. This is useful with providers
to CredentialsModel. (e.g. wordpress.com) that include extra
_credentials_property_name: "Protected" keyword argument not typically fields that the client may want.
provided to this constructor. A string indicating the name of the field _storage_class: "Protected" keyword argument not typically provided
on the _credentials_class where a Credentials object will be stored. to this constructor. A storage class to aid in
Defaults to 'credentials'. storing a Credentials object for a user in the
**kwargs: dict, Keyword arguments are passed along as kwargs to datastore. Defaults to StorageByKeyName.
the OAuth2WebServerFlow constructor. _credentials_class: "Protected" keyword argument not typically
provided to this constructor. A db or ndb Model
""" class to hold credentials. Defaults to
CredentialsModel.
_credentials_property_name: "Protected" keyword argument not
typically provided to this constructor.
A string indicating the name of the
field on the _credentials_class where a
Credentials object will be stored.
Defaults to 'credentials'.
**kwargs: dict, Keyword arguments are passed along as kwargs to
the OAuth2WebServerFlow constructor.
"""
self._tls = threading.local() self._tls = threading.local()
self.flow = None self.flow = None
self.credentials = None self.credentials = None
@@ -698,13 +715,13 @@ class OAuth2Decorator(object):
def oauth_required(self, method): def oauth_required(self, method):
"""Decorator that starts the OAuth 2.0 dance. """Decorator that starts the OAuth 2.0 dance.
Starts the OAuth dance for the logged in user if they haven't already Starts the OAuth dance for the logged in user if they haven't already
granted access for this application. granted access for this application.
Args: Args:
method: callable, to be decorated method of a webapp.RequestHandler method: callable, to be decorated method of a webapp.RequestHandler
instance. instance.
""" """
def check_oauth(request_handler, *args, **kwargs): def check_oauth(request_handler, *args, **kwargs):
if self._in_error: if self._in_error:
@@ -741,13 +758,13 @@ class OAuth2Decorator(object):
def _create_flow(self, request_handler): def _create_flow(self, request_handler):
"""Create the Flow object. """Create the Flow object.
The Flow is calculated lazily since we don't know where this app is The Flow is calculated lazily since we don't know where this app is
running until it receives a request, at which point redirect_uri can be running until it receives a request, at which point redirect_uri can be
calculated and then the Flow object can be constructed. calculated and then the Flow object can be constructed.
Args: Args:
request_handler: webapp.RequestHandler, the request handler. request_handler: webapp.RequestHandler, the request handler.
""" """
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
@@ -762,16 +779,16 @@ class OAuth2Decorator(object):
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.
Does all the setup for the OAuth dance, but doesn't initiate it. Does all the setup for the OAuth dance, but doesn't initiate it.
This decorator is useful if you want to create a page that knows This decorator is useful if you want to create a page that knows
whether or not the user has granted access to this application. whether or not the user has granted access to this application.
From within a method decorated with @oauth_aware the has_credentials() From within a method decorated with @oauth_aware the has_credentials()
and authorize_url() methods can be called. and authorize_url() methods can be called.
Args: Args:
method: callable, to be decorated method of a webapp.RequestHandler method: callable, to be decorated method of a webapp.RequestHandler
instance. instance.
""" """
def setup_oauth(request_handler, *args, **kwargs): def setup_oauth(request_handler, *args, **kwargs):
if self._in_error: if self._in_error:
@@ -801,61 +818,61 @@ class OAuth2Decorator(object):
def has_credentials(self): def has_credentials(self):
"""True if for the logged in user there are valid access Credentials. """True if for the logged in user there are valid access Credentials.
Must only be called from with a webapp.RequestHandler subclassed method Must only be called from with a webapp.RequestHandler subclassed method
that had been decorated with either @oauth_required or @oauth_aware. that had been decorated with either @oauth_required or @oauth_aware.
""" """
return self.credentials is not None and not self.credentials.invalid return self.credentials is not None and not self.credentials.invalid
def authorize_url(self): def authorize_url(self):
"""Returns the URL to start the OAuth dance. """Returns the URL to start the OAuth dance.
Must only be called from with a webapp.RequestHandler subclassed method Must only be called from with a webapp.RequestHandler subclassed method
that had been decorated with either @oauth_required or @oauth_aware. that had been decorated with either @oauth_required or @oauth_aware.
""" """
url = self.flow.step1_get_authorize_url() url = self.flow.step1_get_authorize_url()
return str(url) return str(url)
def http(self, *args, **kwargs): def http(self, *args, **kwargs):
"""Returns an authorized http instance. """Returns an authorized http instance.
Must only be called from within an @oauth_required decorated method, or Must only be called from within an @oauth_required decorated method, or
from within an @oauth_aware decorated method where has_credentials() from within an @oauth_aware decorated method where has_credentials()
returns True. returns True.
Args: Args:
*args: Positional arguments passed to httplib2.Http constructor. *args: Positional arguments passed to httplib2.Http constructor.
**kwargs: Positional arguments passed to httplib2.Http constructor. **kwargs: Positional arguments passed to httplib2.Http constructor.
""" """
return self.credentials.authorize(httplib2.Http(*args, **kwargs)) return self.credentials.authorize(httplib2.Http(*args, **kwargs))
@property @property
def callback_path(self): def callback_path(self):
"""The absolute path where the callback will occur. """The absolute path where the callback will occur.
Note this is the absolute path, not the absolute URI, that will be Note this is the absolute path, not the absolute URI, that will be
calculated by the decorator at runtime. See callback_handler() for how this calculated by the decorator at runtime. See callback_handler() for how
should be used. this should be used.
Returns: Returns:
The callback path as a string. The callback path as a string.
""" """
return self._callback_path return self._callback_path
def callback_handler(self): def callback_handler(self):
"""RequestHandler for the OAuth 2.0 redirect callback. """RequestHandler for the OAuth 2.0 redirect callback.
Usage:: Usage::
app = webapp.WSGIApplication([ app = webapp.WSGIApplication([
('/index', MyIndexHandler), ('/index', MyIndexHandler),
..., ...,
(decorator.callback_path, decorator.callback_handler()) (decorator.callback_path, decorator.callback_handler())
]) ])
Returns: Returns:
A webapp.RequestHandler that handles the redirect back from the A webapp.RequestHandler that handles the redirect back from the
server during the OAuth 2.0 dance. server during the OAuth 2.0 dance.
""" """
decorator = self decorator = self
class OAuth2Handler(webapp.RequestHandler): class OAuth2Handler(webapp.RequestHandler):
@@ -890,13 +907,13 @@ class OAuth2Decorator(object):
def callback_application(self): def callback_application(self):
"""WSGI application for handling the OAuth 2.0 redirect callback. """WSGI application for handling the OAuth 2.0 redirect callback.
If you need finer grained control use `callback_handler` which returns just If you need finer grained control use `callback_handler` which returns
the webapp.RequestHandler. just the webapp.RequestHandler.
Returns: Returns:
A webapp.WSGIApplication that handles the redirect back from the A webapp.WSGIApplication that handles the redirect back from the
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())
]) ])
@@ -905,41 +922,42 @@ class OAuth2Decorator(object):
class OAuth2DecoratorFromClientSecrets(OAuth2Decorator): class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
"""An OAuth2Decorator that builds from a clientsecrets file. """An OAuth2Decorator that builds from a clientsecrets file.
Uses a clientsecrets file as the source for all the information when Uses a clientsecrets file as the source for all the information when
constructing an OAuth2Decorator. constructing an OAuth2Decorator.
:: ::
decorator = OAuth2DecoratorFromClientSecrets( decorator = OAuth2DecoratorFromClientSecrets(
os.path.join(os.path.dirname(__file__), 'client_secrets.json') os.path.join(os.path.dirname(__file__), 'client_secrets.json')
scope='https://www.googleapis.com/auth/plus') scope='https://www.googleapis.com/auth/plus')
class MainHandler(webapp.RequestHandler): class MainHandler(webapp.RequestHandler):
@decorator.oauth_required @decorator.oauth_required
def get(self): def get(self):
http = decorator.http() http = decorator.http()
# http is authorized with the user's Credentials and can be used # http is authorized with the user's Credentials and can be
# in API calls # used in API calls
""" """
@util.positional(3) @util.positional(3)
def __init__(self, filename, scope, message=None, cache=None, **kwargs): def __init__(self, filename, scope, message=None, cache=None, **kwargs):
"""Constructor """Constructor
Args: Args:
filename: string, File name of client secrets. filename: string, File name of client secrets.
scope: string or iterable of strings, scope(s) of the credentials being scope: string or iterable of strings, scope(s) of the credentials
requested. being requested.
message: string, A friendly string to display to the user if the message: string, A friendly string to display to the user if the
clientsecrets file is missing or invalid. The message may contain HTML clientsecrets file is missing or invalid. The message may
and will be presented on the web interface for any method that uses the contain HTML and will be presented on the web interface
decorator. for any method that uses the decorator.
cache: An optional cache service client that implements get() and set() cache: An optional cache service client that implements get() and
methods. See clientsecrets.loadfile() for details. set()
**kwargs: dict, Keyword arguments are passed along as kwargs to methods. See clientsecrets.loadfile() for details.
the OAuth2WebServerFlow constructor. **kwargs: dict, Keyword arguments are passed along as kwargs to
""" the OAuth2WebServerFlow constructor.
"""
client_type, client_info = clientsecrets.loadfile(filename, cache=cache) client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
if client_type not in [ if client_type not in [
clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]: clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
@@ -968,19 +986,18 @@ def oauth2decorator_from_clientsecrets(filename, scope,
message=None, cache=None): message=None, cache=None):
"""Creates an OAuth2Decorator populated from a clientsecrets file. """Creates an OAuth2Decorator populated from a clientsecrets file.
Args: Args:
filename: string, File name of client secrets. filename: string, File name of client secrets.
scope: string or list of strings, scope(s) of the credentials being scope: string or list of strings, scope(s) of the credentials being
requested. requested.
message: string, A friendly string to display to the user if the message: string, A friendly string to display to the user if the
clientsecrets file is missing or invalid. The message may contain HTML and clientsecrets file is missing or invalid. The message may
will be presented on the web interface for any method that uses the contain HTML and will be presented on the web interface for
decorator. any method that uses the decorator.
cache: An optional cache service client that implements get() and set() cache: An optional cache service client that implements get() and set()
methods. See clientsecrets.loadfile() for details. methods. See clientsecrets.loadfile() for details.
Returns: An OAuth2Decorator Returns: An OAuth2Decorator
"""
"""
return OAuth2DecoratorFromClientSecrets(filename, scope, return OAuth2DecoratorFromClientSecrets(filename, scope,
message=message, cache=cache) message=message, cache=cache)

File diff suppressed because it is too large Load Diff

View File

@@ -118,36 +118,36 @@ def _loadfile(filename):
def loadfile(filename, cache=None): def loadfile(filename, cache=None):
"""Loading of client_secrets JSON file, optionally backed by a cache. """Loading of client_secrets JSON file, optionally backed by a cache.
Typical cache storage would be App Engine memcache service, Typical cache storage would be App Engine memcache service,
but you can pass in any other cache client that implements but you can pass in any other cache client that implements
these methods: these methods:
* ``get(key, namespace=ns)`` * ``get(key, namespace=ns)``
* ``set(key, value, namespace=ns)`` * ``set(key, value, namespace=ns)``
Usage:: Usage::
# without caching # without caching
client_type, client_info = loadfile('secrets.json') client_type, client_info = loadfile('secrets.json')
# using App Engine memcache service # using App Engine memcache service
from google.appengine.api import memcache from google.appengine.api import memcache
client_type, client_info = loadfile('secrets.json', cache=memcache) client_type, client_info = loadfile('secrets.json', cache=memcache)
Args: Args:
filename: string, Path to a client_secrets.json file on a filesystem. filename: string, Path to a client_secrets.json file on a filesystem.
cache: An optional cache service client that implements get() and set() cache: An optional cache service client that implements get() and set()
methods. If not specified, the file is always being loaded from methods. If not specified, the file is always being loaded from
a filesystem. a filesystem.
Raises: Raises:
InvalidClientSecretsError: In case of a validation error or some InvalidClientSecretsError: In case of a validation error or some
I/O failure. Can happen only on cache miss. I/O failure. Can happen only on cache miss.
Returns: Returns:
(client_type, client_info) tuple, as _loadfile() normally would. (client_type, client_info) tuple, as _loadfile() normally would.
JSON contents is validated only during first load. Cache hits are not JSON contents is validated only during first load. Cache hits are not
validated. validated.
""" """
_SECRET_NAMESPACE = 'oauth2client:secrets#ns' _SECRET_NAMESPACE = 'oauth2client:secrets#ns'
if not cache: if not cache:

View File

@@ -71,15 +71,15 @@ else:
def make_signed_jwt(signer, payload): def make_signed_jwt(signer, payload):
"""Make a signed JWT. """Make a signed JWT.
See http://self-issued.info/docs/draft-jones-json-web-token.html. See http://self-issued.info/docs/draft-jones-json-web-token.html.
Args: Args:
signer: crypt.Signer, Cryptographic signer. signer: crypt.Signer, Cryptographic signer.
payload: dict, Dictionary of data to convert to JSON and then sign. payload: dict, Dictionary of data to convert to JSON and then sign.
Returns: Returns:
string, The JWT for the payload. string, The JWT for the payload.
""" """
header = {'typ': 'JWT', 'alg': 'RS256'} header = {'typ': 'JWT', 'alg': 'RS256'}
segments = [ segments = [
@@ -99,20 +99,20 @@ def make_signed_jwt(signer, payload):
def verify_signed_jwt_with_certs(jwt, certs, audience): def verify_signed_jwt_with_certs(jwt, certs, audience):
"""Verify a JWT against public certs. """Verify a JWT against public certs.
See http://self-issued.info/docs/draft-jones-json-web-token.html. See http://self-issued.info/docs/draft-jones-json-web-token.html.
Args: Args:
jwt: string, A JWT. jwt: string, A JWT.
certs: dict, Dictionary where values of public keys in PEM format. certs: dict, Dictionary where values of public keys in PEM format.
audience: string, The audience, 'aud', that this JWT should contain. If audience: string, The audience, 'aud', that this JWT should contain. If
None then the JWT's 'aud' parameter is not verified. None then the JWT's 'aud' parameter is not verified.
Returns: Returns:
dict, The deserialized JSON payload in the JWT. dict, The deserialized JSON payload in the JWT.
Raises: Raises:
AppIdentityError if any checks are failed. AppIdentityError if any checks are failed.
""" """
jwt = _to_bytes(jwt) jwt = _to_bytes(jwt)
segments = jwt.split(b'.') segments = jwt.split(b'.')

View File

@@ -43,12 +43,12 @@ CREDENTIAL_INFO_REQUEST_JSON = '[]'
class CredentialInfoResponse(object): 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.
""" """
def __init__(self, json_string): def __init__(self, json_string):
"""Initialize the response data from JSON PBLite array.""" """Initialize the response data from JSON PBLite array."""
@@ -91,14 +91,15 @@ def _SendRecv():
class DevshellCredentials(client.GoogleCredentials): class DevshellCredentials(client.GoogleCredentials):
"""Credentials object for Google Developer Shell environment. """Credentials object for Google Developer Shell environment.
This object will allow a Google Developer Shell session to identify its user This object will allow a Google Developer Shell session to identify its
to Google and other OAuth 2.0 servers that can verify assertions. It can be user to Google and other OAuth 2.0 servers that can verify assertions. It
used for the purpose of accessing data stored under the user account. can be used for the purpose of accessing data stored under the user
account.
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.
""" """
def __init__(self, user_agent=None): def __init__(self, user_agent=None):
super(DevshellCredentials, self).__init__( super(DevshellCredentials, self).__init__(

View File

@@ -79,23 +79,23 @@ class FlowField(models.Field):
class Storage(BaseStorage): class Storage(BaseStorage):
"""Store and retrieve a single credential to and from """Store and retrieve a single credential to and from the datastore.
the datastore.
This Storage helper presumes the Credentials This Storage helper presumes the Credentials
have been stored as a CredenialsField have been stored as a CredenialsField
on a db model class. on a db model class.
""" """
def __init__(self, model_class, key_name, key_value, property_name): def __init__(self, model_class, key_name, key_value, property_name):
"""Constructor for Storage. """Constructor for Storage.
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 CredentialsProperty property_name: string, name of the property that is an
""" CredentialsProperty
"""
self.model_class = model_class self.model_class = model_class
self.key_name = key_name self.key_name = key_name
self.key_value = key_value self.key_value = key_value
@@ -104,9 +104,9 @@ class Storage(BaseStorage):
def locked_get(self): def locked_get(self):
"""Retrieve Credential from datastore. """Retrieve Credential from datastore.
Returns: Returns:
oauth2client.Credentials oauth2client.Credentials
""" """
credential = None credential = None
query = {self.key_name: self.key_value} query = {self.key_name: self.key_value}
@@ -120,11 +120,11 @@ class Storage(BaseStorage):
def locked_put(self, credentials, overwrite=False): def locked_put(self, credentials, overwrite=False):
"""Write a Credentials to the datastore. """Write a Credentials to the datastore.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
overwrite: Boolean, indicates whether you would like these credentials to overwrite: Boolean, indicates whether you would like these
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:

View File

@@ -46,26 +46,27 @@ class Storage(BaseStorage):
def acquire_lock(self): def acquire_lock(self):
"""Acquires any lock necessary to access this Storage. """Acquires any lock necessary to access this Storage.
This lock is not reentrant.""" This lock is not reentrant.
"""
self._lock.acquire() self._lock.acquire()
def release_lock(self): def release_lock(self):
"""Release the Storage lock. """Release the Storage lock.
Trying to release a lock that isn't held will result in a Trying to release a lock that isn't held will result in a
RuntimeError. RuntimeError.
""" """
self._lock.release() self._lock.release()
def locked_get(self): def locked_get(self):
"""Retrieve Credential from file. """Retrieve Credential from file.
Returns: Returns:
oauth2client.client.Credentials oauth2client.client.Credentials
Raises: Raises:
CredentialsFileSymbolicLinkError if the file is a symbolic link. CredentialsFileSymbolicLinkError if the file is a symbolic link.
""" """
credentials = None credentials = None
self._validate_file() self._validate_file()
try: try:
@@ -86,9 +87,9 @@ class Storage(BaseStorage):
def _create_file_if_needed(self): def _create_file_if_needed(self):
"""Create an empty file if necessary. """Create an empty file if necessary.
This method will not initialize the file. Instead it implements a This method will not initialize the file. Instead it implements a
simple version of "touch" to ensure the file has been created. simple version of "touch" to ensure the file has been created.
""" """
if not os.path.exists(self._filename): if not os.path.exists(self._filename):
old_umask = os.umask(0o177) old_umask = os.umask(0o177)
try: try:
@@ -99,13 +100,12 @@ class Storage(BaseStorage):
def locked_put(self, credentials): def locked_put(self, credentials):
"""Write Credentials to file. """Write Credentials to file.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
Raises:
CredentialsFileSymbolicLinkError if the file is a symbolic link.
"""
Raises:
CredentialsFileSymbolicLinkError if the file is a symbolic link.
"""
self._create_file_if_needed() self._create_file_if_needed()
self._validate_file() self._validate_file()
f = open(self._filename, 'w') f = open(self._filename, 'w')
@@ -115,8 +115,7 @@ class Storage(BaseStorage):
def locked_delete(self): def locked_delete(self):
"""Delete Credentials file. """Delete Credentials file.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
os.unlink(self._filename) os.unlink(self._filename)

View File

@@ -337,8 +337,10 @@ class UserOAuth2(object):
return bp return bp
def authorize_view(self): def authorize_view(self):
"""Flask view that starts the authorization flow by redirecting the """Flask view that starts the authorization flow.
user to the OAuth2 provider."""
Starts flow by redirecting the user to the OAuth2 provider.
"""
args = request.args.to_dict() args = request.args.to_dict()
# Scopes will be passed as mutliple args, and to_dict() will only # Scopes will be passed as mutliple args, and to_dict() will only
@@ -355,9 +357,11 @@ class UserOAuth2(object):
return redirect(auth_url) return redirect(auth_url)
def callback_view(self): def callback_view(self):
"""Flask view that handles the user's return from the OAuth2 provider """Flask view that handles the user's return from OAuth2 provider.
and exchanges the authorization code for credentials and stores the
credentials.""" On return, exchanges the authorization code for credentials and stores
the credentials.
"""
if 'error' in request.args: if 'error' in request.args:
reason = request.args.get( reason = request.args.get(
'error_description', request.args.get('error', '')) 'error_description', request.args.get('error', ''))
@@ -429,8 +433,9 @@ class UserOAuth2(object):
@property @property
def user_id(self): def user_id(self):
"""Returns the a unique identifier for the user or None if there are no """Returns the a unique identifier for the user
credentials.
Returns None if there are no credentials.
The id is provided by the current credentials' id_token. The id is provided by the current credentials' id_token.
""" """

View File

@@ -38,24 +38,24 @@ META = ('http://metadata.google.internal/0.1/meta-data/service-accounts/'
class AppAssertionCredentials(AssertionCredentials): class AppAssertionCredentials(AssertionCredentials):
"""Credentials object for Compute Engine Assertion Grants """Credentials object for Compute Engine Assertion Grants
This object will allow a Compute Engine instance to identify itself to This object will allow a Compute Engine instance to identify itself to
Google and other OAuth 2.0 servers that can verify assertions. It can be used Google and other OAuth 2.0 servers that can verify assertions. It can be
for the purpose of accessing data stored under an account assigned to the used for the purpose of accessing data stored under an account assigned to
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 represents
a two legged flow, and therefore has all of the required information to a two legged flow, and therefore has all of the required information to
generate and refresh its own access tokens. generate and refresh its own access tokens.
""" """
@util.positional(2) @util.positional(2)
def __init__(self, scope, **kwargs): def __init__(self, scope, **kwargs):
"""Constructor for AppAssertionCredentials """Constructor for AppAssertionCredentials
Args: Args:
scope: string or iterable of strings, scope(s) of the credentials being scope: string or iterable of strings, scope(s) of the credentials
requested. being requested.
""" """
self.scope = util.scopes_to_string(scope) self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs self.kwargs = kwargs
@@ -70,15 +70,16 @@ class AppAssertionCredentials(AssertionCredentials):
def _refresh(self, http_request): def _refresh(self, http_request):
"""Refreshes the access_token. """Refreshes the access_token.
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 of http_request: callable, a callable that matches the method signature
httplib2.Http.request, used to make the refresh request. of httplib2.Http.request, used to make the refresh
request.
Raises: Raises:
AccessTokenRefreshError: When the refresh fails. AccessTokenRefreshError: When the refresh fails.
""" """
query = '?scope=%s' % urllib.parse.quote(self.scope, '') query = '?scope=%s' % urllib.parse.quote(self.scope, '')
uri = META.replace('{?scope}', query) uri = META.replace('{?scope}', query)
response, content = http_request(uri) response, content = http_request(uri)

View File

@@ -30,32 +30,34 @@ from oauth2client.client import Storage as BaseStorage
class Storage(BaseStorage): class Storage(BaseStorage):
"""Store and retrieve a single credential to and from the keyring. """Store and retrieve a single credential to and from the keyring.
To use this module you must have the keyring module installed. See To use this module you must have the keyring module installed. See
<http://pypi.python.org/pypi/keyring/>. This is an optional module and is not <http://pypi.python.org/pypi/keyring/>. This is an optional module and is
installed with oauth2client by default because it does not work on all the not installed with oauth2client by default because it does not work on all
platforms that oauth2client supports, such as Google App Engine. the platforms that oauth2client supports, such as Google App Engine.
The keyring module <http://pypi.python.org/pypi/keyring/> is a cross-platform The keyring module <http://pypi.python.org/pypi/keyring/> is a
library for access the keyring capabilities of the local system. The user will cross-platform library for access the keyring capabilities of the local
be prompted for their keyring password when this module is used, and the system. The user will be prompted for their keyring password when this
manner in which the user is prompted will vary per platform. module is used, and the manner in which the user is prompted will vary per
platform.
Usage: Usage::
from oauth2client.keyring_storage import Storage
s = Storage('name_of_application', 'user1') from oauth2client.keyring_storage import Storage
credentials = s.get()
""" s = Storage('name_of_application', 'user1')
credentials = s.get()
"""
def __init__(self, service_name, user_name): def __init__(self, service_name, user_name):
"""Constructor. """Constructor.
Args: Args:
service_name: string, The name of the service under which the credentials service_name: string, The name of the service under which the
are stored. credentials are stored.
user_name: string, The name of the user to store credentials for. user_name: string, The name of the user to store credentials for.
""" """
self._service_name = service_name self._service_name = service_name
self._user_name = user_name self._user_name = user_name
self._lock = threading.Lock() self._lock = threading.Lock()
@@ -63,23 +65,24 @@ class Storage(BaseStorage):
def acquire_lock(self): def acquire_lock(self):
"""Acquires any lock necessary to access this Storage. """Acquires any lock necessary to access this Storage.
This lock is not reentrant.""" This lock is not reentrant.
"""
self._lock.acquire() self._lock.acquire()
def release_lock(self): def release_lock(self):
"""Release the Storage lock. """Release the Storage lock.
Trying to release a lock that isn't held will result in a Trying to release a lock that isn't held will result in a
RuntimeError. RuntimeError.
""" """
self._lock.release() self._lock.release()
def locked_get(self): def locked_get(self):
"""Retrieve Credential from file. """Retrieve Credential from file.
Returns: Returns:
oauth2client.client.Credentials oauth2client.client.Credentials
""" """
credentials = None credentials = None
content = keyring.get_password(self._service_name, self._user_name) content = keyring.get_password(self._service_name, self._user_name)
@@ -95,16 +98,16 @@ class Storage(BaseStorage):
def locked_put(self, credentials): def locked_put(self, credentials):
"""Write Credentials to file. """Write Credentials to file.
Args: Args:
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.
Args: Args:
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, '')

View File

@@ -65,11 +65,11 @@ class _Opener(object):
def __init__(self, filename, mode, fallback_mode): def __init__(self, filename, mode, fallback_mode):
"""Create an Opener. """Create an Opener.
Args: Args:
filename: string, The pathname of the file. filename: string, The pathname of the file.
mode: string, The preferred mode to access the file with. mode: string, The preferred mode to access the file with.
fallback_mode: string, The mode to use if locking fails. fallback_mode: string, The mode to use if locking fails.
""" """
self._locked = False self._locked = False
self._filename = filename self._filename = filename
self._mode = mode self._mode = mode
@@ -92,10 +92,10 @@ class _Opener(object):
def open_and_lock(self, timeout, delay): def open_and_lock(self, timeout, delay):
"""Open the file and lock it. """Open the file and lock it.
Args: Args:
timeout: float, How long to try to lock for. timeout: float, How long to try to lock for.
delay: float, How long to wait between retries. delay: float, How long to wait between retries.
""" """
pass pass
def unlock_and_close(self): def unlock_and_close(self):
@@ -109,17 +109,17 @@ class _PosixOpener(_Opener):
def open_and_lock(self, timeout, delay): def open_and_lock(self, timeout, delay):
"""Open the file and lock it. """Open the file and lock it.
Tries to create a .lock file next to the file we're trying to open. Tries to create a .lock file next to the file we're trying to open.
Args: Args:
timeout: float, How long to try to lock for. timeout: float, How long to try to lock for.
delay: float, How long to wait between retries. delay: float, How long to wait between retries.
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)
@@ -182,15 +182,15 @@ try:
def open_and_lock(self, timeout, delay): def open_and_lock(self, timeout, delay):
"""Open the file and lock it. """Open the file and lock it.
Args: Args:
timeout: float, How long to try to lock for. timeout: float, How long to try to lock for.
delay: float, How long to wait between retries delay: float, How long to wait between retries
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)
@@ -258,15 +258,16 @@ try:
def open_and_lock(self, timeout, delay): def open_and_lock(self, timeout, delay):
"""Open the file and lock it. """Open the file and lock it.
Args: Args:
timeout: float, How long to try to lock for. timeout: float, How long to try to lock for.
delay: float, How long to wait between retries delay: float, How long to wait between retries
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)
@@ -333,12 +334,13 @@ class LockedFile(object):
def __init__(self, filename, mode, fallback_mode, use_native_locking=True): def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
"""Construct a LockedFile. """Construct a LockedFile.
Args: Args:
filename: string, The path of the file to open. filename: string, The path of the file to open.
mode: string, The mode to try to open the file with. mode: string, The mode to try to open the file with.
fallback_mode: string, The mode to use if locking fails. fallback_mode: string, The mode to use if locking fails.
use_native_locking: bool, Whether or not fcntl/win32 locking is used. use_native_locking: bool, Whether or not fcntl/win32 locking is
""" used.
"""
opener = None opener = None
if not opener and use_native_locking: if not opener and use_native_locking:
if _Win32Opener: if _Win32Opener:
@@ -366,14 +368,14 @@ class LockedFile(object):
def open_and_lock(self, timeout=0, delay=0.05): def open_and_lock(self, timeout=0, delay=0.05):
"""Open the file, trying to lock it. """Open the file, trying to lock it.
Args: Args:
timeout: float, The number of seconds to try to acquire the lock. timeout: float, The number of seconds to try to acquire the lock.
delay: float, The number of seconds to wait between retry attempts. delay: float, The number of seconds to wait between retry attempts.
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.
""" """
self._opener.open_and_lock(timeout, delay) self._opener.open_and_lock(timeout, delay)
def unlock_and_close(self): def unlock_and_close(self):

View File

@@ -26,21 +26,21 @@ The credential themselves are keyed off of:
The format of the stored data is like so:: The format of the stored data is like so::
{ {
'file_version': 1, 'file_version': 1,
'data': [ 'data': [
{ {
'key': { 'key': {
'clientId': '<client id>', 'clientId': '<client id>',
'userAgent': '<user agent>', 'userAgent': '<user agent>',
'scope': '<scope>' 'scope': '<scope>'
}, },
'credential': { 'credential': {
# JSON serialized Credentials. # JSON serialized Credentials.
} }
} }
] ]
} }
""" """
@@ -77,17 +77,17 @@ def get_credential_storage(filename, client_id, user_agent, scope,
warn_on_readonly=True): warn_on_readonly=True):
"""Get a Storage instance for a credential. """Get a Storage instance for a credential.
Args: Args:
filename: The JSON file storing a set of credentials filename: The JSON file storing a set of credentials
client_id: The client_id for the credential client_id: The client_id for the credential
user_agent: The user agent for the credential user_agent: The user agent for the credential
scope: string or iterable of strings, Scope(s) being requested scope: string or iterable of strings, Scope(s) being requested
warn_on_readonly: if True, log a warning if the store is readonly warn_on_readonly: if True, log a warning if the store is readonly
Returns: Returns:
An object derived from client.Storage for getting/setting the An object derived from client.Storage for getting/setting the
credential. credential.
""" """
# 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)}
@@ -100,18 +100,18 @@ def get_credential_storage_custom_string_key(
filename, key_string, warn_on_readonly=True): filename, key_string, 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
credential storage and retrieval. credential storage and retrieval.
Args: Args:
filename: The JSON file storing a set of credentials filename: The JSON file storing a set of credentials
key_string: A string to use as the key for storing this credential. key_string: A string to use as the key for storing this credential.
warn_on_readonly: if True, log a warning if the store is readonly warn_on_readonly: if True, log a warning if the store is readonly
Returns: Returns:
An object derived from client.Storage for getting/setting the An object derived from client.Storage for getting/setting the
credential. credential.
""" """
# Create a key dictionary that can be used # Create a key dictionary that can be used
key_dict = {'key': key_string} key_dict = {'key': key_string}
return get_credential_storage_custom_key( return get_credential_storage_custom_key(
@@ -123,20 +123,20 @@ def get_credential_storage_custom_key(
filename, key_dict, warn_on_readonly=True): filename, key_dict, 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
credential storage and retrieval. credential storage and retrieval.
Args: Args:
filename: The JSON file storing a set of credentials filename: The JSON file storing a set of credentials
key_dict: A dictionary to use as the key for storing this credential. There key_dict: A dictionary to use as the key for storing this credential.
is no ordering of the keys in the dictionary. Logically equivalent There is no ordering of the keys in the dictionary. Logically
dictionaries will produce equivalent storage keys. equivalent dictionaries will produce equivalent storage keys.
warn_on_readonly: if True, log a warning if the store is readonly warn_on_readonly: if True, log a warning if the store is readonly
Returns: Returns:
An object derived from client.Storage for getting/setting the An object derived from client.Storage for getting/setting the
credential. credential.
""" """
multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly) multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
key = util.dict_to_tuple_key(key_dict) key = util.dict_to_tuple_key(key_dict)
return multistore._get_storage(key) return multistore._get_storage(key)
@@ -146,15 +146,15 @@ def get_credential_storage_custom_key(
def get_all_credential_keys(filename, warn_on_readonly=True): def get_all_credential_keys(filename, warn_on_readonly=True):
"""Gets all the registered credential keys in the given Multistore. """Gets all the registered credential keys in the given Multistore.
Args: Args:
filename: The JSON file storing a set of credentials filename: The JSON file storing a set of credentials
warn_on_readonly: if True, log a warning if the store is readonly warn_on_readonly: if True, log a warning if the store is readonly
Returns: Returns:
A list of the credential keys present in the file. They are returned as A list of the credential keys present in the file. They are returned
dictionaries that can be passed into get_credential_storage_custom_key to as dictionaries that can be passed into
get the actual credentials. get_credential_storage_custom_key to get the actual credentials.
""" """
multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly) multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
multistore._lock() multistore._lock()
try: try:
@@ -167,13 +167,13 @@ def get_all_credential_keys(filename, warn_on_readonly=True):
def _get_multistore(filename, warn_on_readonly=True): def _get_multistore(filename, warn_on_readonly=True):
"""A helper method to initialize the multistore with proper locking. """A helper method to initialize the multistore with proper locking.
Args: Args:
filename: The JSON file storing a set of credentials filename: The JSON file storing a set of credentials
warn_on_readonly: if True, log a warning if the store is readonly warn_on_readonly: if True, log a warning if the store is readonly
Returns: Returns:
A multistore object A multistore object
""" """
filename = os.path.expanduser(filename) filename = os.path.expanduser(filename)
_multistores_lock.acquire() _multistores_lock.acquire()
try: try:
@@ -191,8 +191,8 @@ class _MultiStore(object):
def __init__(self, filename, warn_on_readonly=True): def __init__(self, filename, warn_on_readonly=True):
"""Initialize the class. """Initialize the class.
This will create the file if necessary. This will create the file if necessary.
""" """
self._file = LockedFile(filename, 'r+', 'r') self._file = LockedFile(filename, 'r+', 'r')
self._thread_lock = threading.Lock() self._thread_lock = threading.Lock()
self._read_only = False self._read_only = False
@@ -219,26 +219,26 @@ class _MultiStore(object):
def acquire_lock(self): def acquire_lock(self):
"""Acquires any lock necessary to access this Storage. """Acquires any lock necessary to access this Storage.
This lock is not reentrant. This lock is not reentrant.
""" """
self._multistore._lock() self._multistore._lock()
def release_lock(self): def release_lock(self):
"""Release the Storage lock. """Release the Storage lock.
Trying to release a lock that isn't held will result in a Trying to release a lock that isn't held will result in a
RuntimeError. RuntimeError.
""" """
self._multistore._unlock() self._multistore._unlock()
def locked_get(self): def locked_get(self):
"""Retrieve credential. """Retrieve credential.
The Storage lock must be held when this is called. The Storage lock must be held when this is called.
Returns: Returns:
oauth2client.client.Credentials oauth2client.client.Credentials
""" """
credential = self._multistore._get_credential(self._key) credential = self._multistore._get_credential(self._key)
if credential: if credential:
credential.set_store(self) credential.set_store(self)
@@ -247,29 +247,29 @@ class _MultiStore(object):
def locked_put(self, credentials): def locked_put(self, credentials):
"""Write a credential. """Write a credential.
The Storage lock must be held when this is called. The Storage lock must be held when this is called.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
self._multistore._update_credential(self._key, credentials) self._multistore._update_credential(self._key, credentials)
def locked_delete(self): def locked_delete(self):
"""Delete a credential. """Delete a credential.
The Storage lock must be held when this is called. The Storage lock must be held when this is called.
Args: Args:
credentials: Credentials, the credentials to store. credentials: Credentials, the credentials to store.
""" """
self._multistore._delete_credential(self._key) self._multistore._delete_credential(self._key)
def _create_file_if_needed(self): def _create_file_if_needed(self):
"""Create an empty file if necessary. """Create an empty file if necessary.
This method will not initialize the file. Instead it implements a This method will not initialize the file. Instead it implements a
simple version of "touch" to ensure the file has been created. simple version of "touch" to ensure the file has been created.
""" """
if not os.path.exists(self._file.filename()): if not os.path.exists(self._file.filename()):
old_umask = os.umask(0o177) old_umask = os.umask(0o177)
try: try:
@@ -318,11 +318,11 @@ class _MultiStore(object):
def _locked_json_read(self): def _locked_json_read(self):
"""Get the raw content of the multistore file. """Get the raw content of the multistore file.
The multistore must be locked when this is called. The multistore must be locked when this is called.
Returns: Returns:
The contents of the multistore decoded as JSON. The contents of the multistore decoded as JSON.
""" """
assert self._thread_lock.locked() assert self._thread_lock.locked()
self._file.file_handle().seek(0) self._file.file_handle().seek(0)
return json.load(self._file.file_handle()) return json.load(self._file.file_handle())
@@ -330,11 +330,11 @@ class _MultiStore(object):
def _locked_json_write(self, data): def _locked_json_write(self, data):
"""Write a JSON serializable data structure to the multistore. """Write a JSON serializable data structure to the multistore.
The multistore must be locked when this is called. The multistore must be locked when this is called.
Args: Args:
data: The data to be serialized and written. data: The data to be serialized and written.
""" """
assert self._thread_lock.locked() assert self._thread_lock.locked()
if self._read_only: if self._read_only:
return return
@@ -345,12 +345,12 @@ class _MultiStore(object):
def _refresh_data_cache(self): def _refresh_data_cache(self):
"""Refresh the contents of the multistore. """Refresh the contents of the multistore.
The multistore must be locked when this is called. The multistore must be locked when this is called.
Raises: Raises:
NewerCredentialStoreError: Raised when a newer client has written the NewerCredentialStoreError: Raised when a newer client has written
store. the store.
""" """
self._data = {} self._data = {}
try: try:
raw_data = self._locked_json_read() raw_data = self._locked_json_read()
@@ -387,13 +387,13 @@ class _MultiStore(object):
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.
Args: Args:
cred_entry: A dict entry from the data member of our format cred_entry: A dict entry from the data member of our format
Returns: Returns:
(key, cred) where the key is the key tuple and the cred is the (key, cred) where the key is the key tuple and the cred is the
OAuth2Credential object. OAuth2Credential 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
@@ -403,8 +403,8 @@ class _MultiStore(object):
def _write(self): def _write(self):
"""Write the cached data back out. """Write the cached data back out.
The multistore must be locked. The multistore must be locked.
""" """
raw_data = {'file_version': 1} raw_data = {'file_version': 1}
raw_creds = [] raw_creds = []
raw_data['data'] = raw_creds raw_data['data'] = raw_creds
@@ -417,44 +417,45 @@ class _MultiStore(object):
def _get_all_credential_keys(self): def _get_all_credential_keys(self):
"""Gets all the registered credential keys in the multistore. """Gets all the registered credential keys in the multistore.
Returns: Returns:
A list of dictionaries corresponding to all the keys currently registered A list of dictionaries corresponding to all the keys currently
""" registered
"""
return [dict(key) for key in self._data.keys()] return [dict(key) for key in self._data.keys()]
def _get_credential(self, key): def _get_credential(self, key):
"""Get a credential from the multistore. """Get a credential from the multistore.
The multistore must be locked. The multistore must be locked.
Args: Args:
key: The key used to retrieve the credential key: The key used to retrieve the credential
Returns: Returns:
The credential specified or None if not present The credential specified or None if not present
""" """
return self._data.get(key, None) return self._data.get(key, None)
def _update_credential(self, key, cred): def _update_credential(self, key, cred):
"""Update a credential and write the multistore. """Update a credential and write the multistore.
This must be called when the multistore is locked. This must be called when the multistore is locked.
Args: Args:
key: The key used to retrieve the credential key: The key used to retrieve the credential
cred: The OAuth2Credential to update/set cred: The OAuth2Credential to update/set
""" """
self._data[key] = cred self._data[key] = cred
self._write() self._write()
def _delete_credential(self, key): def _delete_credential(self, key):
"""Delete a credential and write the multistore. """Delete a credential and write the multistore.
This must be called when the multistore is locked. This must be called when the multistore is locked.
Args: Args:
key: The key used to retrieve the credential key: The key used to retrieve the credential
""" """
try: try:
del self._data[key] del self._data[key]
except KeyError: except KeyError:
@@ -464,12 +465,12 @@ class _MultiStore(object):
def _get_storage(self, key): def _get_storage(self, key):
"""Get a Storage object to get/set a credential. """Get a Storage object to get/set a credential.
This Storage is a 'view' into the multistore. This Storage is a 'view' into the multistore.
Args: Args:
key: The key used to retrieve the credential key: The key used to retrieve the credential
Returns: Returns:
A Storage object that can be used to get/set this cred A Storage object that can be used to get/set this cred
""" """
return self._Storage(self, key) return self._Storage(self, key)

View File

@@ -49,42 +49,42 @@ gflags.DEFINE_multi_int('auth_host_port', [8080, 8090],
def run(flow, storage, http=None): def run(flow, storage, http=None):
"""Core code for a command-line application. """Core code for a command-line application.
The ``run()`` function is called from your application and runs The ``run()`` function is called from your application and runs
through all the steps to obtain credentials. It takes a ``Flow`` through all the steps to obtain credentials. It takes a ``Flow``
argument and attempts to open an authorization server page in the argument and attempts to open an authorization server page in the
user's default web browser. The server asks the user to grant your user's default web browser. The server asks the user to grant your
application access to the user's data. If the user grants access, application access to the user's data. If the user grants access,
the ``run()`` function returns new credentials. The new credentials the ``run()`` function returns new credentials. The new credentials
are also stored in the ``storage`` argument, which updates the file are also stored in the ``storage`` argument, which updates the file
associated with the ``Storage`` object. associated with the ``Storage`` object.
It presumes it is run from a command-line application and supports the It presumes it is run from a command-line application and supports the
following flags: following flags:
``--auth_host_name`` (string, default: ``localhost``) ``--auth_host_name`` (string, default: ``localhost``)
Host name to use when running a local web server to handle Host name to use when running a local web server to handle
redirects during OAuth authorization. redirects during OAuth authorization.
``--auth_host_port`` (integer, default: ``[8080, 8090]``) ``--auth_host_port`` (integer, default: ``[8080, 8090]``)
Port to use when running a local web server to handle redirects Port to use when running a local web server to handle redirects
during OAuth authorization. Repeat this option to specify a list during OAuth authorization. Repeat this option to specify a list
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()``.
Args: Args:
flow: Flow, an OAuth 2.0 Flow to step through. flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in. storage: Storage, a ``Storage`` to store the credential in.
http: An instance of ``httplib2.Http.request`` or something that acts http: An instance of ``httplib2.Http.request`` or something that acts
like it. like it.
Returns: Returns:
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 a future '
'version of the library.') 'version of the library.')

View File

@@ -73,26 +73,26 @@ argparser = _CreateArgumentParser()
class ClientRedirectServer(BaseHTTPServer.HTTPServer): class ClientRedirectServer(BaseHTTPServer.HTTPServer):
"""A server to handle OAuth 2.0 redirects back to localhost. """A server to handle OAuth 2.0 redirects back to localhost.
Waits for a single request and parses the query parameters Waits for a single request and parses the query parameters
into query_params and then stops serving. into query_params and then stops serving.
""" """
query_params = {} query_params = {}
class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""A handler for OAuth 2.0 redirects back to localhost. """A handler for OAuth 2.0 redirects back to localhost.
Waits for a single request and parses the query parameters Waits for a single request and parses the query parameters
into the servers query_params and then stops serving. into the servers query_params and then stops serving.
""" """
def do_GET(self): def do_GET(self):
"""Handle a GET request. """Handle a GET request.
Parses the query parameters and prints a message Parses the query parameters and prints a message
if the flow has completed. Note that we can't detect if the flow has completed. Note that we can't detect
if an error occurred. if an error occurred.
""" """
self.send_response(200) self.send_response(200)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.end_headers() self.end_headers()
@@ -111,54 +111,53 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def run_flow(flow, storage, flags, http=None): def run_flow(flow, storage, flags, http=None):
"""Core code for a command-line application. """Core code for a command-line application.
The ``run()`` function is called from your application and runs The ``run()`` function is called from your application and runs
through all the steps to obtain credentials. It takes a ``Flow`` through all the steps to obtain credentials. It takes a ``Flow``
argument and attempts to open an authorization server page in the argument and attempts to open an authorization server page in the
user's default web browser. The server asks the user to grant your user's default web browser. The server asks the user to grant your
application access to the user's data. If the user grants access, application access to the user's data. If the user grants access,
the ``run()`` function returns new credentials. The new credentials the ``run()`` function returns new credentials. The new credentials
are also stored in the ``storage`` argument, which updates the file are also stored in the ``storage`` argument, which updates the file
associated with the ``Storage`` object. associated with the ``Storage`` object.
It presumes it is run from a command-line application and supports the It presumes it is run from a command-line application and supports the
following flags: following flags:
``--auth_host_name`` (string, default: ``localhost``) ``--auth_host_name`` (string, default: ``localhost``)
Host name to use when running a local web server to handle Host name to use when running a local web server to handle
redirects during OAuth authorization. redirects during OAuth authorization.
``--auth_host_port`` (integer, default: ``[8080, 8090]``) ``--auth_host_port`` (integer, default: ``[8080, 8090]``)
Port to use when running a local web server to handle redirects Port to use when running a local web server to handle redirects
during OAuth authorization. Repeat this option to specify a list during OAuth authorization. Repeat this option to specify a list
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.
The tools module defines an ``ArgumentParser`` the already contains the flag
definitions that ``run()`` requires. You can pass that ``ArgumentParser`` to
your ``ArgumentParser`` constructor::
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[tools.argparser])
flags = parser.parse_args(argv)
Args:
flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in.
flags: ``argparse.Namespace``, The command-line flags. This is the
object returned from calling ``parse_args()`` on
``argparse.ArgumentParser`` as described above.
http: An instance of ``httplib2.Http.request`` or something that
acts like it.
The tools module defines an ``ArgumentParser`` the already contains the flag Returns:
definitions that ``run()`` requires. You can pass that ``ArgumentParser`` to your Credentials, the obtained credential.
``ArgumentParser`` constructor:: """
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[tools.argparser])
flags = parser.parse_args(argv)
Args:
flow: Flow, an OAuth 2.0 Flow to step through.
storage: Storage, a ``Storage`` to store the credential in.
flags: ``argparse.Namespace``, The command-line flags. This is the
object returned from calling ``parse_args()`` on
``argparse.ArgumentParser`` as described above.
http: An instance of ``httplib2.Http.request`` or something that
acts like it.
Returns:
Credentials, the obtained credential.
"""
logging.getLogger().setLevel(getattr(logging, flags.logging_level)) logging.getLogger().setLevel(getattr(logging, flags.logging_level))
if not flags.noauth_local_webserver: if not flags.noauth_local_webserver:
success = False success = False

View File

@@ -52,73 +52,74 @@ positional_parameters_enforcement = POSITIONAL_WARNING
def positional(max_positional_args): def positional(max_positional_args):
"""A decorator to declare that only the first N arguments my be positional. """A decorator to declare that only the first N arguments my be positional.
This decorator makes it easy to support Python 3 style keyword-only This decorator makes it easy to support Python 3 style keyword-only
parameters. For example, in Python 3 it is possible to write:: parameters. For example, in Python 3 it is possible to write::
def fn(pos1, *, kwonly1=None, kwonly1=None): def fn(pos1, *, kwonly1=None, kwonly1=None):
... ...
All named parameters after ``*`` must be a keyword:: All named parameters after ``*`` must be a keyword::
fn(10, 'kw1', 'kw2') # Raises exception. fn(10, 'kw1', 'kw2') # Raises exception.
fn(10, kwonly1='kw1') # Ok. fn(10, kwonly1='kw1') # Ok.
Example Example
^^^^^^^ ^^^^^^^
To define a function like above, do:: To define a function like above, do::
@positional(1) @positional(1)
def fn(pos1, kwonly1=None, kwonly2=None): def fn(pos1, kwonly1=None, kwonly2=None):
... ...
If no default value is provided to a keyword argument, it becomes a required If no default value is provided to a keyword argument, it becomes a
keyword argument:: required keyword argument::
@positional(0) @positional(0)
def fn(required_kw): def fn(required_kw):
... ...
This must be called with the keyword parameter:: This must be called with the keyword parameter::
fn() # Raises exception. fn() # Raises exception.
fn(10) # Raises exception. fn(10) # Raises exception.
fn(required_kw=10) # Ok. fn(required_kw=10) # Ok.
When defining instance or class methods always remember to account for When defining instance or class methods always remember to account for
``self`` and ``cls``:: ``self`` and ``cls``::
class MyClass(object): class MyClass(object):
@positional(2) @positional(2)
def my_method(self, pos1, kwonly1=None): def my_method(self, pos1, kwonly1=None):
... ...
@classmethod @classmethod
@positional(2) @positional(2)
def my_method(cls, pos1, kwonly1=None): def my_method(cls, pos1, kwonly1=None):
... ...
The positional decorator behavior is controlled by The positional decorator behavior is controlled by
``util.positional_parameters_enforcement``, which may be set to ``util.positional_parameters_enforcement``, which may be set to
``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
nothing, respectively, if a declaration is violated. nothing, respectively, if a declaration is violated.
Args: Args:
max_positional_arguments: Maximum number of positional arguments. All max_positional_arguments: Maximum number of positional arguments. All
parameters after the this index must be keyword only. parameters after the this index must be
keyword only.
Returns: Returns:
A decorator that prevents using arguments after max_positional_args from A decorator that prevents using arguments after max_positional_args
being used as positional parameters. from being used as positional parameters.
Raises: Raises:
TypeError if a key-word only argument is provided as a positional TypeError: if a key-word only argument is provided as a positional
parameter, but only if util.positional_parameters_enforcement is set to parameter, but only if
POSITIONAL_EXCEPTION. util.positional_parameters_enforcement is set to
POSITIONAL_EXCEPTION.
""" """
def positional_decorator(wrapped): def positional_decorator(wrapped):
@functools.wraps(wrapped) @functools.wraps(wrapped)
@@ -148,16 +149,16 @@ def positional(max_positional_args):
def scopes_to_string(scopes): def scopes_to_string(scopes):
"""Converts scope value to a string. """Converts scope value to a string.
If scopes is a string then it is simply passed through. If scopes is an If scopes is a string then it is simply passed through. If scopes is an
iterable then a string is returned that is all the individual scopes iterable then a string is returned that is all the individual scopes
concatenated with spaces. concatenated with spaces.
Args: Args:
scopes: string or iterable of strings, the scopes. scopes: string or iterable of strings, the scopes.
Returns: Returns:
The scopes formatted as a single string. The scopes formatted as a single string.
""" """
if isinstance(scopes, six.string_types): if isinstance(scopes, six.string_types):
return scopes return scopes
else: else:
@@ -167,15 +168,15 @@ def scopes_to_string(scopes):
def string_to_scopes(scopes): def string_to_scopes(scopes):
"""Converts stringifed scope value to a list. """Converts stringifed scope value to a list.
If scopes is a list then it is simply passed through. If scopes is an If scopes is a list then it is simply passed through. If scopes is an
string then a list of each individual scope is returned. string then a list of each individual scope is returned.
Args: Args:
scopes: a string or iterable of strings, the scopes. scopes: a string or iterable of strings, the scopes.
Returns: Returns:
The scopes in a list. The scopes in a list.
""" """
if not scopes: if not scopes:
return [] return []
if isinstance(scopes, six.string_types): if isinstance(scopes, six.string_types):
@@ -187,31 +188,31 @@ def string_to_scopes(scopes):
def dict_to_tuple_key(dictionary): def dict_to_tuple_key(dictionary):
"""Converts a dictionary to a tuple that can be used as an immutable key. """Converts a dictionary to a tuple that can be used as an immutable key.
The resulting key is always sorted so that logically equivalent dictionaries The resulting key is always sorted so that logically equivalent
always produce an identical tuple for a key. dictionaries always produce an identical tuple for a key.
Args: Args:
dictionary: the dictionary to use as the key. dictionary: the dictionary to use as the key.
Returns: Returns:
A tuple representing the dictionary in it's naturally sorted ordering. A tuple representing the dictionary in it's naturally sorted ordering.
""" """
return tuple(sorted(dictionary.items())) return tuple(sorted(dictionary.items()))
def _add_query_parameter(url, name, value): def _add_query_parameter(url, name, value):
"""Adds a query parameter to a url. """Adds a query parameter to a url.
Replaces the current value if it already exists in the URL. Replaces the current value if it already exists in the URL.
Args: Args:
url: string, url to add the query parameter to. url: string, url to add the query parameter to.
name: string, query parameter name. name: string, query parameter name.
value: string, query parameter value. value: string, query parameter value.
Returns: Returns:
Updated query parameter. Does not update the url if value is None. Updated query parameter. Does not update the url if value is None.
""" """
if value is None: if value is None:
return url return url
else: else:

View File

@@ -47,17 +47,17 @@ def _force_bytes(s):
def generate_token(key, user_id, action_id="", when=None): def generate_token(key, user_id, action_id="", when=None):
"""Generates a URL-safe token for the given user, action, time tuple. """Generates a URL-safe token for the given user, action, time tuple.
Args: Args:
key: secret key to use. key: secret key to use.
user_id: the user ID of the authenticated user. user_id: the user ID of the authenticated user.
action_id: a string identifier of the action they requested action_id: a string identifier of the action they requested
authorization for. authorization for.
when: the time in seconds since the epoch at which the user was when: the time in seconds since the epoch at which the user was
authorized for this action. If not set the current time is used. authorized for this action. If not set the current time is used.
Returns: Returns:
A string XSRF protection token. A string XSRF protection token.
""" """
when = _force_bytes(when or int(time.time())) when = _force_bytes(when or int(time.time()))
digester = hmac.new(_force_bytes(key)) digester = hmac.new(_force_bytes(key))
digester.update(_force_bytes(user_id)) digester.update(_force_bytes(user_id))
@@ -75,20 +75,20 @@ def generate_token(key, user_id, action_id="", when=None):
def validate_token(key, token, user_id, action_id="", current_time=None): def validate_token(key, token, user_id, action_id="", current_time=None):
"""Validates that the given token authorizes the user for the action. """Validates that the given token authorizes the user for the action.
Tokens are invalid if the time of issue is too old or if the token Tokens are invalid if the time of issue is too old or if the token
does not match what generateToken outputs (i.e. the token was forged). does not match what generateToken outputs (i.e. the token was forged).
Args: Args:
key: secret key to use. key: secret key to use.
token: a string of the token generated by generateToken. token: a string of the token generated by generateToken.
user_id: the user ID of the authenticated user. user_id: the user ID of the authenticated user.
action_id: a string identifier of the action they requested action_id: a string identifier of the action they requested
authorization for. authorization for.
Returns: Returns:
A boolean - True if the user is authorized for the action, False A boolean - True if the user is authorized for the action, False
otherwise. otherwise.
""" """
if not token: if not token:
return False return False
try: try: