Added delete option to database storage.
This commit is contained in:
parent
308604a33c
commit
963b5bc68b
1868 changed files with 192402 additions and 13278 deletions
24
venv/Lib/site-packages/google/auth/__init__.py
Normal file
24
venv/Lib/site-packages/google/auth/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google Auth Library for Python."""
|
||||
|
||||
import logging
|
||||
|
||||
from google.auth._default import default, load_credentials_from_file
|
||||
|
||||
__all__ = ["default", "load_credentials_from_file"]
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
152
venv/Lib/site-packages/google/auth/_cloud_sdk.py
Normal file
152
venv/Lib/site-packages/google/auth/_cloud_sdk.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Copyright 2015 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helpers for reading the Google Cloud SDK's configuration."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
|
||||
|
||||
# The ~/.config subdirectory containing gcloud credentials.
|
||||
_CONFIG_DIRECTORY = "gcloud"
|
||||
# Windows systems store config at %APPDATA%\gcloud
|
||||
_WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA"
|
||||
# The name of the file in the Cloud SDK config that contains default
|
||||
# credentials.
|
||||
_CREDENTIALS_FILENAME = "application_default_credentials.json"
|
||||
# The name of the Cloud SDK shell script
|
||||
_CLOUD_SDK_POSIX_COMMAND = "gcloud"
|
||||
_CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd"
|
||||
# The command to get the Cloud SDK configuration
|
||||
_CLOUD_SDK_CONFIG_COMMAND = ("config", "config-helper", "--format", "json")
|
||||
# The command to get google user access token
|
||||
_CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token")
|
||||
# Cloud SDK's application-default client ID
|
||||
CLOUD_SDK_CLIENT_ID = (
|
||||
"764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com"
|
||||
)
|
||||
|
||||
|
||||
def get_config_path():
|
||||
"""Returns the absolute path the the Cloud SDK's configuration directory.
|
||||
|
||||
Returns:
|
||||
str: The Cloud SDK config path.
|
||||
"""
|
||||
# If the path is explicitly set, return that.
|
||||
try:
|
||||
return os.environ[environment_vars.CLOUD_SDK_CONFIG_DIR]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Non-windows systems store this at ~/.config/gcloud
|
||||
if os.name != "nt":
|
||||
return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY)
|
||||
# Windows systems store config at %APPDATA%\gcloud
|
||||
else:
|
||||
try:
|
||||
return os.path.join(
|
||||
os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY
|
||||
)
|
||||
except KeyError:
|
||||
# This should never happen unless someone is really
|
||||
# messing with things, but we'll cover the case anyway.
|
||||
drive = os.environ.get("SystemDrive", "C:")
|
||||
return os.path.join(drive, "\\", _CONFIG_DIRECTORY)
|
||||
|
||||
|
||||
def get_application_default_credentials_path():
|
||||
"""Gets the path to the application default credentials file.
|
||||
|
||||
The path may or may not exist.
|
||||
|
||||
Returns:
|
||||
str: The full path to application default credentials.
|
||||
"""
|
||||
config_path = get_config_path()
|
||||
return os.path.join(config_path, _CREDENTIALS_FILENAME)
|
||||
|
||||
|
||||
def get_project_id():
|
||||
"""Gets the project ID from the Cloud SDK.
|
||||
|
||||
Returns:
|
||||
Optional[str]: The project ID.
|
||||
"""
|
||||
if os.name == "nt":
|
||||
command = _CLOUD_SDK_WINDOWS_COMMAND
|
||||
else:
|
||||
command = _CLOUD_SDK_POSIX_COMMAND
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
(command,) + _CLOUD_SDK_CONFIG_COMMAND, stderr=subprocess.STDOUT
|
||||
)
|
||||
except (subprocess.CalledProcessError, OSError, IOError):
|
||||
return None
|
||||
|
||||
try:
|
||||
configuration = json.loads(output.decode("utf-8"))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
try:
|
||||
return configuration["configuration"]["properties"]["core"]["project"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def get_auth_access_token(account=None):
|
||||
"""Load user access token with the ``gcloud auth print-access-token`` command.
|
||||
|
||||
Args:
|
||||
account (Optional[str]): Account to get the access token for. If not
|
||||
specified, the current active account will be used.
|
||||
|
||||
Returns:
|
||||
str: The user access token.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.UserAccessTokenError: if failed to get access
|
||||
token from gcloud.
|
||||
"""
|
||||
if os.name == "nt":
|
||||
command = _CLOUD_SDK_WINDOWS_COMMAND
|
||||
else:
|
||||
command = _CLOUD_SDK_POSIX_COMMAND
|
||||
|
||||
try:
|
||||
if account:
|
||||
command = (
|
||||
(command,)
|
||||
+ _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND
|
||||
+ ("--account=" + account,)
|
||||
)
|
||||
else:
|
||||
command = (command,) + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND
|
||||
|
||||
access_token = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
# remove the trailing "\n"
|
||||
return access_token.decode("utf-8").strip()
|
||||
except (subprocess.CalledProcessError, OSError, IOError) as caught_exc:
|
||||
new_exc = exceptions.UserAccessTokenError(
|
||||
"Failed to obtain access token", caught_exc
|
||||
)
|
||||
six.raise_from(new_exc, caught_exc)
|
176
venv/Lib/site-packages/google/auth/_credentials_async.py
Normal file
176
venv/Lib/site-packages/google/auth/_credentials_async.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Interfaces for credentials."""
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import credentials
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Credentials(credentials.Credentials):
|
||||
"""Async inherited credentials class from google.auth.credentials.
|
||||
The added functionality is the before_request call which requires
|
||||
async/await syntax.
|
||||
All credentials have a :attr:`token` that is used for authentication and
|
||||
may also optionally set an :attr:`expiry` to indicate when the token will
|
||||
no longer be valid.
|
||||
|
||||
Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
|
||||
Credentials can do this automatically before the first HTTP request in
|
||||
:meth:`before_request`.
|
||||
|
||||
Although the token and expiration will change as the credentials are
|
||||
:meth:`refreshed <refresh>` and used, credentials should be considered
|
||||
immutable. Various credentials will accept configuration such as private
|
||||
keys, scopes, and other options. These options are not changeable after
|
||||
construction. Some classes will provide mechanisms to copy the credentials
|
||||
with modifications such as :meth:`ScopedCredentials.with_scopes`.
|
||||
"""
|
||||
|
||||
async def before_request(self, request, method, url, headers):
|
||||
"""Performs credential-specific before request logic.
|
||||
|
||||
Refreshes the credentials if necessary, then calls :meth:`apply` to
|
||||
apply the token to the authentication header.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
method (str): The request's HTTP method or the RPC method being
|
||||
invoked.
|
||||
url (str): The request's URI or the RPC service's URI.
|
||||
headers (Mapping): The request's headers.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
# (Subclasses may use these arguments to ascertain information about
|
||||
# the http request.)
|
||||
|
||||
if not self.valid:
|
||||
if inspect.iscoroutinefunction(self.refresh):
|
||||
await self.refresh(request)
|
||||
else:
|
||||
self.refresh(request)
|
||||
self.apply(headers)
|
||||
|
||||
|
||||
class CredentialsWithQuotaProject(credentials.CredentialsWithQuotaProject):
|
||||
"""Abstract base for credentials supporting ``with_quota_project`` factory"""
|
||||
|
||||
|
||||
class AnonymousCredentials(credentials.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. This class inherits
|
||||
from the sync anonymous credentials file, but is kept if async credentials
|
||||
is initialized and we would like anonymous credentials.
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ReadOnlyScoped(credentials.ReadOnlyScoped):
|
||||
"""Interface for credentials whose scopes can be queried.
|
||||
|
||||
OAuth 2.0-based credentials allow limiting access using scopes as described
|
||||
in `RFC6749 Section 3.3`_.
|
||||
If a credential class implements this interface then the credentials either
|
||||
use scopes in their implementation.
|
||||
|
||||
Some credentials require scopes in order to obtain a token. You can check
|
||||
if scoping is necessary with :attr:`requires_scopes`::
|
||||
|
||||
if credentials.requires_scopes:
|
||||
# Scoping is required.
|
||||
credentials = _credentials_async.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Credentials that require scopes must either be constructed with scopes::
|
||||
|
||||
credentials = SomeScopedCredentials(scopes=['one', 'two'])
|
||||
|
||||
Or must copy an existing instance using :meth:`with_scopes`::
|
||||
|
||||
scoped_credentials = _credentials_async.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Some credentials have scopes but do not allow or require scopes to be set,
|
||||
these credentials can be used as-is.
|
||||
|
||||
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
|
||||
|
||||
class Scoped(credentials.Scoped):
|
||||
"""Interface for credentials whose scopes can be replaced while copying.
|
||||
|
||||
OAuth 2.0-based credentials allow limiting access using scopes as described
|
||||
in `RFC6749 Section 3.3`_.
|
||||
If a credential class implements this interface then the credentials either
|
||||
use scopes in their implementation.
|
||||
|
||||
Some credentials require scopes in order to obtain a token. You can check
|
||||
if scoping is necessary with :attr:`requires_scopes`::
|
||||
|
||||
if credentials.requires_scopes:
|
||||
# Scoping is required.
|
||||
credentials = _credentials_async.create_scoped(['one', 'two'])
|
||||
|
||||
Credentials that require scopes must either be constructed with scopes::
|
||||
|
||||
credentials = SomeScopedCredentials(scopes=['one', 'two'])
|
||||
|
||||
Or must copy an existing instance using :meth:`with_scopes`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Some credentials have scopes but do not allow or require scopes to be set,
|
||||
these credentials can be used as-is.
|
||||
|
||||
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
|
||||
|
||||
def with_scopes_if_required(credentials, scopes):
|
||||
"""Creates a copy of the credentials with scopes if scoping is required.
|
||||
|
||||
This helper function is useful when you do not know (or care to know) the
|
||||
specific type of credentials you are using (such as when you use
|
||||
:func:`google.auth.default`). This function will call
|
||||
:meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
|
||||
the credentials require scoping. Otherwise, it will return the credentials
|
||||
as-is.
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
scope if necessary.
|
||||
scopes (Sequence[str]): The list of scopes to use.
|
||||
|
||||
Returns:
|
||||
google.auth._credentials_async.Credentials: Either a new set of scoped
|
||||
credentials, or the passed in credentials instance if no scoping
|
||||
was required.
|
||||
"""
|
||||
if isinstance(credentials, Scoped) and credentials.requires_scopes:
|
||||
return credentials.with_scopes(scopes)
|
||||
else:
|
||||
return credentials
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Signing(credentials.Signing):
|
||||
"""Interface for credentials that can cryptographically sign messages."""
|
354
venv/Lib/site-packages/google/auth/_default.py
Normal file
354
venv/Lib/site-packages/google/auth/_default.py
Normal file
|
@ -0,0 +1,354 @@
|
|||
# Copyright 2015 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Application default credentials.
|
||||
|
||||
Implements application default credentials and project ID detection.
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
import google.auth.transport._http_client
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Valid types accepted for file-based credentials.
|
||||
_AUTHORIZED_USER_TYPE = "authorized_user"
|
||||
_SERVICE_ACCOUNT_TYPE = "service_account"
|
||||
_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
|
||||
|
||||
# Help message when no credentials can be found.
|
||||
_HELP_MESSAGE = """\
|
||||
Could not automatically determine credentials. Please set {env} or \
|
||||
explicitly create credentials and re-run the application. For more \
|
||||
information, please see \
|
||||
https://cloud.google.com/docs/authentication/getting-started
|
||||
""".format(
|
||||
env=environment_vars.CREDENTIALS
|
||||
).strip()
|
||||
|
||||
# Warning when using Cloud SDK user credentials
|
||||
_CLOUD_SDK_CREDENTIALS_WARNING = """\
|
||||
Your application has authenticated using end user credentials from Google \
|
||||
Cloud SDK without a quota project. You might receive a "quota exceeded" \
|
||||
or "API not enabled" error. We recommend you rerun \
|
||||
`gcloud auth application-default login` and make sure a quota project is \
|
||||
added. Or you can use service accounts instead. For more information \
|
||||
about service accounts, see https://cloud.google.com/docs/authentication/"""
|
||||
|
||||
|
||||
def _warn_about_problematic_credentials(credentials):
|
||||
"""Determines if the credentials are problematic.
|
||||
|
||||
Credentials from the Cloud SDK that are associated with Cloud SDK's project
|
||||
are problematic because they may not have APIs enabled and have limited
|
||||
quota. If this is the case, warn about it.
|
||||
"""
|
||||
from google.auth import _cloud_sdk
|
||||
|
||||
if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID:
|
||||
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
|
||||
|
||||
|
||||
def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
|
||||
"""Loads Google credentials from a file.
|
||||
|
||||
The credentials file must be a service account key or stored authorized
|
||||
user credentials.
|
||||
|
||||
Args:
|
||||
filename (str): The full path to the credentials file.
|
||||
scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
|
||||
specified, the credentials will automatically be scoped if
|
||||
necessary
|
||||
quota_project_id (Optional[str]): The project ID used for
|
||||
quota and billing.
|
||||
|
||||
Returns:
|
||||
Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
|
||||
credentials and the project ID. Authorized user credentials do not
|
||||
have the project ID information.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.DefaultCredentialsError: if the file is in the
|
||||
wrong format or is missing.
|
||||
"""
|
||||
if not os.path.exists(filename):
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"File {} was not found.".format(filename)
|
||||
)
|
||||
|
||||
with io.open(filename, "r") as file_obj:
|
||||
try:
|
||||
info = json.load(file_obj)
|
||||
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.
|
||||
credential_type = info.get("type")
|
||||
|
||||
if credential_type == _AUTHORIZED_USER_TYPE:
|
||||
from google.oauth2 import credentials
|
||||
|
||||
try:
|
||||
credentials = credentials.Credentials.from_authorized_user_info(
|
||||
info, scopes=scopes
|
||||
)
|
||||
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)
|
||||
if quota_project_id:
|
||||
credentials = credentials.with_quota_project(quota_project_id)
|
||||
if not credentials.quota_project_id:
|
||||
_warn_about_problematic_credentials(credentials)
|
||||
return credentials, None
|
||||
|
||||
elif credential_type == _SERVICE_ACCOUNT_TYPE:
|
||||
from google.oauth2 import service_account
|
||||
|
||||
try:
|
||||
credentials = service_account.Credentials.from_service_account_info(
|
||||
info, scopes=scopes
|
||||
)
|
||||
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)
|
||||
if quota_project_id:
|
||||
credentials = credentials.with_quota_project(quota_project_id)
|
||||
return credentials, info.get("project_id")
|
||||
|
||||
else:
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"The file {file} does not have a valid type. "
|
||||
"Type is {type}, expected one of {valid_types}.".format(
|
||||
file=filename, type=credential_type, valid_types=_VALID_TYPES
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_gcloud_sdk_credentials():
|
||||
"""Gets the credentials and project ID from the Cloud SDK."""
|
||||
from google.auth import _cloud_sdk
|
||||
|
||||
_LOGGER.debug("Checking Cloud SDK credentials as part of auth process...")
|
||||
|
||||
# Check if application default credentials exist.
|
||||
credentials_filename = _cloud_sdk.get_application_default_credentials_path()
|
||||
|
||||
if not os.path.isfile(credentials_filename):
|
||||
_LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
|
||||
return None, None
|
||||
|
||||
credentials, project_id = load_credentials_from_file(credentials_filename)
|
||||
|
||||
if not project_id:
|
||||
project_id = _cloud_sdk.get_project_id()
|
||||
|
||||
return credentials, project_id
|
||||
|
||||
|
||||
def _get_explicit_environ_credentials():
|
||||
"""Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
|
||||
variable."""
|
||||
explicit_file = os.environ.get(environment_vars.CREDENTIALS)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Checking %s for explicit credentials as part of auth process...", explicit_file
|
||||
)
|
||||
|
||||
if explicit_file is not None:
|
||||
credentials, project_id = load_credentials_from_file(
|
||||
os.environ[environment_vars.CREDENTIALS]
|
||||
)
|
||||
|
||||
return credentials, project_id
|
||||
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def _get_gae_credentials():
|
||||
"""Gets Google App Engine App Identity credentials and project ID."""
|
||||
# While this library is normally bundled with app_engine, there are
|
||||
# some cases where it's not available, so we tolerate ImportError.
|
||||
try:
|
||||
_LOGGER.debug("Checking for App Engine runtime as part of auth process...")
|
||||
import google.auth.app_engine as app_engine
|
||||
except ImportError:
|
||||
_LOGGER.warning("Import of App Engine auth library failed.")
|
||||
return None, None
|
||||
|
||||
try:
|
||||
credentials = app_engine.Credentials()
|
||||
project_id = app_engine.get_project_id()
|
||||
return credentials, project_id
|
||||
except EnvironmentError:
|
||||
_LOGGER.debug(
|
||||
"No App Engine library was found so cannot authentication via App Engine Identity Credentials."
|
||||
)
|
||||
return None, None
|
||||
|
||||
|
||||
def _get_gce_credentials(request=None):
|
||||
"""Gets credentials and project ID from the GCE Metadata Service."""
|
||||
# Ping requires a transport, but we want application default credentials
|
||||
# to require no arguments. So, we'll use the _http_client transport which
|
||||
# uses http.client. This is only acceptable because the metadata server
|
||||
# doesn't do SSL and never requires proxies.
|
||||
|
||||
# While this library is normally bundled with compute_engine, there are
|
||||
# some cases where it's not available, so we tolerate ImportError.
|
||||
try:
|
||||
from google.auth import compute_engine
|
||||
from google.auth.compute_engine import _metadata
|
||||
except ImportError:
|
||||
_LOGGER.warning("Import of Compute Engine auth library failed.")
|
||||
return None, None
|
||||
|
||||
if request is None:
|
||||
request = google.auth.transport._http_client.Request()
|
||||
|
||||
if _metadata.ping(request=request):
|
||||
# Get the project ID.
|
||||
try:
|
||||
project_id = _metadata.get_project_id(request=request)
|
||||
except exceptions.TransportError:
|
||||
project_id = None
|
||||
|
||||
return compute_engine.Credentials(), project_id
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Authentication failed using Compute Engine authentication due to unavailable metadata server."
|
||||
)
|
||||
return None, None
|
||||
|
||||
|
||||
def default(scopes=None, request=None, quota_project_id=None):
|
||||
"""Gets the default credentials for the current environment.
|
||||
|
||||
`Application Default Credentials`_ provides an easy way to obtain
|
||||
credentials to call Google APIs for server-to-server or local applications.
|
||||
This function acquires credentials from the environment in the following
|
||||
order:
|
||||
|
||||
1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
|
||||
to the path of a valid service account JSON private key file, then it is
|
||||
loaded and returned. The project ID returned is the project ID defined
|
||||
in the service account file if available (some older files do not
|
||||
contain project ID information).
|
||||
2. If the `Google Cloud SDK`_ is installed and has application default
|
||||
credentials set they are loaded and returned.
|
||||
|
||||
To enable application default credentials with the Cloud SDK run::
|
||||
|
||||
gcloud auth application-default login
|
||||
|
||||
If the Cloud SDK has an active project, the project ID is returned. The
|
||||
active project can be set using::
|
||||
|
||||
gcloud config set project
|
||||
|
||||
3. If the application is running in the `App Engine standard environment`_
|
||||
then the credentials and project ID from the `App Identity Service`_
|
||||
are used.
|
||||
4. If the application is running in `Compute Engine`_ or the
|
||||
`App Engine flexible environment`_ then the credentials and project ID
|
||||
are obtained from the `Metadata Service`_.
|
||||
5. If no credentials are found,
|
||||
:class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
|
||||
|
||||
.. _Application Default Credentials: https://developers.google.com\
|
||||
/identity/protocols/application-default-credentials
|
||||
.. _Google Cloud SDK: https://cloud.google.com/sdk
|
||||
.. _App Engine standard environment: https://cloud.google.com/appengine
|
||||
.. _App Identity Service: https://cloud.google.com/appengine/docs/python\
|
||||
/appidentity/
|
||||
.. _Compute Engine: https://cloud.google.com/compute
|
||||
.. _App Engine flexible environment: https://cloud.google.com\
|
||||
/appengine/flexible
|
||||
.. _Metadata Service: https://cloud.google.com/compute/docs\
|
||||
/storing-retrieving-metadata
|
||||
|
||||
Example::
|
||||
|
||||
import google.auth
|
||||
|
||||
credentials, project_id = google.auth.default()
|
||||
|
||||
Args:
|
||||
scopes (Sequence[str]): The list of scopes for the credentials. If
|
||||
specified, the credentials will automatically be scoped if
|
||||
necessary.
|
||||
request (google.auth.transport.Request): An object used to make
|
||||
HTTP requests. This is used to detect whether the application
|
||||
is running on Compute Engine. If not specified, then it will
|
||||
use the standard library http client to make requests.
|
||||
quota_project_id (Optional[str]): The project ID used for
|
||||
quota and billing.
|
||||
Returns:
|
||||
Tuple[~google.auth.credentials.Credentials, Optional[str]]:
|
||||
the current environment's credentials and project ID. Project ID
|
||||
may be None, which indicates that the Project ID could not be
|
||||
ascertained from the environment.
|
||||
|
||||
Raises:
|
||||
~google.auth.exceptions.DefaultCredentialsError:
|
||||
If no credentials were found, or if the credentials found were
|
||||
invalid.
|
||||
"""
|
||||
from google.auth.credentials import with_scopes_if_required
|
||||
|
||||
explicit_project_id = os.environ.get(
|
||||
environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
|
||||
)
|
||||
|
||||
checkers = (
|
||||
_get_explicit_environ_credentials,
|
||||
_get_gcloud_sdk_credentials,
|
||||
_get_gae_credentials,
|
||||
lambda: _get_gce_credentials(request),
|
||||
)
|
||||
|
||||
for checker in checkers:
|
||||
credentials, project_id = checker()
|
||||
if credentials is not None:
|
||||
credentials = with_scopes_if_required(credentials, scopes)
|
||||
if quota_project_id:
|
||||
credentials = credentials.with_quota_project(quota_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)
|
266
venv/Lib/site-packages/google/auth/_default_async.py
Normal file
266
venv/Lib/site-packages/google/auth/_default_async.py
Normal file
|
@ -0,0 +1,266 @@
|
|||
# Copyright 2020 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Application default credentials.
|
||||
|
||||
Implements application default credentials and project ID detection.
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import _default
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
|
||||
|
||||
def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
|
||||
"""Loads Google credentials from a file.
|
||||
|
||||
The credentials file must be a service account key or stored authorized
|
||||
user credentials.
|
||||
|
||||
Args:
|
||||
filename (str): The full path to the credentials file.
|
||||
scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
|
||||
specified, the credentials will automatically be scoped if
|
||||
necessary
|
||||
quota_project_id (Optional[str]): The project ID used for
|
||||
quota and billing.
|
||||
|
||||
Returns:
|
||||
Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
|
||||
credentials and the project ID. Authorized user credentials do not
|
||||
have the project ID information.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.DefaultCredentialsError: if the file is in the
|
||||
wrong format or is missing.
|
||||
"""
|
||||
if not os.path.exists(filename):
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"File {} was not found.".format(filename)
|
||||
)
|
||||
|
||||
with io.open(filename, "r") as file_obj:
|
||||
try:
|
||||
info = json.load(file_obj)
|
||||
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.
|
||||
credential_type = info.get("type")
|
||||
|
||||
if credential_type == _default._AUTHORIZED_USER_TYPE:
|
||||
from google.oauth2 import _credentials_async as credentials
|
||||
|
||||
try:
|
||||
credentials = credentials.Credentials.from_authorized_user_info(
|
||||
info, scopes=scopes
|
||||
).with_quota_project(quota_project_id)
|
||||
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)
|
||||
if not credentials.quota_project_id:
|
||||
_default._warn_about_problematic_credentials(credentials)
|
||||
return credentials, None
|
||||
|
||||
elif credential_type == _default._SERVICE_ACCOUNT_TYPE:
|
||||
from google.oauth2 import _service_account_async as service_account
|
||||
|
||||
try:
|
||||
credentials = service_account.Credentials.from_service_account_info(
|
||||
info, scopes=scopes
|
||||
).with_quota_project(quota_project_id)
|
||||
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:
|
||||
raise exceptions.DefaultCredentialsError(
|
||||
"The file {file} does not have a valid type. "
|
||||
"Type is {type}, expected one of {valid_types}.".format(
|
||||
file=filename, type=credential_type, valid_types=_default._VALID_TYPES
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_gcloud_sdk_credentials():
|
||||
"""Gets the credentials and project ID from the Cloud SDK."""
|
||||
from google.auth import _cloud_sdk
|
||||
|
||||
# Check if application default credentials exist.
|
||||
credentials_filename = _cloud_sdk.get_application_default_credentials_path()
|
||||
|
||||
if not os.path.isfile(credentials_filename):
|
||||
return None, None
|
||||
|
||||
credentials, project_id = load_credentials_from_file(credentials_filename)
|
||||
|
||||
if not project_id:
|
||||
project_id = _cloud_sdk.get_project_id()
|
||||
|
||||
return credentials, project_id
|
||||
|
||||
|
||||
def _get_explicit_environ_credentials():
|
||||
"""Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
|
||||
variable."""
|
||||
explicit_file = os.environ.get(environment_vars.CREDENTIALS)
|
||||
|
||||
if explicit_file is not None:
|
||||
credentials, project_id = load_credentials_from_file(
|
||||
os.environ[environment_vars.CREDENTIALS]
|
||||
)
|
||||
|
||||
return credentials, project_id
|
||||
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def _get_gae_credentials():
|
||||
"""Gets Google App Engine App Identity credentials and project ID."""
|
||||
# While this library is normally bundled with app_engine, there are
|
||||
# some cases where it's not available, so we tolerate ImportError.
|
||||
|
||||
return _default._get_gae_credentials()
|
||||
|
||||
|
||||
def _get_gce_credentials(request=None):
|
||||
"""Gets credentials and project ID from the GCE Metadata Service."""
|
||||
# Ping requires a transport, but we want application default credentials
|
||||
# to require no arguments. So, we'll use the _http_client transport which
|
||||
# uses http.client. This is only acceptable because the metadata server
|
||||
# doesn't do SSL and never requires proxies.
|
||||
|
||||
# While this library is normally bundled with compute_engine, there are
|
||||
# some cases where it's not available, so we tolerate ImportError.
|
||||
|
||||
return _default._get_gce_credentials(request)
|
||||
|
||||
|
||||
def default_async(scopes=None, request=None, quota_project_id=None):
|
||||
"""Gets the default credentials for the current environment.
|
||||
|
||||
`Application Default Credentials`_ provides an easy way to obtain
|
||||
credentials to call Google APIs for server-to-server or local applications.
|
||||
This function acquires credentials from the environment in the following
|
||||
order:
|
||||
|
||||
1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
|
||||
to the path of a valid service account JSON private key file, then it is
|
||||
loaded and returned. The project ID returned is the project ID defined
|
||||
in the service account file if available (some older files do not
|
||||
contain project ID information).
|
||||
2. If the `Google Cloud SDK`_ is installed and has application default
|
||||
credentials set they are loaded and returned.
|
||||
|
||||
To enable application default credentials with the Cloud SDK run::
|
||||
|
||||
gcloud auth application-default login
|
||||
|
||||
If the Cloud SDK has an active project, the project ID is returned. The
|
||||
active project can be set using::
|
||||
|
||||
gcloud config set project
|
||||
|
||||
3. If the application is running in the `App Engine standard environment`_
|
||||
then the credentials and project ID from the `App Identity Service`_
|
||||
are used.
|
||||
4. If the application is running in `Compute Engine`_ or the
|
||||
`App Engine flexible environment`_ then the credentials and project ID
|
||||
are obtained from the `Metadata Service`_.
|
||||
5. If no credentials are found,
|
||||
:class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
|
||||
|
||||
.. _Application Default Credentials: https://developers.google.com\
|
||||
/identity/protocols/application-default-credentials
|
||||
.. _Google Cloud SDK: https://cloud.google.com/sdk
|
||||
.. _App Engine standard environment: https://cloud.google.com/appengine
|
||||
.. _App Identity Service: https://cloud.google.com/appengine/docs/python\
|
||||
/appidentity/
|
||||
.. _Compute Engine: https://cloud.google.com/compute
|
||||
.. _App Engine flexible environment: https://cloud.google.com\
|
||||
/appengine/flexible
|
||||
.. _Metadata Service: https://cloud.google.com/compute/docs\
|
||||
/storing-retrieving-metadata
|
||||
|
||||
Example::
|
||||
|
||||
import google.auth
|
||||
|
||||
credentials, project_id = google.auth.default()
|
||||
|
||||
Args:
|
||||
scopes (Sequence[str]): The list of scopes for the credentials. If
|
||||
specified, the credentials will automatically be scoped if
|
||||
necessary.
|
||||
request (google.auth.transport.Request): An object used to make
|
||||
HTTP requests. This is used to detect whether the application
|
||||
is running on Compute Engine. If not specified, then it will
|
||||
use the standard library http client to make requests.
|
||||
quota_project_id (Optional[str]): The project ID used for
|
||||
quota and billing.
|
||||
Returns:
|
||||
Tuple[~google.auth.credentials.Credentials, Optional[str]]:
|
||||
the current environment's credentials and project ID. Project ID
|
||||
may be None, which indicates that the Project ID could not be
|
||||
ascertained from the environment.
|
||||
|
||||
Raises:
|
||||
~google.auth.exceptions.DefaultCredentialsError:
|
||||
If no credentials were found, or if the credentials found were
|
||||
invalid.
|
||||
"""
|
||||
from google.auth._credentials_async import with_scopes_if_required
|
||||
|
||||
explicit_project_id = os.environ.get(
|
||||
environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
|
||||
)
|
||||
|
||||
checkers = (
|
||||
_get_explicit_environ_credentials,
|
||||
_get_gcloud_sdk_credentials,
|
||||
_get_gae_credentials,
|
||||
lambda: _get_gce_credentials(request),
|
||||
)
|
||||
|
||||
for checker in checkers:
|
||||
credentials, project_id = checker()
|
||||
if credentials is not None:
|
||||
credentials = with_scopes_if_required(
|
||||
credentials, scopes
|
||||
).with_quota_project(quota_project_id)
|
||||
effective_project_id = explicit_project_id or project_id
|
||||
if not effective_project_id:
|
||||
_default._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(_default._HELP_MESSAGE)
|
232
venv/Lib/site-packages/google/auth/_helpers.py
Normal file
232
venv/Lib/site-packages/google/auth/_helpers.py
Normal file
|
@ -0,0 +1,232 @@
|
|||
# Copyright 2015 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper functions for commonly used utilities."""
|
||||
|
||||
import base64
|
||||
import calendar
|
||||
import datetime
|
||||
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
|
||||
CLOCK_SKEW_SECS = 10 # 10 seconds
|
||||
CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
|
||||
|
||||
|
||||
def copy_docstring(source_class):
|
||||
"""Decorator that copies a method's docstring from another class.
|
||||
|
||||
Args:
|
||||
source_class (type): The class that has the documented method.
|
||||
|
||||
Returns:
|
||||
Callable: A decorator that will copy the docstring of the same
|
||||
named method in the source class to the decorated method.
|
||||
"""
|
||||
|
||||
def decorator(method):
|
||||
"""Decorator implementation.
|
||||
|
||||
Args:
|
||||
method (Callable): The method to copy the docstring to.
|
||||
|
||||
Returns:
|
||||
Callable: the same method passed in with an updated docstring.
|
||||
|
||||
Raises:
|
||||
ValueError: if the method already has a docstring.
|
||||
"""
|
||||
if method.__doc__:
|
||||
raise ValueError("Method already has a docstring.")
|
||||
|
||||
source_method = getattr(source_class, method.__name__)
|
||||
method.__doc__ = source_method.__doc__
|
||||
|
||||
return method
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def utcnow():
|
||||
"""Returns the current UTC datetime.
|
||||
|
||||
Returns:
|
||||
datetime: The current time in UTC.
|
||||
"""
|
||||
return datetime.datetime.utcnow()
|
||||
|
||||
|
||||
def datetime_to_secs(value):
|
||||
"""Convert a datetime object to the number of seconds since the UNIX epoch.
|
||||
|
||||
Args:
|
||||
value (datetime): The datetime to convert.
|
||||
|
||||
Returns:
|
||||
int: The number of seconds since the UNIX epoch.
|
||||
"""
|
||||
return calendar.timegm(value.utctimetuple())
|
||||
|
||||
|
||||
def to_bytes(value, encoding="utf-8"):
|
||||
"""Converts a string value to bytes, if necessary.
|
||||
|
||||
Unfortunately, ``six.b`` is insufficient for this task since in
|
||||
Python 2 because it does not modify ``unicode`` objects.
|
||||
|
||||
Args:
|
||||
value (Union[str, bytes]): The value to be converted.
|
||||
encoding (str): The encoding to use to convert unicode to bytes.
|
||||
Defaults to "utf-8".
|
||||
|
||||
Returns:
|
||||
bytes: The original value converted to bytes (if unicode) or as
|
||||
passed in if it started out as bytes.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value could not be converted to bytes.
|
||||
"""
|
||||
result = value.encode(encoding) if isinstance(value, six.text_type) else value
|
||||
if isinstance(result, six.binary_type):
|
||||
return result
|
||||
else:
|
||||
raise ValueError("{0!r} could not be converted to bytes".format(value))
|
||||
|
||||
|
||||
def from_bytes(value):
|
||||
"""Converts bytes to a string value, if necessary.
|
||||
|
||||
Args:
|
||||
value (Union[str, bytes]): The value to be converted.
|
||||
|
||||
Returns:
|
||||
str: The original value converted to unicode (if bytes) or as passed in
|
||||
if it started out as unicode.
|
||||
|
||||
Raises:
|
||||
ValueError: If the value could not be converted to unicode.
|
||||
"""
|
||||
result = value.decode("utf-8") if isinstance(value, six.binary_type) else value
|
||||
if isinstance(result, six.text_type):
|
||||
return result
|
||||
else:
|
||||
raise ValueError("{0!r} could not be converted to unicode".format(value))
|
||||
|
||||
|
||||
def update_query(url, params, remove=None):
|
||||
"""Updates a URL's query parameters.
|
||||
|
||||
Replaces any current values if they are already present in the URL.
|
||||
|
||||
Args:
|
||||
url (str): The URL to update.
|
||||
params (Mapping[str, str]): A mapping of query parameter
|
||||
keys to values.
|
||||
remove (Sequence[str]): Parameters to remove from the query string.
|
||||
|
||||
Returns:
|
||||
str: The URL with updated query parameters.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> url = 'http://example.com?a=1'
|
||||
>>> update_query(url, {'a': '2'})
|
||||
http://example.com?a=2
|
||||
>>> update_query(url, {'b': '3'})
|
||||
http://example.com?a=1&b=3
|
||||
>> update_query(url, {'b': '3'}, remove=['a'])
|
||||
http://example.com?b=3
|
||||
|
||||
"""
|
||||
if remove is None:
|
||||
remove = []
|
||||
|
||||
# Split the URL into parts.
|
||||
parts = urllib.parse.urlparse(url)
|
||||
# Parse the query string.
|
||||
query_params = urllib.parse.parse_qs(parts.query)
|
||||
# Update the query parameters with the new parameters.
|
||||
query_params.update(params)
|
||||
# Remove any values specified in remove.
|
||||
query_params = {
|
||||
key: value for key, value in six.iteritems(query_params) if key not in remove
|
||||
}
|
||||
# Re-encoded the query string.
|
||||
new_query = urllib.parse.urlencode(query_params, doseq=True)
|
||||
# Unsplit the url.
|
||||
new_parts = parts._replace(query=new_query)
|
||||
return urllib.parse.urlunparse(new_parts)
|
||||
|
||||
|
||||
def scopes_to_string(scopes):
|
||||
"""Converts scope value to a string suitable for sending to OAuth 2.0
|
||||
authorization servers.
|
||||
|
||||
Args:
|
||||
scopes (Sequence[str]): The sequence of scopes to convert.
|
||||
|
||||
Returns:
|
||||
str: The scopes formatted as a single string.
|
||||
"""
|
||||
return " ".join(scopes)
|
||||
|
||||
|
||||
def string_to_scopes(scopes):
|
||||
"""Converts stringifed scopes value to a list.
|
||||
|
||||
Args:
|
||||
scopes (Union[Sequence, str]): The string of space-separated scopes
|
||||
to convert.
|
||||
Returns:
|
||||
Sequence(str): The separated scopes.
|
||||
"""
|
||||
if not scopes:
|
||||
return []
|
||||
|
||||
return scopes.split(" ")
|
||||
|
||||
|
||||
def padded_urlsafe_b64decode(value):
|
||||
"""Decodes base64 strings lacking padding characters.
|
||||
|
||||
Google infrastructure tends to omit the base64 padding characters.
|
||||
|
||||
Args:
|
||||
value (Union[str, bytes]): The encoded value.
|
||||
|
||||
Returns:
|
||||
bytes: The decoded value
|
||||
"""
|
||||
b64string = to_bytes(value)
|
||||
padded = b64string + b"=" * (-len(b64string) % 4)
|
||||
return base64.urlsafe_b64decode(padded)
|
||||
|
||||
|
||||
def unpadded_urlsafe_b64encode(value):
|
||||
"""Encodes base64 strings removing any padding characters.
|
||||
|
||||
`rfc 7515`_ defines Base64url to NOT include any padding
|
||||
characters, but the stdlib doesn't do that by default.
|
||||
|
||||
_rfc7515: https://tools.ietf.org/html/rfc7515#page-6
|
||||
|
||||
Args:
|
||||
value (Union[str|bytes]): The bytes-like value to encode
|
||||
|
||||
Returns:
|
||||
Union[str|bytes]: The encoded value
|
||||
"""
|
||||
return base64.urlsafe_b64encode(value).rstrip(b"=")
|
168
venv/Lib/site-packages/google/auth/_jwt_async.py
Normal file
168
venv/Lib/site-packages/google/auth/_jwt_async.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""JSON Web Tokens
|
||||
|
||||
Provides support for creating (encoding) and verifying (decoding) JWTs,
|
||||
especially JWTs generated and consumed by Google infrastructure.
|
||||
|
||||
See `rfc7519`_ for more details on JWTs.
|
||||
|
||||
To encode a JWT use :func:`encode`::
|
||||
|
||||
from google.auth import crypt
|
||||
from google.auth import jwt_async
|
||||
|
||||
signer = crypt.Signer(private_key)
|
||||
payload = {'some': 'payload'}
|
||||
encoded = jwt_async.encode(signer, payload)
|
||||
|
||||
To decode a JWT and verify claims use :func:`decode`::
|
||||
|
||||
claims = jwt_async.decode(encoded, certs=public_certs)
|
||||
|
||||
You can also skip verification::
|
||||
|
||||
claims = jwt_async.decode(encoded, verify=False)
|
||||
|
||||
.. _rfc7519: https://tools.ietf.org/html/rfc7519
|
||||
|
||||
|
||||
NOTE: This async support is experimental and marked internal. This surface may
|
||||
change in minor releases.
|
||||
"""
|
||||
|
||||
import google.auth
|
||||
from google.auth import jwt
|
||||
|
||||
|
||||
def encode(signer, payload, header=None, key_id=None):
|
||||
"""Make a signed JWT.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign the JWT.
|
||||
payload (Mapping[str, str]): The JWT payload.
|
||||
header (Mapping[str, str]): Additional JWT header payload.
|
||||
key_id (str): The key id to add to the JWT header. If the
|
||||
signer has a key id it will be used as the default. If this is
|
||||
specified it will override the signer's key id.
|
||||
|
||||
Returns:
|
||||
bytes: The encoded JWT.
|
||||
"""
|
||||
return jwt.encode(signer, payload, header, key_id)
|
||||
|
||||
|
||||
def decode(token, certs=None, verify=True, audience=None):
|
||||
"""Decode and verify a JWT.
|
||||
|
||||
Args:
|
||||
token (str): The encoded JWT.
|
||||
certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
|
||||
certificate used to validate the JWT signature. If bytes or string,
|
||||
it must the the public key certificate in PEM format. If a mapping,
|
||||
it must be a mapping of key IDs to public key certificates in PEM
|
||||
format. The mapping must contain the same key ID that's specified
|
||||
in the token's header.
|
||||
verify (bool): Whether to perform signature and claim validation.
|
||||
Verification is done by default.
|
||||
audience (str): The audience claim, 'aud', that this JWT should
|
||||
contain. If None then the JWT's 'aud' parameter is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: The deserialized JSON payload in the JWT.
|
||||
|
||||
Raises:
|
||||
ValueError: if any verification checks failed.
|
||||
"""
|
||||
|
||||
return jwt.decode(token, certs, verify, audience)
|
||||
|
||||
|
||||
class Credentials(
|
||||
jwt.Credentials,
|
||||
google.auth._credentials_async.Signing,
|
||||
google.auth._credentials_async.Credentials,
|
||||
):
|
||||
"""Credentials that use a JWT as the bearer token.
|
||||
|
||||
These credentials require an "audience" claim. This claim identifies the
|
||||
intended recipient of the bearer token.
|
||||
|
||||
The constructor arguments determine the claims for the JWT that is
|
||||
sent with requests. Usually, you'll construct these credentials with
|
||||
one of the helper constructors as shown in the next section.
|
||||
|
||||
To create JWT credentials using a Google service account private key
|
||||
JSON file::
|
||||
|
||||
audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
|
||||
credentials = jwt_async.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
audience=audience)
|
||||
|
||||
If you already have the service account file loaded and parsed::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = jwt_async.Credentials.from_service_account_info(
|
||||
service_account_info,
|
||||
audience=audience)
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify the JWT claims::
|
||||
|
||||
credentials = jwt_async.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
audience=audience,
|
||||
additional_claims={'meta': 'data'})
|
||||
|
||||
You can also construct the credentials directly if you have a
|
||||
:class:`~google.auth.crypt.Signer` instance::
|
||||
|
||||
credentials = jwt_async.Credentials(
|
||||
signer,
|
||||
issuer='your-issuer',
|
||||
subject='your-subject',
|
||||
audience=audience)
|
||||
|
||||
The claims are considered immutable. If you want to modify the claims,
|
||||
you can easily create another instance using :meth:`with_claims`::
|
||||
|
||||
new_audience = (
|
||||
'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
|
||||
new_credentials = credentials.with_claims(audience=new_audience)
|
||||
"""
|
||||
|
||||
|
||||
class OnDemandCredentials(
|
||||
jwt.OnDemandCredentials,
|
||||
google.auth._credentials_async.Signing,
|
||||
google.auth._credentials_async.Credentials,
|
||||
):
|
||||
"""On-demand JWT credentials.
|
||||
|
||||
Like :class:`Credentials`, this class uses a JWT as the bearer token for
|
||||
authentication. However, this class does not require the audience at
|
||||
construction time. Instead, it will generate a new token on-demand for
|
||||
each request using the request URI as the audience. It caches tokens
|
||||
so that multiple requests to the same URI do not incur the overhead
|
||||
of generating a new token every time.
|
||||
|
||||
This behavior is especially useful for `gRPC`_ clients. A gRPC service may
|
||||
have multiple audience and gRPC clients may not know all of the audiences
|
||||
required for accessing a particular service. With these credentials,
|
||||
no knowledge of the audiences is required ahead of time.
|
||||
|
||||
.. _grpc: http://www.grpc.io/
|
||||
"""
|
169
venv/Lib/site-packages/google/auth/_oauth2client.py
Normal file
169
venv/Lib/site-packages/google/auth/_oauth2client.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helpers for transitioning from oauth2client to google-auth.
|
||||
|
||||
.. warning::
|
||||
This module is private as it is intended to assist first-party downstream
|
||||
clients with the transition from oauth2client to google-auth.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import _helpers
|
||||
import google.auth.app_engine
|
||||
import google.auth.compute_engine
|
||||
import google.oauth2.credentials
|
||||
import google.oauth2.service_account
|
||||
|
||||
try:
|
||||
import oauth2client.client
|
||||
import oauth2client.contrib.gce
|
||||
import oauth2client.service_account
|
||||
except ImportError as caught_exc:
|
||||
six.raise_from(ImportError("oauth2client is not installed."), caught_exc)
|
||||
|
||||
try:
|
||||
import oauth2client.contrib.appengine # pytype: disable=import-error
|
||||
|
||||
_HAS_APPENGINE = True
|
||||
except ImportError:
|
||||
_HAS_APPENGINE = False
|
||||
|
||||
|
||||
_CONVERT_ERROR_TMPL = "Unable to convert {} to a google-auth credentials class."
|
||||
|
||||
|
||||
def _convert_oauth2_credentials(credentials):
|
||||
"""Converts to :class:`google.oauth2.credentials.Credentials`.
|
||||
|
||||
Args:
|
||||
credentials (Union[oauth2client.client.OAuth2Credentials,
|
||||
oauth2client.client.GoogleCredentials]): The credentials to
|
||||
convert.
|
||||
|
||||
Returns:
|
||||
google.oauth2.credentials.Credentials: The converted credentials.
|
||||
"""
|
||||
new_credentials = google.oauth2.credentials.Credentials(
|
||||
token=credentials.access_token,
|
||||
refresh_token=credentials.refresh_token,
|
||||
token_uri=credentials.token_uri,
|
||||
client_id=credentials.client_id,
|
||||
client_secret=credentials.client_secret,
|
||||
scopes=credentials.scopes,
|
||||
)
|
||||
|
||||
new_credentials._expires = credentials.token_expiry
|
||||
|
||||
return new_credentials
|
||||
|
||||
|
||||
def _convert_service_account_credentials(credentials):
|
||||
"""Converts to :class:`google.oauth2.service_account.Credentials`.
|
||||
|
||||
Args:
|
||||
credentials (Union[
|
||||
oauth2client.service_account.ServiceAccountCredentials,
|
||||
oauth2client.service_account._JWTAccessCredentials]): The
|
||||
credentials to convert.
|
||||
|
||||
Returns:
|
||||
google.oauth2.service_account.Credentials: The converted credentials.
|
||||
"""
|
||||
info = credentials.serialization_data.copy()
|
||||
info["token_uri"] = credentials.token_uri
|
||||
return google.oauth2.service_account.Credentials.from_service_account_info(info)
|
||||
|
||||
|
||||
def _convert_gce_app_assertion_credentials(credentials):
|
||||
"""Converts to :class:`google.auth.compute_engine.Credentials`.
|
||||
|
||||
Args:
|
||||
credentials (oauth2client.contrib.gce.AppAssertionCredentials): The
|
||||
credentials to convert.
|
||||
|
||||
Returns:
|
||||
google.oauth2.service_account.Credentials: The converted credentials.
|
||||
"""
|
||||
return google.auth.compute_engine.Credentials(
|
||||
service_account_email=credentials.service_account_email
|
||||
)
|
||||
|
||||
|
||||
def _convert_appengine_app_assertion_credentials(credentials):
|
||||
"""Converts to :class:`google.auth.app_engine.Credentials`.
|
||||
|
||||
Args:
|
||||
credentials (oauth2client.contrib.app_engine.AppAssertionCredentials):
|
||||
The credentials to convert.
|
||||
|
||||
Returns:
|
||||
google.oauth2.service_account.Credentials: The converted credentials.
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
return google.auth.app_engine.Credentials(
|
||||
scopes=_helpers.string_to_scopes(credentials.scope),
|
||||
service_account_id=credentials.service_account_id,
|
||||
)
|
||||
|
||||
|
||||
_CLASS_CONVERSION_MAP = {
|
||||
oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials,
|
||||
oauth2client.client.GoogleCredentials: _convert_oauth2_credentials,
|
||||
oauth2client.service_account.ServiceAccountCredentials: _convert_service_account_credentials,
|
||||
oauth2client.service_account._JWTAccessCredentials: _convert_service_account_credentials,
|
||||
oauth2client.contrib.gce.AppAssertionCredentials: _convert_gce_app_assertion_credentials,
|
||||
}
|
||||
|
||||
if _HAS_APPENGINE:
|
||||
_CLASS_CONVERSION_MAP[
|
||||
oauth2client.contrib.appengine.AppAssertionCredentials
|
||||
] = _convert_appengine_app_assertion_credentials
|
||||
|
||||
|
||||
def convert(credentials):
|
||||
"""Convert oauth2client credentials to google-auth credentials.
|
||||
|
||||
This class converts:
|
||||
|
||||
- :class:`oauth2client.client.OAuth2Credentials` to
|
||||
:class:`google.oauth2.credentials.Credentials`.
|
||||
- :class:`oauth2client.client.GoogleCredentials` to
|
||||
:class:`google.oauth2.credentials.Credentials`.
|
||||
- :class:`oauth2client.service_account.ServiceAccountCredentials` to
|
||||
:class:`google.oauth2.service_account.Credentials`.
|
||||
- :class:`oauth2client.service_account._JWTAccessCredentials` to
|
||||
:class:`google.oauth2.service_account.Credentials`.
|
||||
- :class:`oauth2client.contrib.gce.AppAssertionCredentials` to
|
||||
:class:`google.auth.compute_engine.Credentials`.
|
||||
- :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to
|
||||
:class:`google.auth.app_engine.Credentials`.
|
||||
|
||||
Returns:
|
||||
google.auth.credentials.Credentials: The converted credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the credentials could not be converted.
|
||||
"""
|
||||
|
||||
credentials_class = type(credentials)
|
||||
|
||||
try:
|
||||
return _CLASS_CONVERSION_MAP[credentials_class](credentials)
|
||||
except KeyError as caught_exc:
|
||||
new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))
|
||||
six.raise_from(new_exc, caught_exc)
|
74
venv/Lib/site-packages/google/auth/_service_account_info.py
Normal file
74
venv/Lib/site-packages/google/auth/_service_account_info.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper functions for loading data from a Google service account file."""
|
||||
|
||||
import io
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import crypt
|
||||
|
||||
|
||||
def from_dict(data, require=None):
|
||||
"""Validates a dictionary containing Google service account data.
|
||||
|
||||
Creates and returns a :class:`google.auth.crypt.Signer` instance from the
|
||||
private key specified in the data.
|
||||
|
||||
Args:
|
||||
data (Mapping[str, str]): The service account data
|
||||
require (Sequence[str]): List of keys required to be present in the
|
||||
info.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt.Signer: A signer created from the private key in the
|
||||
service account file.
|
||||
|
||||
Raises:
|
||||
ValueError: if the data was in the wrong format, or if one of the
|
||||
required keys is missing.
|
||||
"""
|
||||
keys_needed = set(require if require is not None else [])
|
||||
|
||||
missing = keys_needed.difference(six.iterkeys(data))
|
||||
|
||||
if missing:
|
||||
raise ValueError(
|
||||
"Service account info was not in the expected format, missing "
|
||||
"fields {}.".format(", ".join(missing))
|
||||
)
|
||||
|
||||
# Create a signer.
|
||||
signer = crypt.RSASigner.from_service_account_info(data)
|
||||
|
||||
return signer
|
||||
|
||||
|
||||
def from_filename(filename, require=None):
|
||||
"""Reads a Google service account JSON file and returns its parsed info.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account .json file.
|
||||
require (Sequence[str]): List of keys required to be present in the
|
||||
info.
|
||||
|
||||
Returns:
|
||||
Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified
|
||||
info and a signer instance.
|
||||
"""
|
||||
with io.open(filename, "r", encoding="utf-8") as json_file:
|
||||
data = json.load(json_file)
|
||||
return data, from_dict(data, require=require)
|
170
venv/Lib/site-packages/google/auth/app_engine.py
Normal file
170
venv/Lib/site-packages/google/auth/app_engine.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google App Engine standard environment support.
|
||||
|
||||
This module provides authentication and signing for applications running on App
|
||||
Engine in the standard environment using the `App Identity API`_.
|
||||
|
||||
|
||||
.. _App Identity API:
|
||||
https://cloud.google.com/appengine/docs/python/appidentity/
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import credentials
|
||||
from google.auth import crypt
|
||||
|
||||
# pytype: disable=import-error
|
||||
try:
|
||||
from google.appengine.api import app_identity
|
||||
except ImportError:
|
||||
app_identity = None
|
||||
# pytype: enable=import-error
|
||||
|
||||
|
||||
class Signer(crypt.Signer):
|
||||
"""Signs messages using the App Engine App Identity service.
|
||||
|
||||
This can be used in place of :class:`google.auth.crypt.Signer` when
|
||||
running in the App Engine standard environment.
|
||||
"""
|
||||
|
||||
@property
|
||||
def key_id(self):
|
||||
"""Optional[str]: The key ID used to identify this private key.
|
||||
|
||||
.. warning::
|
||||
This is always ``None``. The key ID used by App Engine can not
|
||||
be reliably determined ahead of time.
|
||||
"""
|
||||
return None
|
||||
|
||||
@_helpers.copy_docstring(crypt.Signer)
|
||||
def sign(self, message):
|
||||
message = _helpers.to_bytes(message)
|
||||
_, signature = app_identity.sign_blob(message)
|
||||
return signature
|
||||
|
||||
|
||||
def get_project_id():
|
||||
"""Gets the project ID for the current App Engine application.
|
||||
|
||||
Returns:
|
||||
str: The project ID
|
||||
|
||||
Raises:
|
||||
EnvironmentError: If the App Engine APIs are unavailable.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc
|
||||
# Pylint rightfully thinks EnvironmentError is OSError, but doesn't
|
||||
# realize it's a valid alias.
|
||||
if app_identity is None:
|
||||
raise EnvironmentError("The App Engine APIs are not available.")
|
||||
return app_identity.get_application_id()
|
||||
|
||||
|
||||
class Credentials(
|
||||
credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject
|
||||
):
|
||||
"""App Engine standard environment credentials.
|
||||
|
||||
These credentials use the App Engine App Identity API to obtain access
|
||||
tokens.
|
||||
"""
|
||||
|
||||
def __init__(self, scopes=None, service_account_id=None, quota_project_id=None):
|
||||
"""
|
||||
Args:
|
||||
scopes (Sequence[str]): Scopes to request from the App Identity
|
||||
API.
|
||||
service_account_id (str): The service account ID passed into
|
||||
:func:`google.appengine.api.app_identity.get_access_token`.
|
||||
If not specified, the default application service account
|
||||
ID will be used.
|
||||
quota_project_id (Optional[str]): The project ID used for quota
|
||||
and billing.
|
||||
|
||||
Raises:
|
||||
EnvironmentError: If the App Engine APIs are unavailable.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc
|
||||
# Pylint rightfully thinks EnvironmentError is OSError, but doesn't
|
||||
# realize it's a valid alias.
|
||||
if app_identity is None:
|
||||
raise EnvironmentError("The App Engine APIs are not available.")
|
||||
|
||||
super(Credentials, self).__init__()
|
||||
self._scopes = scopes
|
||||
self._service_account_id = service_account_id
|
||||
self._signer = Signer()
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
# pylint: disable=unused-argument
|
||||
token, ttl = app_identity.get_access_token(
|
||||
self._scopes, self._service_account_id
|
||||
)
|
||||
expiry = datetime.datetime.utcfromtimestamp(ttl)
|
||||
|
||||
self.token, self.expiry = token, expiry
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
"""The service account email."""
|
||||
if self._service_account_id is None:
|
||||
self._service_account_id = app_identity.get_service_account_name()
|
||||
return self._service_account_id
|
||||
|
||||
@property
|
||||
def requires_scopes(self):
|
||||
"""Checks if the credentials requires scopes.
|
||||
|
||||
Returns:
|
||||
bool: True if there are no scopes set otherwise False.
|
||||
"""
|
||||
return not self._scopes
|
||||
|
||||
@_helpers.copy_docstring(credentials.Scoped)
|
||||
def with_scopes(self, scopes):
|
||||
return self.__class__(
|
||||
scopes=scopes,
|
||||
service_account_id=self._service_account_id,
|
||||
quota_project_id=self.quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
scopes=self._scopes,
|
||||
service_account_id=self._service_account_id,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def sign_bytes(self, message):
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer_email(self):
|
||||
return self.service_account_email
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google Compute Engine authentication."""
|
||||
|
||||
from google.auth.compute_engine.credentials import Credentials
|
||||
from google.auth.compute_engine.credentials import IDTokenCredentials
|
||||
|
||||
|
||||
__all__ = ["Credentials", "IDTokenCredentials"]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
257
venv/Lib/site-packages/google/auth/compute_engine/_metadata.py
Normal file
257
venv/Lib/site-packages/google/auth/compute_engine/_metadata.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Provides helper methods for talking to the Compute Engine metadata server.
|
||||
|
||||
See https://cloud.google.com/compute/docs/metadata for more details.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Environment variable GCE_METADATA_HOST is originally named
|
||||
# GCE_METADATA_ROOT. For compatiblity reasons, here it checks
|
||||
# the new variable first; if not set, the system falls back
|
||||
# to the old variable.
|
||||
_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None)
|
||||
if not _GCE_METADATA_HOST:
|
||||
_GCE_METADATA_HOST = os.getenv(
|
||||
environment_vars.GCE_METADATA_ROOT, "metadata.google.internal"
|
||||
)
|
||||
_METADATA_ROOT = "http://{}/computeMetadata/v1/".format(_GCE_METADATA_HOST)
|
||||
|
||||
# This is used to ping the metadata server, it avoids the cost of a DNS
|
||||
# lookup.
|
||||
_METADATA_IP_ROOT = "http://{}".format(
|
||||
os.getenv(environment_vars.GCE_METADATA_IP, "169.254.169.254")
|
||||
)
|
||||
_METADATA_FLAVOR_HEADER = "metadata-flavor"
|
||||
_METADATA_FLAVOR_VALUE = "Google"
|
||||
_METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE}
|
||||
|
||||
# Timeout in seconds to wait for the GCE metadata server when detecting the
|
||||
# GCE environment.
|
||||
try:
|
||||
_METADATA_DEFAULT_TIMEOUT = int(os.getenv("GCE_METADATA_TIMEOUT", 3))
|
||||
except ValueError: # pragma: NO COVER
|
||||
_METADATA_DEFAULT_TIMEOUT = 3
|
||||
|
||||
|
||||
def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
|
||||
"""Checks to see if the metadata server is available.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
timeout (int): How long to wait for the metadata server to respond.
|
||||
retry_count (int): How many times to attempt connecting to metadata
|
||||
server using above timeout.
|
||||
|
||||
Returns:
|
||||
bool: True if the metadata server is reachable, False otherwise.
|
||||
"""
|
||||
# NOTE: The explicit ``timeout`` is a workaround. The underlying
|
||||
# issue is that resolving an unknown host on some networks will take
|
||||
# 20-30 seconds; making this timeout short fixes the issue, but
|
||||
# could lead to false negatives in the event that we are on GCE, but
|
||||
# the metadata resolution was particularly slow. The latter case is
|
||||
# "unlikely".
|
||||
retries = 0
|
||||
while retries < retry_count:
|
||||
try:
|
||||
response = request(
|
||||
url=_METADATA_IP_ROOT,
|
||||
method="GET",
|
||||
headers=_METADATA_HEADERS,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER)
|
||||
return (
|
||||
response.status == http_client.OK
|
||||
and metadata_flavor == _METADATA_FLAVOR_VALUE
|
||||
)
|
||||
|
||||
except exceptions.TransportError as e:
|
||||
_LOGGER.warning(
|
||||
"Compute Engine Metadata server unavailable on"
|
||||
"attempt %s of %s. Reason: %s",
|
||||
retries + 1,
|
||||
retry_count,
|
||||
e,
|
||||
)
|
||||
retries += 1
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5):
|
||||
"""Fetch a resource from the metadata server.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
path (str): The resource to retrieve. For example,
|
||||
``'instance/service-accounts/default'``.
|
||||
root (str): The full path to the metadata server root.
|
||||
recursive (bool): Whether to do a recursive query of metadata. See
|
||||
https://cloud.google.com/compute/docs/metadata#aggcontents for more
|
||||
details.
|
||||
retry_count (int): How many times to attempt connecting to metadata
|
||||
server using above timeout.
|
||||
|
||||
Returns:
|
||||
Union[Mapping, str]: If the metadata server returns JSON, a mapping of
|
||||
the decoded JSON is return. Otherwise, the response content is
|
||||
returned as a string.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: if an error occurred while
|
||||
retrieving metadata.
|
||||
"""
|
||||
base_url = urlparse.urljoin(root, path)
|
||||
query_params = {}
|
||||
|
||||
if recursive:
|
||||
query_params["recursive"] = "true"
|
||||
|
||||
url = _helpers.update_query(base_url, query_params)
|
||||
|
||||
retries = 0
|
||||
while retries < retry_count:
|
||||
try:
|
||||
response = request(url=url, method="GET", headers=_METADATA_HEADERS)
|
||||
break
|
||||
|
||||
except exceptions.TransportError as e:
|
||||
_LOGGER.warning(
|
||||
"Compute Engine Metadata server unavailable on"
|
||||
"attempt %s of %s. Reason: %s",
|
||||
retries + 1,
|
||||
retry_count,
|
||||
e,
|
||||
)
|
||||
retries += 1
|
||||
else:
|
||||
raise exceptions.TransportError(
|
||||
"Failed to retrieve {} from the Google Compute Engine"
|
||||
"metadata service. Compute Engine Metadata server unavailable".format(url)
|
||||
)
|
||||
|
||||
if response.status == http_client.OK:
|
||||
content = _helpers.from_bytes(response.data)
|
||||
if response.headers["content-type"] == "application/json":
|
||||
try:
|
||||
return json.loads(content)
|
||||
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:
|
||||
raise exceptions.TransportError(
|
||||
"Failed to retrieve {} from the Google Compute Engine"
|
||||
"metadata service. Status: {} Response:\n{}".format(
|
||||
url, response.status, response.data
|
||||
),
|
||||
response,
|
||||
)
|
||||
|
||||
|
||||
def get_project_id(request):
|
||||
"""Get the Google Cloud Project ID from the metadata server.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
|
||||
Returns:
|
||||
str: The project ID
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: if an error occurred while
|
||||
retrieving metadata.
|
||||
"""
|
||||
return get(request, "project/project-id")
|
||||
|
||||
|
||||
def get_service_account_info(request, service_account="default"):
|
||||
"""Get information about a service account from the metadata server.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
service_account (str): The string 'default' or a service account email
|
||||
address. The determines which service account for which to acquire
|
||||
information.
|
||||
|
||||
Returns:
|
||||
Mapping: The service account's information, for example::
|
||||
|
||||
{
|
||||
'email': '...',
|
||||
'scopes': ['scope', ...],
|
||||
'aliases': ['default', '...']
|
||||
}
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: if an error occurred while
|
||||
retrieving metadata.
|
||||
"""
|
||||
return get(
|
||||
request,
|
||||
"instance/service-accounts/{0}/".format(service_account),
|
||||
recursive=True,
|
||||
)
|
||||
|
||||
|
||||
def get_service_account_token(request, service_account="default"):
|
||||
"""Get the OAuth 2.0 access token for a service account.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): A callable used to make
|
||||
HTTP requests.
|
||||
service_account (str): The string 'default' or a service account email
|
||||
address. The determines which service account for which to acquire
|
||||
an access token.
|
||||
|
||||
Returns:
|
||||
Union[str, datetime]: The access token and its expiration.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: if an error occurred while
|
||||
retrieving metadata.
|
||||
"""
|
||||
token_json = get(
|
||||
request, "instance/service-accounts/{0}/token".format(service_account)
|
||||
)
|
||||
token_expiry = _helpers.utcnow() + datetime.timedelta(
|
||||
seconds=token_json["expires_in"]
|
||||
)
|
||||
return token_json["access_token"], token_expiry
|
392
venv/Lib/site-packages/google/auth/compute_engine/credentials.py
Normal file
392
venv/Lib/site-packages/google/auth/compute_engine/credentials.py
Normal file
|
@ -0,0 +1,392 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google Compute Engine credentials.
|
||||
|
||||
This module provides authentication for application running on Google Compute
|
||||
Engine using the Compute Engine metadata server.
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import credentials
|
||||
from google.auth import exceptions
|
||||
from google.auth import iam
|
||||
from google.auth import jwt
|
||||
from google.auth.compute_engine import _metadata
|
||||
from google.oauth2 import _client
|
||||
|
||||
|
||||
class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
|
||||
"""Compute Engine Credentials.
|
||||
|
||||
These credentials use the Google Compute Engine metadata server to obtain
|
||||
OAuth 2.0 access tokens associated with the instance's service account.
|
||||
|
||||
For more information about Compute Engine authentication, including how
|
||||
to configure scopes, see the `Compute Engine authentication
|
||||
documentation`_.
|
||||
|
||||
.. note:: Compute Engine instances can be created with scopes and therefore
|
||||
these credentials are considered to be 'scoped'. However, you can
|
||||
not use :meth:`~google.auth.credentials.ScopedCredentials.with_scopes`
|
||||
because it is not possible to change the scopes that the instance
|
||||
has. Also note that
|
||||
:meth:`~google.auth.credentials.ScopedCredentials.has_scopes` will not
|
||||
work until the credentials have been refreshed.
|
||||
|
||||
.. _Compute Engine authentication documentation:
|
||||
https://cloud.google.com/compute/docs/authentication#using
|
||||
"""
|
||||
|
||||
def __init__(self, service_account_email="default", quota_project_id=None):
|
||||
"""
|
||||
Args:
|
||||
service_account_email (str): The service account email to use, or
|
||||
'default'. A Compute Engine instance may have multiple service
|
||||
accounts.
|
||||
quota_project_id (Optional[str]): The project ID used for quota and
|
||||
billing.
|
||||
"""
|
||||
super(Credentials, self).__init__()
|
||||
self._service_account_email = service_account_email
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
def _retrieve_info(self, request):
|
||||
"""Retrieve information about the service account.
|
||||
|
||||
Updates the scopes and retrieves the full service account email.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
"""
|
||||
info = _metadata.get_service_account_info(
|
||||
request, service_account=self._service_account_email
|
||||
)
|
||||
|
||||
self._service_account_email = info["email"]
|
||||
self._scopes = info["scopes"]
|
||||
|
||||
def refresh(self, request):
|
||||
"""Refresh the access token and scopes.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the Compute Engine metadata
|
||||
service can't be reached if if the instance has not
|
||||
credentials.
|
||||
"""
|
||||
try:
|
||||
self._retrieve_info(request)
|
||||
self.token, self.expiry = _metadata.get_service_account_token(
|
||||
request, service_account=self._service_account_email
|
||||
)
|
||||
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):
|
||||
"""The service account email.
|
||||
|
||||
.. note:: This is not guaranteed to be set until :meth:`refresh` has been
|
||||
called.
|
||||
"""
|
||||
return self._service_account_email
|
||||
|
||||
@property
|
||||
def requires_scopes(self):
|
||||
"""False: Compute Engine credentials can not be scoped."""
|
||||
return False
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
service_account_email=self._service_account_email,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
|
||||
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"
|
||||
|
||||
|
||||
class IDTokenCredentials(credentials.CredentialsWithQuotaProject, credentials.Signing):
|
||||
"""Open ID Connect ID Token-based service account credentials.
|
||||
|
||||
These credentials relies on the default service account of a GCE instance.
|
||||
|
||||
ID token can be requested from `GCE metadata server identity endpoint`_, IAM
|
||||
token endpoint or other token endpoints you specify. If metadata server
|
||||
identity endpoint is not used, the GCE instance must have been started with
|
||||
a service account that has access to the IAM Cloud API.
|
||||
|
||||
.. _GCE metadata server identity endpoint:
|
||||
https://cloud.google.com/compute/docs/instances/verifying-instance-identity
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
target_audience,
|
||||
token_uri=None,
|
||||
additional_claims=None,
|
||||
service_account_email=None,
|
||||
signer=None,
|
||||
use_metadata_identity_endpoint=False,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
target_audience (str): The intended audience for these credentials,
|
||||
used when requesting the ID Token. The ID Token's ``aud`` claim
|
||||
will be set to this string.
|
||||
token_uri (str): The OAuth 2.0 Token URI.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT assertion used in the authorization grant.
|
||||
service_account_email (str): Optional explicit service account to
|
||||
use to sign JWT tokens.
|
||||
By default, this is the default GCE service account.
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
In case the signer is specified, the request argument will be
|
||||
ignored.
|
||||
use_metadata_identity_endpoint (bool): Whether to use GCE metadata
|
||||
identity endpoint. For backward compatibility the default value
|
||||
is False. If set to True, ``token_uri``, ``additional_claims``,
|
||||
``service_account_email``, ``signer`` argument should not be set;
|
||||
otherwise ValueError will be raised.
|
||||
quota_project_id (Optional[str]): The project ID used for quota and
|
||||
billing.
|
||||
|
||||
Raises:
|
||||
ValueError:
|
||||
If ``use_metadata_identity_endpoint`` is set to True, and one of
|
||||
``token_uri``, ``additional_claims``, ``service_account_email``,
|
||||
``signer`` arguments is set.
|
||||
"""
|
||||
super(IDTokenCredentials, self).__init__()
|
||||
|
||||
self._quota_project_id = quota_project_id
|
||||
self._use_metadata_identity_endpoint = use_metadata_identity_endpoint
|
||||
self._target_audience = target_audience
|
||||
|
||||
if use_metadata_identity_endpoint:
|
||||
if token_uri or additional_claims or service_account_email or signer:
|
||||
raise ValueError(
|
||||
"If use_metadata_identity_endpoint is set, token_uri, "
|
||||
"additional_claims, service_account_email, signer arguments"
|
||||
" must not be set"
|
||||
)
|
||||
self._token_uri = None
|
||||
self._additional_claims = None
|
||||
self._signer = None
|
||||
|
||||
if service_account_email is None:
|
||||
sa_info = _metadata.get_service_account_info(request)
|
||||
self._service_account_email = sa_info["email"]
|
||||
else:
|
||||
self._service_account_email = service_account_email
|
||||
|
||||
if not use_metadata_identity_endpoint:
|
||||
if signer is None:
|
||||
signer = iam.Signer(
|
||||
request=request,
|
||||
credentials=Credentials(),
|
||||
service_account_email=self._service_account_email,
|
||||
)
|
||||
self._signer = signer
|
||||
self._token_uri = token_uri or _DEFAULT_TOKEN_URI
|
||||
|
||||
if additional_claims is not None:
|
||||
self._additional_claims = additional_claims
|
||||
else:
|
||||
self._additional_claims = {}
|
||||
|
||||
def with_target_audience(self, target_audience):
|
||||
"""Create a copy of these credentials with the specified target
|
||||
audience.
|
||||
Args:
|
||||
target_audience (str): The intended audience for these credentials,
|
||||
used when requesting the ID Token.
|
||||
Returns:
|
||||
google.auth.service_account.IDTokenCredentials: A new credentials
|
||||
instance.
|
||||
"""
|
||||
# since the signer is already instantiated,
|
||||
# the request is not needed
|
||||
if self._use_metadata_identity_endpoint:
|
||||
return self.__class__(
|
||||
None,
|
||||
target_audience=target_audience,
|
||||
use_metadata_identity_endpoint=True,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
else:
|
||||
return self.__class__(
|
||||
None,
|
||||
service_account_email=self._service_account_email,
|
||||
token_uri=self._token_uri,
|
||||
target_audience=target_audience,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
signer=self.signer,
|
||||
use_metadata_identity_endpoint=False,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
|
||||
# since the signer is already instantiated,
|
||||
# the request is not needed
|
||||
if self._use_metadata_identity_endpoint:
|
||||
return self.__class__(
|
||||
None,
|
||||
target_audience=self._target_audience,
|
||||
use_metadata_identity_endpoint=True,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
else:
|
||||
return self.__class__(
|
||||
None,
|
||||
service_account_email=self._service_account_email,
|
||||
token_uri=self._token_uri,
|
||||
target_audience=self._target_audience,
|
||||
additional_claims=self._additional_claims.copy(),
|
||||
signer=self.signer,
|
||||
use_metadata_identity_endpoint=False,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
def _make_authorization_grant_assertion(self):
|
||||
"""Create the OAuth 2.0 assertion.
|
||||
This assertion is used during the OAuth 2.0 grant to acquire an
|
||||
ID token.
|
||||
Returns:
|
||||
bytes: The authorization grant assertion.
|
||||
"""
|
||||
now = _helpers.utcnow()
|
||||
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
|
||||
expiry = now + lifetime
|
||||
|
||||
payload = {
|
||||
"iat": _helpers.datetime_to_secs(now),
|
||||
"exp": _helpers.datetime_to_secs(expiry),
|
||||
# The issuer must be the service account email.
|
||||
"iss": self.service_account_email,
|
||||
# The audience must be the auth token endpoint's URI
|
||||
"aud": self._token_uri,
|
||||
# The target audience specifies which service the ID token is
|
||||
# intended for.
|
||||
"target_audience": self._target_audience,
|
||||
}
|
||||
|
||||
payload.update(self._additional_claims)
|
||||
|
||||
token = jwt.encode(self._signer, payload)
|
||||
|
||||
return token
|
||||
|
||||
def _call_metadata_identity_endpoint(self, request):
|
||||
"""Request ID token from metadata identity endpoint.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
|
||||
Returns:
|
||||
Tuple[str, datetime.datetime]: The ID token and the expiry of the ID token.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the Compute Engine metadata
|
||||
service can't be reached or if the instance has no credentials.
|
||||
ValueError: If extracting expiry from the obtained ID token fails.
|
||||
"""
|
||||
try:
|
||||
id_token = _metadata.get(
|
||||
request,
|
||||
"instance/service-accounts/default/identity?audience={}&format=full".format(
|
||||
self._target_audience
|
||||
),
|
||||
)
|
||||
except exceptions.TransportError as caught_exc:
|
||||
new_exc = exceptions.RefreshError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
_, payload, _, _ = jwt._unverified_decode(id_token)
|
||||
return id_token, datetime.datetime.fromtimestamp(payload["exp"])
|
||||
|
||||
def refresh(self, request):
|
||||
"""Refreshes the ID token.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the credentials could
|
||||
not be refreshed.
|
||||
ValueError: If extracting expiry from the obtained ID token fails.
|
||||
"""
|
||||
if self._use_metadata_identity_endpoint:
|
||||
self.token, self.expiry = self._call_metadata_identity_endpoint(request)
|
||||
else:
|
||||
assertion = self._make_authorization_grant_assertion()
|
||||
access_token, expiry, _ = _client.id_token_jwt_grant(
|
||||
request, self._token_uri, assertion
|
||||
)
|
||||
self.token = access_token
|
||||
self.expiry = expiry
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
||||
|
||||
def sign_bytes(self, message):
|
||||
"""Signs the given message.
|
||||
|
||||
Args:
|
||||
message (bytes): The message to sign.
|
||||
|
||||
Returns:
|
||||
bytes: The message's cryptographic signature.
|
||||
|
||||
Raises:
|
||||
ValueError:
|
||||
Signer is not available if metadata identity endpoint is used.
|
||||
"""
|
||||
if self._use_metadata_identity_endpoint:
|
||||
raise ValueError(
|
||||
"Signer is not available if metadata identity endpoint is used"
|
||||
)
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
"""The service account email."""
|
||||
return self._service_account_email
|
||||
|
||||
@property
|
||||
def signer_email(self):
|
||||
return self._service_account_email
|
351
venv/Lib/site-packages/google/auth/credentials.py
Normal file
351
venv/Lib/site-packages/google/auth/credentials.py
Normal file
|
@ -0,0 +1,351 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
"""Interfaces for credentials."""
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import _helpers
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Credentials(object):
|
||||
"""Base class for all credentials.
|
||||
|
||||
All credentials have a :attr:`token` that is used for authentication and
|
||||
may also optionally set an :attr:`expiry` to indicate when the token will
|
||||
no longer be valid.
|
||||
|
||||
Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
|
||||
Credentials can do this automatically before the first HTTP request in
|
||||
:meth:`before_request`.
|
||||
|
||||
Although the token and expiration will change as the credentials are
|
||||
:meth:`refreshed <refresh>` and used, credentials should be considered
|
||||
immutable. Various credentials will accept configuration such as private
|
||||
keys, scopes, and other options. These options are not changeable after
|
||||
construction. Some classes will provide mechanisms to copy the credentials
|
||||
with modifications such as :meth:`ScopedCredentials.with_scopes`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.token = None
|
||||
"""str: The bearer token that can be used in HTTP headers to make
|
||||
authenticated requests."""
|
||||
self.expiry = None
|
||||
"""Optional[datetime]: When the token expires and is no longer valid.
|
||||
If this is None, the token is assumed to never expire."""
|
||||
self._quota_project_id = None
|
||||
"""Optional[str]: Project to use for quota and billing purposes."""
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
"""Checks if the credentials are expired.
|
||||
|
||||
Note that credentials can be invalid but not expired because
|
||||
Credentials with :attr:`expiry` set to None is considered to never
|
||||
expire.
|
||||
"""
|
||||
if not self.expiry:
|
||||
return False
|
||||
|
||||
# Remove 5 minutes from expiry to err on the side of reporting
|
||||
# expiration early so that we avoid the 401-refresh-retry loop.
|
||||
skewed_expiry = self.expiry - _helpers.CLOCK_SKEW
|
||||
return _helpers.utcnow() >= skewed_expiry
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"""Checks the validity of the credentials.
|
||||
|
||||
This is True if the credentials have a :attr:`token` and the token
|
||||
is not :attr:`expired`.
|
||||
"""
|
||||
return self.token is not None and not self.expired
|
||||
|
||||
@property
|
||||
def quota_project_id(self):
|
||||
"""Project to use for quota and billing purposes."""
|
||||
return self._quota_project_id
|
||||
|
||||
@abc.abstractmethod
|
||||
def refresh(self, request):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.RefreshError: If the credentials could
|
||||
not be refreshed.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Refresh must be implemented")
|
||||
|
||||
def apply(self, headers, token=None):
|
||||
"""Apply the token to the authentication header.
|
||||
|
||||
Args:
|
||||
headers (Mapping): The HTTP request headers.
|
||||
token (Optional[str]): If specified, overrides the current access
|
||||
token.
|
||||
"""
|
||||
headers["authorization"] = "Bearer {}".format(
|
||||
_helpers.from_bytes(token or self.token)
|
||||
)
|
||||
if self.quota_project_id:
|
||||
headers["x-goog-user-project"] = self.quota_project_id
|
||||
|
||||
def before_request(self, request, method, url, headers):
|
||||
"""Performs credential-specific before request logic.
|
||||
|
||||
Refreshes the credentials if necessary, then calls :meth:`apply` to
|
||||
apply the token to the authentication header.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
method (str): The request's HTTP method or the RPC method being
|
||||
invoked.
|
||||
url (str): The request's URI or the RPC service's URI.
|
||||
headers (Mapping): The request's headers.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
# (Subclasses may use these arguments to ascertain information about
|
||||
# the http request.)
|
||||
if not self.valid:
|
||||
self.refresh(request)
|
||||
self.apply(headers)
|
||||
|
||||
|
||||
class CredentialsWithQuotaProject(Credentials):
|
||||
"""Abstract base for credentials supporting ``with_quota_project`` factory"""
|
||||
|
||||
def with_quota_project(self, quota_project_id):
|
||||
"""Returns a copy of these credentials with a modified quota project.
|
||||
|
||||
Args:
|
||||
quota_project_id (str): The project to use for quota and
|
||||
billing purposes
|
||||
|
||||
Returns:
|
||||
google.oauth2.credentials.Credentials: A new credentials instance.
|
||||
"""
|
||||
raise NotImplementedError("This credential does not support quota project.")
|
||||
|
||||
|
||||
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.
|
||||
|
||||
OAuth 2.0-based credentials allow limiting access using scopes as described
|
||||
in `RFC6749 Section 3.3`_.
|
||||
If a credential class implements this interface then the credentials either
|
||||
use scopes in their implementation.
|
||||
|
||||
Some credentials require scopes in order to obtain a token. You can check
|
||||
if scoping is necessary with :attr:`requires_scopes`::
|
||||
|
||||
if credentials.requires_scopes:
|
||||
# Scoping is required.
|
||||
credentials = credentials.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Credentials that require scopes must either be constructed with scopes::
|
||||
|
||||
credentials = SomeScopedCredentials(scopes=['one', 'two'])
|
||||
|
||||
Or must copy an existing instance using :meth:`with_scopes`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Some credentials have scopes but do not allow or require scopes to be set,
|
||||
these credentials can be used as-is.
|
||||
|
||||
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(ReadOnlyScoped, self).__init__()
|
||||
self._scopes = None
|
||||
|
||||
@property
|
||||
def scopes(self):
|
||||
"""Sequence[str]: the credentials' current set of scopes."""
|
||||
return self._scopes
|
||||
|
||||
@abc.abstractproperty
|
||||
def requires_scopes(self):
|
||||
"""True if these credentials require scopes to obtain an access token.
|
||||
"""
|
||||
return False
|
||||
|
||||
def has_scopes(self, scopes):
|
||||
"""Checks if the credentials have the given scopes.
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
return set(scopes).issubset(set(self._scopes or []))
|
||||
|
||||
|
||||
class Scoped(ReadOnlyScoped):
|
||||
"""Interface for credentials whose scopes can be replaced while copying.
|
||||
|
||||
OAuth 2.0-based credentials allow limiting access using scopes as described
|
||||
in `RFC6749 Section 3.3`_.
|
||||
If a credential class implements this interface then the credentials either
|
||||
use scopes in their implementation.
|
||||
|
||||
Some credentials require scopes in order to obtain a token. You can check
|
||||
if scoping is necessary with :attr:`requires_scopes`::
|
||||
|
||||
if credentials.requires_scopes:
|
||||
# Scoping is required.
|
||||
credentials = credentials.create_scoped(['one', 'two'])
|
||||
|
||||
Credentials that require scopes must either be constructed with scopes::
|
||||
|
||||
credentials = SomeScopedCredentials(scopes=['one', 'two'])
|
||||
|
||||
Or must copy an existing instance using :meth:`with_scopes`::
|
||||
|
||||
scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
|
||||
|
||||
Some credentials have scopes but do not allow or require scopes to be set,
|
||||
these credentials can be used as-is.
|
||||
|
||||
.. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def with_scopes(self, scopes):
|
||||
"""Create a copy of these credentials with the specified scopes.
|
||||
|
||||
Args:
|
||||
scopes (Sequence[str]): The list of scopes to attach to the
|
||||
current credentials.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If the credentials' scopes can not be changed.
|
||||
This can be avoided by checking :attr:`requires_scopes` before
|
||||
calling this method.
|
||||
"""
|
||||
raise NotImplementedError("This class does not require scoping.")
|
||||
|
||||
|
||||
def with_scopes_if_required(credentials, scopes):
|
||||
"""Creates a copy of the credentials with scopes if scoping is required.
|
||||
|
||||
This helper function is useful when you do not know (or care to know) the
|
||||
specific type of credentials you are using (such as when you use
|
||||
:func:`google.auth.default`). This function will call
|
||||
:meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
|
||||
the credentials require scoping. Otherwise, it will return the credentials
|
||||
as-is.
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
scope if necessary.
|
||||
scopes (Sequence[str]): The list of scopes to use.
|
||||
|
||||
Returns:
|
||||
google.auth.credentials.Credentials: Either a new set of scoped
|
||||
credentials, or the passed in credentials instance if no scoping
|
||||
was required.
|
||||
"""
|
||||
if isinstance(credentials, Scoped) and credentials.requires_scopes:
|
||||
return credentials.with_scopes(scopes)
|
||||
else:
|
||||
return credentials
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Signing(object):
|
||||
"""Interface for credentials that can cryptographically sign messages."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign_bytes(self, message):
|
||||
"""Signs the given message.
|
||||
|
||||
Args:
|
||||
message (bytes): The message to sign.
|
||||
|
||||
Returns:
|
||||
bytes: The message's cryptographic signature.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc,redundant-returns-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Sign bytes must be implemented.")
|
||||
|
||||
@abc.abstractproperty
|
||||
def signer_email(self):
|
||||
"""Optional[str]: An email address that identifies the signer."""
|
||||
# pylint: disable=missing-raises-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Signer email must be implemented.")
|
||||
|
||||
@abc.abstractproperty
|
||||
def signer(self):
|
||||
"""google.auth.crypt.Signer: The signer used to sign bytes."""
|
||||
# pylint: disable=missing-raises-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Signer must be implemented.")
|
100
venv/Lib/site-packages/google/auth/crypt/__init__.py
Normal file
100
venv/Lib/site-packages/google/auth/crypt/__init__.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Cryptography helpers for verifying and signing messages.
|
||||
|
||||
The simplest way to verify signatures is using :func:`verify_signature`::
|
||||
|
||||
cert = open('certs.pem').read()
|
||||
valid = crypt.verify_signature(message, signature, cert)
|
||||
|
||||
If you're going to verify many messages with the same certificate, you can use
|
||||
:class:`RSAVerifier`::
|
||||
|
||||
cert = open('certs.pem').read()
|
||||
verifier = crypt.RSAVerifier.from_string(cert)
|
||||
valid = verifier.verify(message, signature)
|
||||
|
||||
To sign messages use :class:`RSASigner` with a private key::
|
||||
|
||||
private_key = open('private_key.pem').read()
|
||||
signer = crypt.RSASigner.from_string(private_key)
|
||||
signature = signer.sign(message)
|
||||
|
||||
The code above also works for :class:`ES256Signer` and :class:`ES256Verifier`.
|
||||
Note that these two classes are only available if your `cryptography` dependency
|
||||
version is at least 1.4.0.
|
||||
"""
|
||||
|
||||
import six
|
||||
|
||||
from google.auth.crypt import base
|
||||
from google.auth.crypt import rsa
|
||||
|
||||
try:
|
||||
from google.auth.crypt import es256
|
||||
except ImportError: # pragma: NO COVER
|
||||
es256 = None
|
||||
|
||||
if es256 is not None: # pragma: NO COVER
|
||||
__all__ = [
|
||||
"ES256Signer",
|
||||
"ES256Verifier",
|
||||
"RSASigner",
|
||||
"RSAVerifier",
|
||||
"Signer",
|
||||
"Verifier",
|
||||
]
|
||||
else: # pragma: NO COVER
|
||||
__all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"]
|
||||
|
||||
|
||||
# Aliases to maintain the v1.0.0 interface, as the crypt module was split
|
||||
# into submodules.
|
||||
Signer = base.Signer
|
||||
Verifier = base.Verifier
|
||||
RSASigner = rsa.RSASigner
|
||||
RSAVerifier = rsa.RSAVerifier
|
||||
|
||||
if es256 is not None: # pragma: NO COVER
|
||||
ES256Signer = es256.ES256Signer
|
||||
ES256Verifier = es256.ES256Verifier
|
||||
|
||||
|
||||
def verify_signature(message, signature, certs, verifier_cls=rsa.RSAVerifier):
|
||||
"""Verify an RSA or ECDSA cryptographic signature.
|
||||
|
||||
Checks that the provided ``signature`` was generated from ``bytes`` using
|
||||
the private key associated with the ``cert``.
|
||||
|
||||
Args:
|
||||
message (Union[str, bytes]): The plaintext message.
|
||||
signature (Union[str, bytes]): The cryptographic signature to check.
|
||||
certs (Union[Sequence, str, bytes]): The certificate or certificates
|
||||
to use to check the signature.
|
||||
verifier_cls (Optional[~google.auth.crypt.base.Signer]): Which verifier
|
||||
class to use for verification. This can be used to select different
|
||||
algorithms, such as RSA or ECDSA. Default value is :class:`RSAVerifier`.
|
||||
|
||||
Returns:
|
||||
bool: True if the signature is valid, otherwise False.
|
||||
"""
|
||||
if isinstance(certs, (six.text_type, six.binary_type)):
|
||||
certs = [certs]
|
||||
|
||||
for cert in certs:
|
||||
verifier = verifier_cls.from_string(cert)
|
||||
if verifier.verify(message, signature):
|
||||
return True
|
||||
return False
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
149
venv/Lib/site-packages/google/auth/crypt/_cryptography_rsa.py
Normal file
149
venv/Lib/site-packages/google/auth/crypt/_cryptography_rsa.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
# Copyright 2017 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""RSA verifier and signer that use the ``cryptography`` library.
|
||||
|
||||
This is a much faster implementation than the default (in
|
||||
``google.auth.crypt._python_rsa``), which depends on the pure-Python
|
||||
``rsa`` library.
|
||||
"""
|
||||
|
||||
import cryptography.exceptions
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
import cryptography.x509
|
||||
import pkg_resources
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth.crypt import base
|
||||
|
||||
_IMPORT_ERROR_MSG = (
|
||||
"cryptography>=1.4.0 is required to use cryptography-based RSA " "implementation."
|
||||
)
|
||||
|
||||
try: # pragma: NO COVER
|
||||
release = pkg_resources.get_distribution("cryptography").parsed_version
|
||||
if release < pkg_resources.parse_version("1.4.0"):
|
||||
raise ImportError(_IMPORT_ERROR_MSG)
|
||||
except pkg_resources.DistributionNotFound: # pragma: NO COVER
|
||||
raise ImportError(_IMPORT_ERROR_MSG)
|
||||
|
||||
|
||||
_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
|
||||
_BACKEND = backends.default_backend()
|
||||
_PADDING = padding.PKCS1v15()
|
||||
_SHA256 = hashes.SHA256()
|
||||
|
||||
|
||||
class RSAVerifier(base.Verifier):
|
||||
"""Verifies RSA cryptographic signatures using public keys.
|
||||
|
||||
Args:
|
||||
public_key (
|
||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
|
||||
The public key used to verify signatures.
|
||||
"""
|
||||
|
||||
def __init__(self, public_key):
|
||||
self._pubkey = public_key
|
||||
|
||||
@_helpers.copy_docstring(base.Verifier)
|
||||
def verify(self, message, signature):
|
||||
message = _helpers.to_bytes(message)
|
||||
try:
|
||||
self._pubkey.verify(signature, message, _PADDING, _SHA256)
|
||||
return True
|
||||
except (ValueError, cryptography.exceptions.InvalidSignature):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, public_key):
|
||||
"""Construct an Verifier instance from a public key or public
|
||||
certificate string.
|
||||
|
||||
Args:
|
||||
public_key (Union[str, bytes]): The public key in PEM format or the
|
||||
x509 public key certificate.
|
||||
|
||||
Returns:
|
||||
Verifier: The constructed verifier.
|
||||
|
||||
Raises:
|
||||
ValueError: If the public key can't be parsed.
|
||||
"""
|
||||
public_key_data = _helpers.to_bytes(public_key)
|
||||
|
||||
if _CERTIFICATE_MARKER in public_key_data:
|
||||
cert = cryptography.x509.load_pem_x509_certificate(
|
||||
public_key_data, _BACKEND
|
||||
)
|
||||
pubkey = cert.public_key()
|
||||
|
||||
else:
|
||||
pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)
|
||||
|
||||
return cls(pubkey)
|
||||
|
||||
|
||||
class RSASigner(base.Signer, base.FromServiceAccountMixin):
|
||||
"""Signs messages with an RSA private key.
|
||||
|
||||
Args:
|
||||
private_key (
|
||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
The private key to sign with.
|
||||
key_id (str): Optional key ID used to identify this private key. This
|
||||
can be useful to associate the private key with its associated
|
||||
public key or certificate.
|
||||
"""
|
||||
|
||||
def __init__(self, private_key, key_id=None):
|
||||
self._key = private_key
|
||||
self._key_id = key_id
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def key_id(self):
|
||||
return self._key_id
|
||||
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def sign(self, message):
|
||||
message = _helpers.to_bytes(message)
|
||||
return self._key.sign(message, _PADDING, _SHA256)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, key, key_id=None):
|
||||
"""Construct a RSASigner from a private key in PEM format.
|
||||
|
||||
Args:
|
||||
key (Union[bytes, str]): Private key in PEM format.
|
||||
key_id (str): An optional key id used to identify the private key.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt._cryptography_rsa.RSASigner: The
|
||||
constructed signer.
|
||||
|
||||
Raises:
|
||||
ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
|
||||
UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
|
||||
into a UTF-8 ``str``.
|
||||
ValueError: If ``cryptography`` "Could not deserialize key data."
|
||||
"""
|
||||
key = _helpers.to_bytes(key)
|
||||
private_key = serialization.load_pem_private_key(
|
||||
key, password=None, backend=_BACKEND
|
||||
)
|
||||
return cls(private_key, key_id=key_id)
|
0
venv/Lib/site-packages/google/auth/crypt/_helpers.py
Normal file
0
venv/Lib/site-packages/google/auth/crypt/_helpers.py
Normal file
173
venv/Lib/site-packages/google/auth/crypt/_python_rsa.py
Normal file
173
venv/Lib/site-packages/google/auth/crypt/_python_rsa.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Pure-Python RSA cryptography implementation.
|
||||
|
||||
Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages
|
||||
to parse PEM files storing PKCS#1 or PKCS#8 keys as well as
|
||||
certificates. There is no support for p12 files.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pyasn1.codec.der import decoder
|
||||
from pyasn1_modules import pem
|
||||
from pyasn1_modules.rfc2459 import Certificate
|
||||
from pyasn1_modules.rfc5208 import PrivateKeyInfo
|
||||
import rsa
|
||||
import six
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth.crypt import base
|
||||
|
||||
_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
|
||||
_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
|
||||
_PKCS1_MARKER = ("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----")
|
||||
_PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----")
|
||||
_PKCS8_SPEC = PrivateKeyInfo()
|
||||
|
||||
|
||||
def _bit_list_to_bytes(bit_list):
|
||||
"""Converts an iterable of 1s and 0s to bytes.
|
||||
|
||||
Combines the list 8 at a time, treating each group of 8 bits
|
||||
as a single byte.
|
||||
|
||||
Args:
|
||||
bit_list (Sequence): Sequence of 1s and 0s.
|
||||
|
||||
Returns:
|
||||
bytes: The decoded bytes.
|
||||
"""
|
||||
num_bits = len(bit_list)
|
||||
byte_vals = bytearray()
|
||||
for start in six.moves.xrange(0, num_bits, 8):
|
||||
curr_bits = bit_list[start : start + 8]
|
||||
char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits))
|
||||
byte_vals.append(char_val)
|
||||
return bytes(byte_vals)
|
||||
|
||||
|
||||
class RSAVerifier(base.Verifier):
|
||||
"""Verifies RSA cryptographic signatures using public keys.
|
||||
|
||||
Args:
|
||||
public_key (rsa.key.PublicKey): The public key used to verify
|
||||
signatures.
|
||||
"""
|
||||
|
||||
def __init__(self, public_key):
|
||||
self._pubkey = public_key
|
||||
|
||||
@_helpers.copy_docstring(base.Verifier)
|
||||
def verify(self, message, signature):
|
||||
message = _helpers.to_bytes(message)
|
||||
try:
|
||||
return rsa.pkcs1.verify(message, signature, self._pubkey)
|
||||
except (ValueError, rsa.pkcs1.VerificationError):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, public_key):
|
||||
"""Construct an Verifier instance from a public key or public
|
||||
certificate string.
|
||||
|
||||
Args:
|
||||
public_key (Union[str, bytes]): The public key in PEM format or the
|
||||
x509 public key certificate.
|
||||
|
||||
Returns:
|
||||
Verifier: The constructed verifier.
|
||||
|
||||
Raises:
|
||||
ValueError: If the public_key can't be parsed.
|
||||
"""
|
||||
public_key = _helpers.to_bytes(public_key)
|
||||
is_x509_cert = _CERTIFICATE_MARKER in public_key
|
||||
|
||||
# If this is a certificate, extract the public key info.
|
||||
if is_x509_cert:
|
||||
der = rsa.pem.load_pem(public_key, "CERTIFICATE")
|
||||
asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
|
||||
if remaining != b"":
|
||||
raise ValueError("Unused bytes", remaining)
|
||||
|
||||
cert_info = asn1_cert["tbsCertificate"]["subjectPublicKeyInfo"]
|
||||
key_bytes = _bit_list_to_bytes(cert_info["subjectPublicKey"])
|
||||
pubkey = rsa.PublicKey.load_pkcs1(key_bytes, "DER")
|
||||
else:
|
||||
pubkey = rsa.PublicKey.load_pkcs1(public_key, "PEM")
|
||||
return cls(pubkey)
|
||||
|
||||
|
||||
class RSASigner(base.Signer, base.FromServiceAccountMixin):
|
||||
"""Signs messages with an RSA private key.
|
||||
|
||||
Args:
|
||||
private_key (rsa.key.PrivateKey): The private key to sign with.
|
||||
key_id (str): Optional key ID used to identify this private key. This
|
||||
can be useful to associate the private key with its associated
|
||||
public key or certificate.
|
||||
"""
|
||||
|
||||
def __init__(self, private_key, key_id=None):
|
||||
self._key = private_key
|
||||
self._key_id = key_id
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def key_id(self):
|
||||
return self._key_id
|
||||
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def sign(self, message):
|
||||
message = _helpers.to_bytes(message)
|
||||
return rsa.pkcs1.sign(message, self._key, "SHA-256")
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, key, key_id=None):
|
||||
"""Construct an Signer instance from a private key in PEM format.
|
||||
|
||||
Args:
|
||||
key (str): Private key in PEM format.
|
||||
key_id (str): An optional key id used to identify the private key.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt.Signer: The constructed signer.
|
||||
|
||||
Raises:
|
||||
ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
|
||||
PEM format.
|
||||
"""
|
||||
key = _helpers.from_bytes(key) # PEM expects str in Python 3
|
||||
marker_id, key_bytes = pem.readPemBlocksFromFile(
|
||||
six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER
|
||||
)
|
||||
|
||||
# Key is in pkcs1 format.
|
||||
if marker_id == 0:
|
||||
private_key = rsa.key.PrivateKey.load_pkcs1(key_bytes, format="DER")
|
||||
# Key is in pkcs8.
|
||||
elif marker_id == 1:
|
||||
key_info, remaining = decoder.decode(key_bytes, asn1Spec=_PKCS8_SPEC)
|
||||
if remaining != b"":
|
||||
raise ValueError("Unused bytes", remaining)
|
||||
private_key_info = key_info.getComponentByName("privateKey")
|
||||
private_key = rsa.key.PrivateKey.load_pkcs1(
|
||||
private_key_info.asOctets(), format="DER"
|
||||
)
|
||||
else:
|
||||
raise ValueError("No key could be detected.")
|
||||
|
||||
return cls(private_key, key_id=key_id)
|
131
venv/Lib/site-packages/google/auth/crypt/base.py
Normal file
131
venv/Lib/site-packages/google/auth/crypt/base.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Base classes for cryptographic signers and verifiers."""
|
||||
|
||||
import abc
|
||||
import io
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
|
||||
_JSON_FILE_PRIVATE_KEY = "private_key"
|
||||
_JSON_FILE_PRIVATE_KEY_ID = "private_key_id"
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Verifier(object):
|
||||
"""Abstract base class for crytographic signature verifiers."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def verify(self, message, signature):
|
||||
"""Verifies a message against a cryptographic signature.
|
||||
|
||||
Args:
|
||||
message (Union[str, bytes]): The message to verify.
|
||||
signature (Union[str, bytes]): The cryptography signature to check.
|
||||
|
||||
Returns:
|
||||
bool: True if message was signed by the private key associated
|
||||
with the public key that this object was constructed with.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc,redundant-returns-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Verify must be implemented")
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Signer(object):
|
||||
"""Abstract base class for cryptographic signers."""
|
||||
|
||||
@abc.abstractproperty
|
||||
def key_id(self):
|
||||
"""Optional[str]: The key ID used to identify this private key."""
|
||||
raise NotImplementedError("Key id must be implemented")
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(self, message):
|
||||
"""Signs a message.
|
||||
|
||||
Args:
|
||||
message (Union[str, bytes]): The message to be signed.
|
||||
|
||||
Returns:
|
||||
bytes: The signature of the message.
|
||||
"""
|
||||
# pylint: disable=missing-raises-doc,redundant-returns-doc
|
||||
# (pylint doesn't recognize that this is abstract)
|
||||
raise NotImplementedError("Sign must be implemented")
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class FromServiceAccountMixin(object):
|
||||
"""Mix-in to enable factory constructors for a Signer."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_string(cls, key, key_id=None):
|
||||
"""Construct an Signer instance from a private key string.
|
||||
|
||||
Args:
|
||||
key (str): Private key as a string.
|
||||
key_id (str): An optional key id used to identify the private key.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt.Signer: The constructed signer.
|
||||
|
||||
Raises:
|
||||
ValueError: If the key cannot be parsed.
|
||||
"""
|
||||
raise NotImplementedError("from_string must be implemented")
|
||||
|
||||
@classmethod
|
||||
def from_service_account_info(cls, info):
|
||||
"""Creates a Signer instance instance from a dictionary containing
|
||||
service account info in Google format.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The service account info in Google
|
||||
format.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt.Signer: The constructed signer.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
if _JSON_FILE_PRIVATE_KEY not in info:
|
||||
raise ValueError(
|
||||
"The private_key field was not found in the service account " "info."
|
||||
)
|
||||
|
||||
return cls.from_string(
|
||||
info[_JSON_FILE_PRIVATE_KEY], info.get(_JSON_FILE_PRIVATE_KEY_ID)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_file(cls, filename):
|
||||
"""Creates a Signer instance from a service account .json file
|
||||
in Google format.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account .json file.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt.Signer: The constructed signer.
|
||||
"""
|
||||
with io.open(filename, "r", encoding="utf-8") as json_file:
|
||||
data = json.load(json_file)
|
||||
|
||||
return cls.from_service_account_info(data)
|
160
venv/Lib/site-packages/google/auth/crypt/es256.py
Normal file
160
venv/Lib/site-packages/google/auth/crypt/es256.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
# Copyright 2017 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library.
|
||||
"""
|
||||
|
||||
from cryptography import utils
|
||||
import cryptography.exceptions
|
||||
from cryptography.hazmat import backends
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
||||
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
|
||||
import cryptography.x509
|
||||
import pkg_resources
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth.crypt import base
|
||||
|
||||
_IMPORT_ERROR_MSG = (
|
||||
"cryptography>=1.4.0 is required to use cryptography-based ECDSA " "algorithms"
|
||||
)
|
||||
|
||||
try: # pragma: NO COVER
|
||||
release = pkg_resources.get_distribution("cryptography").parsed_version
|
||||
if release < pkg_resources.parse_version("1.4.0"):
|
||||
raise ImportError(_IMPORT_ERROR_MSG)
|
||||
except pkg_resources.DistributionNotFound: # pragma: NO COVER
|
||||
raise ImportError(_IMPORT_ERROR_MSG)
|
||||
|
||||
|
||||
_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
|
||||
_BACKEND = backends.default_backend()
|
||||
_PADDING = padding.PKCS1v15()
|
||||
|
||||
|
||||
class ES256Verifier(base.Verifier):
|
||||
"""Verifies ECDSA cryptographic signatures using public keys.
|
||||
|
||||
Args:
|
||||
public_key (
|
||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey):
|
||||
The public key used to verify signatures.
|
||||
"""
|
||||
|
||||
def __init__(self, public_key):
|
||||
self._pubkey = public_key
|
||||
|
||||
@_helpers.copy_docstring(base.Verifier)
|
||||
def verify(self, message, signature):
|
||||
# First convert (r||s) raw signature to ASN1 encoded signature.
|
||||
sig_bytes = _helpers.to_bytes(signature)
|
||||
if len(sig_bytes) != 64:
|
||||
return False
|
||||
r = utils.int_from_bytes(sig_bytes[:32], byteorder="big")
|
||||
s = utils.int_from_bytes(sig_bytes[32:], byteorder="big")
|
||||
asn1_sig = encode_dss_signature(r, s)
|
||||
|
||||
message = _helpers.to_bytes(message)
|
||||
try:
|
||||
self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256()))
|
||||
return True
|
||||
except (ValueError, cryptography.exceptions.InvalidSignature):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, public_key):
|
||||
"""Construct an Verifier instance from a public key or public
|
||||
certificate string.
|
||||
|
||||
Args:
|
||||
public_key (Union[str, bytes]): The public key in PEM format or the
|
||||
x509 public key certificate.
|
||||
|
||||
Returns:
|
||||
Verifier: The constructed verifier.
|
||||
|
||||
Raises:
|
||||
ValueError: If the public key can't be parsed.
|
||||
"""
|
||||
public_key_data = _helpers.to_bytes(public_key)
|
||||
|
||||
if _CERTIFICATE_MARKER in public_key_data:
|
||||
cert = cryptography.x509.load_pem_x509_certificate(
|
||||
public_key_data, _BACKEND
|
||||
)
|
||||
pubkey = cert.public_key()
|
||||
|
||||
else:
|
||||
pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)
|
||||
|
||||
return cls(pubkey)
|
||||
|
||||
|
||||
class ES256Signer(base.Signer, base.FromServiceAccountMixin):
|
||||
"""Signs messages with an ECDSA private key.
|
||||
|
||||
Args:
|
||||
private_key (
|
||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSAPrivateKey):
|
||||
The private key to sign with.
|
||||
key_id (str): Optional key ID used to identify this private key. This
|
||||
can be useful to associate the private key with its associated
|
||||
public key or certificate.
|
||||
"""
|
||||
|
||||
def __init__(self, private_key, key_id=None):
|
||||
self._key = private_key
|
||||
self._key_id = key_id
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def key_id(self):
|
||||
return self._key_id
|
||||
|
||||
@_helpers.copy_docstring(base.Signer)
|
||||
def sign(self, message):
|
||||
message = _helpers.to_bytes(message)
|
||||
asn1_signature = self._key.sign(message, ec.ECDSA(hashes.SHA256()))
|
||||
|
||||
# Convert ASN1 encoded signature to (r||s) raw signature.
|
||||
(r, s) = decode_dss_signature(asn1_signature)
|
||||
return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, key, key_id=None):
|
||||
"""Construct a RSASigner from a private key in PEM format.
|
||||
|
||||
Args:
|
||||
key (Union[bytes, str]): Private key in PEM format.
|
||||
key_id (str): An optional key id used to identify the private key.
|
||||
|
||||
Returns:
|
||||
google.auth.crypt._cryptography_rsa.RSASigner: The
|
||||
constructed signer.
|
||||
|
||||
Raises:
|
||||
ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
|
||||
UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
|
||||
into a UTF-8 ``str``.
|
||||
ValueError: If ``cryptography`` "Could not deserialize key data."
|
||||
"""
|
||||
key = _helpers.to_bytes(key)
|
||||
private_key = serialization.load_pem_private_key(
|
||||
key, password=None, backend=_BACKEND
|
||||
)
|
||||
return cls(private_key, key_id=key_id)
|
30
venv/Lib/site-packages/google/auth/crypt/rsa.py
Normal file
30
venv/Lib/site-packages/google/auth/crypt/rsa.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2017 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""RSA cryptography signer and verifier."""
|
||||
|
||||
|
||||
try:
|
||||
# Prefer cryptograph-based RSA implementation.
|
||||
from google.auth.crypt import _cryptography_rsa
|
||||
|
||||
RSASigner = _cryptography_rsa.RSASigner
|
||||
RSAVerifier = _cryptography_rsa.RSAVerifier
|
||||
except ImportError: # pragma: NO COVER
|
||||
# Fallback to pure-python RSA implementation if cryptography is
|
||||
# unavailable.
|
||||
from google.auth.crypt import _python_rsa
|
||||
|
||||
RSASigner = _python_rsa.RSASigner
|
||||
RSAVerifier = _python_rsa.RSAVerifier
|
61
venv/Lib/site-packages/google/auth/environment_vars.py
Normal file
61
venv/Lib/site-packages/google/auth/environment_vars.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Environment variables used by :mod:`google.auth`."""
|
||||
|
||||
|
||||
PROJECT = "GOOGLE_CLOUD_PROJECT"
|
||||
"""Environment variable defining default project.
|
||||
|
||||
This used by :func:`google.auth.default` to explicitly set a project ID. This
|
||||
environment variable is also used by the Google Cloud Python Library.
|
||||
"""
|
||||
|
||||
LEGACY_PROJECT = "GCLOUD_PROJECT"
|
||||
"""Previously used environment variable defining the default project.
|
||||
|
||||
This environment variable is used instead of the current one in some
|
||||
situations (such as Google App Engine).
|
||||
"""
|
||||
|
||||
CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
"""Environment variable defining the location of Google application default
|
||||
credentials."""
|
||||
|
||||
# The environment variable name which can replace ~/.config if set.
|
||||
CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG"
|
||||
"""Environment variable defines the location of Google Cloud SDK's config
|
||||
files."""
|
||||
|
||||
# These two variables allow for customization of the addresses used when
|
||||
# contacting the GCE metadata service.
|
||||
GCE_METADATA_HOST = "GCE_METADATA_HOST"
|
||||
GCE_METADATA_ROOT = "GCE_METADATA_ROOT"
|
||||
"""Environment variable providing an alternate hostname or host:port to be
|
||||
used for GCE metadata requests.
|
||||
|
||||
This environment variable is originally named GCE_METADATA_ROOT. System will
|
||||
check the new variable first; should there be no value present,
|
||||
the system falls back to the old variable.
|
||||
"""
|
||||
|
||||
GCE_METADATA_IP = "GCE_METADATA_IP"
|
||||
"""Environment variable providing an alternate ip:port to be used for ip-only
|
||||
GCE metadata requests."""
|
||||
|
||||
GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
|
||||
"""Environment variable controlling whether to use client certificate or not.
|
||||
|
||||
The default value is false. Users have to explicitly set this value to true
|
||||
in order to use client certificate to establish a mutual TLS channel."""
|
45
venv/Lib/site-packages/google/auth/exceptions.py
Normal file
45
venv/Lib/site-packages/google/auth/exceptions.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Exceptions used in the google.auth package."""
|
||||
|
||||
|
||||
class GoogleAuthError(Exception):
|
||||
"""Base class for all google.auth errors."""
|
||||
|
||||
|
||||
class TransportError(GoogleAuthError):
|
||||
"""Used to indicate an error occurred during an HTTP request."""
|
||||
|
||||
|
||||
class RefreshError(GoogleAuthError):
|
||||
"""Used to indicate that an refreshing the credentials' access token
|
||||
failed."""
|
||||
|
||||
|
||||
class UserAccessTokenError(GoogleAuthError):
|
||||
"""Used to indicate ``gcloud auth print-access-token`` command failed."""
|
||||
|
||||
|
||||
class DefaultCredentialsError(GoogleAuthError):
|
||||
"""Used to indicate that acquiring default credentials failed."""
|
||||
|
||||
|
||||
class MutualTLSChannelError(GoogleAuthError):
|
||||
"""Used to indicate that mutual TLS channel creation is failed, or mutual
|
||||
TLS channel credentials is missing or invalid."""
|
||||
|
||||
|
||||
class ClientCertError(GoogleAuthError):
|
||||
"""Used to indicate that client certificate is missing or invalid."""
|
100
venv/Lib/site-packages/google/auth/iam.py
Normal file
100
venv/Lib/site-packages/google/auth/iam.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2017 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Tools for using the Google `Cloud Identity and Access Management (IAM)
|
||||
API`_'s auth-related functionality.
|
||||
|
||||
.. _Cloud Identity and Access Management (IAM) API:
|
||||
https://cloud.google.com/iam/docs/
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
from six.moves import http_client
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import crypt
|
||||
from google.auth import exceptions
|
||||
|
||||
_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1"
|
||||
_SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json"
|
||||
|
||||
|
||||
class Signer(crypt.Signer):
|
||||
"""Signs messages using the IAM `signBlob API`_.
|
||||
|
||||
This is useful when you need to sign bytes but do not have access to the
|
||||
credential's private key file.
|
||||
|
||||
.. _signBlob API:
|
||||
https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts
|
||||
/signBlob
|
||||
"""
|
||||
|
||||
def __init__(self, request, credentials, service_account_email):
|
||||
"""
|
||||
Args:
|
||||
request (google.auth.transport.Request): The object used to make
|
||||
HTTP requests.
|
||||
credentials (google.auth.credentials.Credentials): The credentials
|
||||
that will be used to authenticate the request to the IAM API.
|
||||
The credentials must have of one the following scopes:
|
||||
|
||||
- https://www.googleapis.com/auth/iam
|
||||
- https://www.googleapis.com/auth/cloud-platform
|
||||
service_account_email (str): The service account email identifying
|
||||
which service account to use to sign bytes. Often, this can
|
||||
be the same as the service account email in the given
|
||||
credentials.
|
||||
"""
|
||||
self._request = request
|
||||
self._credentials = credentials
|
||||
self._service_account_email = service_account_email
|
||||
|
||||
def _make_signing_request(self, message):
|
||||
"""Makes a request to the API signBlob API."""
|
||||
message = _helpers.to_bytes(message)
|
||||
|
||||
method = "POST"
|
||||
url = _SIGN_BLOB_URI.format(self._service_account_email)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
body = json.dumps(
|
||||
{"payload": base64.b64encode(message).decode("utf-8")}
|
||||
).encode("utf-8")
|
||||
|
||||
self._credentials.before_request(self._request, method, url, headers)
|
||||
response = self._request(url=url, method=method, body=body, headers=headers)
|
||||
|
||||
if response.status != http_client.OK:
|
||||
raise exceptions.TransportError(
|
||||
"Error calling the IAM signBytes API: {}".format(response.data)
|
||||
)
|
||||
|
||||
return json.loads(response.data.decode("utf-8"))
|
||||
|
||||
@property
|
||||
def key_id(self):
|
||||
"""Optional[str]: The key ID used to identify this private key.
|
||||
|
||||
.. warning::
|
||||
This is always ``None``. The key ID used by IAM can not
|
||||
be reliably determined ahead of time.
|
||||
"""
|
||||
return None
|
||||
|
||||
@_helpers.copy_docstring(crypt.Signer)
|
||||
def sign(self, message):
|
||||
response = self._make_signing_request(message)
|
||||
return base64.b64decode(response["signedBlob"])
|
398
venv/Lib/site-packages/google/auth/impersonated_credentials.py
Normal file
398
venv/Lib/site-packages/google/auth/impersonated_credentials.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
# Copyright 2018 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google Cloud Impersonated credentials.
|
||||
|
||||
This module provides authentication for applications where local credentials
|
||||
impersonates a remote service account using `IAM Credentials API`_.
|
||||
|
||||
This class can be used to impersonate a service account as long as the original
|
||||
Credential object has the "Service Account Token Creator" role on the target
|
||||
service account.
|
||||
|
||||
.. _IAM Credentials API:
|
||||
https://cloud.google.com/iam/credentials/reference/rest/
|
||||
"""
|
||||
|
||||
import base64
|
||||
import copy
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import credentials
|
||||
from google.auth import exceptions
|
||||
from google.auth import jwt
|
||||
from google.auth.transport.requests import AuthorizedSession
|
||||
|
||||
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
|
||||
_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"]
|
||||
|
||||
_IAM_ENDPOINT = (
|
||||
"https://iamcredentials.googleapis.com/v1/projects/-"
|
||||
+ "/serviceAccounts/{}:generateAccessToken"
|
||||
)
|
||||
|
||||
_IAM_SIGN_ENDPOINT = (
|
||||
"https://iamcredentials.googleapis.com/v1/projects/-"
|
||||
+ "/serviceAccounts/{}:signBlob"
|
||||
)
|
||||
|
||||
_IAM_IDTOKEN_ENDPOINT = (
|
||||
"https://iamcredentials.googleapis.com/v1/"
|
||||
+ "projects/-/serviceAccounts/{}:generateIdToken"
|
||||
)
|
||||
|
||||
_REFRESH_ERROR = "Unable to acquire impersonated credentials"
|
||||
|
||||
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
|
||||
_DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token"
|
||||
|
||||
|
||||
def _make_iam_token_request(request, principal, headers, body):
|
||||
"""Makes a request to the Google Cloud IAM service for an access token.
|
||||
Args:
|
||||
request (Request): The Request object to use.
|
||||
principal (str): The principal to request an access token for.
|
||||
headers (Mapping[str, str]): Map of headers to transmit.
|
||||
body (Mapping[str, str]): JSON Payload body for the iamcredentials
|
||||
API call.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: Raised if there is an underlying
|
||||
HTTP connection error
|
||||
google.auth.exceptions.RefreshError: Raised if the impersonated
|
||||
credentials are not available. Common reasons are
|
||||
`iamcredentials.googleapis.com` is not enabled or the
|
||||
`Service Account Token Creator` is not assigned
|
||||
"""
|
||||
iam_endpoint = _IAM_ENDPOINT.format(principal)
|
||||
|
||||
body = json.dumps(body).encode("utf-8")
|
||||
|
||||
response = request(url=iam_endpoint, method="POST", headers=headers, body=body)
|
||||
|
||||
# support both string and bytes type response.data
|
||||
response_body = (
|
||||
response.data.decode("utf-8")
|
||||
if hasattr(response.data, "decode")
|
||||
else response.data
|
||||
)
|
||||
|
||||
if response.status != http_client.OK:
|
||||
exceptions.RefreshError(_REFRESH_ERROR, response_body)
|
||||
|
||||
try:
|
||||
token_response = json.loads(response_body)
|
||||
token = token_response["accessToken"]
|
||||
expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
return token, expiry
|
||||
|
||||
except (KeyError, ValueError) as caught_exc:
|
||||
new_exc = exceptions.RefreshError(
|
||||
"{}: No access token or invalid expiration in response.".format(
|
||||
_REFRESH_ERROR
|
||||
),
|
||||
response_body,
|
||||
)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
|
||||
class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing):
|
||||
"""This module defines impersonated credentials which are essentially
|
||||
impersonated identities.
|
||||
|
||||
Impersonated Credentials allows credentials issued to a user or
|
||||
service account to impersonate another. The target service account must
|
||||
grant the originating credential principal the
|
||||
`Service Account Token Creator`_ IAM role:
|
||||
|
||||
For more information about Token Creator IAM role and
|
||||
IAMCredentials API, see
|
||||
`Creating Short-Lived Service Account Credentials`_.
|
||||
|
||||
.. _Service Account Token Creator:
|
||||
https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role
|
||||
|
||||
.. _Creating Short-Lived Service Account Credentials:
|
||||
https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials
|
||||
|
||||
Usage:
|
||||
|
||||
First grant source_credentials the `Service Account Token Creator`
|
||||
role on the target account to impersonate. In this example, the
|
||||
service account represented by svc_account.json has the
|
||||
token creator role on
|
||||
`impersonated-account@_project_.iam.gserviceaccount.com`.
|
||||
|
||||
Enable the IAMCredentials API on the source project:
|
||||
`gcloud services enable iamcredentials.googleapis.com`.
|
||||
|
||||
Initialize a source credential which does not have access to
|
||||
list bucket::
|
||||
|
||||
from google.oauth2 import service_acccount
|
||||
|
||||
target_scopes = [
|
||||
'https://www.googleapis.com/auth/devstorage.read_only']
|
||||
|
||||
source_credentials = (
|
||||
service_account.Credentials.from_service_account_file(
|
||||
'/path/to/svc_account.json',
|
||||
scopes=target_scopes))
|
||||
|
||||
Now use the source credentials to acquire credentials to impersonate
|
||||
another service account::
|
||||
|
||||
from google.auth import impersonated_credentials
|
||||
|
||||
target_credentials = impersonated_credentials.Credentials(
|
||||
source_credentials=source_credentials,
|
||||
target_principal='impersonated-account@_project_.iam.gserviceaccount.com',
|
||||
target_scopes = target_scopes,
|
||||
lifetime=500)
|
||||
|
||||
Resource access is granted::
|
||||
|
||||
client = storage.Client(credentials=target_credentials)
|
||||
buckets = client.list_buckets(project='your_project')
|
||||
for bucket in buckets:
|
||||
print(bucket.name)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_credentials,
|
||||
target_principal,
|
||||
target_scopes,
|
||||
delegates=None,
|
||||
lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
source_credentials (google.auth.Credentials): The source credential
|
||||
used as to acquire the impersonated credentials.
|
||||
target_principal (str): The service account to impersonate.
|
||||
target_scopes (Sequence[str]): Scopes to request during the
|
||||
authorization grant.
|
||||
delegates (Sequence[str]): The chained list of delegates required
|
||||
to grant the final access_token. If set, the sequence of
|
||||
identities must have "Service Account Token Creator" capability
|
||||
granted to the prceeding identity. For example, if set to
|
||||
[serviceAccountB, serviceAccountC], the source_credential
|
||||
must have the Token Creator role on serviceAccountB.
|
||||
serviceAccountB must have the Token Creator on
|
||||
serviceAccountC.
|
||||
Finally, C must have Token Creator on target_principal.
|
||||
If left unset, source_credential must have that role on
|
||||
target_principal.
|
||||
lifetime (int): Number of seconds the delegated credential should
|
||||
be valid for (upto 3600).
|
||||
quota_project_id (Optional[str]): The project ID used for quota and billing.
|
||||
This project may be different from the project used to
|
||||
create the credentials.
|
||||
"""
|
||||
|
||||
super(Credentials, self).__init__()
|
||||
|
||||
self._source_credentials = copy.copy(source_credentials)
|
||||
# Service account source credentials must have the _IAM_SCOPE
|
||||
# added to refresh correctly. User credentials cannot have
|
||||
# their original scopes modified.
|
||||
if isinstance(self._source_credentials, credentials.Scoped):
|
||||
self._source_credentials = self._source_credentials.with_scopes(_IAM_SCOPE)
|
||||
self._target_principal = target_principal
|
||||
self._target_scopes = target_scopes
|
||||
self._delegates = delegates
|
||||
self._lifetime = lifetime
|
||||
self.token = None
|
||||
self.expiry = _helpers.utcnow()
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
self._update_token(request)
|
||||
|
||||
def _update_token(self, request):
|
||||
"""Updates credentials with a new access_token representing
|
||||
the impersonated account.
|
||||
|
||||
Args:
|
||||
request (google.auth.transport.requests.Request): Request object
|
||||
to use for refreshing credentials.
|
||||
"""
|
||||
|
||||
# Refresh our source credentials if it is not valid.
|
||||
if not self._source_credentials.valid:
|
||||
self._source_credentials.refresh(request)
|
||||
|
||||
body = {
|
||||
"delegates": self._delegates,
|
||||
"scope": self._target_scopes,
|
||||
"lifetime": str(self._lifetime) + "s",
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
# Apply the source credentials authentication info.
|
||||
self._source_credentials.apply(headers)
|
||||
|
||||
self.token, self.expiry = _make_iam_token_request(
|
||||
request=request,
|
||||
principal=self._target_principal,
|
||||
headers=headers,
|
||||
body=body,
|
||||
)
|
||||
|
||||
def sign_bytes(self, message):
|
||||
|
||||
iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal)
|
||||
|
||||
body = {
|
||||
"payload": base64.b64encode(message).decode("utf-8"),
|
||||
"delegates": self._delegates,
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
authed_session = AuthorizedSession(self._source_credentials)
|
||||
|
||||
response = authed_session.post(
|
||||
url=iam_sign_endpoint, headers=headers, json=body
|
||||
)
|
||||
|
||||
return base64.b64decode(response.json()["signedBlob"])
|
||||
|
||||
@property
|
||||
def signer_email(self):
|
||||
return self._target_principal
|
||||
|
||||
@property
|
||||
def service_account_email(self):
|
||||
return self._target_principal
|
||||
|
||||
@property
|
||||
def signer(self):
|
||||
return self
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
self._source_credentials,
|
||||
target_principal=self._target_principal,
|
||||
target_scopes=self._target_scopes,
|
||||
delegates=self._delegates,
|
||||
lifetime=self._lifetime,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
|
||||
class IDTokenCredentials(credentials.CredentialsWithQuotaProject):
|
||||
"""Open ID Connect ID Token-based service account credentials.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target_credentials,
|
||||
target_audience=None,
|
||||
include_email=False,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
target_credentials (google.auth.Credentials): The target
|
||||
credential used as to acquire the id tokens for.
|
||||
target_audience (string): Audience to issue the token for.
|
||||
include_email (bool): Include email in IdToken
|
||||
quota_project_id (Optional[str]): The project ID used for
|
||||
quota and billing.
|
||||
"""
|
||||
super(IDTokenCredentials, self).__init__()
|
||||
|
||||
if not isinstance(target_credentials, Credentials):
|
||||
raise exceptions.GoogleAuthError(
|
||||
"Provided Credential must be " "impersonated_credentials"
|
||||
)
|
||||
self._target_credentials = target_credentials
|
||||
self._target_audience = target_audience
|
||||
self._include_email = include_email
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
def from_credentials(self, target_credentials, target_audience=None):
|
||||
return self.__class__(
|
||||
target_credentials=self._target_credentials,
|
||||
target_audience=target_audience,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
def with_target_audience(self, target_audience):
|
||||
return self.__class__(
|
||||
target_credentials=self._target_credentials,
|
||||
target_audience=target_audience,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
def with_include_email(self, include_email):
|
||||
return self.__class__(
|
||||
target_credentials=self._target_credentials,
|
||||
target_audience=self._target_audience,
|
||||
include_email=include_email,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
target_credentials=self._target_credentials,
|
||||
target_audience=self._target_audience,
|
||||
include_email=self._include_email,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(credentials.Credentials)
|
||||
def refresh(self, request):
|
||||
|
||||
iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format(
|
||||
self._target_credentials.signer_email
|
||||
)
|
||||
|
||||
body = {
|
||||
"audience": self._target_audience,
|
||||
"delegates": self._target_credentials._delegates,
|
||||
"includeEmail": self._include_email,
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
authed_session = AuthorizedSession(
|
||||
self._target_credentials._source_credentials, auth_request=request
|
||||
)
|
||||
|
||||
response = authed_session.post(
|
||||
url=iam_sign_endpoint,
|
||||
headers=headers,
|
||||
data=json.dumps(body).encode("utf-8"),
|
||||
)
|
||||
|
||||
id_token = response.json()["token"]
|
||||
self.token = id_token
|
||||
self.expiry = datetime.fromtimestamp(jwt.decode(id_token, verify=False)["exp"])
|
844
venv/Lib/site-packages/google/auth/jwt.py
Normal file
844
venv/Lib/site-packages/google/auth/jwt.py
Normal file
|
@ -0,0 +1,844 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""JSON Web Tokens
|
||||
|
||||
Provides support for creating (encoding) and verifying (decoding) JWTs,
|
||||
especially JWTs generated and consumed by Google infrastructure.
|
||||
|
||||
See `rfc7519`_ for more details on JWTs.
|
||||
|
||||
To encode a JWT use :func:`encode`::
|
||||
|
||||
from google.auth import crypt
|
||||
from google.auth import jwt
|
||||
|
||||
signer = crypt.Signer(private_key)
|
||||
payload = {'some': 'payload'}
|
||||
encoded = jwt.encode(signer, payload)
|
||||
|
||||
To decode a JWT and verify claims use :func:`decode`::
|
||||
|
||||
claims = jwt.decode(encoded, certs=public_certs)
|
||||
|
||||
You can also skip verification::
|
||||
|
||||
claims = jwt.decode(encoded, verify=False)
|
||||
|
||||
.. _rfc7519: https://tools.ietf.org/html/rfc7519
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
from collections.abc import Mapping
|
||||
# Python 2.7 compatibility
|
||||
except ImportError: # pragma: NO COVER
|
||||
from collections import Mapping
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import cachetools
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from google.auth import _helpers
|
||||
from google.auth import _service_account_info
|
||||
from google.auth import crypt
|
||||
from google.auth import exceptions
|
||||
import google.auth.credentials
|
||||
|
||||
try:
|
||||
from google.auth.crypt import es256
|
||||
except ImportError: # pragma: NO COVER
|
||||
es256 = None
|
||||
|
||||
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
||||
_DEFAULT_MAX_CACHE_SIZE = 10
|
||||
_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier}
|
||||
_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"])
|
||||
|
||||
if es256 is not None: # pragma: NO COVER
|
||||
_ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier
|
||||
|
||||
|
||||
def encode(signer, payload, header=None, key_id=None):
|
||||
"""Make a signed JWT.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign the JWT.
|
||||
payload (Mapping[str, str]): The JWT payload.
|
||||
header (Mapping[str, str]): Additional JWT header payload.
|
||||
key_id (str): The key id to add to the JWT header. If the
|
||||
signer has a key id it will be used as the default. If this is
|
||||
specified it will override the signer's key id.
|
||||
|
||||
Returns:
|
||||
bytes: The encoded JWT.
|
||||
"""
|
||||
if header is None:
|
||||
header = {}
|
||||
|
||||
if key_id is None:
|
||||
key_id = signer.key_id
|
||||
|
||||
header.update({"typ": "JWT"})
|
||||
|
||||
if es256 is not None and isinstance(signer, es256.ES256Signer):
|
||||
header.update({"alg": "ES256"})
|
||||
else:
|
||||
header.update({"alg": "RS256"})
|
||||
|
||||
if key_id is not None:
|
||||
header["kid"] = key_id
|
||||
|
||||
segments = [
|
||||
_helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
|
||||
_helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
|
||||
]
|
||||
|
||||
signing_input = b".".join(segments)
|
||||
signature = signer.sign(signing_input)
|
||||
segments.append(_helpers.unpadded_urlsafe_b64encode(signature))
|
||||
|
||||
return b".".join(segments)
|
||||
|
||||
|
||||
def _decode_jwt_segment(encoded_section):
|
||||
"""Decodes a single JWT segment."""
|
||||
section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
|
||||
try:
|
||||
return json.loads(section_bytes.decode("utf-8"))
|
||||
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):
|
||||
"""Decodes a token and does no verification.
|
||||
|
||||
Args:
|
||||
token (Union[str, bytes]): The encoded JWT.
|
||||
|
||||
Returns:
|
||||
Tuple[str, str, str, str]: header, payload, signed_section, and
|
||||
signature.
|
||||
|
||||
Raises:
|
||||
ValueError: if there are an incorrect amount of segments in the token.
|
||||
"""
|
||||
token = _helpers.to_bytes(token)
|
||||
|
||||
if token.count(b".") != 2:
|
||||
raise ValueError("Wrong number of segments in token: {0}".format(token))
|
||||
|
||||
encoded_header, encoded_payload, signature = token.split(b".")
|
||||
signed_section = encoded_header + b"." + encoded_payload
|
||||
signature = _helpers.padded_urlsafe_b64decode(signature)
|
||||
|
||||
# Parse segments
|
||||
header = _decode_jwt_segment(encoded_header)
|
||||
payload = _decode_jwt_segment(encoded_payload)
|
||||
|
||||
return header, payload, signed_section, signature
|
||||
|
||||
|
||||
def decode_header(token):
|
||||
"""Return the decoded header of a token.
|
||||
|
||||
No verification is done. This is useful to extract the key id from
|
||||
the header in order to acquire the appropriate certificate to verify
|
||||
the token.
|
||||
|
||||
Args:
|
||||
token (Union[str, bytes]): the encoded JWT.
|
||||
|
||||
Returns:
|
||||
Mapping: The decoded JWT header.
|
||||
"""
|
||||
header, _, _, _ = _unverified_decode(token)
|
||||
return header
|
||||
|
||||
|
||||
def _verify_iat_and_exp(payload):
|
||||
"""Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
|
||||
payload.
|
||||
|
||||
Args:
|
||||
payload (Mapping[str, str]): The JWT payload.
|
||||
|
||||
Raises:
|
||||
ValueError: if any checks failed.
|
||||
"""
|
||||
now = _helpers.datetime_to_secs(_helpers.utcnow())
|
||||
|
||||
# Make sure the iat and exp claims are present.
|
||||
for key in ("iat", "exp"):
|
||||
if key not in payload:
|
||||
raise ValueError("Token does not contain required claim {}".format(key))
|
||||
|
||||
# Make sure the token wasn't issued in the future.
|
||||
iat = payload["iat"]
|
||||
# Err on the side of accepting a token that is slightly early to account
|
||||
# for clock skew.
|
||||
earliest = iat - _helpers.CLOCK_SKEW_SECS
|
||||
if now < earliest:
|
||||
raise ValueError("Token used too early, {} < {}".format(now, iat))
|
||||
|
||||
# Make sure the token wasn't issued in the past.
|
||||
exp = payload["exp"]
|
||||
# Err on the side of accepting a token that is slightly out of date
|
||||
# to account for clow skew.
|
||||
latest = exp + _helpers.CLOCK_SKEW_SECS
|
||||
if latest < now:
|
||||
raise ValueError("Token expired, {} < {}".format(latest, now))
|
||||
|
||||
|
||||
def decode(token, certs=None, verify=True, audience=None):
|
||||
"""Decode and verify a JWT.
|
||||
|
||||
Args:
|
||||
token (str): The encoded JWT.
|
||||
certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
|
||||
certificate used to validate the JWT signature. If bytes or string,
|
||||
it must the the public key certificate in PEM format. If a mapping,
|
||||
it must be a mapping of key IDs to public key certificates in PEM
|
||||
format. The mapping must contain the same key ID that's specified
|
||||
in the token's header.
|
||||
verify (bool): Whether to perform signature and claim validation.
|
||||
Verification is done by default.
|
||||
audience (str): The audience claim, 'aud', that this JWT should
|
||||
contain. If None then the JWT's 'aud' parameter is not verified.
|
||||
|
||||
Returns:
|
||||
Mapping[str, str]: The deserialized JSON payload in the JWT.
|
||||
|
||||
Raises:
|
||||
ValueError: if any verification checks failed.
|
||||
"""
|
||||
header, payload, signed_section, signature = _unverified_decode(token)
|
||||
|
||||
if not verify:
|
||||
return payload
|
||||
|
||||
# Pluck the key id and algorithm from the header and make sure we have
|
||||
# a verifier that can support it.
|
||||
key_alg = header.get("alg")
|
||||
key_id = header.get("kid")
|
||||
|
||||
try:
|
||||
verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
|
||||
except KeyError as exc:
|
||||
if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
|
||||
six.raise_from(
|
||||
ValueError(
|
||||
"The key algorithm {} requires the cryptography package "
|
||||
"to be installed.".format(key_alg)
|
||||
),
|
||||
exc,
|
||||
)
|
||||
else:
|
||||
six.raise_from(
|
||||
ValueError("Unsupported signature algorithm {}".format(key_alg)), exc
|
||||
)
|
||||
|
||||
# If certs is specified as a dictionary of key IDs to certificates, then
|
||||
# use the certificate identified by the key ID in the token header.
|
||||
if isinstance(certs, Mapping):
|
||||
if key_id:
|
||||
if key_id not in certs:
|
||||
raise ValueError("Certificate for key id {} not found.".format(key_id))
|
||||
certs_to_check = [certs[key_id]]
|
||||
# If there's no key id in the header, check against all of the certs.
|
||||
else:
|
||||
certs_to_check = certs.values()
|
||||
else:
|
||||
certs_to_check = certs
|
||||
|
||||
# Verify that the signature matches the message.
|
||||
if not crypt.verify_signature(
|
||||
signed_section, signature, certs_to_check, verifier_cls
|
||||
):
|
||||
raise ValueError("Could not verify token signature.")
|
||||
|
||||
# Verify the issued at and created times in the payload.
|
||||
_verify_iat_and_exp(payload)
|
||||
|
||||
# Check audience.
|
||||
if audience is not None:
|
||||
claim_audience = payload.get("aud")
|
||||
if audience != claim_audience:
|
||||
raise ValueError(
|
||||
"Token has wrong audience {}, expected {}".format(
|
||||
claim_audience, audience
|
||||
)
|
||||
)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class Credentials(
|
||||
google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
|
||||
):
|
||||
"""Credentials that use a JWT as the bearer token.
|
||||
|
||||
These credentials require an "audience" claim. This claim identifies the
|
||||
intended recipient of the bearer token.
|
||||
|
||||
The constructor arguments determine the claims for the JWT that is
|
||||
sent with requests. Usually, you'll construct these credentials with
|
||||
one of the helper constructors as shown in the next section.
|
||||
|
||||
To create JWT credentials using a Google service account private key
|
||||
JSON file::
|
||||
|
||||
audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
|
||||
credentials = jwt.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
audience=audience)
|
||||
|
||||
If you already have the service account file loaded and parsed::
|
||||
|
||||
service_account_info = json.load(open('service_account.json'))
|
||||
credentials = jwt.Credentials.from_service_account_info(
|
||||
service_account_info,
|
||||
audience=audience)
|
||||
|
||||
Both helper methods pass on arguments to the constructor, so you can
|
||||
specify the JWT claims::
|
||||
|
||||
credentials = jwt.Credentials.from_service_account_file(
|
||||
'service-account.json',
|
||||
audience=audience,
|
||||
additional_claims={'meta': 'data'})
|
||||
|
||||
You can also construct the credentials directly if you have a
|
||||
:class:`~google.auth.crypt.Signer` instance::
|
||||
|
||||
credentials = jwt.Credentials(
|
||||
signer,
|
||||
issuer='your-issuer',
|
||||
subject='your-subject',
|
||||
audience=audience)
|
||||
|
||||
The claims are considered immutable. If you want to modify the claims,
|
||||
you can easily create another instance using :meth:`with_claims`::
|
||||
|
||||
new_audience = (
|
||||
'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
|
||||
new_credentials = credentials.with_claims(audience=new_audience)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
signer,
|
||||
issuer,
|
||||
subject,
|
||||
audience,
|
||||
additional_claims=None,
|
||||
token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
issuer (str): The `iss` claim.
|
||||
subject (str): The `sub` claim.
|
||||
audience (str): the `aud` claim. The intended audience for the
|
||||
credentials.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT payload.
|
||||
token_lifetime (int): The amount of time in seconds for
|
||||
which the token is valid. Defaults to 1 hour.
|
||||
quota_project_id (Optional[str]): The project ID used for quota
|
||||
and billing.
|
||||
"""
|
||||
super(Credentials, self).__init__()
|
||||
self._signer = signer
|
||||
self._issuer = issuer
|
||||
self._subject = subject
|
||||
self._audience = audience
|
||||
self._token_lifetime = token_lifetime
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
if additional_claims is None:
|
||||
additional_claims = {}
|
||||
|
||||
self._additional_claims = additional_claims
|
||||
|
||||
@classmethod
|
||||
def _from_signer_and_info(cls, signer, info, **kwargs):
|
||||
"""Creates a Credentials instance from a signer and service account
|
||||
info.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
info (Mapping[str, str]): The service account info.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
kwargs.setdefault("subject", info["client_email"])
|
||||
kwargs.setdefault("issuer", info["client_email"])
|
||||
return cls(signer, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_info(cls, info, **kwargs):
|
||||
"""Creates an Credentials instance from a dictionary.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The service account info in Google
|
||||
format.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
signer = _service_account_info.from_dict(info, require=["client_email"])
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_file(cls, filename, **kwargs):
|
||||
"""Creates a Credentials instance from a service account .json file
|
||||
in Google format.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account .json file.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: The constructed credentials.
|
||||
"""
|
||||
info, signer = _service_account_info.from_filename(
|
||||
filename, require=["client_email"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_signing_credentials(cls, credentials, audience, **kwargs):
|
||||
"""Creates a new :class:`google.auth.jwt.Credentials` instance from an
|
||||
existing :class:`google.auth.credentials.Signing` instance.
|
||||
|
||||
The new instance will use the same signer as the existing instance and
|
||||
will use the existing instance's signer email as the issuer and
|
||||
subject by default.
|
||||
|
||||
Example::
|
||||
|
||||
svc_creds = service_account.Credentials.from_service_account_file(
|
||||
'service_account.json')
|
||||
audience = (
|
||||
'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
|
||||
jwt_creds = jwt.Credentials.from_signing_credentials(
|
||||
svc_creds, audience=audience)
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Signing): The credentials to
|
||||
use to construct the new credentials.
|
||||
audience (str): the `aud` claim. The intended audience for the
|
||||
credentials.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: A new Credentials instance.
|
||||
"""
|
||||
kwargs.setdefault("issuer", credentials.signer_email)
|
||||
kwargs.setdefault("subject", credentials.signer_email)
|
||||
return cls(credentials.signer, audience=audience, **kwargs)
|
||||
|
||||
def with_claims(
|
||||
self, issuer=None, subject=None, audience=None, additional_claims=None
|
||||
):
|
||||
"""Returns a copy of these credentials with modified claims.
|
||||
|
||||
Args:
|
||||
issuer (str): The `iss` claim. If unspecified the current issuer
|
||||
claim will be used.
|
||||
subject (str): The `sub` claim. If unspecified the current subject
|
||||
claim will be used.
|
||||
audience (str): the `aud` claim. If unspecified the current
|
||||
audience claim will be used.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT payload. This will be merged with the current
|
||||
additional claims.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: A new credentials instance.
|
||||
"""
|
||||
new_additional_claims = copy.deepcopy(self._additional_claims)
|
||||
new_additional_claims.update(additional_claims or {})
|
||||
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
issuer=issuer if issuer is not None else self._issuer,
|
||||
subject=subject if subject is not None else self._subject,
|
||||
audience=audience if audience is not None else self._audience,
|
||||
additional_claims=new_additional_claims,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
issuer=self._issuer,
|
||||
subject=self._subject,
|
||||
audience=self._audience,
|
||||
additional_claims=self._additional_claims,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
def _make_jwt(self):
|
||||
"""Make a signed JWT.
|
||||
|
||||
Returns:
|
||||
Tuple[bytes, datetime]: The encoded JWT and the expiration.
|
||||
"""
|
||||
now = _helpers.utcnow()
|
||||
lifetime = datetime.timedelta(seconds=self._token_lifetime)
|
||||
expiry = now + lifetime
|
||||
|
||||
payload = {
|
||||
"iss": self._issuer,
|
||||
"sub": self._subject,
|
||||
"iat": _helpers.datetime_to_secs(now),
|
||||
"exp": _helpers.datetime_to_secs(expiry),
|
||||
"aud": self._audience,
|
||||
}
|
||||
|
||||
payload.update(self._additional_claims)
|
||||
|
||||
jwt = encode(self._signer, payload)
|
||||
|
||||
return jwt, expiry
|
||||
|
||||
def refresh(self, request):
|
||||
"""Refreshes the access token.
|
||||
|
||||
Args:
|
||||
request (Any): Unused.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
# (pylint doesn't correctly recognize overridden methods.)
|
||||
self.token, self.expiry = self._make_jwt()
|
||||
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def sign_bytes(self, message):
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def signer_email(self):
|
||||
return self._issuer
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
||||
|
||||
|
||||
class OnDemandCredentials(
|
||||
google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
|
||||
):
|
||||
"""On-demand JWT credentials.
|
||||
|
||||
Like :class:`Credentials`, this class uses a JWT as the bearer token for
|
||||
authentication. However, this class does not require the audience at
|
||||
construction time. Instead, it will generate a new token on-demand for
|
||||
each request using the request URI as the audience. It caches tokens
|
||||
so that multiple requests to the same URI do not incur the overhead
|
||||
of generating a new token every time.
|
||||
|
||||
This behavior is especially useful for `gRPC`_ clients. A gRPC service may
|
||||
have multiple audience and gRPC clients may not know all of the audiences
|
||||
required for accessing a particular service. With these credentials,
|
||||
no knowledge of the audiences is required ahead of time.
|
||||
|
||||
.. _grpc: http://www.grpc.io/
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
signer,
|
||||
issuer,
|
||||
subject,
|
||||
additional_claims=None,
|
||||
token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
|
||||
max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
|
||||
quota_project_id=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
issuer (str): The `iss` claim.
|
||||
subject (str): The `sub` claim.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT payload.
|
||||
token_lifetime (int): The amount of time in seconds for
|
||||
which the token is valid. Defaults to 1 hour.
|
||||
max_cache_size (int): The maximum number of JWT tokens to keep in
|
||||
cache. Tokens are cached using :class:`cachetools.LRUCache`.
|
||||
quota_project_id (Optional[str]): The project ID used for quota
|
||||
and billing.
|
||||
|
||||
"""
|
||||
super(OnDemandCredentials, self).__init__()
|
||||
self._signer = signer
|
||||
self._issuer = issuer
|
||||
self._subject = subject
|
||||
self._token_lifetime = token_lifetime
|
||||
self._quota_project_id = quota_project_id
|
||||
|
||||
if additional_claims is None:
|
||||
additional_claims = {}
|
||||
|
||||
self._additional_claims = additional_claims
|
||||
self._cache = cachetools.LRUCache(maxsize=max_cache_size)
|
||||
|
||||
@classmethod
|
||||
def _from_signer_and_info(cls, signer, info, **kwargs):
|
||||
"""Creates an OnDemandCredentials instance from a signer and service
|
||||
account info.
|
||||
|
||||
Args:
|
||||
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
|
||||
info (Mapping[str, str]): The service account info.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.OnDemandCredentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
kwargs.setdefault("subject", info["client_email"])
|
||||
kwargs.setdefault("issuer", info["client_email"])
|
||||
return cls(signer, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_info(cls, info, **kwargs):
|
||||
"""Creates an OnDemandCredentials instance from a dictionary.
|
||||
|
||||
Args:
|
||||
info (Mapping[str, str]): The service account info in Google
|
||||
format.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.OnDemandCredentials: The constructed credentials.
|
||||
|
||||
Raises:
|
||||
ValueError: If the info is not in the expected format.
|
||||
"""
|
||||
signer = _service_account_info.from_dict(info, require=["client_email"])
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_service_account_file(cls, filename, **kwargs):
|
||||
"""Creates an OnDemandCredentials instance from a service account .json
|
||||
file in Google format.
|
||||
|
||||
Args:
|
||||
filename (str): The path to the service account .json file.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.OnDemandCredentials: The constructed credentials.
|
||||
"""
|
||||
info, signer = _service_account_info.from_filename(
|
||||
filename, require=["client_email"]
|
||||
)
|
||||
return cls._from_signer_and_info(signer, info, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_signing_credentials(cls, credentials, **kwargs):
|
||||
"""Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
|
||||
from an existing :class:`google.auth.credentials.Signing` instance.
|
||||
|
||||
The new instance will use the same signer as the existing instance and
|
||||
will use the existing instance's signer email as the issuer and
|
||||
subject by default.
|
||||
|
||||
Example::
|
||||
|
||||
svc_creds = service_account.Credentials.from_service_account_file(
|
||||
'service_account.json')
|
||||
jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
|
||||
svc_creds)
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Signing): The credentials to
|
||||
use to construct the new credentials.
|
||||
kwargs: Additional arguments to pass to the constructor.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.Credentials: A new Credentials instance.
|
||||
"""
|
||||
kwargs.setdefault("issuer", credentials.signer_email)
|
||||
kwargs.setdefault("subject", credentials.signer_email)
|
||||
return cls(credentials.signer, **kwargs)
|
||||
|
||||
def with_claims(self, issuer=None, subject=None, additional_claims=None):
|
||||
"""Returns a copy of these credentials with modified claims.
|
||||
|
||||
Args:
|
||||
issuer (str): The `iss` claim. If unspecified the current issuer
|
||||
claim will be used.
|
||||
subject (str): The `sub` claim. If unspecified the current subject
|
||||
claim will be used.
|
||||
additional_claims (Mapping[str, str]): Any additional claims for
|
||||
the JWT payload. This will be merged with the current
|
||||
additional claims.
|
||||
|
||||
Returns:
|
||||
google.auth.jwt.OnDemandCredentials: A new credentials instance.
|
||||
"""
|
||||
new_additional_claims = copy.deepcopy(self._additional_claims)
|
||||
new_additional_claims.update(additional_claims or {})
|
||||
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
issuer=issuer if issuer is not None else self._issuer,
|
||||
subject=subject if subject is not None else self._subject,
|
||||
additional_claims=new_additional_claims,
|
||||
max_cache_size=self._cache.maxsize,
|
||||
quota_project_id=self._quota_project_id,
|
||||
)
|
||||
|
||||
@_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
|
||||
def with_quota_project(self, quota_project_id):
|
||||
|
||||
return self.__class__(
|
||||
self._signer,
|
||||
issuer=self._issuer,
|
||||
subject=self._subject,
|
||||
additional_claims=self._additional_claims,
|
||||
max_cache_size=self._cache.maxsize,
|
||||
quota_project_id=quota_project_id,
|
||||
)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"""Checks the validity of the credentials.
|
||||
|
||||
These credentials are always valid because it generates tokens on
|
||||
demand.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _make_jwt_for_audience(self, audience):
|
||||
"""Make a new JWT for the given audience.
|
||||
|
||||
Args:
|
||||
audience (str): The intended audience.
|
||||
|
||||
Returns:
|
||||
Tuple[bytes, datetime]: The encoded JWT and the expiration.
|
||||
"""
|
||||
now = _helpers.utcnow()
|
||||
lifetime = datetime.timedelta(seconds=self._token_lifetime)
|
||||
expiry = now + lifetime
|
||||
|
||||
payload = {
|
||||
"iss": self._issuer,
|
||||
"sub": self._subject,
|
||||
"iat": _helpers.datetime_to_secs(now),
|
||||
"exp": _helpers.datetime_to_secs(expiry),
|
||||
"aud": audience,
|
||||
}
|
||||
|
||||
payload.update(self._additional_claims)
|
||||
|
||||
jwt = encode(self._signer, payload)
|
||||
|
||||
return jwt, expiry
|
||||
|
||||
def _get_jwt_for_audience(self, audience):
|
||||
"""Get a JWT For a given audience.
|
||||
|
||||
If there is already an existing, non-expired token in the cache for
|
||||
the audience, that token is used. Otherwise, a new token will be
|
||||
created.
|
||||
|
||||
Args:
|
||||
audience (str): The intended audience.
|
||||
|
||||
Returns:
|
||||
bytes: The encoded JWT.
|
||||
"""
|
||||
token, expiry = self._cache.get(audience, (None, None))
|
||||
|
||||
if token is None or expiry < _helpers.utcnow():
|
||||
token, expiry = self._make_jwt_for_audience(audience)
|
||||
self._cache[audience] = token, expiry
|
||||
|
||||
return token
|
||||
|
||||
def refresh(self, request):
|
||||
"""Raises an exception, these credentials can not be directly
|
||||
refreshed.
|
||||
|
||||
Args:
|
||||
request (Any): Unused.
|
||||
|
||||
Raises:
|
||||
google.auth.RefreshError
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
# (pylint doesn't correctly recognize overridden methods.)
|
||||
raise exceptions.RefreshError(
|
||||
"OnDemandCredentials can not be directly refreshed."
|
||||
)
|
||||
|
||||
def before_request(self, request, method, url, headers):
|
||||
"""Performs credential-specific before request logic.
|
||||
|
||||
Args:
|
||||
request (Any): Unused. JWT credentials do not need to make an
|
||||
HTTP request to refresh.
|
||||
method (str): The request's HTTP method.
|
||||
url (str): The request's URI. This is used as the audience claim
|
||||
when generating the JWT.
|
||||
headers (Mapping): The request's headers.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
# (pylint doesn't correctly recognize overridden methods.)
|
||||
parts = urllib.parse.urlsplit(url)
|
||||
# Strip query string and fragment
|
||||
audience = urllib.parse.urlunsplit(
|
||||
(parts.scheme, parts.netloc, parts.path, "", "")
|
||||
)
|
||||
token = self._get_jwt_for_audience(audience)
|
||||
self.apply(headers, token=token)
|
||||
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def sign_bytes(self, message):
|
||||
return self._signer.sign(message)
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def signer_email(self):
|
||||
return self._issuer
|
||||
|
||||
@property
|
||||
@_helpers.copy_docstring(google.auth.credentials.Signing)
|
||||
def signer(self):
|
||||
return self._signer
|
97
venv/Lib/site-packages/google/auth/transport/__init__.py
Normal file
97
venv/Lib/site-packages/google/auth/transport/__init__.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Transport - HTTP client library support.
|
||||
|
||||
:mod:`google.auth` is designed to work with various HTTP client libraries such
|
||||
as urllib3 and requests. In order to work across these libraries with different
|
||||
interfaces some abstraction is needed.
|
||||
|
||||
This module provides two interfaces that are implemented by transport adapters
|
||||
to support HTTP libraries. :class:`Request` defines the interface expected by
|
||||
:mod:`google.auth` to make requests. :class:`Response` defines the interface
|
||||
for the return value of :class:`Request`.
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,)
|
||||
"""Sequence[int]: Which HTTP status code indicate that credentials should be
|
||||
refreshed and a request should be retried.
|
||||
"""
|
||||
|
||||
DEFAULT_MAX_REFRESH_ATTEMPTS = 2
|
||||
"""int: How many times to refresh the credentials and retry a request."""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Response(object):
|
||||
"""HTTP Response data."""
|
||||
|
||||
@abc.abstractproperty
|
||||
def status(self):
|
||||
"""int: The HTTP status code."""
|
||||
raise NotImplementedError("status must be implemented.")
|
||||
|
||||
@abc.abstractproperty
|
||||
def headers(self):
|
||||
"""Mapping[str, str]: The HTTP response headers."""
|
||||
raise NotImplementedError("headers must be implemented.")
|
||||
|
||||
@abc.abstractproperty
|
||||
def data(self):
|
||||
"""bytes: The response body."""
|
||||
raise NotImplementedError("data must be implemented.")
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Request(object):
|
||||
"""Interface for a callable that makes HTTP requests.
|
||||
|
||||
Specific transport implementations should provide an implementation of
|
||||
this that adapts their specific request / response API.
|
||||
|
||||
.. automethod:: __call__
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(
|
||||
self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
|
||||
):
|
||||
"""Make an HTTP request.
|
||||
|
||||
Args:
|
||||
url (str): The URI to be requested.
|
||||
method (str): The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body (bytes): The payload / body in HTTP request.
|
||||
headers (Mapping[str, str]): Request headers.
|
||||
timeout (Optional[int]): The number of seconds to wait for a
|
||||
response from the server. If not specified or if None, the
|
||||
transport-specific default timeout will be used.
|
||||
kwargs: Additionally arguments passed on to the transport's
|
||||
request method.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: If any exception occurred.
|
||||
"""
|
||||
# pylint: disable=redundant-returns-doc, missing-raises-doc
|
||||
# (pylint doesn't play well with abstract docstrings.)
|
||||
raise NotImplementedError("__call__ must be implemented.")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,384 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Transport adapter for Async HTTP (aiohttp).
|
||||
|
||||
NOTE: This async support is experimental and marked internal. This surface may
|
||||
change in minor releases.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
import aiohttp
|
||||
import six
|
||||
import urllib3
|
||||
|
||||
from google.auth import exceptions
|
||||
from google.auth import transport
|
||||
from google.auth.transport import requests
|
||||
|
||||
# Timeout can be re-defined depending on async requirement. Currently made 60s more than
|
||||
# sync timeout.
|
||||
_DEFAULT_TIMEOUT = 180 # in seconds
|
||||
|
||||
|
||||
class _CombinedResponse(transport.Response):
|
||||
"""
|
||||
In order to more closely resemble the `requests` interface, where a raw
|
||||
and deflated content could be accessed at once, this class lazily reads the
|
||||
stream in `transport.Response` so both return forms can be used.
|
||||
|
||||
The gzip and deflate transfer-encodings are automatically decoded for you
|
||||
because the default parameter for autodecompress into the ClientSession is set
|
||||
to False, and therefore we add this class to act as a wrapper for a user to be
|
||||
able to access both the raw and decoded response bodies - mirroring the sync
|
||||
implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
self._raw_content = None
|
||||
|
||||
def _is_compressed(self):
|
||||
headers = self._response.headers
|
||||
return "Content-Encoding" in headers and (
|
||||
headers["Content-Encoding"] == "gzip"
|
||||
or headers["Content-Encoding"] == "deflate"
|
||||
)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._response.status
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._response.headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._response.content
|
||||
|
||||
async def raw_content(self):
|
||||
if self._raw_content is None:
|
||||
self._raw_content = await self._response.content.read()
|
||||
return self._raw_content
|
||||
|
||||
async def content(self):
|
||||
# Load raw_content if necessary
|
||||
await self.raw_content()
|
||||
if self._is_compressed():
|
||||
decoder = urllib3.response.MultiDecoder(
|
||||
self._response.headers["Content-Encoding"]
|
||||
)
|
||||
decompressed = decoder.decompress(self._raw_content)
|
||||
return decompressed
|
||||
|
||||
return self._raw_content
|
||||
|
||||
|
||||
class _Response(transport.Response):
|
||||
"""
|
||||
Requests transport response adapter.
|
||||
|
||||
Args:
|
||||
response (requests.Response): The raw Requests response.
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._response.status
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._response.headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._response.content
|
||||
|
||||
|
||||
class Request(transport.Request):
|
||||
"""Requests request adapter.
|
||||
|
||||
This class is used internally for making requests using asyncio transports
|
||||
in a consistent way. If you use :class:`AuthorizedSession` you do not need
|
||||
to construct or use this class directly.
|
||||
|
||||
This class can be useful if you want to manually refresh a
|
||||
:class:`~google.auth.credentials.Credentials` instance::
|
||||
|
||||
import google.auth.transport.aiohttp_requests
|
||||
|
||||
request = google.auth.transport.aiohttp_requests.Request()
|
||||
|
||||
credentials.refresh(request)
|
||||
|
||||
Args:
|
||||
session (aiohttp.ClientSession): An instance :class: aiohttp.ClientSession used
|
||||
to make HTTP requests. If not specified, a session will be created.
|
||||
|
||||
.. automethod:: __call__
|
||||
"""
|
||||
|
||||
def __init__(self, session=None):
|
||||
self.session = None
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
url,
|
||||
method="GET",
|
||||
body=None,
|
||||
headers=None,
|
||||
timeout=_DEFAULT_TIMEOUT,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Make an HTTP request using aiohttp.
|
||||
|
||||
Args:
|
||||
url (str): The URL to be requested.
|
||||
method (str): The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body (bytes): The payload / body in HTTP request.
|
||||
headers (Mapping[str, str]): Request headers.
|
||||
timeout (Optional[int]): The number of seconds to wait for a
|
||||
response from the server. If not specified or if None, the
|
||||
requests default timeout will be used.
|
||||
kwargs: Additional arguments passed through to the underlying
|
||||
requests :meth:`~requests.Session.request` method.
|
||||
|
||||
Returns:
|
||||
google.auth.transport.Response: The HTTP response.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: If any exception occurred.
|
||||
"""
|
||||
|
||||
try:
|
||||
if self.session is None: # pragma: NO COVER
|
||||
self.session = aiohttp.ClientSession(
|
||||
auto_decompress=False
|
||||
) # pragma: NO COVER
|
||||
requests._LOGGER.debug("Making request: %s %s", method, url)
|
||||
response = await self.session.request(
|
||||
method, url, data=body, headers=headers, timeout=timeout, **kwargs
|
||||
)
|
||||
return _CombinedResponse(response)
|
||||
|
||||
except aiohttp.ClientError as caught_exc:
|
||||
new_exc = exceptions.TransportError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
except asyncio.TimeoutError as caught_exc:
|
||||
new_exc = exceptions.TransportError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
|
||||
class AuthorizedSession(aiohttp.ClientSession):
|
||||
"""This is an async implementation of the Authorized Session class. We utilize an
|
||||
aiohttp transport instance, and the interface mirrors the google.auth.transport.requests
|
||||
Authorized Session class, except for the change in the transport used in the async use case.
|
||||
|
||||
A Requests Session class with credentials.
|
||||
|
||||
This class is used to perform requests to API endpoints that require
|
||||
authorization::
|
||||
|
||||
from google.auth.transport import aiohttp_requests
|
||||
|
||||
async with aiohttp_requests.AuthorizedSession(credentials) as authed_session:
|
||||
response = await authed_session.request(
|
||||
'GET', 'https://www.googleapis.com/storage/v1/b')
|
||||
|
||||
The underlying :meth:`request` implementation handles adding the
|
||||
credentials' headers to the request and refreshing credentials as needed.
|
||||
|
||||
Args:
|
||||
credentials (google.auth._credentials_async.Credentials): The credentials to
|
||||
add to the request.
|
||||
refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
|
||||
that credentials should be refreshed and the request should be
|
||||
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.
|
||||
auth_request (google.auth.transport.aiohttp_requests.Request):
|
||||
(Optional) An instance of
|
||||
:class:`~google.auth.transport.aiohttp_requests.Request` used when
|
||||
refreshing credentials. If not passed,
|
||||
an instance of :class:`~google.auth.transport.aiohttp_requests.Request`
|
||||
is created.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
credentials,
|
||||
refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
|
||||
max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
|
||||
refresh_timeout=None,
|
||||
auth_request=None,
|
||||
auto_decompress=False,
|
||||
):
|
||||
super(AuthorizedSession, self).__init__()
|
||||
self.credentials = credentials
|
||||
self._refresh_status_codes = refresh_status_codes
|
||||
self._max_refresh_attempts = max_refresh_attempts
|
||||
self._refresh_timeout = refresh_timeout
|
||||
self._is_mtls = False
|
||||
self._auth_request = auth_request
|
||||
self._auth_request_session = None
|
||||
self._loop = asyncio.get_event_loop()
|
||||
self._refresh_lock = asyncio.Lock()
|
||||
self._auto_decompress = auto_decompress
|
||||
|
||||
async def request(
|
||||
self,
|
||||
method,
|
||||
url,
|
||||
data=None,
|
||||
headers=None,
|
||||
max_allowed_time=None,
|
||||
timeout=_DEFAULT_TIMEOUT,
|
||||
auto_decompress=False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
"""Implementation of Authorized Session aiohttp request.
|
||||
|
||||
Args:
|
||||
method: The http request method used (e.g. GET, PUT, DELETE)
|
||||
|
||||
url: The url at which the http request is sent.
|
||||
|
||||
data, headers: These fields parallel the associated data and headers
|
||||
fields of a regular http request. Using the aiohttp client session to
|
||||
send the http request allows us to use this parallel corresponding structure
|
||||
in our Authorized Session class.
|
||||
|
||||
timeout (Optional[Union[float, aiohttp.ClientTimeout]]):
|
||||
The amount of time in seconds to wait for the server response
|
||||
with each individual request.
|
||||
|
||||
Can also be passed as an `aiohttp.ClientTimeout` object.
|
||||
|
||||
max_allowed_time (Optional[float]):
|
||||
If the method runs longer than this, a ``Timeout`` exception is
|
||||
automatically raised. Unlike the ``timeout` parameter, this
|
||||
value applies to the total method execution time, even if
|
||||
multiple requests are made under the hood.
|
||||
|
||||
Mind that it is not guaranteed that the timeout error is raised
|
||||
at ``max_allowed_time`. It might take longer, for example, if
|
||||
an underlying request takes a lot of time, but the request
|
||||
itself does not timeout, e.g. if a large file is being
|
||||
transmitted. The timout error will be raised after such
|
||||
request completes.
|
||||
"""
|
||||
# Headers come in as bytes which isn't expected behavior, the resumable
|
||||
# media libraries in some cases expect a str type for the header values,
|
||||
# but sometimes the operations return these in bytes types.
|
||||
if headers:
|
||||
for key in headers.keys():
|
||||
if type(headers[key]) is bytes:
|
||||
headers[key] = headers[key].decode("utf-8")
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
auto_decompress=self._auto_decompress
|
||||
) as self._auth_request_session:
|
||||
auth_request = Request(self._auth_request_session)
|
||||
self._auth_request = auth_request
|
||||
|
||||
# Use a kwarg for this instead of an attribute to maintain
|
||||
# thread-safety.
|
||||
_credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)
|
||||
# Make a copy of the headers. They will be modified by the credentials
|
||||
# and we want to pass the original headers if we recurse.
|
||||
request_headers = headers.copy() if headers is not None else {}
|
||||
|
||||
# Do not apply the timeout unconditionally in order to not override the
|
||||
# _auth_request's default timeout.
|
||||
auth_request = (
|
||||
self._auth_request
|
||||
if timeout is None
|
||||
else functools.partial(self._auth_request, timeout=timeout)
|
||||
)
|
||||
|
||||
remaining_time = max_allowed_time
|
||||
|
||||
with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard:
|
||||
await self.credentials.before_request(
|
||||
auth_request, method, url, request_headers
|
||||
)
|
||||
|
||||
with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard:
|
||||
response = await super(AuthorizedSession, self).request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
headers=request_headers,
|
||||
timeout=timeout,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
remaining_time = guard.remaining_timeout
|
||||
|
||||
if (
|
||||
response.status in self._refresh_status_codes
|
||||
and _credential_refresh_attempt < self._max_refresh_attempts
|
||||
):
|
||||
|
||||
requests._LOGGER.info(
|
||||
"Refreshing credentials due to a %s response. Attempt %s/%s.",
|
||||
response.status,
|
||||
_credential_refresh_attempt + 1,
|
||||
self._max_refresh_attempts,
|
||||
)
|
||||
|
||||
# Do not apply the timeout unconditionally in order to not override the
|
||||
# _auth_request's default timeout.
|
||||
auth_request = (
|
||||
self._auth_request
|
||||
if timeout is None
|
||||
else functools.partial(self._auth_request, timeout=timeout)
|
||||
)
|
||||
|
||||
with requests.TimeoutGuard(
|
||||
remaining_time, asyncio.TimeoutError
|
||||
) as guard:
|
||||
async with self._refresh_lock:
|
||||
await self._loop.run_in_executor(
|
||||
None, self.credentials.refresh, auth_request
|
||||
)
|
||||
|
||||
remaining_time = guard.remaining_timeout
|
||||
|
||||
return await self.request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
max_allowed_time=remaining_time,
|
||||
timeout=timeout,
|
||||
_credential_refresh_attempt=_credential_refresh_attempt + 1,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return response
|
115
venv/Lib/site-packages/google/auth/transport/_http_client.py
Normal file
115
venv/Lib/site-packages/google/auth/transport/_http_client.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Transport adapter for http.client, for internal use only."""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from google.auth import exceptions
|
||||
from google.auth import transport
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Response(transport.Response):
|
||||
"""http.client transport response adapter.
|
||||
|
||||
Args:
|
||||
response (http.client.HTTPResponse): The raw http client response.
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
self._status = response.status
|
||||
self._headers = {key.lower(): value for key, value in response.getheaders()}
|
||||
self._data = response.read()
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
|
||||
class Request(transport.Request):
|
||||
"""http.client transport request adapter."""
|
||||
|
||||
def __call__(
|
||||
self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
|
||||
):
|
||||
"""Make an HTTP request using http.client.
|
||||
|
||||
Args:
|
||||
url (str): The URI to be requested.
|
||||
method (str): The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body (bytes): The payload / body in HTTP request.
|
||||
headers (Mapping): Request headers.
|
||||
timeout (Optional(int)): The number of seconds to wait for a
|
||||
response from the server. If not specified or if None, the
|
||||
socket global default timeout will be used.
|
||||
kwargs: Additional arguments passed throught to the underlying
|
||||
:meth:`~http.client.HTTPConnection.request` method.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: If any exception occurred.
|
||||
"""
|
||||
# socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client.
|
||||
if timeout is None:
|
||||
timeout = socket._GLOBAL_DEFAULT_TIMEOUT
|
||||
|
||||
# http.client doesn't allow None as the headers argument.
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
# http.client needs the host and path parts specified separately.
|
||||
parts = urllib.parse.urlsplit(url)
|
||||
path = urllib.parse.urlunsplit(
|
||||
("", "", parts.path, parts.query, parts.fragment)
|
||||
)
|
||||
|
||||
if parts.scheme != "http":
|
||||
raise exceptions.TransportError(
|
||||
"http.client transport only supports the http scheme, {}"
|
||||
"was specified".format(parts.scheme)
|
||||
)
|
||||
|
||||
connection = http_client.HTTPConnection(parts.netloc, timeout=timeout)
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Making request: %s %s", method, url)
|
||||
|
||||
connection.request(method, path, body=body, headers=headers, **kwargs)
|
||||
response = connection.getresponse()
|
||||
return Response(response)
|
||||
|
||||
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()
|
250
venv/Lib/site-packages/google/auth/transport/_mtls_helper.py
Normal file
250
venv/Lib/site-packages/google/auth/transport/_mtls_helper.py
Normal file
|
@ -0,0 +1,250 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper functions for getting mTLS cert and key."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from os import path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import exceptions
|
||||
|
||||
CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json"
|
||||
_CERT_PROVIDER_COMMAND = "cert_provider_command"
|
||||
_CERT_REGEX = re.compile(
|
||||
b"-----BEGIN CERTIFICATE-----.+-----END CERTIFICATE-----\r?\n?", re.DOTALL
|
||||
)
|
||||
|
||||
# support various format of key files, e.g.
|
||||
# "-----BEGIN PRIVATE KEY-----...",
|
||||
# "-----BEGIN EC PRIVATE KEY-----...",
|
||||
# "-----BEGIN RSA PRIVATE KEY-----..."
|
||||
# "-----BEGIN ENCRYPTED PRIVATE KEY-----"
|
||||
_KEY_REGEX = re.compile(
|
||||
b"-----BEGIN [A-Z ]*PRIVATE KEY-----.+-----END [A-Z ]*PRIVATE KEY-----\r?\n?",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_PASSPHRASE_REGEX = re.compile(
|
||||
b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def _check_dca_metadata_path(metadata_path):
|
||||
"""Checks for context aware metadata. If it exists, returns the absolute path;
|
||||
otherwise returns None.
|
||||
|
||||
Args:
|
||||
metadata_path (str): context aware metadata path.
|
||||
|
||||
Returns:
|
||||
str: absolute path if exists and None otherwise.
|
||||
"""
|
||||
metadata_path = path.expanduser(metadata_path)
|
||||
if not path.exists(metadata_path):
|
||||
_LOGGER.debug("%s is not found, skip client SSL authentication.", metadata_path)
|
||||
return None
|
||||
return metadata_path
|
||||
|
||||
|
||||
def _read_dca_metadata_file(metadata_path):
|
||||
"""Loads context aware metadata from the given path.
|
||||
|
||||
Args:
|
||||
metadata_path (str): context aware metadata path.
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: The metadata.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.ClientCertError: If failed to parse metadata as JSON.
|
||||
"""
|
||||
try:
|
||||
with open(metadata_path) as f:
|
||||
metadata = json.load(f)
|
||||
except ValueError as caught_exc:
|
||||
new_exc = exceptions.ClientCertError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def _run_cert_provider_command(command, expect_encrypted_key=False):
|
||||
"""Run the provided command, and return client side mTLS cert, key and
|
||||
passphrase.
|
||||
|
||||
Args:
|
||||
command (List[str]): cert provider command.
|
||||
expect_encrypted_key (bool): If encrypted private key is expected.
|
||||
|
||||
Returns:
|
||||
Tuple[bytes, bytes, bytes]: client certificate bytes in PEM format, key
|
||||
bytes in PEM format and passphrase bytes.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.ClientCertError: if problems occurs when running
|
||||
the cert provider command or generating cert, key and passphrase.
|
||||
"""
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = process.communicate()
|
||||
except OSError as caught_exc:
|
||||
new_exc = exceptions.ClientCertError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
# Check cert provider command execution error.
|
||||
if process.returncode != 0:
|
||||
raise exceptions.ClientCertError(
|
||||
"Cert provider command returns non-zero status code %s" % process.returncode
|
||||
)
|
||||
|
||||
# Extract certificate (chain), key and passphrase.
|
||||
cert_match = re.findall(_CERT_REGEX, stdout)
|
||||
if len(cert_match) != 1:
|
||||
raise exceptions.ClientCertError("Client SSL certificate is missing or invalid")
|
||||
key_match = re.findall(_KEY_REGEX, stdout)
|
||||
if len(key_match) != 1:
|
||||
raise exceptions.ClientCertError("Client SSL key is missing or invalid")
|
||||
passphrase_match = re.findall(_PASSPHRASE_REGEX, stdout)
|
||||
|
||||
if expect_encrypted_key:
|
||||
if len(passphrase_match) != 1:
|
||||
raise exceptions.ClientCertError("Passphrase is missing or invalid")
|
||||
if b"ENCRYPTED" not in key_match[0]:
|
||||
raise exceptions.ClientCertError("Encrypted private key is expected")
|
||||
return cert_match[0], key_match[0], passphrase_match[0].strip()
|
||||
|
||||
if b"ENCRYPTED" in key_match[0]:
|
||||
raise exceptions.ClientCertError("Encrypted private key is not expected")
|
||||
if len(passphrase_match) > 0:
|
||||
raise exceptions.ClientCertError("Passphrase is not expected")
|
||||
return cert_match[0], key_match[0], None
|
||||
|
||||
|
||||
def get_client_ssl_credentials(generate_encrypted_key=False):
|
||||
"""Returns the client side certificate, private key and passphrase.
|
||||
|
||||
Args:
|
||||
generate_encrypted_key (bool): If set to True, encrypted private key
|
||||
and passphrase will be generated; otherwise, unencrypted private key
|
||||
will be generated and passphrase will be None.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, bytes, bytes, bytes]:
|
||||
A boolean indicating if cert, key and passphrase are obtained, the
|
||||
cert bytes and key bytes both in PEM format, and passphrase bytes.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.ClientCertError: if problems occurs when getting
|
||||
the cert, key and passphrase.
|
||||
"""
|
||||
metadata_path = _check_dca_metadata_path(CONTEXT_AWARE_METADATA_PATH)
|
||||
|
||||
if metadata_path:
|
||||
metadata_json = _read_dca_metadata_file(metadata_path)
|
||||
|
||||
if _CERT_PROVIDER_COMMAND not in metadata_json:
|
||||
raise exceptions.ClientCertError("Cert provider command is not found")
|
||||
|
||||
command = metadata_json[_CERT_PROVIDER_COMMAND]
|
||||
|
||||
if generate_encrypted_key and "--with_passphrase" not in command:
|
||||
command.append("--with_passphrase")
|
||||
|
||||
# Execute the command.
|
||||
cert, key, passphrase = _run_cert_provider_command(
|
||||
command, expect_encrypted_key=generate_encrypted_key
|
||||
)
|
||||
return True, cert, key, passphrase
|
||||
|
||||
return False, None, None, None
|
||||
|
||||
|
||||
def get_client_cert_and_key(client_cert_callback=None):
|
||||
"""Returns the client side certificate and private key. The function first
|
||||
tries to get certificate and key from client_cert_callback; if the callback
|
||||
is None or doesn't provide certificate and key, the function tries application
|
||||
default SSL credentials.
|
||||
|
||||
Args:
|
||||
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An
|
||||
optional callback which returns client certificate bytes and private
|
||||
key bytes both in PEM format.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, bytes, bytes]:
|
||||
A boolean indicating if cert and key are obtained, the cert bytes
|
||||
and key bytes both in PEM format.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.ClientCertError: if problems occurs when getting
|
||||
the cert and key.
|
||||
"""
|
||||
if client_cert_callback:
|
||||
cert, key = client_cert_callback()
|
||||
return True, cert, key
|
||||
|
||||
has_cert, cert, key, _ = get_client_ssl_credentials(generate_encrypted_key=False)
|
||||
return has_cert, cert, key
|
||||
|
||||
|
||||
def decrypt_private_key(key, passphrase):
|
||||
"""A helper function to decrypt the private key with the given passphrase.
|
||||
google-auth library doesn't support passphrase protected private key for
|
||||
mutual TLS channel. This helper function can be used to decrypt the
|
||||
passphrase protected private key in order to estalish mutual TLS channel.
|
||||
|
||||
For example, if you have a function which produces client cert, passphrase
|
||||
protected private key and passphrase, you can convert it to a client cert
|
||||
callback function accepted by google-auth::
|
||||
|
||||
from google.auth.transport import _mtls_helper
|
||||
|
||||
def your_client_cert_function():
|
||||
return cert, encrypted_key, passphrase
|
||||
|
||||
# callback accepted by google-auth for mutual TLS channel.
|
||||
def client_cert_callback():
|
||||
cert, encrypted_key, passphrase = your_client_cert_function()
|
||||
decrypted_key = _mtls_helper.decrypt_private_key(encrypted_key,
|
||||
passphrase)
|
||||
return cert, decrypted_key
|
||||
|
||||
Args:
|
||||
key (bytes): The private key bytes in PEM format.
|
||||
passphrase (bytes): The passphrase bytes.
|
||||
|
||||
Returns:
|
||||
bytes: The decrypted private key in PEM format.
|
||||
|
||||
Raises:
|
||||
ImportError: If pyOpenSSL is not installed.
|
||||
OpenSSL.crypto.Error: If there is any problem decrypting the private key.
|
||||
"""
|
||||
from OpenSSL import crypto
|
||||
|
||||
# First convert encrypted_key_bytes to PKey object
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key, passphrase=passphrase)
|
||||
|
||||
# Then dump the decrypted key bytes
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
|
334
venv/Lib/site-packages/google/auth/transport/grpc.py
Normal file
334
venv/Lib/site-packages/google/auth/transport/grpc.py
Normal file
|
@ -0,0 +1,334 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Authorization support for gRPC."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
from google.auth.transport import _mtls_helper
|
||||
|
||||
try:
|
||||
import grpc
|
||||
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,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthMetadataPlugin(grpc.AuthMetadataPlugin):
|
||||
"""A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
|
||||
request.
|
||||
|
||||
.. _gRPC AuthMetadataPlugin:
|
||||
http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
add to requests.
|
||||
request (google.auth.transport.Request): A HTTP transport request
|
||||
object used to refresh credentials as needed.
|
||||
"""
|
||||
|
||||
def __init__(self, credentials, request):
|
||||
# pylint: disable=no-value-for-parameter
|
||||
# pylint doesn't realize that the super method takes no arguments
|
||||
# because this class is the same name as the superclass.
|
||||
super(AuthMetadataPlugin, self).__init__()
|
||||
self._credentials = credentials
|
||||
self._request = request
|
||||
|
||||
def _get_authorization_headers(self, context):
|
||||
"""Gets the authorization headers for a request.
|
||||
|
||||
Returns:
|
||||
Sequence[Tuple[str, str]]: A list of request headers (key, value)
|
||||
to add to the request.
|
||||
"""
|
||||
headers = {}
|
||||
self._credentials.before_request(
|
||||
self._request, context.method_name, context.service_url, headers
|
||||
)
|
||||
|
||||
return list(six.iteritems(headers))
|
||||
|
||||
def __call__(self, context, callback):
|
||||
"""Passes authorization metadata into the given callback.
|
||||
|
||||
Args:
|
||||
context (grpc.AuthMetadataContext): The RPC context.
|
||||
callback (grpc.AuthMetadataPluginCallback): The callback that will
|
||||
be invoked to pass in the authorization metadata.
|
||||
"""
|
||||
callback(self._get_authorization_headers(context), None)
|
||||
|
||||
|
||||
def secure_authorized_channel(
|
||||
credentials,
|
||||
request,
|
||||
target,
|
||||
ssl_credentials=None,
|
||||
client_cert_callback=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Creates a secure authorized gRPC channel.
|
||||
|
||||
This creates a channel with SSL and :class:`AuthMetadataPlugin`. This
|
||||
channel can be used to create a stub that can make authorized requests.
|
||||
Users can configure client certificate or rely on device certificates to
|
||||
establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
variable is explicitly set to `true`.
|
||||
|
||||
Example::
|
||||
|
||||
import google.auth
|
||||
import google.auth.transport.grpc
|
||||
import google.auth.transport.requests
|
||||
from google.cloud.speech.v1 import cloud_speech_pb2
|
||||
|
||||
# Get credentials.
|
||||
credentials, _ = google.auth.default()
|
||||
|
||||
# Get an HTTP request function to refresh credentials.
|
||||
request = google.auth.transport.requests.Request()
|
||||
|
||||
# Create a channel.
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, regular_endpoint, request,
|
||||
ssl_credentials=grpc.ssl_channel_credentials())
|
||||
|
||||
# Use the channel to create a stub.
|
||||
cloud_speech.create_Speech_stub(channel)
|
||||
|
||||
Usage:
|
||||
|
||||
There are actually a couple of options to create a channel, depending on if
|
||||
you want to create a regular or mutual TLS channel.
|
||||
|
||||
First let's list the endpoints (regular vs mutual TLS) to choose from::
|
||||
|
||||
regular_endpoint = 'speech.googleapis.com:443'
|
||||
mtls_endpoint = 'speech.mtls.googleapis.com:443'
|
||||
|
||||
Option 1: create a regular (non-mutual) TLS channel by explicitly setting
|
||||
the ssl_credentials::
|
||||
|
||||
regular_ssl_credentials = grpc.ssl_channel_credentials()
|
||||
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, regular_endpoint, request,
|
||||
ssl_credentials=regular_ssl_credentials)
|
||||
|
||||
Option 2: create a mutual TLS channel by calling a callback which returns
|
||||
the client side certificate and the key (Note that
|
||||
`GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
|
||||
set to `true`)::
|
||||
|
||||
def my_client_cert_callback():
|
||||
code_to_load_client_cert_and_key()
|
||||
if loaded:
|
||||
return (pem_cert_bytes, pem_key_bytes)
|
||||
raise MyClientCertFailureException()
|
||||
|
||||
try:
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, mtls_endpoint, request,
|
||||
client_cert_callback=my_client_cert_callback)
|
||||
except MyClientCertFailureException:
|
||||
# handle the exception
|
||||
|
||||
Option 3: use application default SSL credentials. It searches and uses
|
||||
the command in a context aware metadata file, which is available on devices
|
||||
with endpoint verification support (Note that
|
||||
`GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
|
||||
set to `true`).
|
||||
See https://cloud.google.com/endpoint-verification/docs/overview::
|
||||
|
||||
try:
|
||||
default_ssl_credentials = SslCredentials()
|
||||
except:
|
||||
# Exception can be raised if the context aware metadata is malformed.
|
||||
# See :class:`SslCredentials` for the possible exceptions.
|
||||
|
||||
# Choose the endpoint based on the SSL credentials type.
|
||||
if default_ssl_credentials.is_mtls:
|
||||
endpoint_to_use = mtls_endpoint
|
||||
else:
|
||||
endpoint_to_use = regular_endpoint
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, endpoint_to_use, request,
|
||||
ssl_credentials=default_ssl_credentials)
|
||||
|
||||
Option 4: not setting ssl_credentials and client_cert_callback. For devices
|
||||
without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
environment variable is not `true`, a regular TLS channel is created;
|
||||
otherwise, a mutual TLS channel is created, however, the call should be
|
||||
wrapped in a try/except block in case of malformed context aware metadata.
|
||||
|
||||
The following code uses regular_endpoint, it works the same no matter the
|
||||
created channle is regular or mutual TLS. Regular endpoint ignores client
|
||||
certificate and key::
|
||||
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, regular_endpoint, request)
|
||||
|
||||
The following code uses mtls_endpoint, if the created channle is regular,
|
||||
and API mtls_endpoint is confgured to require client SSL credentials, API
|
||||
calls using this channel will be rejected::
|
||||
|
||||
channel = google.auth.transport.grpc.secure_authorized_channel(
|
||||
credentials, mtls_endpoint, request)
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
add to requests.
|
||||
request (google.auth.transport.Request): A HTTP transport request
|
||||
object used to refresh credentials as needed. Even though gRPC
|
||||
is a separate transport, there's no way to refresh the credentials
|
||||
without using a standard http transport.
|
||||
target (str): The host and port of the service.
|
||||
ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
|
||||
credentials. This can be used to specify different certificates.
|
||||
This argument is mutually exclusive with client_cert_callback;
|
||||
providing both will raise an exception.
|
||||
If ssl_credentials and client_cert_callback are None, application
|
||||
default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
environment variable is explicitly set to `true`, otherwise one way TLS
|
||||
SSL credentials are used.
|
||||
client_cert_callback (Callable[[], (bytes, bytes)]): Optional
|
||||
callback function to obtain client certicate and key for mutual TLS
|
||||
connection. This argument is mutually exclusive with
|
||||
ssl_credentials; providing both will raise an exception.
|
||||
This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
environment variable is explicitly set to `true`.
|
||||
kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.
|
||||
|
||||
Returns:
|
||||
grpc.Channel: The created gRPC channel.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
|
||||
creation failed for any reason.
|
||||
"""
|
||||
# Create the metadata plugin for inserting the authorization header.
|
||||
metadata_plugin = AuthMetadataPlugin(credentials, request)
|
||||
|
||||
# Create a set of grpc.CallCredentials using the metadata plugin.
|
||||
google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
|
||||
|
||||
if ssl_credentials and client_cert_callback:
|
||||
raise ValueError(
|
||||
"Received both ssl_credentials and client_cert_callback; "
|
||||
"these are mutually exclusive."
|
||||
)
|
||||
|
||||
# If SSL credentials are not explicitly set, try client_cert_callback and ADC.
|
||||
if not ssl_credentials:
|
||||
use_client_cert = os.getenv(
|
||||
environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
|
||||
)
|
||||
if use_client_cert == "true" and client_cert_callback:
|
||||
# Use the callback if provided.
|
||||
cert, key = client_cert_callback()
|
||||
ssl_credentials = grpc.ssl_channel_credentials(
|
||||
certificate_chain=cert, private_key=key
|
||||
)
|
||||
elif use_client_cert == "true":
|
||||
# Use application default SSL credentials.
|
||||
adc_ssl_credentils = SslCredentials()
|
||||
ssl_credentials = adc_ssl_credentils.ssl_credentials
|
||||
else:
|
||||
ssl_credentials = grpc.ssl_channel_credentials()
|
||||
|
||||
# Combine the ssl credentials and the authorization credentials.
|
||||
composite_credentials = grpc.composite_channel_credentials(
|
||||
ssl_credentials, google_auth_credentials
|
||||
)
|
||||
|
||||
return grpc.secure_channel(target, composite_credentials, **kwargs)
|
||||
|
||||
|
||||
class SslCredentials:
|
||||
"""Class for application default SSL credentials.
|
||||
|
||||
The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment
|
||||
variable whose default value is `false`. Client certificate will not be used
|
||||
unless the environment variable is explicitly set to `true`. See
|
||||
https://google.aip.dev/auth/4114
|
||||
|
||||
If the environment variable is `true`, then for devices with endpoint verification
|
||||
support, a device certificate will be automatically loaded and mutual TLS will
|
||||
be established.
|
||||
See https://cloud.google.com/endpoint-verification/docs/overview.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
use_client_cert = os.getenv(
|
||||
environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
|
||||
)
|
||||
if use_client_cert != "true":
|
||||
self._is_mtls = False
|
||||
else:
|
||||
# Load client SSL credentials.
|
||||
metadata_path = _mtls_helper._check_dca_metadata_path(
|
||||
_mtls_helper.CONTEXT_AWARE_METADATA_PATH
|
||||
)
|
||||
self._is_mtls = metadata_path is not None
|
||||
|
||||
@property
|
||||
def ssl_credentials(self):
|
||||
"""Get the created SSL channel credentials.
|
||||
|
||||
For devices with endpoint verification support, if the device certificate
|
||||
loading has any problems, corresponding exceptions will be raised. For
|
||||
a device without endpoint verification support, no exceptions will be
|
||||
raised.
|
||||
|
||||
Returns:
|
||||
grpc.ChannelCredentials: The created grpc channel credentials.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
|
||||
creation failed for any reason.
|
||||
"""
|
||||
if self._is_mtls:
|
||||
try:
|
||||
_, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
|
||||
self._ssl_credentials = grpc.ssl_channel_credentials(
|
||||
certificate_chain=cert, private_key=key
|
||||
)
|
||||
except exceptions.ClientCertError as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
else:
|
||||
self._ssl_credentials = grpc.ssl_channel_credentials()
|
||||
|
||||
return self._ssl_credentials
|
||||
|
||||
@property
|
||||
def is_mtls(self):
|
||||
"""Indicates if the created SSL channel credentials is mutual TLS."""
|
||||
return self._is_mtls
|
105
venv/Lib/site-packages/google/auth/transport/mtls.py
Normal file
105
venv/Lib/site-packages/google/auth/transport/mtls.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utilites for mutual TLS."""
|
||||
|
||||
import six
|
||||
|
||||
from google.auth import exceptions
|
||||
from google.auth.transport import _mtls_helper
|
||||
|
||||
|
||||
def has_default_client_cert_source():
|
||||
"""Check if default client SSL credentials exists on the device.
|
||||
|
||||
Returns:
|
||||
bool: indicating if the default client cert source exists.
|
||||
"""
|
||||
metadata_path = _mtls_helper._check_dca_metadata_path(
|
||||
_mtls_helper.CONTEXT_AWARE_METADATA_PATH
|
||||
)
|
||||
return metadata_path is not None
|
||||
|
||||
|
||||
def default_client_cert_source():
|
||||
"""Get a callback which returns the default client SSL credentials.
|
||||
|
||||
Returns:
|
||||
Callable[[], [bytes, bytes]]: A callback which returns the default
|
||||
client certificate bytes and private key bytes, both in PEM format.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.DefaultClientCertSourceError: If the default
|
||||
client SSL credentials don't exist or are malformed.
|
||||
"""
|
||||
if not has_default_client_cert_source():
|
||||
raise exceptions.MutualTLSChannelError(
|
||||
"Default client cert source doesn't exist"
|
||||
)
|
||||
|
||||
def callback():
|
||||
try:
|
||||
_, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key()
|
||||
except (OSError, RuntimeError, ValueError) as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
return cert_bytes, key_bytes
|
||||
|
||||
return callback
|
||||
|
||||
|
||||
def default_client_encrypted_cert_source(cert_path, key_path):
|
||||
"""Get a callback which returns the default encrpyted client SSL credentials.
|
||||
|
||||
Args:
|
||||
cert_path (str): The cert file path. The default client certificate will
|
||||
be written to this file when the returned callback is called.
|
||||
key_path (str): The key file path. The default encrypted client key will
|
||||
be written to this file when the returned callback is called.
|
||||
|
||||
Returns:
|
||||
Callable[[], [str, str, bytes]]: A callback which generates the default
|
||||
client certificate, encrpyted private key and passphrase. It writes
|
||||
the certificate and private key into the cert_path and key_path, and
|
||||
returns the cert_path, key_path and passphrase bytes.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.DefaultClientCertSourceError: If any problem
|
||||
occurs when loading or saving the client certificate and key.
|
||||
"""
|
||||
if not has_default_client_cert_source():
|
||||
raise exceptions.MutualTLSChannelError(
|
||||
"Default client encrypted cert source doesn't exist"
|
||||
)
|
||||
|
||||
def callback():
|
||||
try:
|
||||
(
|
||||
_,
|
||||
cert_bytes,
|
||||
key_bytes,
|
||||
passphrase_bytes,
|
||||
) = _mtls_helper.get_client_ssl_credentials(generate_encrypted_key=True)
|
||||
with open(cert_path, "wb") as cert_file:
|
||||
cert_file.write(cert_bytes)
|
||||
with open(key_path, "wb") as key_file:
|
||||
key_file.write(key_bytes)
|
||||
except (exceptions.ClientCertError, OSError) as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
return cert_path, key_path, passphrase_bytes
|
||||
|
||||
return callback
|
521
venv/Lib/site-packages/google/auth/transport/requests.py
Normal file
521
venv/Lib/site-packages/google/auth/transport/requests.py
Normal file
|
@ -0,0 +1,521 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Transport adapter for Requests."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import numbers
|
||||
import os
|
||||
import time
|
||||
|
||||
try:
|
||||
import requests
|
||||
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
|
||||
from requests.packages.urllib3.util.ssl_ import (
|
||||
create_urllib3_context,
|
||||
) # pylint: disable=ungrouped-imports
|
||||
import six # pylint: disable=ungrouped-imports
|
||||
|
||||
from google.auth import environment_vars
|
||||
from google.auth import exceptions
|
||||
from google.auth import transport
|
||||
import google.auth.transport._mtls_helper
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_DEFAULT_TIMEOUT = 120 # in seconds
|
||||
|
||||
|
||||
class _Response(transport.Response):
|
||||
"""Requests transport response adapter.
|
||||
|
||||
Args:
|
||||
response (requests.Response): The raw Requests response.
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._response.status_code
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._response.headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._response.content
|
||||
|
||||
|
||||
class TimeoutGuard(object):
|
||||
"""A context manager raising an error if the suite execution took too long.
|
||||
|
||||
Args:
|
||||
timeout ([Union[None, float, Tuple[float, float]]]):
|
||||
The maximum number of seconds a suite can run without the context
|
||||
manager raising a timeout exception on exit. If passed as a tuple,
|
||||
the smaller of the values is taken as a timeout. If ``None``, a
|
||||
timeout error is never raised.
|
||||
timeout_error_type (Optional[Exception]):
|
||||
The type of the error to raise on timeout. Defaults to
|
||||
:class:`requests.exceptions.Timeout`.
|
||||
"""
|
||||
|
||||
def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout):
|
||||
self._timeout = timeout
|
||||
self.remaining_timeout = timeout
|
||||
self._timeout_error_type = timeout_error_type
|
||||
|
||||
def __enter__(self):
|
||||
self._start = time.time()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if exc_value:
|
||||
return # let the error bubble up automatically
|
||||
|
||||
if self._timeout is None:
|
||||
return # nothing to do, the timeout was not specified
|
||||
|
||||
elapsed = time.time() - self._start
|
||||
deadline_hit = False
|
||||
|
||||
if isinstance(self._timeout, numbers.Number):
|
||||
self.remaining_timeout = self._timeout - elapsed
|
||||
deadline_hit = self.remaining_timeout <= 0
|
||||
else:
|
||||
self.remaining_timeout = tuple(x - elapsed for x in self._timeout)
|
||||
deadline_hit = min(self.remaining_timeout) <= 0
|
||||
|
||||
if deadline_hit:
|
||||
raise self._timeout_error_type()
|
||||
|
||||
|
||||
class Request(transport.Request):
|
||||
"""Requests request adapter.
|
||||
|
||||
This class is used internally for making requests using various transports
|
||||
in a consistent way. If you use :class:`AuthorizedSession` you do not need
|
||||
to construct or use this class directly.
|
||||
|
||||
This class can be useful if you want to manually refresh a
|
||||
:class:`~google.auth.credentials.Credentials` instance::
|
||||
|
||||
import google.auth.transport.requests
|
||||
import requests
|
||||
|
||||
request = google.auth.transport.requests.Request()
|
||||
|
||||
credentials.refresh(request)
|
||||
|
||||
Args:
|
||||
session (requests.Session): An instance :class:`requests.Session` used
|
||||
to make HTTP requests. If not specified, a session will be created.
|
||||
|
||||
.. automethod:: __call__
|
||||
"""
|
||||
|
||||
def __init__(self, session=None):
|
||||
if not session:
|
||||
session = requests.Session()
|
||||
|
||||
self.session = session
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
url,
|
||||
method="GET",
|
||||
body=None,
|
||||
headers=None,
|
||||
timeout=_DEFAULT_TIMEOUT,
|
||||
**kwargs
|
||||
):
|
||||
"""Make an HTTP request using requests.
|
||||
|
||||
Args:
|
||||
url (str): The URI to be requested.
|
||||
method (str): The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body (bytes): The payload / body in HTTP request.
|
||||
headers (Mapping[str, str]): Request headers.
|
||||
timeout (Optional[int]): The number of seconds to wait for a
|
||||
response from the server. If not specified or if None, the
|
||||
requests default timeout will be used.
|
||||
kwargs: Additional arguments passed through to the underlying
|
||||
requests :meth:`~requests.Session.request` method.
|
||||
|
||||
Returns:
|
||||
google.auth.transport.Response: The HTTP response.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: If any exception occurred.
|
||||
"""
|
||||
try:
|
||||
_LOGGER.debug("Making request: %s %s", method, url)
|
||||
response = self.session.request(
|
||||
method, url, data=body, headers=headers, timeout=timeout, **kwargs
|
||||
)
|
||||
return _Response(response)
|
||||
except requests.exceptions.RequestException as caught_exc:
|
||||
new_exc = exceptions.TransportError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
|
||||
class _MutualTlsAdapter(requests.adapters.HTTPAdapter):
|
||||
"""
|
||||
A TransportAdapter that enables mutual TLS.
|
||||
|
||||
Args:
|
||||
cert (bytes): client certificate in PEM format
|
||||
key (bytes): client private key in PEM format
|
||||
|
||||
Raises:
|
||||
ImportError: if certifi or pyOpenSSL is not installed
|
||||
OpenSSL.crypto.Error: if client cert or key is invalid
|
||||
"""
|
||||
|
||||
def __init__(self, cert, key):
|
||||
import certifi
|
||||
from OpenSSL import crypto
|
||||
import urllib3.contrib.pyopenssl
|
||||
|
||||
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
||||
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
|
||||
ctx_poolmanager = create_urllib3_context()
|
||||
ctx_poolmanager.load_verify_locations(cafile=certifi.where())
|
||||
ctx_poolmanager._ctx.use_certificate(x509)
|
||||
ctx_poolmanager._ctx.use_privatekey(pkey)
|
||||
self._ctx_poolmanager = ctx_poolmanager
|
||||
|
||||
ctx_proxymanager = create_urllib3_context()
|
||||
ctx_proxymanager.load_verify_locations(cafile=certifi.where())
|
||||
ctx_proxymanager._ctx.use_certificate(x509)
|
||||
ctx_proxymanager._ctx.use_privatekey(pkey)
|
||||
self._ctx_proxymanager = ctx_proxymanager
|
||||
|
||||
super(_MutualTlsAdapter, self).__init__()
|
||||
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs["ssl_context"] = self._ctx_poolmanager
|
||||
super(_MutualTlsAdapter, self).init_poolmanager(*args, **kwargs)
|
||||
|
||||
def proxy_manager_for(self, *args, **kwargs):
|
||||
kwargs["ssl_context"] = self._ctx_proxymanager
|
||||
return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs)
|
||||
|
||||
|
||||
class AuthorizedSession(requests.Session):
|
||||
"""A Requests Session class with credentials.
|
||||
|
||||
This class is used to perform requests to API endpoints that require
|
||||
authorization::
|
||||
|
||||
from google.auth.transport.requests import AuthorizedSession
|
||||
|
||||
authed_session = AuthorizedSession(credentials)
|
||||
|
||||
response = authed_session.request(
|
||||
'GET', 'https://www.googleapis.com/storage/v1/b')
|
||||
|
||||
The underlying :meth:`request` implementation handles adding the
|
||||
credentials' headers to the request and refreshing credentials as needed.
|
||||
|
||||
This class also supports mutual TLS via :meth:`configure_mtls_channel`
|
||||
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
environment variable must be explicitly set to `true`, otherwise it does
|
||||
nothing. Assume the environment is set to `true`, the method behaves in the
|
||||
following manner:
|
||||
If client_cert_callback is provided, client certificate and private
|
||||
key are loaded using the callback; if client_cert_callback is None,
|
||||
application default SSL credentials will be used. Exceptions are raised if
|
||||
there are problems with the certificate, private key, or the loading process,
|
||||
so it should be called within a try/except block.
|
||||
|
||||
First we set the environment variable to `true`, then create an :class:`AuthorizedSession`
|
||||
instance and specify the endpoints::
|
||||
|
||||
regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics'
|
||||
mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics'
|
||||
|
||||
authed_session = AuthorizedSession(credentials)
|
||||
|
||||
Now we can pass a callback to :meth:`configure_mtls_channel`::
|
||||
|
||||
def my_cert_callback():
|
||||
# some code to load client cert bytes and private key bytes, both in
|
||||
# PEM format.
|
||||
some_code_to_load_client_cert_and_key()
|
||||
if loaded:
|
||||
return cert, key
|
||||
raise MyClientCertFailureException()
|
||||
|
||||
# Always call configure_mtls_channel within a try/except block.
|
||||
try:
|
||||
authed_session.configure_mtls_channel(my_cert_callback)
|
||||
except:
|
||||
# handle exceptions.
|
||||
|
||||
if authed_session.is_mtls:
|
||||
response = authed_session.request('GET', mtls_endpoint)
|
||||
else:
|
||||
response = authed_session.request('GET', regular_endpoint)
|
||||
|
||||
You can alternatively use application default SSL credentials like this::
|
||||
|
||||
try:
|
||||
authed_session.configure_mtls_channel()
|
||||
except:
|
||||
# handle exceptions.
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
add to the request.
|
||||
refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
|
||||
that credentials should be refreshed and the request should be
|
||||
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.
|
||||
auth_request (google.auth.transport.requests.Request):
|
||||
(Optional) An instance of
|
||||
:class:`~google.auth.transport.requests.Request` used when
|
||||
refreshing credentials. If not passed,
|
||||
an instance of :class:`~google.auth.transport.requests.Request`
|
||||
is created.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
credentials,
|
||||
refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
|
||||
max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
|
||||
refresh_timeout=None,
|
||||
auth_request=None,
|
||||
):
|
||||
super(AuthorizedSession, self).__init__()
|
||||
self.credentials = credentials
|
||||
self._refresh_status_codes = refresh_status_codes
|
||||
self._max_refresh_attempts = max_refresh_attempts
|
||||
self._refresh_timeout = refresh_timeout
|
||||
self._is_mtls = False
|
||||
|
||||
if auth_request is None:
|
||||
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)
|
||||
|
||||
# Do not pass `self` as the session here, as it can lead to
|
||||
# infinite recursion.
|
||||
auth_request = Request(auth_request_session)
|
||||
|
||||
# Request instance used by internal methods (for example,
|
||||
# credentials.refresh).
|
||||
self._auth_request = auth_request
|
||||
|
||||
def configure_mtls_channel(self, client_cert_callback=None):
|
||||
"""Configure the client certificate and key for SSL connection.
|
||||
|
||||
The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is
|
||||
explicitly set to `true`. In this case if client certificate and key are
|
||||
successfully obtained (from the given client_cert_callback or from application
|
||||
default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted
|
||||
to "https://" prefix.
|
||||
|
||||
Args:
|
||||
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
|
||||
The optional callback returns the client certificate and private
|
||||
key bytes both in PEM format.
|
||||
If the callback is None, application default SSL credentials
|
||||
will be used.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
|
||||
creation failed for any reason.
|
||||
"""
|
||||
use_client_cert = os.getenv(
|
||||
environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
|
||||
)
|
||||
if use_client_cert != "true":
|
||||
self._is_mtls = False
|
||||
return
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
try:
|
||||
(
|
||||
self._is_mtls,
|
||||
cert,
|
||||
key,
|
||||
) = google.auth.transport._mtls_helper.get_client_cert_and_key(
|
||||
client_cert_callback
|
||||
)
|
||||
|
||||
if self._is_mtls:
|
||||
mtls_adapter = _MutualTlsAdapter(cert, key)
|
||||
self.mount("https://", mtls_adapter)
|
||||
except (
|
||||
exceptions.ClientCertError,
|
||||
ImportError,
|
||||
OpenSSL.crypto.Error,
|
||||
) as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
def request(
|
||||
self,
|
||||
method,
|
||||
url,
|
||||
data=None,
|
||||
headers=None,
|
||||
max_allowed_time=None,
|
||||
timeout=_DEFAULT_TIMEOUT,
|
||||
**kwargs
|
||||
):
|
||||
"""Implementation of Requests' request.
|
||||
|
||||
Args:
|
||||
timeout (Optional[Union[float, Tuple[float, float]]]):
|
||||
The amount of time in seconds to wait for the server response
|
||||
with each individual request.
|
||||
|
||||
Can also be passed as a tuple (connect_timeout, read_timeout).
|
||||
See :meth:`requests.Session.request` documentation for details.
|
||||
|
||||
max_allowed_time (Optional[float]):
|
||||
If the method runs longer than this, a ``Timeout`` exception is
|
||||
automatically raised. Unlike the ``timeout` parameter, this
|
||||
value applies to the total method execution time, even if
|
||||
multiple requests are made under the hood.
|
||||
|
||||
Mind that it is not guaranteed that the timeout error is raised
|
||||
at ``max_allowed_time`. It might take longer, for example, if
|
||||
an underlying request takes a lot of time, but the request
|
||||
itself does not timeout, e.g. if a large file is being
|
||||
transmitted. The timout error will be raised after such
|
||||
request completes.
|
||||
"""
|
||||
# pylint: disable=arguments-differ
|
||||
# Requests has a ton of arguments to request, but only two
|
||||
# (method, url) are required. We pass through all of the other
|
||||
# arguments to super, so no need to exhaustively list them here.
|
||||
|
||||
# Use a kwarg for this instead of an attribute to maintain
|
||||
# thread-safety.
|
||||
_credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)
|
||||
|
||||
# Make a copy of the headers. They will be modified by the credentials
|
||||
# and we want to pass the original headers if we recurse.
|
||||
request_headers = headers.copy() if headers is not None else {}
|
||||
|
||||
# Do not apply the timeout unconditionally in order to not override the
|
||||
# _auth_request's default timeout.
|
||||
auth_request = (
|
||||
self._auth_request
|
||||
if timeout is None
|
||||
else functools.partial(self._auth_request, timeout=timeout)
|
||||
)
|
||||
|
||||
remaining_time = max_allowed_time
|
||||
|
||||
with TimeoutGuard(remaining_time) as guard:
|
||||
self.credentials.before_request(auth_request, method, url, request_headers)
|
||||
remaining_time = guard.remaining_timeout
|
||||
|
||||
with TimeoutGuard(remaining_time) as guard:
|
||||
response = super(AuthorizedSession, self).request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
headers=request_headers,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
)
|
||||
remaining_time = guard.remaining_timeout
|
||||
|
||||
# If the response indicated that the credentials needed to be
|
||||
# refreshed, then refresh the credentials and re-attempt the
|
||||
# request.
|
||||
# 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.
|
||||
if (
|
||||
response.status_code in self._refresh_status_codes
|
||||
and _credential_refresh_attempt < self._max_refresh_attempts
|
||||
):
|
||||
|
||||
_LOGGER.info(
|
||||
"Refreshing credentials due to a %s response. Attempt %s/%s.",
|
||||
response.status_code,
|
||||
_credential_refresh_attempt + 1,
|
||||
self._max_refresh_attempts,
|
||||
)
|
||||
|
||||
# Do not apply the timeout unconditionally in order to not override the
|
||||
# _auth_request's default timeout.
|
||||
auth_request = (
|
||||
self._auth_request
|
||||
if timeout is None
|
||||
else functools.partial(self._auth_request, timeout=timeout)
|
||||
)
|
||||
|
||||
with TimeoutGuard(remaining_time) as guard:
|
||||
self.credentials.refresh(auth_request)
|
||||
remaining_time = guard.remaining_timeout
|
||||
|
||||
# Recurse. Pass in the original headers, not our modified set, but
|
||||
# do pass the adjusted max allowed time (i.e. the remaining total time).
|
||||
return self.request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
max_allowed_time=remaining_time,
|
||||
timeout=timeout,
|
||||
_credential_refresh_attempt=_credential_refresh_attempt + 1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@property
|
||||
def is_mtls(self):
|
||||
"""Indicates if the created SSL channel is mutual TLS."""
|
||||
return self._is_mtls
|
426
venv/Lib/site-packages/google/auth/transport/urllib3.py
Normal file
426
venv/Lib/site-packages/google/auth/transport/urllib3.py
Normal file
|
@ -0,0 +1,426 @@
|
|||
# Copyright 2016 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Transport adapter for urllib3."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
# Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle
|
||||
# to verify HTTPS requests, and certifi is the recommended and most reliable
|
||||
# way to get a root certificate bundle. See
|
||||
# http://urllib3.readthedocs.io/en/latest/user-guide.html\
|
||||
# #certificate-verification
|
||||
# For more details.
|
||||
try:
|
||||
import certifi
|
||||
except ImportError: # pragma: NO COVER
|
||||
certifi = None
|
||||
|
||||
try:
|
||||
import urllib3
|
||||
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 environment_vars
|
||||
from google.auth import exceptions
|
||||
from google.auth import transport
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _Response(transport.Response):
|
||||
"""urllib3 transport response adapter.
|
||||
|
||||
Args:
|
||||
response (urllib3.response.HTTPResponse): The raw urllib3 response.
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
self._response = response
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._response.status
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return self._response.headers
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._response.data
|
||||
|
||||
|
||||
class Request(transport.Request):
|
||||
"""urllib3 request adapter.
|
||||
|
||||
This class is used internally for making requests using various transports
|
||||
in a consistent way. If you use :class:`AuthorizedHttp` you do not need
|
||||
to construct or use this class directly.
|
||||
|
||||
This class can be useful if you want to manually refresh a
|
||||
:class:`~google.auth.credentials.Credentials` instance::
|
||||
|
||||
import google.auth.transport.urllib3
|
||||
import urllib3
|
||||
|
||||
http = urllib3.PoolManager()
|
||||
request = google.auth.transport.urllib3.Request(http)
|
||||
|
||||
credentials.refresh(request)
|
||||
|
||||
Args:
|
||||
http (urllib3.request.RequestMethods): An instance of any urllib3
|
||||
class that implements :class:`~urllib3.request.RequestMethods`,
|
||||
usually :class:`urllib3.PoolManager`.
|
||||
|
||||
.. automethod:: __call__
|
||||
"""
|
||||
|
||||
def __init__(self, http):
|
||||
self.http = http
|
||||
|
||||
def __call__(
|
||||
self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
|
||||
):
|
||||
"""Make an HTTP request using urllib3.
|
||||
|
||||
Args:
|
||||
url (str): The URI to be requested.
|
||||
method (str): The HTTP method to use for the request. Defaults
|
||||
to 'GET'.
|
||||
body (bytes): The payload / body in HTTP request.
|
||||
headers (Mapping[str, str]): Request headers.
|
||||
timeout (Optional[int]): The number of seconds to wait for a
|
||||
response from the server. If not specified or if None, the
|
||||
urllib3 default timeout will be used.
|
||||
kwargs: Additional arguments passed throught to the underlying
|
||||
urllib3 :meth:`urlopen` method.
|
||||
|
||||
Returns:
|
||||
google.auth.transport.Response: The HTTP response.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.TransportError: If any exception occurred.
|
||||
"""
|
||||
# urllib3 uses a sentinel default value for timeout, so only set it if
|
||||
# specified.
|
||||
if timeout is not None:
|
||||
kwargs["timeout"] = timeout
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Making request: %s %s", method, url)
|
||||
response = self.http.request(
|
||||
method, url, body=body, headers=headers, **kwargs
|
||||
)
|
||||
return _Response(response)
|
||||
except urllib3.exceptions.HTTPError as caught_exc:
|
||||
new_exc = exceptions.TransportError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
|
||||
def _make_default_http():
|
||||
if certifi is not None:
|
||||
return urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where())
|
||||
else:
|
||||
return urllib3.PoolManager()
|
||||
|
||||
|
||||
def _make_mutual_tls_http(cert, key):
|
||||
"""Create a mutual TLS HTTP connection with the given client cert and key.
|
||||
See https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415
|
||||
|
||||
Args:
|
||||
cert (bytes): client certificate in PEM format
|
||||
key (bytes): client private key in PEM format
|
||||
|
||||
Returns:
|
||||
urllib3.PoolManager: Mutual TLS HTTP connection.
|
||||
|
||||
Raises:
|
||||
ImportError: If certifi or pyOpenSSL is not installed.
|
||||
OpenSSL.crypto.Error: If the cert or key is invalid.
|
||||
"""
|
||||
import certifi
|
||||
from OpenSSL import crypto
|
||||
import urllib3.contrib.pyopenssl
|
||||
|
||||
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
||||
ctx = urllib3.util.ssl_.create_urllib3_context()
|
||||
ctx.load_verify_locations(cafile=certifi.where())
|
||||
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
|
||||
ctx._ctx.use_certificate(x509)
|
||||
ctx._ctx.use_privatekey(pkey)
|
||||
|
||||
http = urllib3.PoolManager(ssl_context=ctx)
|
||||
return http
|
||||
|
||||
|
||||
class AuthorizedHttp(urllib3.request.RequestMethods):
|
||||
"""A urllib3 HTTP class with credentials.
|
||||
|
||||
This class is used to perform requests to API endpoints that require
|
||||
authorization::
|
||||
|
||||
from google.auth.transport.urllib3 import AuthorizedHttp
|
||||
|
||||
authed_http = AuthorizedHttp(credentials)
|
||||
|
||||
response = authed_http.request(
|
||||
'GET', 'https://www.googleapis.com/storage/v1/b')
|
||||
|
||||
This class implements :class:`urllib3.request.RequestMethods` and can be
|
||||
used just like any other :class:`urllib3.PoolManager`.
|
||||
|
||||
The underlying :meth:`urlopen` implementation handles adding the
|
||||
credentials' headers to the request and refreshing credentials as needed.
|
||||
|
||||
This class also supports mutual TLS via :meth:`configure_mtls_channel`
|
||||
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
|
||||
environment variable must be explicitly set to `true`, otherwise it does
|
||||
nothing. Assume the environment is set to `true`, the method behaves in the
|
||||
following manner:
|
||||
If client_cert_callback is provided, client certificate and private
|
||||
key are loaded using the callback; if client_cert_callback is None,
|
||||
application default SSL credentials will be used. Exceptions are raised if
|
||||
there are problems with the certificate, private key, or the loading process,
|
||||
so it should be called within a try/except block.
|
||||
|
||||
First we set the environment variable to `true`, then create an :class:`AuthorizedHttp`
|
||||
instance and specify the endpoints::
|
||||
|
||||
regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics'
|
||||
mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics'
|
||||
|
||||
authed_http = AuthorizedHttp(credentials)
|
||||
|
||||
Now we can pass a callback to :meth:`configure_mtls_channel`::
|
||||
|
||||
def my_cert_callback():
|
||||
# some code to load client cert bytes and private key bytes, both in
|
||||
# PEM format.
|
||||
some_code_to_load_client_cert_and_key()
|
||||
if loaded:
|
||||
return cert, key
|
||||
raise MyClientCertFailureException()
|
||||
|
||||
# Always call configure_mtls_channel within a try/except block.
|
||||
try:
|
||||
is_mtls = authed_http.configure_mtls_channel(my_cert_callback)
|
||||
except:
|
||||
# handle exceptions.
|
||||
|
||||
if is_mtls:
|
||||
response = authed_http.request('GET', mtls_endpoint)
|
||||
else:
|
||||
response = authed_http.request('GET', regular_endpoint)
|
||||
|
||||
You can alternatively use application default SSL credentials like this::
|
||||
|
||||
try:
|
||||
is_mtls = authed_http.configure_mtls_channel()
|
||||
except:
|
||||
# handle exceptions.
|
||||
|
||||
Args:
|
||||
credentials (google.auth.credentials.Credentials): The credentials to
|
||||
add to the request.
|
||||
http (urllib3.PoolManager): The underlying HTTP object to
|
||||
use to make requests. If not specified, a
|
||||
:class:`urllib3.PoolManager` instance will be constructed with
|
||||
sane defaults.
|
||||
refresh_status_codes (Sequence[int]): Which HTTP status codes indicate
|
||||
that credentials should be refreshed and the request should be
|
||||
retried.
|
||||
max_refresh_attempts (int): The maximum number of times to attempt to
|
||||
refresh the credentials and retry the request.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
credentials,
|
||||
http=None,
|
||||
refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
|
||||
max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
|
||||
):
|
||||
if http is None:
|
||||
self.http = _make_default_http()
|
||||
self._has_user_provided_http = False
|
||||
else:
|
||||
self.http = http
|
||||
self._has_user_provided_http = True
|
||||
|
||||
self.credentials = credentials
|
||||
self._refresh_status_codes = refresh_status_codes
|
||||
self._max_refresh_attempts = max_refresh_attempts
|
||||
# Request instance used by internal methods (for example,
|
||||
# credentials.refresh).
|
||||
self._request = Request(self.http)
|
||||
|
||||
super(AuthorizedHttp, self).__init__()
|
||||
|
||||
def configure_mtls_channel(self, client_cert_callback=None):
|
||||
"""Configures mutual TLS channel using the given client_cert_callback or
|
||||
application default SSL credentials. The behavior is controlled by
|
||||
`GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable.
|
||||
(1) If the environment variable value is `true`, the function returns True
|
||||
if the channel is mutual TLS and False otherwise. The `http` provided
|
||||
in the constructor will be overwritten.
|
||||
(2) If the environment variable is not set or `false`, the function does
|
||||
nothing and it always return False.
|
||||
|
||||
Args:
|
||||
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
|
||||
The optional callback returns the client certificate and private
|
||||
key bytes both in PEM format.
|
||||
If the callback is None, application default SSL credentials
|
||||
will be used.
|
||||
|
||||
Returns:
|
||||
True if the channel is mutual TLS and False otherwise.
|
||||
|
||||
Raises:
|
||||
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
|
||||
creation failed for any reason.
|
||||
"""
|
||||
use_client_cert = os.getenv(
|
||||
environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
|
||||
)
|
||||
if use_client_cert != "true":
|
||||
return False
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
try:
|
||||
found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key(
|
||||
client_cert_callback
|
||||
)
|
||||
|
||||
if found_cert_key:
|
||||
self.http = _make_mutual_tls_http(cert, key)
|
||||
else:
|
||||
self.http = _make_default_http()
|
||||
except (
|
||||
exceptions.ClientCertError,
|
||||
ImportError,
|
||||
OpenSSL.crypto.Error,
|
||||
) as caught_exc:
|
||||
new_exc = exceptions.MutualTLSChannelError(caught_exc)
|
||||
six.raise_from(new_exc, caught_exc)
|
||||
|
||||
if self._has_user_provided_http:
|
||||
self._has_user_provided_http = False
|
||||
warnings.warn(
|
||||
"`http` provided in the constructor is overwritten", UserWarning
|
||||
)
|
||||
|
||||
return found_cert_key
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None, **kwargs):
|
||||
"""Implementation of urllib3's urlopen."""
|
||||
# pylint: disable=arguments-differ
|
||||
# We use kwargs to collect additional args that we don't need to
|
||||
# introspect here. However, we do explicitly collect the two
|
||||
# positional arguments.
|
||||
|
||||
# Use a kwarg for this instead of an attribute to maintain
|
||||
# thread-safety.
|
||||
_credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)
|
||||
|
||||
if headers is None:
|
||||
headers = self.headers
|
||||
|
||||
# Make a copy of the headers. They will be modified by the credentials
|
||||
# and we want to pass the original headers if we recurse.
|
||||
request_headers = headers.copy()
|
||||
|
||||
self.credentials.before_request(self._request, method, url, request_headers)
|
||||
|
||||
response = self.http.urlopen(
|
||||
method, url, body=body, headers=request_headers, **kwargs
|
||||
)
|
||||
|
||||
# If the response indicated that the credentials needed to be
|
||||
# refreshed, then refresh the credentials and re-attempt the
|
||||
# request.
|
||||
# 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.
|
||||
# The reason urllib3's retries aren't used is because they
|
||||
# don't allow you to modify the request headers. :/
|
||||
if (
|
||||
response.status in self._refresh_status_codes
|
||||
and _credential_refresh_attempt < self._max_refresh_attempts
|
||||
):
|
||||
|
||||
_LOGGER.info(
|
||||
"Refreshing credentials due to a %s response. Attempt %s/%s.",
|
||||
response.status,
|
||||
_credential_refresh_attempt + 1,
|
||||
self._max_refresh_attempts,
|
||||
)
|
||||
|
||||
self.credentials.refresh(self._request)
|
||||
|
||||
# Recurse. Pass in the original headers, not our modified set.
|
||||
return self.urlopen(
|
||||
method,
|
||||
url,
|
||||
body=body,
|
||||
headers=headers,
|
||||
_credential_refresh_attempt=_credential_refresh_attempt + 1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# Proxy methods for compliance with the urllib3.PoolManager interface
|
||||
|
||||
def __enter__(self):
|
||||
"""Proxy to ``self.http``."""
|
||||
return self.http.__enter__()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Proxy to ``self.http``."""
|
||||
return self.http.__exit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Proxy to ``self.http``."""
|
||||
return self.http.headers
|
||||
|
||||
@headers.setter
|
||||
def headers(self, value):
|
||||
"""Proxy to ``self.http``."""
|
||||
self.http.headers = value
|
Loading…
Add table
Add a link
Reference in a new issue