Update google libraries

This commit is contained in:
Jay Lee
2018-02-09 12:35:13 -05:00
parent a80625d5f7
commit 35b986e2be
15 changed files with 223 additions and 97 deletions

View File

@ -18,13 +18,9 @@ import json
import os
import subprocess
import six
from google.auth import environment_vars
import google.oauth2.credentials
# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
# The ~/.config subdirectory containing gcloud credentials.
_CONFIG_DIRECTORY = 'gcloud'
@ -94,20 +90,8 @@ def load_authorized_user_credentials(info):
Raises:
ValueError: if the info is in the wrong format or missing data.
"""
keys_needed = set(('refresh_token', 'client_id', 'client_secret'))
missing = keys_needed.difference(six.iterkeys(info))
if missing:
raise ValueError(
'Authorized user info was not in the expected format, missing '
'fields {}.'.format(', '.join(missing)))
return google.oauth2.credentials.Credentials(
None, # No access token, must be refreshed.
refresh_token=info['refresh_token'],
token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT,
client_id=info['client_id'],
client_secret=info['client_secret'])
return google.oauth2.credentials.Credentials.from_authorized_user_info(
info)
def get_project_id():

View File

@ -22,6 +22,8 @@ import json
import logging
import os
import six
from google.auth import environment_vars
from google.auth import exceptions
import google.auth.transport._http_client
@ -67,9 +69,11 @@ def _load_credentials_from_file(filename):
with io.open(filename, 'r') as file_obj:
try:
info = json.load(file_obj)
except ValueError as exc:
raise exceptions.DefaultCredentialsError(
'File {} is not a valid json file.'.format(filename), exc)
except ValueError as caught_exc:
new_exc = exceptions.DefaultCredentialsError(
'File {} is not a valid json file.'.format(filename),
caught_exc)
six.raise_from(new_exc, caught_exc)
# The type key should indicate that the file is either a service account
# credentials file or an authorized user credentials file.
@ -80,10 +84,11 @@ def _load_credentials_from_file(filename):
try:
credentials = _cloud_sdk.load_authorized_user_credentials(info)
except ValueError as exc:
raise exceptions.DefaultCredentialsError(
'Failed to load authorized user credentials from {}'.format(
filename), exc)
except ValueError as caught_exc:
msg = 'Failed to load authorized user credentials from {}'.format(
filename)
new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
six.raise_from(new_exc, caught_exc)
# Authorized user credentials do not contain the project ID.
return credentials, None
@ -93,10 +98,11 @@ def _load_credentials_from_file(filename):
try:
credentials = (
service_account.Credentials.from_service_account_info(info))
except ValueError as exc:
raise exceptions.DefaultCredentialsError(
'Failed to load service account credentials from {}'.format(
filename), exc)
except ValueError as caught_exc:
msg = 'Failed to load service account credentials from {}'.format(
filename)
new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
six.raise_from(new_exc, caught_exc)
return credentials, info.get('project_id')
else:
@ -123,12 +129,6 @@ def _get_gcloud_sdk_credentials():
if not project_id:
project_id = _cloud_sdk.get_project_id()
if not project_id:
_LOGGER.warning(
'No project ID could be determined from the Cloud SDK '
'configuration. Consider running `gcloud config set project` or '
'setting the %s environment variable', environment_vars.PROJECT)
return credentials, project_id
@ -141,12 +141,6 @@ def _get_explicit_environ_credentials():
credentials, project_id = _load_credentials_from_file(
os.environ[environment_vars.CREDENTIALS])
if not project_id:
_LOGGER.warning(
'No project ID could be determined from the credentials at %s '
'Consider setting the %s environment variable',
environment_vars.CREDENTIALS, environment_vars.PROJECT)
return credentials, project_id
else:
@ -182,10 +176,6 @@ def _get_gce_credentials(request=None):
try:
project_id = _metadata.get_project_id(request=request)
except exceptions.TransportError:
_LOGGER.warning(
'No project ID could be determined from the Compute Engine '
'metadata service. Consider setting the %s environment '
'variable.', environment_vars.PROJECT)
project_id = None
return compute_engine.Credentials(), project_id
@ -281,6 +271,13 @@ def default(scopes=None, request=None):
credentials, project_id = checker()
if credentials is not None:
credentials = with_scopes_if_required(credentials, scopes)
return credentials, explicit_project_id or project_id
effective_project_id = explicit_project_id or project_id
if not effective_project_id:
_LOGGER.warning(
'No project ID could be determined. Consider running '
'`gcloud config set project` or setting the %s '
'environment variable',
environment_vars.PROJECT)
return credentials, effective_project_id
raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)

View File

@ -21,6 +21,8 @@
from __future__ import absolute_import
import six
from google.auth import _helpers
import google.auth.app_engine
import google.oauth2.credentials
@ -30,8 +32,9 @@ try:
import oauth2client.client
import oauth2client.contrib.gce
import oauth2client.service_account
except ImportError:
raise ImportError('oauth2client is not installed.')
except ImportError as caught_exc:
six.raise_from(
ImportError('oauth2client is not installed.'), caught_exc)
try:
import oauth2client.contrib.appengine
@ -162,5 +165,6 @@ def convert(credentials):
try:
return _CLASS_CONVERSION_MAP[credentials_class](credentials)
except KeyError:
raise ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))
except KeyError as caught_exc:
new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))
six.raise_from(new_exc, caught_exc)

View File

@ -22,6 +22,7 @@ import json
import logging
import os
import six
from six.moves import http_client
from six.moves.urllib import parse as urlparse
@ -118,10 +119,11 @@ def get(request, path, root=_METADATA_ROOT, recursive=False):
if response.headers['content-type'] == 'application/json':
try:
return json.loads(content)
except ValueError:
raise exceptions.TransportError(
except ValueError as caught_exc:
new_exc = exceptions.TransportError(
'Received invalid JSON from the Google Compute Engine'
'metadata service: {:.20}'.format(content))
six.raise_from(new_exc, caught_exc)
else:
return content
else:

View File

@ -19,6 +19,8 @@ Engine using the Compute Engine metadata server.
"""
import six
from google.auth import credentials
from google.auth import exceptions
from google.auth.compute_engine import _metadata
@ -89,8 +91,9 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials):
self.token, self.expiry = _metadata.get_service_account_token(
request,
service_account=self._service_account_email)
except exceptions.TransportError as exc:
raise exceptions.RefreshError(exc)
except exceptions.TransportError as caught_exc:
new_exc = exceptions.RefreshError(caught_exc)
six.raise_from(new_exc, caught_exc)
@property
def service_account_email(self):

