upgrade to oauth2client 3.0. Override some strings in GAM

to reduce custom changes to oauth2client.
This commit is contained in:
Jay Lee
2016-08-20 16:25:49 -04:00
parent f66aa71ec1
commit 219509853f
38 changed files with 2284 additions and 1017 deletions

View File

@ -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))