mirror of
https://github.com/GAM-team/GAM.git
synced 2025-07-04 11:43:45 +00:00
upgrade to oauth2client 3.0. Override some strings in GAM
to reduce custom changes to oauth2client.
This commit is contained in:
@ -17,32 +17,25 @@
|
||||
Tools for interacting with OAuth 2.0 protected resources.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import collections
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import shutil
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
import httplib2
|
||||
from oauth2client import GOOGLE_AUTH_URI
|
||||
from oauth2client import GOOGLE_DEVICE_URI
|
||||
from oauth2client import GOOGLE_REVOKE_URI
|
||||
from oauth2client import GOOGLE_TOKEN_URI
|
||||
from oauth2client import GOOGLE_TOKEN_INFO_URI
|
||||
from oauth2client._helpers import _from_bytes
|
||||
from oauth2client._helpers import _to_bytes
|
||||
from oauth2client._helpers import _urlsafe_b64decode
|
||||
import oauth2client
|
||||
from oauth2client import _helpers
|
||||
from oauth2client import clientsecrets
|
||||
from oauth2client import transport
|
||||
from oauth2client import util
|
||||
|
||||
|
||||
@ -53,9 +46,8 @@ HAS_CRYPTO = False
|
||||
try:
|
||||
from oauth2client import crypt
|
||||
HAS_CRYPTO = True
|
||||
if crypt.OpenSSLVerifier is not None:
|
||||
HAS_OPENSSL = True
|
||||
except ImportError:
|
||||
HAS_OPENSSL = crypt.OpenSSLVerifier is not None
|
||||
except ImportError: # pragma: NO COVER
|
||||
pass
|
||||
|
||||
|
||||
@ -73,9 +65,6 @@ ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
|
||||
# Constant to use for the out of band OAuth 2.0 flow.
|
||||
OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
|
||||
# Google Data client libraries may need to set this to [401, 403].
|
||||
REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,)
|
||||
|
||||
# The value representing user credentials.
|
||||
AUTHORIZED_USER = 'authorized_user'
|
||||
|
||||
@ -113,6 +102,14 @@ DEFAULT_ENV_NAME = 'UNKNOWN'
|
||||
# If set to True _get_environment avoid GCE check (_detect_gce_environment)
|
||||
NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
|
||||
|
||||
# Timeout in seconds to wait for the GCE metadata server when detecting the
|
||||
# GCE environment.
|
||||
try:
|
||||
GCE_METADATA_TIMEOUT = int(
|
||||
os.environ.setdefault('GCE_METADATA_TIMEOUT', '3'))
|
||||
except ValueError: # pragma: NO COVER
|
||||
GCE_METADATA_TIMEOUT = 3
|
||||
|
||||
_SERVER_SOFTWARE = 'SERVER_SOFTWARE'
|
||||
_GCE_METADATA_HOST = '169.254.169.254'
|
||||
_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
|
||||
@ -122,6 +119,12 @@ _DESIRED_METADATA_FLAVOR = 'Google'
|
||||
# easier testing (by replacing with a stub).
|
||||
_UTCNOW = datetime.datetime.utcnow
|
||||
|
||||
# NOTE: These names were previously defined in this module but have been
|
||||
# moved into `oauth2client.transport`,
|
||||
clean_headers = transport.clean_headers
|
||||
MemoryCache = transport.MemoryCache
|
||||
REFRESH_STATUS_CODES = transport.REFRESH_STATUS_CODES
|
||||
|
||||
|
||||
class SETTINGS(object):
|
||||
"""Settings namespace for globally defined values."""
|
||||
@ -179,26 +182,6 @@ class CryptoUnavailableError(Error, NotImplementedError):
|
||||
"""Raised when a crypto library is required, but none is available."""
|
||||
|
||||
|
||||
def _abstract():
|
||||
raise NotImplementedError('You need to override this function')
|
||||
|
||||
|
||||
class MemoryCache(object):
|
||||
"""httplib2 Cache implementation which only caches locally."""
|
||||
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
|
||||
def get(self, key):
|
||||
return self.cache.get(key)
|
||||
|
||||
def set(self, key, value):
|
||||
self.cache[key] = value
|
||||
|
||||
def delete(self, key):
|
||||
self.cache.pop(key, None)
|
||||
|
||||
|
||||
def _parse_expiry(expiry):
|
||||
if expiry and isinstance(expiry, datetime.datetime):
|
||||
return expiry.strftime(EXPIRY_FORMAT)
|
||||
@ -229,7 +212,7 @@ class Credentials(object):
|
||||
http: httplib2.Http, an http object to be used to make the refresh
|
||||
request.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def refresh(self, http):
|
||||
"""Forces a refresh of the access_token.
|
||||
@ -238,7 +221,7 @@ class Credentials(object):
|
||||
http: httplib2.Http, an http object to be used to make the refresh
|
||||
request.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def revoke(self, http):
|
||||
"""Revokes a refresh_token and makes the credentials void.
|
||||
@ -247,7 +230,7 @@ class Credentials(object):
|
||||
http: httplib2.Http, an http object to be used to make the revoke
|
||||
request.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def apply(self, headers):
|
||||
"""Add the authorization to the headers.
|
||||
@ -255,7 +238,7 @@ class Credentials(object):
|
||||
Args:
|
||||
headers: dict, the headers to add the Authorization header to.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def _to_json(self, strip, to_serialize=None):
|
||||
"""Utility function that creates JSON repr. of a Credentials object.
|
||||
@ -264,8 +247,8 @@ class Credentials(object):
|
||||
strip: array, An array of names of members to exclude from the
|
||||
JSON.
|
||||
to_serialize: dict, (Optional) The properties for this object
|
||||
that will be serialized. This allows callers to modify
|
||||
before serializing.
|
||||
that will be serialized. This allows callers to
|
||||
modify before serializing.
|
||||
|
||||
Returns:
|
||||
string, a JSON representation of this instance, suitable to pass to
|
||||
@ -274,6 +257,9 @@ class Credentials(object):
|
||||
curr_type = self.__class__
|
||||
if to_serialize is None:
|
||||
to_serialize = copy.copy(self.__dict__)
|
||||
else:
|
||||
# Assumes it is a str->str dictionary, so we don't deep copy.
|
||||
to_serialize = copy.copy(to_serialize)
|
||||
for member in strip:
|
||||
if member in to_serialize:
|
||||
del to_serialize[member]
|
||||
@ -311,7 +297,7 @@ class Credentials(object):
|
||||
An instance of the subclass of Credentials that was serialized with
|
||||
to_json().
|
||||
"""
|
||||
json_data_as_unicode = _from_bytes(json_data)
|
||||
json_data_as_unicode = _helpers._from_bytes(json_data)
|
||||
data = json.loads(json_data_as_unicode)
|
||||
# Find and call the right classmethod from_json() to restore
|
||||
# the object.
|
||||
@ -361,7 +347,8 @@ class Storage(object):
|
||||
|
||||
Args:
|
||||
lock: An optional threading.Lock-like object. Must implement at
|
||||
least acquire() and release(). Does not need to be re-entrant.
|
||||
least acquire() and release(). Does not need to be
|
||||
re-entrant.
|
||||
"""
|
||||
self._lock = lock
|
||||
|
||||
@ -390,7 +377,7 @@ class Storage(object):
|
||||
Returns:
|
||||
oauth2client.client.Credentials
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def locked_put(self, credentials):
|
||||
"""Write a credential.
|
||||
@ -400,14 +387,14 @@ class Storage(object):
|
||||
Args:
|
||||
credentials: Credentials, the credentials to store.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def locked_delete(self):
|
||||
"""Delete a credential.
|
||||
|
||||
The Storage lock must be held when this is called.
|
||||
"""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def get(self):
|
||||
"""Retrieve credential.
|
||||
@ -453,32 +440,6 @@ class Storage(object):
|
||||
self.release_lock()
|
||||
|
||||
|
||||
def clean_headers(headers):
|
||||
"""Forces header keys and values to be strings, i.e not unicode.
|
||||
|
||||
The httplib module just concats the header keys and values in a way that
|
||||
may make the message header a unicode string, which, if it then tries to
|
||||
contatenate to a binary request body may result in a unicode decode error.
|
||||
|
||||
Args:
|
||||
headers: dict, A dictionary of headers.
|
||||
|
||||
Returns:
|
||||
The same dictionary but with all the keys converted to strings.
|
||||
"""
|
||||
clean = {}
|
||||
try:
|
||||
for k, v in six.iteritems(headers):
|
||||
if not isinstance(k, six.binary_type):
|
||||
k = str(k)
|
||||
if not isinstance(v, six.binary_type):
|
||||
v = str(v)
|
||||
clean[_to_bytes(k)] = _to_bytes(v)
|
||||
except UnicodeEncodeError:
|
||||
raise NonAsciiHeaderError(k, ': ', v)
|
||||
return clean
|
||||
|
||||
|
||||
def _update_query_params(uri, params):
|
||||
"""Updates a URI with new query parameters.
|
||||
|
||||
@ -586,67 +547,7 @@ class OAuth2Credentials(Credentials):
|
||||
that adds in the Authorization header and then calls the original
|
||||
version of 'request()'.
|
||||
"""
|
||||
request_orig = http.request
|
||||
|
||||
# The closure that will replace 'httplib2.Http.request'.
|
||||
def new_request(uri, method='GET', body=None, headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS,
|
||||
connection_type=None):
|
||||
if not self.access_token:
|
||||
logger.info('Attempting refresh to obtain '
|
||||
'initial access_token')
|
||||
self._refresh(request_orig)
|
||||
|
||||
# Clone and modify the request headers to add the appropriate
|
||||
# Authorization header.
|
||||
if headers is None:
|
||||
headers = {}
|
||||
else:
|
||||
headers = dict(headers)
|
||||
self.apply(headers)
|
||||
|
||||
if self.user_agent is not None:
|
||||
if 'user-agent' in headers:
|
||||
headers['user-agent'] = (self.user_agent + ' ' +
|
||||
headers['user-agent'])
|
||||
else:
|
||||
headers['user-agent'] = self.user_agent
|
||||
|
||||
body_stream_position = None
|
||||
if all(getattr(body, stream_prop, None) for stream_prop in
|
||||
('read', 'seek', 'tell')):
|
||||
body_stream_position = body.tell()
|
||||
|
||||
resp, content = request_orig(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
# A stored token may expire between the time it is retrieved and
|
||||
# the time the request is made, so we may need to try twice.
|
||||
max_refresh_attempts = 2
|
||||
for refresh_attempt in range(max_refresh_attempts):
|
||||
if resp.status not in REFRESH_STATUS_CODES:
|
||||
break
|
||||
logger.info('Refreshing due to a %s (attempt %s/%s)',
|
||||
resp.status, refresh_attempt + 1,
|
||||
max_refresh_attempts)
|
||||
self._refresh(request_orig)
|
||||
self.apply(headers)
|
||||
if body_stream_position is not None:
|
||||
body.seek(body_stream_position)
|
||||
|
||||
resp, content = request_orig(uri, method, body,
|
||||
clean_headers(headers),
|
||||
redirections, connection_type)
|
||||
|
||||
return (resp, content)
|
||||
|
||||
# Replace the request method with our own closure.
|
||||
http.request = new_request
|
||||
|
||||
# Set credentials as a property of the request method.
|
||||
setattr(http.request, 'credentials', self)
|
||||
|
||||
transport.wrap_http_for_auth(self, http)
|
||||
return http
|
||||
|
||||
def refresh(self, http):
|
||||
@ -721,7 +622,7 @@ class OAuth2Credentials(Credentials):
|
||||
Returns:
|
||||
An instance of a Credentials subclass.
|
||||
"""
|
||||
data = json.loads(_from_bytes(json_data))
|
||||
data = json.loads(_helpers._from_bytes(json_data))
|
||||
if (data.get('token_expiry') and
|
||||
not isinstance(data['token_expiry'], datetime.datetime)):
|
||||
try:
|
||||
@ -772,7 +673,7 @@ class OAuth2Credentials(Credentials):
|
||||
"""
|
||||
if not self.access_token or self.access_token_expired:
|
||||
if not http:
|
||||
http = httplib2.Http()
|
||||
http = transport.get_http_object()
|
||||
self.refresh(http)
|
||||
return AccessTokenInfo(access_token=self.access_token,
|
||||
expires_in=self._expires_in())
|
||||
@ -894,7 +795,7 @@ class OAuth2Credentials(Credentials):
|
||||
logger.info('Refreshing access_token')
|
||||
resp, content = http_request(
|
||||
self.token_uri, method='POST', body=body, headers=headers)
|
||||
content = _from_bytes(content)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
d = json.loads(content)
|
||||
self.token_response = d
|
||||
@ -918,7 +819,7 @@ class OAuth2Credentials(Credentials):
|
||||
# An {'error':...} response body means the token is expired or
|
||||
# revoked, so we flag the credentials as such.
|
||||
logger.info('Failed to retrieve access token: %s', content)
|
||||
error_msg = 'Invalid response %s.' % resp['status']
|
||||
error_msg = 'Invalid response {0}.'.format(resp['status'])
|
||||
try:
|
||||
d = json.loads(content)
|
||||
if 'error' in d:
|
||||
@ -926,7 +827,7 @@ class OAuth2Credentials(Credentials):
|
||||
if 'error_description' in d:
|
||||
error_msg += ': ' + d['error_description']
|
||||
self.invalid = True
|
||||
if self.store:
|
||||
if self.store is not None:
|
||||
self.store.locked_put(self)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
@ -963,9 +864,9 @@ class OAuth2Credentials(Credentials):
|
||||
if resp.status == http_client.OK:
|
||||
self.invalid = True
|
||||
else:
|
||||
error_msg = 'Invalid response %s.' % resp.status
|
||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||
try:
|
||||
d = json.loads(_from_bytes(content))
|
||||
d = json.loads(_helpers._from_bytes(content))
|
||||
if 'error' in d:
|
||||
error_msg = d['error']
|
||||
except (TypeError, ValueError):
|
||||
@ -1004,12 +905,12 @@ class OAuth2Credentials(Credentials):
|
||||
token_info_uri = _update_query_params(self.token_info_uri,
|
||||
query_params)
|
||||
resp, content = http_request(token_info_uri)
|
||||
content = _from_bytes(content)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
d = json.loads(content)
|
||||
self.scopes = set(util.string_to_scopes(d.get('scope', '')))
|
||||
else:
|
||||
error_msg = 'Invalid response %s.' % (resp.status,)
|
||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||
try:
|
||||
d = json.loads(content)
|
||||
if 'error_description' in d:
|
||||
@ -1070,7 +971,7 @@ class AccessTokenCredentials(OAuth2Credentials):
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_data):
|
||||
data = json.loads(_from_bytes(json_data))
|
||||
data = json.loads(_helpers._from_bytes(json_data))
|
||||
retval = AccessTokenCredentials(
|
||||
data['access_token'],
|
||||
data['user_agent'])
|
||||
@ -1105,7 +1006,7 @@ def _detect_gce_environment():
|
||||
# the metadata resolution was particularly slow. The latter case is
|
||||
# "unlikely".
|
||||
connection = six.moves.http_client.HTTPConnection(
|
||||
_GCE_METADATA_HOST, timeout=1)
|
||||
_GCE_METADATA_HOST, timeout=GCE_METADATA_TIMEOUT)
|
||||
|
||||
try:
|
||||
headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
|
||||
@ -1186,14 +1087,14 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
print(response)
|
||||
"""
|
||||
|
||||
NON_SERIALIZED_MEMBERS = (
|
||||
NON_SERIALIZED_MEMBERS = (
|
||||
frozenset(['_private_key']) |
|
||||
OAuth2Credentials.NON_SERIALIZED_MEMBERS)
|
||||
"""Members that aren't serialized when object is converted to JSON."""
|
||||
|
||||
def __init__(self, access_token, client_id, client_secret, refresh_token,
|
||||
token_expiry, token_uri, user_agent,
|
||||
revoke_uri=GOOGLE_REVOKE_URI):
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI):
|
||||
"""Create an instance of GoogleCredentials.
|
||||
|
||||
This constructor is not usually called by the user, instead
|
||||
@ -1211,8 +1112,8 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
user_agent: string, The HTTP User-Agent to provide for this
|
||||
application.
|
||||
revoke_uri: string, URI for revoke endpoint. Defaults to
|
||||
GOOGLE_REVOKE_URI; a token can't be revoked if this
|
||||
is None.
|
||||
oauth2client.GOOGLE_REVOKE_URI; a token can't be
|
||||
revoked if this is None.
|
||||
"""
|
||||
super(GoogleCredentials, self).__init__(
|
||||
access_token, client_id, client_secret, refresh_token,
|
||||
@ -1237,14 +1138,17 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
def from_json(cls, json_data):
|
||||
# TODO(issue 388): eliminate the circularity that is the reason for
|
||||
# this non-top-level import.
|
||||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
data = json.loads(_from_bytes(json_data))
|
||||
from oauth2client import service_account
|
||||
data = json.loads(_helpers._from_bytes(json_data))
|
||||
|
||||
# We handle service_account.ServiceAccountCredentials since it is a
|
||||
# possible return type of GoogleCredentials.get_application_default()
|
||||
if (data['_module'] == 'oauth2client.service_account' and
|
||||
data['_class'] == 'ServiceAccountCredentials'):
|
||||
return ServiceAccountCredentials.from_json(data)
|
||||
data['_class'] == 'ServiceAccountCredentials'):
|
||||
return service_account.ServiceAccountCredentials.from_json(data)
|
||||
elif (data['_module'] == 'oauth2client.service_account' and
|
||||
data['_class'] == '_JWTAccessCredentials'):
|
||||
return service_account._JWTAccessCredentials.from_json(data)
|
||||
|
||||
token_expiry = _parse_expiry(data.get('token_expiry'))
|
||||
google_credentials = cls(
|
||||
@ -1348,10 +1252,10 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
"""Gets credentials implicitly from the environment.
|
||||
|
||||
Checks environment in order of precedence:
|
||||
- Google App Engine (production and testing)
|
||||
- Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to
|
||||
a file with stored credentials information.
|
||||
- Stored "well known" file associated with `gcloud` command line tool.
|
||||
- Google App Engine (production and testing)
|
||||
- Google Compute Engine production environment.
|
||||
|
||||
Raises:
|
||||
@ -1360,8 +1264,8 @@ class GoogleCredentials(OAuth2Credentials):
|
||||
"""
|
||||
# Environ checks (in order).
|
||||
environ_checkers = [
|
||||
cls._implicit_credentials_from_gae,
|
||||
cls._implicit_credentials_from_files,
|
||||
cls._implicit_credentials_from_gae,
|
||||
cls._implicit_credentials_from_gce,
|
||||
]
|
||||
|
||||
@ -1446,7 +1350,8 @@ def save_to_well_known_file(credentials, well_known_file=None):
|
||||
|
||||
config_dir = os.path.dirname(well_known_file)
|
||||
if not os.path.isdir(config_dir):
|
||||
raise OSError('Config directory does not exist: %s' % config_dir)
|
||||
raise OSError(
|
||||
'Config directory does not exist: {0}'.format(config_dir))
|
||||
|
||||
credentials_data = credentials.serialization_data
|
||||
_save_private_file(well_known_file, credentials_data)
|
||||
@ -1454,8 +1359,7 @@ def save_to_well_known_file(credentials, well_known_file=None):
|
||||
|
||||
def _get_environment_variable_file():
|
||||
application_default_credential_filename = (
|
||||
os.environ.get(GOOGLE_APPLICATION_CREDENTIALS,
|
||||
None))
|
||||
os.environ.get(GOOGLE_APPLICATION_CREDENTIALS, None))
|
||||
|
||||
if application_default_credential_filename:
|
||||
if os.path.isfile(application_default_credential_filename):
|
||||
@ -1521,11 +1425,11 @@ def _get_application_default_credential_from_file(filename):
|
||||
client_secret=client_credentials['client_secret'],
|
||||
refresh_token=client_credentials['refresh_token'],
|
||||
token_expiry=None,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
user_agent='Python client library')
|
||||
else: # client_credentials['type'] == SERVICE_ACCOUNT
|
||||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
return ServiceAccountCredentials.from_json_keyfile_dict(
|
||||
from oauth2client import service_account
|
||||
return service_account._JWTAccessCredentials.from_json_keyfile_dict(
|
||||
client_credentials)
|
||||
|
||||
|
||||
@ -1538,8 +1442,8 @@ def _raise_exception_for_reading_json(credential_file,
|
||||
extra_help,
|
||||
error):
|
||||
raise ApplicationDefaultCredentialsError(
|
||||
'An error was encountered while reading json file: ' +
|
||||
credential_file + extra_help + ': ' + str(error))
|
||||
'An error was encountered while reading json file: ' +
|
||||
credential_file + extra_help + ': ' + str(error))
|
||||
|
||||
|
||||
def _get_application_default_credential_GAE():
|
||||
@ -1567,8 +1471,8 @@ class AssertionCredentials(GoogleCredentials):
|
||||
|
||||
@util.positional(2)
|
||||
def __init__(self, assertion_type, user_agent=None,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||
**unused_kwargs):
|
||||
"""Constructor for AssertionFlowCredentials.
|
||||
|
||||
@ -1605,7 +1509,7 @@ class AssertionCredentials(GoogleCredentials):
|
||||
|
||||
def _generate_assertion(self):
|
||||
"""Generate assertion string to be used in the access token request."""
|
||||
_abstract()
|
||||
raise NotImplementedError
|
||||
|
||||
def _revoke(self, http_request):
|
||||
"""Revokes the access_token and deletes the store if available.
|
||||
@ -1630,7 +1534,7 @@ class AssertionCredentials(GoogleCredentials):
|
||||
raise NotImplementedError('This method is abstract.')
|
||||
|
||||
|
||||
def _RequireCryptoOrDie():
|
||||
def _require_crypto_or_die():
|
||||
"""Ensure we have a crypto library, or throw CryptoUnavailableError.
|
||||
|
||||
The oauth2client.crypt module requires either PyCrypto or PyOpenSSL
|
||||
@ -1641,11 +1545,6 @@ def _RequireCryptoOrDie():
|
||||
raise CryptoUnavailableError('No crypto library available')
|
||||
|
||||
|
||||
# Only used in verify_id_token(), which is always calling to the same URI
|
||||
# for the certs.
|
||||
_cached_http = httplib2.Http(MemoryCache())
|
||||
|
||||
|
||||
@util.positional(2)
|
||||
def verify_id_token(id_token, audience, http=None,
|
||||
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
|
||||
@ -1669,16 +1568,16 @@ def verify_id_token(id_token, audience, http=None,
|
||||
oauth2client.crypt.AppIdentityError: if the JWT fails to verify.
|
||||
CryptoUnavailableError: if no crypto library is available.
|
||||
"""
|
||||
_RequireCryptoOrDie()
|
||||
_require_crypto_or_die()
|
||||
if http is None:
|
||||
http = _cached_http
|
||||
http = transport.get_cached_http()
|
||||
|
||||
resp, content = http.request(cert_uri)
|
||||
if resp.status == http_client.OK:
|
||||
certs = json.loads(_from_bytes(content))
|
||||
certs = json.loads(_helpers._from_bytes(content))
|
||||
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
|
||||
else:
|
||||
raise VerifyJwtTokenError('Status code: %d' % resp.status)
|
||||
raise VerifyJwtTokenError('Status code: {0}'.format(resp.status))
|
||||
|
||||
|
||||
def _extract_id_token(id_token):
|
||||
@ -1699,9 +1598,10 @@ def _extract_id_token(id_token):
|
||||
|
||||
if len(segments) != 3:
|
||||
raise VerifyJwtTokenError(
|
||||
'Wrong number of segments in token: %s' % id_token)
|
||||
'Wrong number of segments in token: {0}'.format(id_token))
|
||||
|
||||
return json.loads(_from_bytes(_urlsafe_b64decode(segments[1])))
|
||||
return json.loads(
|
||||
_helpers._from_bytes(_helpers._urlsafe_b64decode(segments[1])))
|
||||
|
||||
|
||||
def _parse_exchange_token_response(content):
|
||||
@ -1718,7 +1618,7 @@ def _parse_exchange_token_response(content):
|
||||
i.e. {}. That basically indicates a failure.
|
||||
"""
|
||||
resp = {}
|
||||
content = _from_bytes(content)
|
||||
content = _helpers._from_bytes(content)
|
||||
try:
|
||||
resp = json.loads(content)
|
||||
except Exception:
|
||||
@ -1736,11 +1636,12 @@ def _parse_exchange_token_response(content):
|
||||
@util.positional(4)
|
||||
def credentials_from_code(client_id, client_secret, scope, code,
|
||||
redirect_uri='postmessage', http=None,
|
||||
user_agent=None, token_uri=GOOGLE_TOKEN_URI,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
device_uri=GOOGLE_DEVICE_URI,
|
||||
token_info_uri=GOOGLE_TOKEN_INFO_URI):
|
||||
user_agent=None,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI):
|
||||
"""Exchanges an authorization code for an OAuth2Credentials object.
|
||||
|
||||
Args:
|
||||
@ -1864,11 +1765,38 @@ class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
|
||||
})
|
||||
if 'expires_in' in response:
|
||||
kwargs['user_code_expiry'] = (
|
||||
datetime.datetime.now() +
|
||||
_UTCNOW() +
|
||||
datetime.timedelta(seconds=int(response['expires_in'])))
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
def _oauth2_web_server_flow_params(kwargs):
|
||||
"""Configures redirect URI parameters for OAuth2WebServerFlow."""
|
||||
params = {
|
||||
'access_type': 'offline',
|
||||
'response_type': 'code',
|
||||
}
|
||||
|
||||
params.update(kwargs)
|
||||
|
||||
# Check for the presence of the deprecated approval_prompt param and
|
||||
# warn appropriately.
|
||||
approval_prompt = params.get('approval_prompt')
|
||||
if approval_prompt is not None:
|
||||
logger.warning(
|
||||
'The approval_prompt parameter for OAuth2WebServerFlow is '
|
||||
'deprecated. Please use the prompt parameter instead.')
|
||||
|
||||
if approval_prompt == 'force':
|
||||
logger.warning(
|
||||
'approval_prompt="force" has been adjusted to '
|
||||
'prompt="consent"')
|
||||
params['prompt'] = 'consent'
|
||||
del params['approval_prompt']
|
||||
|
||||
return params
|
||||
|
||||
|
||||
class OAuth2WebServerFlow(Flow):
|
||||
"""Does the Web Server Flow for OAuth 2.0.
|
||||
|
||||
@ -1881,18 +1809,18 @@ class OAuth2WebServerFlow(Flow):
|
||||
scope=None,
|
||||
redirect_uri=None,
|
||||
user_agent=None,
|
||||
auth_uri=GOOGLE_AUTH_URI,
|
||||
token_uri=GOOGLE_TOKEN_URI,
|
||||
revoke_uri=GOOGLE_REVOKE_URI,
|
||||
auth_uri=oauth2client.GOOGLE_AUTH_URI,
|
||||
token_uri=oauth2client.GOOGLE_TOKEN_URI,
|
||||
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
|
||||
login_hint=None,
|
||||
device_uri=GOOGLE_DEVICE_URI,
|
||||
token_info_uri=GOOGLE_TOKEN_INFO_URI,
|
||||
device_uri=oauth2client.GOOGLE_DEVICE_URI,
|
||||
token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
|
||||
authorization_header=None,
|
||||
**kwargs):
|
||||
"""Constructor for OAuth2WebServerFlow.
|
||||
|
||||
The kwargs argument is used to set extra query parameters on the
|
||||
auth_uri. For example, the access_type and approval_prompt
|
||||
auth_uri. For example, the access_type and prompt
|
||||
query parameters can be set via kwargs.
|
||||
|
||||
Args:
|
||||
@ -1944,11 +1872,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
self.device_uri = device_uri
|
||||
self.token_info_uri = token_info_uri
|
||||
self.authorization_header = authorization_header
|
||||
self.params = {
|
||||
'access_type': 'offline',
|
||||
'response_type': 'code',
|
||||
}
|
||||
self.params.update(kwargs)
|
||||
self.params = _oauth2_web_server_flow_params(kwargs)
|
||||
|
||||
@util.positional(1)
|
||||
def step1_get_authorize_url(self, redirect_uri=None, state=None):
|
||||
@ -2014,25 +1938,25 @@ class OAuth2WebServerFlow(Flow):
|
||||
headers['user-agent'] = self.user_agent
|
||||
|
||||
if http is None:
|
||||
http = httplib2.Http()
|
||||
http = transport.get_http_object()
|
||||
|
||||
resp, content = http.request(self.device_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
content = _from_bytes(content)
|
||||
content = _helpers._from_bytes(content)
|
||||
if resp.status == http_client.OK:
|
||||
try:
|
||||
flow_info = json.loads(content)
|
||||
except ValueError as e:
|
||||
except ValueError as exc:
|
||||
raise OAuth2DeviceCodeError(
|
||||
'Could not parse server response as JSON: "%s", '
|
||||
'error: "%s"' % (content, e))
|
||||
'Could not parse server response as JSON: "{0}", '
|
||||
'error: "{1}"'.format(content, exc))
|
||||
return DeviceFlowInfo.FromResponse(flow_info)
|
||||
else:
|
||||
error_msg = 'Invalid response %s.' % resp.status
|
||||
error_msg = 'Invalid response {0}.'.format(resp.status)
|
||||
try:
|
||||
d = json.loads(content)
|
||||
if 'error' in d:
|
||||
error_msg += ' Error: %s' % d['error']
|
||||
error_dict = json.loads(content)
|
||||
if 'error' in error_dict:
|
||||
error_msg += ' Error: {0}'.format(error_dict['error'])
|
||||
except ValueError:
|
||||
# Couldn't decode a JSON response, stick with the
|
||||
# default message.
|
||||
@ -2069,7 +1993,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
|
||||
if code is None:
|
||||
code = device_flow_info.device_code
|
||||
elif not isinstance(code, six.string_types):
|
||||
elif not isinstance(code, (six.string_types, six.binary_type)):
|
||||
if 'code' not in code:
|
||||
raise FlowExchangeError(code.get(
|
||||
'error', 'No code was supplied in the query parameters.'))
|
||||
@ -2097,7 +2021,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
headers['user-agent'] = self.user_agent
|
||||
|
||||
if http is None:
|
||||
http = httplib2.Http()
|
||||
http = transport.get_http_object()
|
||||
|
||||
resp, content = http.request(self.token_uri, method='POST', body=body,
|
||||
headers=headers)
|
||||
@ -2108,7 +2032,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
if not refresh_token:
|
||||
logger.info(
|
||||
'Received token response with no refresh_token. Consider '
|
||||
"reauthenticating with approval_prompt='force'.")
|
||||
"reauthenticating with prompt='consent'.")
|
||||
token_expiry = None
|
||||
if 'expires_in' in d:
|
||||
delta = datetime.timedelta(seconds=int(d['expires_in']))
|
||||
@ -2132,7 +2056,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
error_msg = (str(d['error']) +
|
||||
str(d.get('error_description', '')))
|
||||
else:
|
||||
error_msg = 'Invalid response: %s.' % str(resp.status)
|
||||
error_msg = 'Invalid response: {0}.'.format(str(resp.status))
|
||||
raise FlowExchangeError(error_msg)
|
||||
|
||||
|
||||
@ -2196,11 +2120,14 @@ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
|
||||
client_info['client_id'], client_info['client_secret'],
|
||||
scope, **constructor_kwargs)
|
||||
|
||||
except clientsecrets.InvalidClientSecretsError:
|
||||
if message:
|
||||
except clientsecrets.InvalidClientSecretsError as e:
|
||||
if message is not None:
|
||||
if e.args:
|
||||
message = ('The client secrets were invalid: '
|
||||
'\n{0}\n{1}'.format(e, message))
|
||||
sys.exit(message)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise UnknownClientSecretsFlowError(
|
||||
'This OAuth 2.0 flow is unsupported: %r' % client_type)
|
||||
'This OAuth 2.0 flow is unsupported: {0!r}'.format(client_type))
|
||||
|
Reference in New Issue
Block a user