View File

@ -122,6 +122,43 @@ class Credentials(object):
self.apply(headers)
class AnonymousCredentials(Credentials):
"""Credentials that do not provide any authentication information.
These are useful in the case of services that support anonymous access or
local service emulators that do not use credentials.
"""
@property
def expired(self):
"""Returns `False`, anonymous credentials never expire."""
return False
@property
def valid(self):
"""Returns `True`, anonymous credentials are always valid."""
return True
def refresh(self, request):
"""Raises :class:`ValueError``, anonymous credentials cannot be
refreshed."""
raise ValueError("Anonymous credentials cannot be refreshed.")
def apply(self, headers, token=None):
"""Anonymous credentials do nothing to the request.
The optional ``token`` argument is not supported.
Raises:
ValueError: If a token was specified.
"""
if token is not None:
raise ValueError("Anonymous credentials don't support tokens.")
def before_request(self, request, method, url, headers):
"""Anonymous credentials do nothing to the request."""
@six.add_metaclass(abc.ABCMeta)
class ReadOnlyScoped(object):
"""Interface for credentials whose scopes can be queried.
@ -136,7 +173,7 @@ class ReadOnlyScoped(object):
if credentials.requires_scopes:
# Scoping is required.
credentials = credentials.create_scoped(['one', 'two'])
credentials = credentials.with_scopes(scopes=['one', 'two'])
Credentials that require scopes must either be constructed with scopes::
@ -172,6 +209,9 @@ class ReadOnlyScoped(object):
.. warning: This method is not guaranteed to be accurate if the
credentials are :attr:`~Credentials.invalid`.
Args:
scopes (Sequence[str]): The list of scopes to check.
Returns:
bool: True if the credentials have the given scopes.
"""
@ -211,7 +251,8 @@ class Scoped(ReadOnlyScoped):
"""Create a copy of these credentials with the specified scopes.
Args:
scopes (Sequence[str]): The list of scopes to request.
scopes (Sequence[str]): The list of scopes to attach to the
current credentials.
Raises:
NotImplementedError: If the credentials' scopes can not be changed.

View File

@ -29,7 +29,7 @@ If you're going to verify many messages with the same certificate, you can use
To sign messages use :class:`RSASigner` with a private key::
private_key = open('private_key.pem').read()
signer = crypt.RSASigner(private_key)
signer = crypt.RSASigner.from_string(private_key)
signature = signer.sign(message)
"""

View File

@ -47,6 +47,7 @@ import datetime
import json
import cachetools
import six
from six.moves import urllib
from google.auth import _helpers
@ -101,8 +102,9 @@ def _decode_jwt_segment(encoded_section):
section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
try:
return json.loads(section_bytes.decode('utf-8'))
except ValueError:
raise ValueError('Can\'t parse segment: {0}'.format(section_bytes))
except ValueError as caught_exc:
new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes))
six.raise_from(new_exc, caught_exc)
def _unverified_decode(token):

View File

@ -17,6 +17,7 @@
import logging
import socket
import six
from six.moves import http_client
from six.moves import urllib
@ -104,8 +105,9 @@ class Request(transport.Request):
response = connection.getresponse()
return Response(response)
except (http_client.HTTPException, socket.error) as exc:
raise exceptions.TransportError(exc)
except (http_client.HTTPException, socket.error) as caught_exc:
new_exc = exceptions.TransportError(caught_exc)
six.raise_from(new_exc, caught_exc)
finally:
connection.close()

View File

@ -16,13 +16,17 @@
from __future__ import absolute_import
import six
try:
import grpc
except ImportError: # pragma: NO COVER
raise ImportError(
'gRPC is not installed, please install the grpcio package to use the '
'gRPC transport.')
import six
except ImportError as caught_exc: # pragma: NO COVER
six.raise_from(
ImportError(
'gRPC is not installed, please install the grpcio package '
'to use the gRPC transport.'
),
caught_exc,
)
class AuthMetadataPlugin(grpc.AuthMetadataPlugin):

View File

@ -16,15 +16,23 @@
from __future__ import absolute_import
import functools
import logging
try:
import requests
except ImportError: # pragma: NO COVER
raise ImportError(
'The requests library is not installed, please install the requests '
'package to use the requests transport.')
import requests.exceptions
except ImportError as caught_exc: # pragma: NO COVER
import six
six.raise_from(
ImportError(
'The requests library is not installed, please install the '
'requests package to use the requests transport.'
),
caught_exc,
)
import requests.adapters # pylint: disable=ungrouped-imports
import requests.exceptions # pylint: disable=ungrouped-imports
import six # pylint: disable=ungrouped-imports
from google.auth import exceptions
from google.auth import transport
@ -111,8 +119,9 @@ class Request(transport.Request):
method, url, data=body, headers=headers, timeout=timeout,
**kwargs)
return _Response(response)
except requests.exceptions.RequestException as exc:
raise exceptions.TransportError(exc)
except requests.exceptions.RequestException as caught_exc:
new_exc = exceptions.TransportError(caught_exc)
six.raise_from(new_exc, caught_exc)
class AuthorizedSession(requests.Session):
@ -139,22 +148,35 @@ class AuthorizedSession(requests.Session):
retried.
max_refresh_attempts (int): The maximum number of times to attempt to
refresh the credentials and retry the request.
refresh_timeout (Optional[int]): The timeout value in seconds for
credential refresh HTTP requests.
kwargs: Additional arguments passed to the :class:`requests.Session`
constructor.
"""
def __init__(self, credentials,
refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
refresh_timeout=None,
**kwargs):
super(AuthorizedSession, self).__init__(**kwargs)
self.credentials = credentials
self._refresh_status_codes = refresh_status_codes
self._max_refresh_attempts = max_refresh_attempts
self._refresh_timeout = refresh_timeout
auth_request_session = requests.Session()
# Using an adapter to make HTTP requests robust to network errors.
# This adapter retrys HTTP requests when network errors occur
# and the requests seems safely retryable.
retry_adapter = requests.adapters.HTTPAdapter(max_retries=3)
auth_request_session.mount("https://", retry_adapter)
# Request instance used by internal methods (for example,
# credentials.refresh).
# Do not pass `self` as the session here, as it can lead to infinite
# recursion.
self._auth_request = Request()
self._auth_request = Request(auth_request_session)
def request(self, method, url, data=None, headers=None, **kwargs):
"""Implementation of Requests' request."""
@ -191,7 +213,9 @@ class AuthorizedSession(requests.Session):
response.status_code, _credential_refresh_attempt + 1,
self._max_refresh_attempts)
self.credentials.refresh(self._auth_request)
auth_request_with_timeout = functools.partial(
self._auth_request, timeout=self._refresh_timeout)
self.credentials.refresh(auth_request_with_timeout)
# Recurse. Pass in the original headers, not our modified set.
return self.request(

View File

@ -32,11 +32,17 @@ except ImportError: # pragma: NO COVER
try:
import urllib3
except ImportError: # pragma: NO COVER
raise ImportError(
'The urllib3 library is not installed, please install the urllib3 '
'package to use the urllib3 transport.')
import urllib3.exceptions
except ImportError as caught_exc: # pragma: NO COVER
import six
six.raise_from(
ImportError(
'The urllib3 library is not installed, please install the '
'urllib3 package to use the urllib3 transport.'
),
caught_exc,
)
import six
import urllib3.exceptions # pylint: disable=ungrouped-imports
from google.auth import exceptions
from google.auth import transport
@ -126,8 +132,9 @@ class Request(transport.Request):
response = self.http.request(
method, url, body=body, headers=headers, **kwargs)
return _Response(response)
except urllib3.exceptions.HTTPError as exc:
raise exceptions.TransportError(exc)
except urllib3.exceptions.HTTPError as caught_exc:
new_exc = exceptions.TransportError(caught_exc)
six.raise_from(new_exc, caught_exc)
def _make_default_http():

View File

@ -26,6 +26,7 @@ For more information about the token endpoint, see
import datetime
import json
import six
from six.moves import http_client
from six.moves import urllib
@ -144,9 +145,10 @@ def jwt_grant(request, token_uri, assertion):
try:
access_token = response_data['access_token']
except KeyError:
raise exceptions.RefreshError(
except KeyError as caught_exc:
new_exc = exceptions.RefreshError(
'No access token in response.', response_data)
six.raise_from(new_exc, caught_exc)
expiry = _parse_expiry(response_data)
@ -190,9 +192,10 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret):
try:
access_token = response_data['access_token']
except KeyError:
raise exceptions.RefreshError(
except KeyError as caught_exc:
new_exc = exceptions.RefreshError(
'No access token in response.', response_data)
six.raise_from(new_exc, caught_exc)
refresh_token = response_data.get('refresh_token', refresh_token)
expiry = _parse_expiry(response_data)

View File

@ -31,12 +31,21 @@ Authorization Code grant flow.
.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
"""
import io
import json
import six
from google.auth import _helpers
from google.auth import credentials
from google.oauth2 import _client
class Credentials(credentials.Scoped, credentials.Credentials):
# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
class Credentials(credentials.ReadOnlyScoped, credentials.Credentials):
"""Credentials using OAuth 2.0 access and refresh tokens."""
def __init__(self, token, refresh_token=None, id_token=None,
@ -109,15 +118,6 @@ class Credentials(credentials.Scoped, credentials.Credentials):
the initial token is requested and can not be changed."""
return False
def with_scopes(self, scopes):
"""Unavailable, OAuth 2.0 credentials can not be re-scoped.
OAuth 2.0 credentials have their scopes set when the initial token is
requested and can not be changed.
"""
raise NotImplementedError(
'OAuth 2.0 Credentials can not modify their scopes.')
@_helpers.copy_docstring(credentials.Credentials)
def refresh(self, request):
access_token, refresh_token, expiry, grant_response = (
@ -129,3 +129,56 @@ class Credentials(credentials.Scoped, credentials.Credentials):
self.expiry = expiry
self._refresh_token = refresh_token
self._id_token = grant_response.get('id_token')
@classmethod
def from_authorized_user_info(cls, info, scopes=None):
"""Creates a Credentials instance from parsed authorized user info.
Args:
info (Mapping[str, str]): The authorized user info in Google
format.
scopes (Sequence[str]): Optional list of scopes to include in the
credentials.
Returns:
google.oauth2.credentials.Credentials: The constructed
credentials.
Raises:
ValueError: If the info is not in the expected format.
"""
keys_needed = set(('refresh_token', 'client_id', 'client_secret'))
missing = keys_needed.difference(six.iterkeys(info))
if missing:
raise ValueError(
'Authorized user info was not in the expected format, missing '
'fields {}.'.format(', '.join(missing)))
return Credentials(
None, # No access token, must be refreshed.
refresh_token=info['refresh_token'],
token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT,
scopes=scopes,
client_id=info['client_id'],
client_secret=info['client_secret'])
@classmethod
def from_authorized_user_file(cls, filename, scopes=None):
"""Creates a Credentials instance from an authorized user json file.
Args:
filename (str): The path to the authorized user json file.
scopes (Sequence[str]): Optional list of scopes to include in the
credentials.
Returns:
google.oauth2.credentials.Credentials: The constructed
credentials.
Raises:
ValueError: If the file is not in the expected format.
"""
with io.open(filename, 'r', encoding='utf-8') as json_file:
data = json.load(json_file)
return cls.from_authorized_user_info(data, scopes)

View File

@ -79,7 +79,7 @@ from google.auth import credentials
from google.auth import jwt
from google.oauth2 import _client
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
class Credentials(credentials.Signing,