MNghia commited on
Commit
808fec0
·
verified ·
1 Parent(s): 486b039

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.10/site-packages/cachetools/__pycache__/__init__.cpython-310.pyc +0 -0
  2. lib/python3.10/site-packages/cachetools/__pycache__/_decorators.cpython-310.pyc +0 -0
  3. lib/python3.10/site-packages/cachetools/__pycache__/func.cpython-310.pyc +0 -0
  4. lib/python3.10/site-packages/cachetools/__pycache__/keys.cpython-310.pyc +0 -0
  5. lib/python3.10/site-packages/comm-0.2.3.dist-info/licenses/LICENSE +29 -0
  6. lib/python3.10/site-packages/filelock/__pycache__/__init__.cpython-310.pyc +0 -0
  7. lib/python3.10/site-packages/filelock/__pycache__/_api.cpython-310.pyc +0 -0
  8. lib/python3.10/site-packages/filelock/__pycache__/_error.cpython-310.pyc +0 -0
  9. lib/python3.10/site-packages/filelock/__pycache__/_soft.cpython-310.pyc +0 -0
  10. lib/python3.10/site-packages/filelock/__pycache__/_unix.cpython-310.pyc +0 -0
  11. lib/python3.10/site-packages/filelock/__pycache__/_util.cpython-310.pyc +0 -0
  12. lib/python3.10/site-packages/filelock/__pycache__/_windows.cpython-310.pyc +0 -0
  13. lib/python3.10/site-packages/filelock/__pycache__/asyncio.cpython-310.pyc +0 -0
  14. lib/python3.10/site-packages/filelock/__pycache__/version.cpython-310.pyc +0 -0
  15. lib/python3.10/site-packages/google/auth/__init__.py +53 -0
  16. lib/python3.10/site-packages/google/auth/_cloud_sdk.py +153 -0
  17. lib/python3.10/site-packages/google/auth/_credentials_async.py +171 -0
  18. lib/python3.10/site-packages/google/auth/_credentials_base.py +75 -0
  19. lib/python3.10/site-packages/google/auth/_default.py +685 -0
  20. lib/python3.10/site-packages/google/auth/_default_async.py +282 -0
  21. lib/python3.10/site-packages/google/auth/_exponential_backoff.py +164 -0
  22. lib/python3.10/site-packages/google/auth/_helpers.py +513 -0
  23. lib/python3.10/site-packages/google/auth/_jwt_async.py +164 -0
  24. lib/python3.10/site-packages/google/auth/_oauth2client.py +167 -0
  25. lib/python3.10/site-packages/google/auth/_refresh_worker.py +109 -0
  26. lib/python3.10/site-packages/google/auth/_service_account_info.py +80 -0
  27. lib/python3.10/site-packages/google/auth/aio/__init__.py +25 -0
  28. lib/python3.10/site-packages/google/auth/aio/__pycache__/__init__.cpython-310.pyc +0 -0
  29. lib/python3.10/site-packages/google/auth/aio/__pycache__/_helpers.cpython-310.pyc +0 -0
  30. lib/python3.10/site-packages/google/auth/aio/__pycache__/credentials.cpython-310.pyc +0 -0
  31. lib/python3.10/site-packages/google/auth/aio/_helpers.py +62 -0
  32. lib/python3.10/site-packages/google/auth/aio/credentials.py +143 -0
  33. lib/python3.10/site-packages/google/auth/aio/transport/__init__.py +144 -0
  34. lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/__init__.cpython-310.pyc +0 -0
  35. lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/aiohttp.cpython-310.pyc +0 -0
  36. lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/sessions.cpython-310.pyc +0 -0
  37. lib/python3.10/site-packages/google/auth/aio/transport/aiohttp.py +190 -0
  38. lib/python3.10/site-packages/google/auth/aio/transport/sessions.py +268 -0
  39. lib/python3.10/site-packages/google/auth/api_key.py +76 -0
  40. lib/python3.10/site-packages/google/auth/app_engine.py +180 -0
  41. lib/python3.10/site-packages/google/auth/aws.py +861 -0
  42. lib/python3.10/site-packages/google/auth/compute_engine/_metadata.py +379 -0
  43. lib/python3.10/site-packages/google/auth/compute_engine/credentials.py +497 -0
  44. lib/python3.10/site-packages/google/auth/credentials.py +522 -0
  45. lib/python3.10/site-packages/google/auth/downscoped.py +512 -0
  46. lib/python3.10/site-packages/google/auth/environment_vars.py +84 -0
  47. lib/python3.10/site-packages/google/auth/exceptions.py +108 -0
  48. lib/python3.10/site-packages/google/auth/external_account.py +628 -0
  49. lib/python3.10/site-packages/google/auth/external_account_authorized_user.py +380 -0
  50. lib/python3.10/site-packages/google/auth/iam.py +136 -0
lib/python3.10/site-packages/cachetools/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (23.1 kB). View file
 
lib/python3.10/site-packages/cachetools/__pycache__/_decorators.cpython-310.pyc ADDED
Binary file (4.83 kB). View file
 
lib/python3.10/site-packages/cachetools/__pycache__/func.cpython-310.pyc ADDED
Binary file (3.92 kB). View file
 
lib/python3.10/site-packages/cachetools/__pycache__/keys.cpython-310.pyc ADDED
Binary file (2.47 kB). View file
 
lib/python3.10/site-packages/comm-0.2.3.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, Jupyter
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
lib/python3.10/site-packages/filelock/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (1.4 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_api.cpython-310.pyc ADDED
Binary file (12.9 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_error.cpython-310.pyc ADDED
Binary file (1.48 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_soft.cpython-310.pyc ADDED
Binary file (1.59 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_unix.cpython-310.pyc ADDED
Binary file (2.22 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_util.cpython-310.pyc ADDED
Binary file (1.53 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/_windows.cpython-310.pyc ADDED
Binary file (2.1 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/asyncio.cpython-310.pyc ADDED
Binary file (11.9 kB). View file
 
lib/python3.10/site-packages/filelock/__pycache__/version.cpython-310.pyc ADDED
Binary file (575 Bytes). View file
 
lib/python3.10/site-packages/google/auth/__init__.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google Auth Library for Python."""
16
+
17
+ import logging
18
+ import sys
19
+ import warnings
20
+
21
+ from google.auth import version as google_auth_version
22
+ from google.auth._default import (
23
+ default,
24
+ load_credentials_from_dict,
25
+ load_credentials_from_file,
26
+ )
27
+
28
+
29
+ __version__ = google_auth_version.__version__
30
+
31
+
32
+ __all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"]
33
+
34
+
35
+ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER
36
+ """
37
+ Deprecation warning raised when Python 3.7 runtime is detected.
38
+ Python 3.7 support will be dropped after January 1, 2024.
39
+ """
40
+
41
+ pass
42
+
43
+
44
+ # Checks if the current runtime is Python 3.7.
45
+ if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER
46
+ message = (
47
+ "After January 1, 2024, new releases of this library will drop support "
48
+ "for Python 3.7."
49
+ )
50
+ warnings.warn(message, Python37DeprecationWarning)
51
+
52
+ # Set default logging handler to avoid "No handler found" warnings.
53
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
lib/python3.10/site-packages/google/auth/_cloud_sdk.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for reading the Google Cloud SDK's configuration."""
16
+
17
+ import os
18
+ import subprocess
19
+
20
+ from google.auth import _helpers
21
+ from google.auth import environment_vars
22
+ from google.auth import exceptions
23
+
24
+
25
+ # The ~/.config subdirectory containing gcloud credentials.
26
+ _CONFIG_DIRECTORY = "gcloud"
27
+ # Windows systems store config at %APPDATA%\gcloud
28
+ _WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA"
29
+ # The name of the file in the Cloud SDK config that contains default
30
+ # credentials.
31
+ _CREDENTIALS_FILENAME = "application_default_credentials.json"
32
+ # The name of the Cloud SDK shell script
33
+ _CLOUD_SDK_POSIX_COMMAND = "gcloud"
34
+ _CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd"
35
+ # The command to get the Cloud SDK configuration
36
+ _CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND = ("config", "get", "project")
37
+ # The command to get google user access token
38
+ _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token")
39
+ # Cloud SDK's application-default client ID
40
+ CLOUD_SDK_CLIENT_ID = (
41
+ "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com"
42
+ )
43
+
44
+
45
+ def get_config_path():
46
+ """Returns the absolute path the the Cloud SDK's configuration directory.
47
+
48
+ Returns:
49
+ str: The Cloud SDK config path.
50
+ """
51
+ # If the path is explicitly set, return that.
52
+ try:
53
+ return os.environ[environment_vars.CLOUD_SDK_CONFIG_DIR]
54
+ except KeyError:
55
+ pass
56
+
57
+ # Non-windows systems store this at ~/.config/gcloud
58
+ if os.name != "nt":
59
+ return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY)
60
+ # Windows systems store config at %APPDATA%\gcloud
61
+ else:
62
+ try:
63
+ return os.path.join(
64
+ os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY
65
+ )
66
+ except KeyError:
67
+ # This should never happen unless someone is really
68
+ # messing with things, but we'll cover the case anyway.
69
+ drive = os.environ.get("SystemDrive", "C:")
70
+ return os.path.join(drive, "\\", _CONFIG_DIRECTORY)
71
+
72
+
73
+ def get_application_default_credentials_path():
74
+ """Gets the path to the application default credentials file.
75
+
76
+ The path may or may not exist.
77
+
78
+ Returns:
79
+ str: The full path to application default credentials.
80
+ """
81
+ config_path = get_config_path()
82
+ return os.path.join(config_path, _CREDENTIALS_FILENAME)
83
+
84
+
85
+ def _run_subprocess_ignore_stderr(command):
86
+ """ Return subprocess.check_output with the given command and ignores stderr."""
87
+ with open(os.devnull, "w") as devnull:
88
+ output = subprocess.check_output(command, stderr=devnull)
89
+ return output
90
+
91
+
92
+ def get_project_id():
93
+ """Gets the project ID from the Cloud SDK.
94
+
95
+ Returns:
96
+ Optional[str]: The project ID.
97
+ """
98
+ if os.name == "nt":
99
+ command = _CLOUD_SDK_WINDOWS_COMMAND
100
+ else:
101
+ command = _CLOUD_SDK_POSIX_COMMAND
102
+
103
+ try:
104
+ # Ignore the stderr coming from gcloud, so it won't be mixed into the output.
105
+ # https://github.com/googleapis/google-auth-library-python/issues/673
106
+ project = _run_subprocess_ignore_stderr(
107
+ (command,) + _CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND
108
+ )
109
+
110
+ # Turn bytes into a string and remove "\n"
111
+ project = _helpers.from_bytes(project).strip()
112
+ return project if project else None
113
+ except (subprocess.CalledProcessError, OSError, IOError):
114
+ return None
115
+
116
+
117
+ def get_auth_access_token(account=None):
118
+ """Load user access token with the ``gcloud auth print-access-token`` command.
119
+
120
+ Args:
121
+ account (Optional[str]): Account to get the access token for. If not
122
+ specified, the current active account will be used.
123
+
124
+ Returns:
125
+ str: The user access token.
126
+
127
+ Raises:
128
+ google.auth.exceptions.UserAccessTokenError: if failed to get access
129
+ token from gcloud.
130
+ """
131
+ if os.name == "nt":
132
+ command = _CLOUD_SDK_WINDOWS_COMMAND
133
+ else:
134
+ command = _CLOUD_SDK_POSIX_COMMAND
135
+
136
+ try:
137
+ if account:
138
+ command = (
139
+ (command,)
140
+ + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND
141
+ + ("--account=" + account,)
142
+ )
143
+ else:
144
+ command = (command,) + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND
145
+
146
+ access_token = subprocess.check_output(command, stderr=subprocess.STDOUT)
147
+ # remove the trailing "\n"
148
+ return access_token.decode("utf-8").strip()
149
+ except (subprocess.CalledProcessError, OSError, IOError) as caught_exc:
150
+ new_exc = exceptions.UserAccessTokenError(
151
+ "Failed to obtain access token", caught_exc
152
+ )
153
+ raise new_exc from caught_exc
lib/python3.10/site-packages/google/auth/_credentials_async.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Interfaces for credentials."""
17
+
18
+ import abc
19
+ import inspect
20
+
21
+ from google.auth import credentials
22
+
23
+
24
+ class Credentials(credentials.Credentials, metaclass=abc.ABCMeta):
25
+ """Async inherited credentials class from google.auth.credentials.
26
+ The added functionality is the before_request call which requires
27
+ async/await syntax.
28
+ All credentials have a :attr:`token` that is used for authentication and
29
+ may also optionally set an :attr:`expiry` to indicate when the token will
30
+ no longer be valid.
31
+
32
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
33
+ Credentials can do this automatically before the first HTTP request in
34
+ :meth:`before_request`.
35
+
36
+ Although the token and expiration will change as the credentials are
37
+ :meth:`refreshed <refresh>` and used, credentials should be considered
38
+ immutable. Various credentials will accept configuration such as private
39
+ keys, scopes, and other options. These options are not changeable after
40
+ construction. Some classes will provide mechanisms to copy the credentials
41
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
42
+ """
43
+
44
+ async def before_request(self, request, method, url, headers):
45
+ """Performs credential-specific before request logic.
46
+
47
+ Refreshes the credentials if necessary, then calls :meth:`apply` to
48
+ apply the token to the authentication header.
49
+
50
+ Args:
51
+ request (google.auth.transport.Request): The object used to make
52
+ HTTP requests.
53
+ method (str): The request's HTTP method or the RPC method being
54
+ invoked.
55
+ url (str): The request's URI or the RPC service's URI.
56
+ headers (Mapping): The request's headers.
57
+ """
58
+ # pylint: disable=unused-argument
59
+ # (Subclasses may use these arguments to ascertain information about
60
+ # the http request.)
61
+
62
+ if not self.valid:
63
+ if inspect.iscoroutinefunction(self.refresh):
64
+ await self.refresh(request)
65
+ else:
66
+ self.refresh(request)
67
+ self.apply(headers)
68
+
69
+
70
+ class CredentialsWithQuotaProject(credentials.CredentialsWithQuotaProject):
71
+ """Abstract base for credentials supporting ``with_quota_project`` factory"""
72
+
73
+
74
+ class AnonymousCredentials(credentials.AnonymousCredentials, Credentials):
75
+ """Credentials that do not provide any authentication information.
76
+
77
+ These are useful in the case of services that support anonymous access or
78
+ local service emulators that do not use credentials. This class inherits
79
+ from the sync anonymous credentials file, but is kept if async credentials
80
+ is initialized and we would like anonymous credentials.
81
+ """
82
+
83
+
84
+ class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta):
85
+ """Interface for credentials whose scopes can be queried.
86
+
87
+ OAuth 2.0-based credentials allow limiting access using scopes as described
88
+ in `RFC6749 Section 3.3`_.
89
+ If a credential class implements this interface then the credentials either
90
+ use scopes in their implementation.
91
+
92
+ Some credentials require scopes in order to obtain a token. You can check
93
+ if scoping is necessary with :attr:`requires_scopes`::
94
+
95
+ if credentials.requires_scopes:
96
+ # Scoping is required.
97
+ credentials = _credentials_async.with_scopes(scopes=['one', 'two'])
98
+
99
+ Credentials that require scopes must either be constructed with scopes::
100
+
101
+ credentials = SomeScopedCredentials(scopes=['one', 'two'])
102
+
103
+ Or must copy an existing instance using :meth:`with_scopes`::
104
+
105
+ scoped_credentials = _credentials_async.with_scopes(scopes=['one', 'two'])
106
+
107
+ Some credentials have scopes but do not allow or require scopes to be set,
108
+ these credentials can be used as-is.
109
+
110
+ .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
111
+ """
112
+
113
+
114
+ class Scoped(credentials.Scoped):
115
+ """Interface for credentials whose scopes can be replaced while copying.
116
+
117
+ OAuth 2.0-based credentials allow limiting access using scopes as described
118
+ in `RFC6749 Section 3.3`_.
119
+ If a credential class implements this interface then the credentials either
120
+ use scopes in their implementation.
121
+
122
+ Some credentials require scopes in order to obtain a token. You can check
123
+ if scoping is necessary with :attr:`requires_scopes`::
124
+
125
+ if credentials.requires_scopes:
126
+ # Scoping is required.
127
+ credentials = _credentials_async.create_scoped(['one', 'two'])
128
+
129
+ Credentials that require scopes must either be constructed with scopes::
130
+
131
+ credentials = SomeScopedCredentials(scopes=['one', 'two'])
132
+
133
+ Or must copy an existing instance using :meth:`with_scopes`::
134
+
135
+ scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
136
+
137
+ Some credentials have scopes but do not allow or require scopes to be set,
138
+ these credentials can be used as-is.
139
+
140
+ .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
141
+ """
142
+
143
+
144
+ def with_scopes_if_required(credentials, scopes):
145
+ """Creates a copy of the credentials with scopes if scoping is required.
146
+
147
+ This helper function is useful when you do not know (or care to know) the
148
+ specific type of credentials you are using (such as when you use
149
+ :func:`google.auth.default`). This function will call
150
+ :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
151
+ the credentials require scoping. Otherwise, it will return the credentials
152
+ as-is.
153
+
154
+ Args:
155
+ credentials (google.auth.credentials.Credentials): The credentials to
156
+ scope if necessary.
157
+ scopes (Sequence[str]): The list of scopes to use.
158
+
159
+ Returns:
160
+ google.auth._credentials_async.Credentials: Either a new set of scoped
161
+ credentials, or the passed in credentials instance if no scoping
162
+ was required.
163
+ """
164
+ if isinstance(credentials, Scoped) and credentials.requires_scopes:
165
+ return credentials.with_scopes(scopes)
166
+ else:
167
+ return credentials
168
+
169
+
170
+ class Signing(credentials.Signing, metaclass=abc.ABCMeta):
171
+ """Interface for credentials that can cryptographically sign messages."""
lib/python3.10/site-packages/google/auth/_credentials_base.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Interface for base credentials."""
17
+
18
+ import abc
19
+
20
+ from google.auth import _helpers
21
+
22
+
23
+ class _BaseCredentials(metaclass=abc.ABCMeta):
24
+ """Base class for all credentials.
25
+
26
+ All credentials have a :attr:`token` that is used for authentication and
27
+ may also optionally set an :attr:`expiry` to indicate when the token will
28
+ no longer be valid.
29
+
30
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
31
+ Credentials can do this automatically before the first HTTP request in
32
+ :meth:`before_request`.
33
+
34
+ Although the token and expiration will change as the credentials are
35
+ :meth:`refreshed <refresh>` and used, credentials should be considered
36
+ immutable. Various credentials will accept configuration such as private
37
+ keys, scopes, and other options. These options are not changeable after
38
+ construction. Some classes will provide mechanisms to copy the credentials
39
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
40
+
41
+ Attributes:
42
+ token (Optional[str]): The bearer token that can be used in HTTP headers to make
43
+ authenticated requests.
44
+ """
45
+
46
+ def __init__(self):
47
+ self.token = None
48
+
49
+ @abc.abstractmethod
50
+ def refresh(self, request):
51
+ """Refreshes the access token.
52
+
53
+ Args:
54
+ request (google.auth.transport.Request): The object used to make
55
+ HTTP requests.
56
+
57
+ Raises:
58
+ google.auth.exceptions.RefreshError: If the credentials could
59
+ not be refreshed.
60
+ """
61
+ # pylint: disable=missing-raises-doc
62
+ # (pylint doesn't recognize that this is abstract)
63
+ raise NotImplementedError("Refresh must be implemented")
64
+
65
+ def _apply(self, headers, token=None):
66
+ """Apply the token to the authentication header.
67
+
68
+ Args:
69
+ headers (Mapping): The HTTP request headers.
70
+ token (Optional[str]): If specified, overrides the current access
71
+ token.
72
+ """
73
+ headers["authorization"] = "Bearer {}".format(
74
+ _helpers.from_bytes(token or self.token)
75
+ )
lib/python3.10/site-packages/google/auth/_default.py ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Application default credentials.
16
+
17
+ Implements application default credentials and project ID detection.
18
+ """
19
+
20
+ import io
21
+ import json
22
+ import logging
23
+ import os
24
+ import warnings
25
+
26
+ from google.auth import environment_vars
27
+ from google.auth import exceptions
28
+ import google.auth.transport._http_client
29
+
30
+ _LOGGER = logging.getLogger(__name__)
31
+
32
+ # Valid types accepted for file-based credentials.
33
+ _AUTHORIZED_USER_TYPE = "authorized_user"
34
+ _SERVICE_ACCOUNT_TYPE = "service_account"
35
+ _EXTERNAL_ACCOUNT_TYPE = "external_account"
36
+ _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user"
37
+ _IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account"
38
+ _GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account"
39
+ _VALID_TYPES = (
40
+ _AUTHORIZED_USER_TYPE,
41
+ _SERVICE_ACCOUNT_TYPE,
42
+ _EXTERNAL_ACCOUNT_TYPE,
43
+ _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE,
44
+ _IMPERSONATED_SERVICE_ACCOUNT_TYPE,
45
+ _GDCH_SERVICE_ACCOUNT_TYPE,
46
+ )
47
+
48
+ # Help message when no credentials can be found.
49
+ _CLOUD_SDK_MISSING_CREDENTIALS = """\
50
+ Your default credentials were not found. To set up Application Default Credentials, \
51
+ see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\
52
+ """
53
+
54
+ # Warning when using Cloud SDK user credentials
55
+ _CLOUD_SDK_CREDENTIALS_WARNING = """\
56
+ Your application has authenticated using end user credentials from Google \
57
+ Cloud SDK without a quota project. You might receive a "quota exceeded" \
58
+ or "API not enabled" error. See the following page for troubleshooting: \
59
+ https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \
60
+ """
61
+
62
+ # The subject token type used for AWS external_account credentials.
63
+ _AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
64
+
65
+
66
+ def _warn_about_problematic_credentials(credentials):
67
+ """Determines if the credentials are problematic.
68
+
69
+ Credentials from the Cloud SDK that are associated with Cloud SDK's project
70
+ are problematic because they may not have APIs enabled and have limited
71
+ quota. If this is the case, warn about it.
72
+ """
73
+ from google.auth import _cloud_sdk
74
+
75
+ if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID:
76
+ warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
77
+
78
+
79
+ def load_credentials_from_file(
80
+ filename, scopes=None, default_scopes=None, quota_project_id=None, request=None
81
+ ):
82
+ """Loads Google credentials from a file.
83
+
84
+ The credentials file must be a service account key, stored authorized
85
+ user credentials, external account credentials, or impersonated service
86
+ account credentials.
87
+
88
+ .. warning::
89
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
90
+ from an external source for authentication to Google Cloud Platform, you must
91
+ validate it before providing it to any Google API or client library. Providing an
92
+ unvalidated credential configuration to Google APIs or libraries can compromise
93
+ the security of your systems and data. For more information, refer to
94
+ `Validate credential configurations from external sources`_.
95
+
96
+ .. _Validate credential configurations from external sources:
97
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
98
+
99
+ Args:
100
+ filename (str): The full path to the credentials file.
101
+ scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
102
+ specified, the credentials will automatically be scoped if
103
+ necessary
104
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
105
+ Google client library. Use 'scopes' for user-defined scopes.
106
+ quota_project_id (Optional[str]): The project ID used for
107
+ quota and billing.
108
+ request (Optional[google.auth.transport.Request]): An object used to make
109
+ HTTP requests. This is used to determine the associated project ID
110
+ for a workload identity pool resource (external account credentials).
111
+ If not specified, then it will use a
112
+ google.auth.transport.requests.Request client to make requests.
113
+
114
+ Returns:
115
+ Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
116
+ credentials and the project ID. Authorized user credentials do not
117
+ have the project ID information. External account credentials project
118
+ IDs may not always be determined.
119
+
120
+ Raises:
121
+ google.auth.exceptions.DefaultCredentialsError: if the file is in the
122
+ wrong format or is missing.
123
+ """
124
+ if not os.path.exists(filename):
125
+ raise exceptions.DefaultCredentialsError(
126
+ "File {} was not found.".format(filename)
127
+ )
128
+
129
+ with io.open(filename, "r") as file_obj:
130
+ try:
131
+ info = json.load(file_obj)
132
+ except ValueError as caught_exc:
133
+ new_exc = exceptions.DefaultCredentialsError(
134
+ "File {} is not a valid json file.".format(filename), caught_exc
135
+ )
136
+ raise new_exc from caught_exc
137
+ return _load_credentials_from_info(
138
+ filename, info, scopes, default_scopes, quota_project_id, request
139
+ )
140
+
141
+
142
+ def load_credentials_from_dict(
143
+ info, scopes=None, default_scopes=None, quota_project_id=None, request=None
144
+ ):
145
+ """Loads Google credentials from a dict.
146
+
147
+ The credentials file must be a service account key, stored authorized
148
+ user credentials, external account credentials, or impersonated service
149
+ account credentials.
150
+
151
+ .. warning::
152
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
153
+ from an external source for authentication to Google Cloud Platform, you must
154
+ validate it before providing it to any Google API or client library. Providing an
155
+ unvalidated credential configuration to Google APIs or libraries can compromise
156
+ the security of your systems and data. For more information, refer to
157
+ `Validate credential configurations from external sources`_.
158
+
159
+ .. _Validate credential configurations from external sources:
160
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
161
+
162
+ Args:
163
+ info (Dict[str, Any]): A dict object containing the credentials
164
+ scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
165
+ specified, the credentials will automatically be scoped if
166
+ necessary
167
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
168
+ Google client library. Use 'scopes' for user-defined scopes.
169
+ quota_project_id (Optional[str]): The project ID used for
170
+ quota and billing.
171
+ request (Optional[google.auth.transport.Request]): An object used to make
172
+ HTTP requests. This is used to determine the associated project ID
173
+ for a workload identity pool resource (external account credentials).
174
+ If not specified, then it will use a
175
+ google.auth.transport.requests.Request client to make requests.
176
+
177
+ Returns:
178
+ Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
179
+ credentials and the project ID. Authorized user credentials do not
180
+ have the project ID information. External account credentials project
181
+ IDs may not always be determined.
182
+
183
+ Raises:
184
+ google.auth.exceptions.DefaultCredentialsError: if the file is in the
185
+ wrong format or is missing.
186
+ """
187
+ if not isinstance(info, dict):
188
+ raise exceptions.DefaultCredentialsError(
189
+ "info object was of type {} but dict type was expected.".format(type(info))
190
+ )
191
+
192
+ return _load_credentials_from_info(
193
+ "dict object", info, scopes, default_scopes, quota_project_id, request
194
+ )
195
+
196
+
197
+ def _load_credentials_from_info(
198
+ filename, info, scopes, default_scopes, quota_project_id, request
199
+ ):
200
+ from google.auth.credentials import CredentialsWithQuotaProject
201
+
202
+ credential_type = info.get("type")
203
+
204
+ if credential_type == _AUTHORIZED_USER_TYPE:
205
+ credentials, project_id = _get_authorized_user_credentials(
206
+ filename, info, scopes
207
+ )
208
+
209
+ elif credential_type == _SERVICE_ACCOUNT_TYPE:
210
+ credentials, project_id = _get_service_account_credentials(
211
+ filename, info, scopes, default_scopes
212
+ )
213
+
214
+ elif credential_type == _EXTERNAL_ACCOUNT_TYPE:
215
+ credentials, project_id = _get_external_account_credentials(
216
+ info,
217
+ filename,
218
+ scopes=scopes,
219
+ default_scopes=default_scopes,
220
+ request=request,
221
+ )
222
+
223
+ elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE:
224
+ credentials, project_id = _get_external_account_authorized_user_credentials(
225
+ filename, info, request
226
+ )
227
+
228
+ elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE:
229
+ credentials, project_id = _get_impersonated_service_account_credentials(
230
+ filename, info, scopes
231
+ )
232
+ elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE:
233
+ credentials, project_id = _get_gdch_service_account_credentials(filename, info)
234
+ else:
235
+ raise exceptions.DefaultCredentialsError(
236
+ "The file {file} does not have a valid type. "
237
+ "Type is {type}, expected one of {valid_types}.".format(
238
+ file=filename, type=credential_type, valid_types=_VALID_TYPES
239
+ )
240
+ )
241
+ if isinstance(credentials, CredentialsWithQuotaProject):
242
+ credentials = _apply_quota_project_id(credentials, quota_project_id)
243
+ return credentials, project_id
244
+
245
+
246
+ def _get_gcloud_sdk_credentials(quota_project_id=None):
247
+ """Gets the credentials and project ID from the Cloud SDK."""
248
+ from google.auth import _cloud_sdk
249
+
250
+ _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...")
251
+
252
+ # Check if application default credentials exist.
253
+ credentials_filename = _cloud_sdk.get_application_default_credentials_path()
254
+
255
+ if not os.path.isfile(credentials_filename):
256
+ _LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
257
+ return None, None
258
+
259
+ credentials, project_id = load_credentials_from_file(
260
+ credentials_filename, quota_project_id=quota_project_id
261
+ )
262
+ credentials._cred_file_path = credentials_filename
263
+
264
+ if not project_id:
265
+ project_id = _cloud_sdk.get_project_id()
266
+
267
+ return credentials, project_id
268
+
269
+
270
+ def _get_explicit_environ_credentials(quota_project_id=None):
271
+ """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
272
+ variable."""
273
+ from google.auth import _cloud_sdk
274
+
275
+ cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path()
276
+ explicit_file = os.environ.get(environment_vars.CREDENTIALS)
277
+
278
+ _LOGGER.debug(
279
+ "Checking %s for explicit credentials as part of auth process...", explicit_file
280
+ )
281
+
282
+ if explicit_file is not None and explicit_file == cloud_sdk_adc_path:
283
+ # Cloud sdk flow calls gcloud to fetch project id, so if the explicit
284
+ # file path is cloud sdk credentials path, then we should fall back
285
+ # to cloud sdk flow, otherwise project id cannot be obtained.
286
+ _LOGGER.debug(
287
+ "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...",
288
+ explicit_file,
289
+ )
290
+ return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)
291
+
292
+ if explicit_file is not None:
293
+ credentials, project_id = load_credentials_from_file(
294
+ os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id
295
+ )
296
+ credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable"
297
+
298
+ return credentials, project_id
299
+
300
+ else:
301
+ return None, None
302
+
303
+
304
+ def _get_gae_credentials():
305
+ """Gets Google App Engine App Identity credentials and project ID."""
306
+ # If not GAE gen1, prefer the metadata service even if the GAE APIs are
307
+ # available as per https://google.aip.dev/auth/4115.
308
+ if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27":
309
+ return None, None
310
+
311
+ # While this library is normally bundled with app_engine, there are
312
+ # some cases where it's not available, so we tolerate ImportError.
313
+ try:
314
+ _LOGGER.debug("Checking for App Engine runtime as part of auth process...")
315
+ import google.auth.app_engine as app_engine
316
+ except ImportError:
317
+ _LOGGER.warning("Import of App Engine auth library failed.")
318
+ return None, None
319
+
320
+ try:
321
+ credentials = app_engine.Credentials()
322
+ project_id = app_engine.get_project_id()
323
+ return credentials, project_id
324
+ except EnvironmentError:
325
+ _LOGGER.debug(
326
+ "No App Engine library was found so cannot authentication via App Engine Identity Credentials."
327
+ )
328
+ return None, None
329
+
330
+
331
+ def _get_gce_credentials(request=None, quota_project_id=None):
332
+ """Gets credentials and project ID from the GCE Metadata Service."""
333
+ # Ping requires a transport, but we want application default credentials
334
+ # to require no arguments. So, we'll use the _http_client transport which
335
+ # uses http.client. This is only acceptable because the metadata server
336
+ # doesn't do SSL and never requires proxies.
337
+
338
+ # While this library is normally bundled with compute_engine, there are
339
+ # some cases where it's not available, so we tolerate ImportError.
340
+ try:
341
+ from google.auth import compute_engine
342
+ from google.auth.compute_engine import _metadata
343
+ except ImportError:
344
+ _LOGGER.warning("Import of Compute Engine auth library failed.")
345
+ return None, None
346
+
347
+ if request is None:
348
+ request = google.auth.transport._http_client.Request()
349
+
350
+ if _metadata.is_on_gce(request=request):
351
+ # Get the project ID.
352
+ try:
353
+ project_id = _metadata.get_project_id(request=request)
354
+ except exceptions.TransportError:
355
+ project_id = None
356
+
357
+ cred = compute_engine.Credentials()
358
+ cred = _apply_quota_project_id(cred, quota_project_id)
359
+
360
+ return cred, project_id
361
+ else:
362
+ _LOGGER.warning(
363
+ "Authentication failed using Compute Engine authentication due to unavailable metadata server."
364
+ )
365
+ return None, None
366
+
367
+
368
+ def _get_external_account_credentials(
369
+ info, filename, scopes=None, default_scopes=None, request=None
370
+ ):
371
+ """Loads external account Credentials from the parsed external account info.
372
+
373
+ The credentials information must correspond to a supported external account
374
+ credentials.
375
+
376
+ Args:
377
+ info (Mapping[str, str]): The external account info in Google format.
378
+ filename (str): The full path to the credentials file.
379
+ scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
380
+ specified, the credentials will automatically be scoped if
381
+ necessary.
382
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
383
+ Google client library. Use 'scopes' for user-defined scopes.
384
+ request (Optional[google.auth.transport.Request]): An object used to make
385
+ HTTP requests. This is used to determine the associated project ID
386
+ for a workload identity pool resource (external account credentials).
387
+ If not specified, then it will use a
388
+ google.auth.transport.requests.Request client to make requests.
389
+
390
+ Returns:
391
+ Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
392
+ credentials and the project ID. External account credentials project
393
+ IDs may not always be determined.
394
+
395
+ Raises:
396
+ google.auth.exceptions.DefaultCredentialsError: if the info dictionary
397
+ is in the wrong format or is missing required information.
398
+ """
399
+ # There are currently 3 types of external_account credentials.
400
+ if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE:
401
+ # Check if configuration corresponds to an AWS credentials.
402
+ from google.auth import aws
403
+
404
+ credentials = aws.Credentials.from_info(
405
+ info, scopes=scopes, default_scopes=default_scopes
406
+ )
407
+ elif (
408
+ info.get("credential_source") is not None
409
+ and info.get("credential_source").get("executable") is not None
410
+ ):
411
+ from google.auth import pluggable
412
+
413
+ credentials = pluggable.Credentials.from_info(
414
+ info, scopes=scopes, default_scopes=default_scopes
415
+ )
416
+ else:
417
+ try:
418
+ # Check if configuration corresponds to an Identity Pool credentials.
419
+ from google.auth import identity_pool
420
+
421
+ credentials = identity_pool.Credentials.from_info(
422
+ info, scopes=scopes, default_scopes=default_scopes
423
+ )
424
+ except ValueError:
425
+ # If the configuration is invalid or does not correspond to any
426
+ # supported external_account credentials, raise an error.
427
+ raise exceptions.DefaultCredentialsError(
428
+ "Failed to load external account credentials from {}".format(filename)
429
+ )
430
+ if request is None:
431
+ import google.auth.transport.requests
432
+
433
+ request = google.auth.transport.requests.Request()
434
+
435
+ return credentials, credentials.get_project_id(request=request)
436
+
437
+
438
+ def _get_external_account_authorized_user_credentials(
439
+ filename, info, scopes=None, default_scopes=None, request=None
440
+ ):
441
+ try:
442
+ from google.auth import external_account_authorized_user
443
+
444
+ credentials = external_account_authorized_user.Credentials.from_info(info)
445
+ except ValueError:
446
+ raise exceptions.DefaultCredentialsError(
447
+ "Failed to load external account authorized user credentials from {}".format(
448
+ filename
449
+ )
450
+ )
451
+
452
+ return credentials, None
453
+
454
+
455
+ def _get_authorized_user_credentials(filename, info, scopes=None):
456
+ from google.oauth2 import credentials
457
+
458
+ try:
459
+ credentials = credentials.Credentials.from_authorized_user_info(
460
+ info, scopes=scopes
461
+ )
462
+ except ValueError as caught_exc:
463
+ msg = "Failed to load authorized user credentials from {}".format(filename)
464
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
465
+ raise new_exc from caught_exc
466
+ return credentials, None
467
+
468
+
469
+ def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None):
470
+ from google.oauth2 import service_account
471
+
472
+ try:
473
+ credentials = service_account.Credentials.from_service_account_info(
474
+ info, scopes=scopes, default_scopes=default_scopes
475
+ )
476
+ except ValueError as caught_exc:
477
+ msg = "Failed to load service account credentials from {}".format(filename)
478
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
479
+ raise new_exc from caught_exc
480
+ return credentials, info.get("project_id")
481
+
482
+
483
+ def _get_impersonated_service_account_credentials(filename, info, scopes):
484
+ from google.auth import impersonated_credentials
485
+
486
+ try:
487
+ credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info(
488
+ info, scopes=scopes
489
+ )
490
+ except ValueError as caught_exc:
491
+ msg = "Failed to load impersonated service account credentials from {}".format(
492
+ filename
493
+ )
494
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
495
+ raise new_exc from caught_exc
496
+ return credentials, None
497
+
498
+
499
+ def _get_gdch_service_account_credentials(filename, info):
500
+ from google.oauth2 import gdch_credentials
501
+
502
+ try:
503
+ credentials = gdch_credentials.ServiceAccountCredentials.from_service_account_info(
504
+ info
505
+ )
506
+ except ValueError as caught_exc:
507
+ msg = "Failed to load GDCH service account credentials from {}".format(filename)
508
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
509
+ raise new_exc from caught_exc
510
+ return credentials, info.get("project")
511
+
512
+
513
+ def get_api_key_credentials(key):
514
+ """Return credentials with the given API key."""
515
+ from google.auth import api_key
516
+
517
+ return api_key.Credentials(key)
518
+
519
+
520
+ def _apply_quota_project_id(credentials, quota_project_id):
521
+ if quota_project_id:
522
+ credentials = credentials.with_quota_project(quota_project_id)
523
+ else:
524
+ credentials = credentials.with_quota_project_from_environment()
525
+
526
+ from google.oauth2 import credentials as authorized_user_credentials
527
+
528
+ if isinstance(credentials, authorized_user_credentials.Credentials) and (
529
+ not credentials.quota_project_id
530
+ ):
531
+ _warn_about_problematic_credentials(credentials)
532
+ return credentials
533
+
534
+
535
+ def default(scopes=None, request=None, quota_project_id=None, default_scopes=None):
536
+ """Gets the default credentials for the current environment.
537
+
538
+ `Application Default Credentials`_ provides an easy way to obtain
539
+ credentials to call Google APIs for server-to-server or local applications.
540
+ This function acquires credentials from the environment in the following
541
+ order:
542
+
543
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
544
+ to the path of a valid service account JSON private key file, then it is
545
+ loaded and returned. The project ID returned is the project ID defined
546
+ in the service account file if available (some older files do not
547
+ contain project ID information).
548
+
549
+ If the environment variable is set to the path of a valid external
550
+ account JSON configuration file (workload identity federation), then the
551
+ configuration file is used to determine and retrieve the external
552
+ credentials from the current environment (AWS, Azure, etc).
553
+ These will then be exchanged for Google access tokens via the Google STS
554
+ endpoint.
555
+ The project ID returned in this case is the one corresponding to the
556
+ underlying workload identity pool resource if determinable.
557
+
558
+ If the environment variable is set to the path of a valid GDCH service
559
+ account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH
560
+ credential will be returned. The project ID returned is the project
561
+ specified in the JSON file.
562
+ 2. If the `Google Cloud SDK`_ is installed and has application default
563
+ credentials set they are loaded and returned.
564
+
565
+ To enable application default credentials with the Cloud SDK run::
566
+
567
+ gcloud auth application-default login
568
+
569
+ If the Cloud SDK has an active project, the project ID is returned. The
570
+ active project can be set using::
571
+
572
+ gcloud config set project
573
+
574
+ 3. If the application is running in the `App Engine standard environment`_
575
+ (first generation) then the credentials and project ID from the
576
+ `App Identity Service`_ are used.
577
+ 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
578
+ the `App Engine flexible environment`_ or the `App Engine standard
579
+ environment`_ (second generation) then the credentials and project ID
580
+ are obtained from the `Metadata Service`_.
581
+ 5. If no credentials are found,
582
+ :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
583
+
584
+ .. _Application Default Credentials: https://developers.google.com\
585
+ /identity/protocols/application-default-credentials
586
+ .. _Google Cloud SDK: https://cloud.google.com/sdk
587
+ .. _App Engine standard environment: https://cloud.google.com/appengine
588
+ .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
589
+ /appidentity/
590
+ .. _Compute Engine: https://cloud.google.com/compute
591
+ .. _App Engine flexible environment: https://cloud.google.com\
592
+ /appengine/flexible
593
+ .. _Metadata Service: https://cloud.google.com/compute/docs\
594
+ /storing-retrieving-metadata
595
+ .. _Cloud Run: https://cloud.google.com/run
596
+ .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\
597
+ /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted
598
+
599
+ Example::
600
+
601
+ import google.auth
602
+
603
+ credentials, project_id = google.auth.default()
604
+
605
+ Args:
606
+ scopes (Sequence[str]): The list of scopes for the credentials. If
607
+ specified, the credentials will automatically be scoped if
608
+ necessary.
609
+ request (Optional[google.auth.transport.Request]): An object used to make
610
+ HTTP requests. This is used to either detect whether the application
611
+ is running on Compute Engine or to determine the associated project
612
+ ID for a workload identity pool resource (external account
613
+ credentials). If not specified, then it will either use the standard
614
+ library http client to make requests for Compute Engine credentials
615
+ or a google.auth.transport.requests.Request client for external
616
+ account credentials.
617
+ quota_project_id (Optional[str]): The project ID used for
618
+ quota and billing.
619
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
620
+ Google client library. Use 'scopes' for user-defined scopes.
621
+ Returns:
622
+ Tuple[~google.auth.credentials.Credentials, Optional[str]]:
623
+ the current environment's credentials and project ID. Project ID
624
+ may be None, which indicates that the Project ID could not be
625
+ ascertained from the environment.
626
+
627
+ Raises:
628
+ ~google.auth.exceptions.DefaultCredentialsError:
629
+ If no credentials were found, or if the credentials found were
630
+ invalid.
631
+ """
632
+ from google.auth.credentials import with_scopes_if_required
633
+ from google.auth.credentials import CredentialsWithQuotaProject
634
+
635
+ explicit_project_id = os.environ.get(
636
+ environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
637
+ )
638
+
639
+ checkers = (
640
+ # Avoid passing scopes here to prevent passing scopes to user credentials.
641
+ # with_scopes_if_required() below will ensure scopes/default scopes are
642
+ # safely set on the returned credentials since requires_scopes will
643
+ # guard against setting scopes on user credentials.
644
+ lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
645
+ lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id),
646
+ _get_gae_credentials,
647
+ lambda: _get_gce_credentials(request, quota_project_id=quota_project_id),
648
+ )
649
+
650
+ for checker in checkers:
651
+ credentials, project_id = checker()
652
+ if credentials is not None:
653
+ credentials = with_scopes_if_required(
654
+ credentials, scopes, default_scopes=default_scopes
655
+ )
656
+
657
+ effective_project_id = explicit_project_id or project_id
658
+
659
+ # For external account credentials, scopes are required to determine
660
+ # the project ID. Try to get the project ID again if not yet
661
+ # determined.
662
+ if not effective_project_id and callable(
663
+ getattr(credentials, "get_project_id", None)
664
+ ):
665
+ if request is None:
666
+ import google.auth.transport.requests
667
+
668
+ request = google.auth.transport.requests.Request()
669
+ effective_project_id = credentials.get_project_id(request=request)
670
+
671
+ if quota_project_id and isinstance(
672
+ credentials, CredentialsWithQuotaProject
673
+ ):
674
+ credentials = credentials.with_quota_project(quota_project_id)
675
+
676
+ if not effective_project_id:
677
+ _LOGGER.warning(
678
+ "No project ID could be determined. Consider running "
679
+ "`gcloud config set project` or setting the %s "
680
+ "environment variable",
681
+ environment_vars.PROJECT,
682
+ )
683
+ return credentials, effective_project_id
684
+
685
+ raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS)
lib/python3.10/site-packages/google/auth/_default_async.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Application default credentials.
16
+
17
+ Implements application default credentials and project ID detection.
18
+ """
19
+
20
+ import io
21
+ import json
22
+ import os
23
+
24
+ from google.auth import _default
25
+ from google.auth import environment_vars
26
+ from google.auth import exceptions
27
+
28
+
29
+ def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
30
+ """Loads Google credentials from a file.
31
+
32
+ The credentials file must be a service account key or stored authorized
33
+ user credentials.
34
+
35
+ Args:
36
+ filename (str): The full path to the credentials file.
37
+ scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
38
+ specified, the credentials will automatically be scoped if
39
+ necessary
40
+ quota_project_id (Optional[str]): The project ID used for
41
+ quota and billing.
42
+
43
+ Returns:
44
+ Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
45
+ credentials and the project ID. Authorized user credentials do not
46
+ have the project ID information.
47
+
48
+ Raises:
49
+ google.auth.exceptions.DefaultCredentialsError: if the file is in the
50
+ wrong format or is missing.
51
+ """
52
+ if not os.path.exists(filename):
53
+ raise exceptions.DefaultCredentialsError(
54
+ "File {} was not found.".format(filename)
55
+ )
56
+
57
+ with io.open(filename, "r") as file_obj:
58
+ try:
59
+ info = json.load(file_obj)
60
+ except ValueError as caught_exc:
61
+ new_exc = exceptions.DefaultCredentialsError(
62
+ "File {} is not a valid json file.".format(filename), caught_exc
63
+ )
64
+ raise new_exc from caught_exc
65
+
66
+ # The type key should indicate that the file is either a service account
67
+ # credentials file or an authorized user credentials file.
68
+ credential_type = info.get("type")
69
+
70
+ if credential_type == _default._AUTHORIZED_USER_TYPE:
71
+ from google.oauth2 import _credentials_async as credentials
72
+
73
+ try:
74
+ credentials = credentials.Credentials.from_authorized_user_info(
75
+ info, scopes=scopes
76
+ )
77
+ except ValueError as caught_exc:
78
+ msg = "Failed to load authorized user credentials from {}".format(filename)
79
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
80
+ raise new_exc from caught_exc
81
+ if quota_project_id:
82
+ credentials = credentials.with_quota_project(quota_project_id)
83
+ if not credentials.quota_project_id:
84
+ _default._warn_about_problematic_credentials(credentials)
85
+ return credentials, None
86
+
87
+ elif credential_type == _default._SERVICE_ACCOUNT_TYPE:
88
+ from google.oauth2 import _service_account_async as service_account
89
+
90
+ try:
91
+ credentials = service_account.Credentials.from_service_account_info(
92
+ info, scopes=scopes
93
+ ).with_quota_project(quota_project_id)
94
+ except ValueError as caught_exc:
95
+ msg = "Failed to load service account credentials from {}".format(filename)
96
+ new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
97
+ raise new_exc from caught_exc
98
+ return credentials, info.get("project_id")
99
+
100
+ else:
101
+ raise exceptions.DefaultCredentialsError(
102
+ "The file {file} does not have a valid type. "
103
+ "Type is {type}, expected one of {valid_types}.".format(
104
+ file=filename, type=credential_type, valid_types=_default._VALID_TYPES
105
+ )
106
+ )
107
+
108
+
109
+ def _get_gcloud_sdk_credentials(quota_project_id=None):
110
+ """Gets the credentials and project ID from the Cloud SDK."""
111
+ from google.auth import _cloud_sdk
112
+
113
+ # Check if application default credentials exist.
114
+ credentials_filename = _cloud_sdk.get_application_default_credentials_path()
115
+
116
+ if not os.path.isfile(credentials_filename):
117
+ return None, None
118
+
119
+ credentials, project_id = load_credentials_from_file(
120
+ credentials_filename, quota_project_id=quota_project_id
121
+ )
122
+
123
+ if not project_id:
124
+ project_id = _cloud_sdk.get_project_id()
125
+
126
+ return credentials, project_id
127
+
128
+
129
+ def _get_explicit_environ_credentials(quota_project_id=None):
130
+ """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
131
+ variable."""
132
+ from google.auth import _cloud_sdk
133
+
134
+ cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path()
135
+ explicit_file = os.environ.get(environment_vars.CREDENTIALS)
136
+
137
+ if explicit_file is not None and explicit_file == cloud_sdk_adc_path:
138
+ # Cloud sdk flow calls gcloud to fetch project id, so if the explicit
139
+ # file path is cloud sdk credentials path, then we should fall back
140
+ # to cloud sdk flow, otherwise project id cannot be obtained.
141
+ return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)
142
+
143
+ if explicit_file is not None:
144
+ credentials, project_id = load_credentials_from_file(
145
+ os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id
146
+ )
147
+
148
+ return credentials, project_id
149
+
150
+ else:
151
+ return None, None
152
+
153
+
154
+ def _get_gae_credentials():
155
+ """Gets Google App Engine App Identity credentials and project ID."""
156
+ # While this library is normally bundled with app_engine, there are
157
+ # some cases where it's not available, so we tolerate ImportError.
158
+
159
+ return _default._get_gae_credentials()
160
+
161
+
162
+ def _get_gce_credentials(request=None):
163
+ """Gets credentials and project ID from the GCE Metadata Service."""
164
+ # Ping requires a transport, but we want application default credentials
165
+ # to require no arguments. So, we'll use the _http_client transport which
166
+ # uses http.client. This is only acceptable because the metadata server
167
+ # doesn't do SSL and never requires proxies.
168
+
169
+ # While this library is normally bundled with compute_engine, there are
170
+ # some cases where it's not available, so we tolerate ImportError.
171
+
172
+ return _default._get_gce_credentials(request)
173
+
174
+
175
+ def default_async(scopes=None, request=None, quota_project_id=None):
176
+ """Gets the default credentials for the current environment.
177
+
178
+ `Application Default Credentials`_ provides an easy way to obtain
179
+ credentials to call Google APIs for server-to-server or local applications.
180
+ This function acquires credentials from the environment in the following
181
+ order:
182
+
183
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
184
+ to the path of a valid service account JSON private key file, then it is
185
+ loaded and returned. The project ID returned is the project ID defined
186
+ in the service account file if available (some older files do not
187
+ contain project ID information).
188
+ 2. If the `Google Cloud SDK`_ is installed and has application default
189
+ credentials set they are loaded and returned.
190
+
191
+ To enable application default credentials with the Cloud SDK run::
192
+
193
+ gcloud auth application-default login
194
+
195
+ If the Cloud SDK has an active project, the project ID is returned. The
196
+ active project can be set using::
197
+
198
+ gcloud config set project
199
+
200
+ 3. If the application is running in the `App Engine standard environment`_
201
+ (first generation) then the credentials and project ID from the
202
+ `App Identity Service`_ are used.
203
+ 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
204
+ the `App Engine flexible environment`_ or the `App Engine standard
205
+ environment`_ (second generation) then the credentials and project ID
206
+ are obtained from the `Metadata Service`_.
207
+ 5. If no credentials are found,
208
+ :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
209
+
210
+ .. _Application Default Credentials: https://developers.google.com\
211
+ /identity/protocols/application-default-credentials
212
+ .. _Google Cloud SDK: https://cloud.google.com/sdk
213
+ .. _App Engine standard environment: https://cloud.google.com/appengine
214
+ .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
215
+ /appidentity/
216
+ .. _Compute Engine: https://cloud.google.com/compute
217
+ .. _App Engine flexible environment: https://cloud.google.com\
218
+ /appengine/flexible
219
+ .. _Metadata Service: https://cloud.google.com/compute/docs\
220
+ /storing-retrieving-metadata
221
+ .. _Cloud Run: https://cloud.google.com/run
222
+
223
+ Example::
224
+
225
+ import google.auth
226
+
227
+ credentials, project_id = google.auth.default()
228
+
229
+ Args:
230
+ scopes (Sequence[str]): The list of scopes for the credentials. If
231
+ specified, the credentials will automatically be scoped if
232
+ necessary.
233
+ request (google.auth.transport.Request): An object used to make
234
+ HTTP requests. This is used to detect whether the application
235
+ is running on Compute Engine. If not specified, then it will
236
+ use the standard library http client to make requests.
237
+ quota_project_id (Optional[str]): The project ID used for
238
+ quota and billing.
239
+ Returns:
240
+ Tuple[~google.auth.credentials.Credentials, Optional[str]]:
241
+ the current environment's credentials and project ID. Project ID
242
+ may be None, which indicates that the Project ID could not be
243
+ ascertained from the environment.
244
+
245
+ Raises:
246
+ ~google.auth.exceptions.DefaultCredentialsError:
247
+ If no credentials were found, or if the credentials found were
248
+ invalid.
249
+ """
250
+ from google.auth._credentials_async import with_scopes_if_required
251
+ from google.auth.credentials import CredentialsWithQuotaProject
252
+
253
+ explicit_project_id = os.environ.get(
254
+ environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
255
+ )
256
+
257
+ checkers = (
258
+ lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
259
+ lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id),
260
+ _get_gae_credentials,
261
+ lambda: _get_gce_credentials(request),
262
+ )
263
+
264
+ for checker in checkers:
265
+ credentials, project_id = checker()
266
+ if credentials is not None:
267
+ credentials = with_scopes_if_required(credentials, scopes)
268
+ if quota_project_id and isinstance(
269
+ credentials, CredentialsWithQuotaProject
270
+ ):
271
+ credentials = credentials.with_quota_project(quota_project_id)
272
+ effective_project_id = explicit_project_id or project_id
273
+ if not effective_project_id:
274
+ _default._LOGGER.warning(
275
+ "No project ID could be determined. Consider running "
276
+ "`gcloud config set project` or setting the %s "
277
+ "environment variable",
278
+ environment_vars.PROJECT,
279
+ )
280
+ return credentials, effective_project_id
281
+
282
+ raise exceptions.DefaultCredentialsError(_default._CLOUD_SDK_MISSING_CREDENTIALS)
lib/python3.10/site-packages/google/auth/_exponential_backoff.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import random
17
+ import time
18
+
19
+ from google.auth import exceptions
20
+
21
+ # The default amount of retry attempts
22
+ _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3
23
+
24
+ # The default initial backoff period (1.0 second).
25
+ _DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0
26
+
27
+ # The default randomization factor (0.1 which results in a random period ranging
28
+ # between 10% below and 10% above the retry interval).
29
+ _DEFAULT_RANDOMIZATION_FACTOR = 0.1
30
+
31
+ # The default multiplier value (2 which is 100% increase per back off).
32
+ _DEFAULT_MULTIPLIER = 2.0
33
+
34
+ """Exponential Backoff Utility
35
+
36
+ This is a private module that implements the exponential back off algorithm.
37
+ It can be used as a utility for code that needs to retry on failure, for example
38
+ an HTTP request.
39
+ """
40
+
41
+
42
+ class _BaseExponentialBackoff:
43
+ """An exponential backoff iterator base class.
44
+
45
+ Args:
46
+ total_attempts Optional[int]:
47
+ The maximum amount of retries that should happen.
48
+ The default value is 3 attempts.
49
+ initial_wait_seconds Optional[int]:
50
+ The amount of time to sleep in the first backoff. This parameter
51
+ should be in seconds.
52
+ The default value is 1 second.
53
+ randomization_factor Optional[float]:
54
+ The amount of jitter that should be in each backoff. For example,
55
+ a value of 0.1 will introduce a jitter range of 10% to the
56
+ current backoff period.
57
+ The default value is 0.1.
58
+ multiplier Optional[float]:
59
+ The backoff multipler. This adjusts how much each backoff will
60
+ increase. For example a value of 2.0 leads to a 200% backoff
61
+ on each attempt. If the initial_wait is 1.0 it would look like
62
+ this sequence [1.0, 2.0, 4.0, 8.0].
63
+ The default value is 2.0.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS,
69
+ initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS,
70
+ randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR,
71
+ multiplier=_DEFAULT_MULTIPLIER,
72
+ ):
73
+ if total_attempts < 1:
74
+ raise exceptions.InvalidValue(
75
+ f"total_attempts must be greater than or equal to 1 but was {total_attempts}"
76
+ )
77
+
78
+ self._total_attempts = total_attempts
79
+ self._initial_wait_seconds = initial_wait_seconds
80
+
81
+ self._current_wait_in_seconds = self._initial_wait_seconds
82
+
83
+ self._randomization_factor = randomization_factor
84
+ self._multiplier = multiplier
85
+ self._backoff_count = 0
86
+
87
+ @property
88
+ def total_attempts(self):
89
+ """The total amount of backoff attempts that will be made."""
90
+ return self._total_attempts
91
+
92
+ @property
93
+ def backoff_count(self):
94
+ """The current amount of backoff attempts that have been made."""
95
+ return self._backoff_count
96
+
97
+ def _reset(self):
98
+ self._backoff_count = 0
99
+ self._current_wait_in_seconds = self._initial_wait_seconds
100
+
101
+ def _calculate_jitter(self):
102
+ jitter_variance = self._current_wait_in_seconds * self._randomization_factor
103
+ jitter = random.uniform(
104
+ self._current_wait_in_seconds - jitter_variance,
105
+ self._current_wait_in_seconds + jitter_variance,
106
+ )
107
+
108
+ return jitter
109
+
110
+
111
+ class ExponentialBackoff(_BaseExponentialBackoff):
112
+ """An exponential backoff iterator. This can be used in a for loop to
113
+ perform requests with exponential backoff.
114
+ """
115
+
116
+ def __init__(self, *args, **kwargs):
117
+ super(ExponentialBackoff, self).__init__(*args, **kwargs)
118
+
119
+ def __iter__(self):
120
+ self._reset()
121
+ return self
122
+
123
+ def __next__(self):
124
+ if self._backoff_count >= self._total_attempts:
125
+ raise StopIteration
126
+ self._backoff_count += 1
127
+
128
+ if self._backoff_count <= 1:
129
+ return self._backoff_count
130
+
131
+ jitter = self._calculate_jitter()
132
+
133
+ time.sleep(jitter)
134
+
135
+ self._current_wait_in_seconds *= self._multiplier
136
+ return self._backoff_count
137
+
138
+
139
+ class AsyncExponentialBackoff(_BaseExponentialBackoff):
140
+ """An async exponential backoff iterator. This can be used in a for loop to
141
+ perform async requests with exponential backoff.
142
+ """
143
+
144
+ def __init__(self, *args, **kwargs):
145
+ super(AsyncExponentialBackoff, self).__init__(*args, **kwargs)
146
+
147
+ def __aiter__(self):
148
+ self._reset()
149
+ return self
150
+
151
+ async def __anext__(self):
152
+ if self._backoff_count >= self._total_attempts:
153
+ raise StopAsyncIteration
154
+ self._backoff_count += 1
155
+
156
+ if self._backoff_count <= 1:
157
+ return self._backoff_count
158
+
159
+ jitter = self._calculate_jitter()
160
+
161
+ await asyncio.sleep(jitter)
162
+
163
+ self._current_wait_in_seconds *= self._multiplier
164
+ return self._backoff_count
lib/python3.10/site-packages/google/auth/_helpers.py ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helper functions for commonly used utilities."""
16
+
17
+ import base64
18
+ import calendar
19
+ import datetime
20
+ from email.message import Message
21
+ import hashlib
22
+ import json
23
+ import logging
24
+ import sys
25
+ from typing import Any, Dict, Mapping, Optional, Union
26
+ import urllib
27
+
28
+ from google.auth import exceptions
29
+
30
+
31
+ # _BASE_LOGGER_NAME is the base logger for all google-based loggers.
32
+ _BASE_LOGGER_NAME = "google"
33
+
34
+ # _LOGGING_INITIALIZED ensures that base logger is only configured once
35
+ # (unless already configured by the end-user).
36
+ _LOGGING_INITIALIZED = False
37
+
38
+
39
+ # The smallest MDS cache used by this library stores tokens until 4 minutes from
40
+ # expiry.
41
+ REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45)
42
+
43
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1684): Audit and update the list below.
44
+ _SENSITIVE_FIELDS = {
45
+ "accessToken",
46
+ "access_token",
47
+ "id_token",
48
+ "client_id",
49
+ "refresh_token",
50
+ "client_secret",
51
+ }
52
+
53
+
54
+ def copy_docstring(source_class):
55
+ """Decorator that copies a method's docstring from another class.
56
+
57
+ Args:
58
+ source_class (type): The class that has the documented method.
59
+
60
+ Returns:
61
+ Callable: A decorator that will copy the docstring of the same
62
+ named method in the source class to the decorated method.
63
+ """
64
+
65
+ def decorator(method):
66
+ """Decorator implementation.
67
+
68
+ Args:
69
+ method (Callable): The method to copy the docstring to.
70
+
71
+ Returns:
72
+ Callable: the same method passed in with an updated docstring.
73
+
74
+ Raises:
75
+ google.auth.exceptions.InvalidOperation: if the method already has a docstring.
76
+ """
77
+ if method.__doc__:
78
+ raise exceptions.InvalidOperation("Method already has a docstring.")
79
+
80
+ source_method = getattr(source_class, method.__name__)
81
+ method.__doc__ = source_method.__doc__
82
+
83
+ return method
84
+
85
+ return decorator
86
+
87
+
88
+ def parse_content_type(header_value):
89
+ """Parse a 'content-type' header value to get just the plain media-type (without parameters).
90
+
91
+ This is done using the class Message from email.message as suggested in PEP 594
92
+ (because the cgi is now deprecated and will be removed in python 3.13,
93
+ see https://peps.python.org/pep-0594/#cgi).
94
+
95
+ Args:
96
+ header_value (str): The value of a 'content-type' header as a string.
97
+
98
+ Returns:
99
+ str: A string with just the lowercase media-type from the parsed 'content-type' header.
100
+ If the provided content-type is not parsable, returns 'text/plain',
101
+ the default value for textual files.
102
+ """
103
+ m = Message()
104
+ m["content-type"] = header_value
105
+ return (
106
+ m.get_content_type()
107
+ ) # Despite the name, actually returns just the media-type
108
+
109
+
110
+ def utcnow():
111
+ """Returns the current UTC datetime.
112
+
113
+ Returns:
114
+ datetime: The current time in UTC.
115
+ """
116
+ # We used datetime.utcnow() before, since it's deprecated from python 3.12,
117
+ # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native
118
+ # (no timezone info), but "now()" is offset-aware (with timezone info).
119
+ # This will cause datetime comparison problem. For backward compatibility,
120
+ # we need to remove the timezone info.
121
+ now = datetime.datetime.now(datetime.timezone.utc)
122
+ now = now.replace(tzinfo=None)
123
+ return now
124
+
125
+
126
+ def datetime_to_secs(value):
127
+ """Convert a datetime object to the number of seconds since the UNIX epoch.
128
+
129
+ Args:
130
+ value (datetime): The datetime to convert.
131
+
132
+ Returns:
133
+ int: The number of seconds since the UNIX epoch.
134
+ """
135
+ return calendar.timegm(value.utctimetuple())
136
+
137
+
138
+ def to_bytes(value, encoding="utf-8"):
139
+ """Converts a string value to bytes, if necessary.
140
+
141
+ Args:
142
+ value (Union[str, bytes]): The value to be converted.
143
+ encoding (str): The encoding to use to convert unicode to bytes.
144
+ Defaults to "utf-8".
145
+
146
+ Returns:
147
+ bytes: The original value converted to bytes (if unicode) or as
148
+ passed in if it started out as bytes.
149
+
150
+ Raises:
151
+ google.auth.exceptions.InvalidValue: If the value could not be converted to bytes.
152
+ """
153
+ result = value.encode(encoding) if isinstance(value, str) else value
154
+ if isinstance(result, bytes):
155
+ return result
156
+ else:
157
+ raise exceptions.InvalidValue(
158
+ "{0!r} could not be converted to bytes".format(value)
159
+ )
160
+
161
+
162
+ def from_bytes(value):
163
+ """Converts bytes to a string value, if necessary.
164
+
165
+ Args:
166
+ value (Union[str, bytes]): The value to be converted.
167
+
168
+ Returns:
169
+ str: The original value converted to unicode (if bytes) or as passed in
170
+ if it started out as unicode.
171
+
172
+ Raises:
173
+ google.auth.exceptions.InvalidValue: If the value could not be converted to unicode.
174
+ """
175
+ result = value.decode("utf-8") if isinstance(value, bytes) else value
176
+ if isinstance(result, str):
177
+ return result
178
+ else:
179
+ raise exceptions.InvalidValue(
180
+ "{0!r} could not be converted to unicode".format(value)
181
+ )
182
+
183
+
184
+ def update_query(url, params, remove=None):
185
+ """Updates a URL's query parameters.
186
+
187
+ Replaces any current values if they are already present in the URL.
188
+
189
+ Args:
190
+ url (str): The URL to update.
191
+ params (Mapping[str, str]): A mapping of query parameter
192
+ keys to values.
193
+ remove (Sequence[str]): Parameters to remove from the query string.
194
+
195
+ Returns:
196
+ str: The URL with updated query parameters.
197
+
198
+ Examples:
199
+
200
+ >>> url = 'http://example.com?a=1'
201
+ >>> update_query(url, {'a': '2'})
202
+ http://example.com?a=2
203
+ >>> update_query(url, {'b': '3'})
204
+ http://example.com?a=1&b=3
205
+ >> update_query(url, {'b': '3'}, remove=['a'])
206
+ http://example.com?b=3
207
+
208
+ """
209
+ if remove is None:
210
+ remove = []
211
+
212
+ # Split the URL into parts.
213
+ parts = urllib.parse.urlparse(url)
214
+ # Parse the query string.
215
+ query_params = urllib.parse.parse_qs(parts.query)
216
+ # Update the query parameters with the new parameters.
217
+ query_params.update(params)
218
+ # Remove any values specified in remove.
219
+ query_params = {
220
+ key: value for key, value in query_params.items() if key not in remove
221
+ }
222
+ # Re-encoded the query string.
223
+ new_query = urllib.parse.urlencode(query_params, doseq=True)
224
+ # Unsplit the url.
225
+ new_parts = parts._replace(query=new_query)
226
+ return urllib.parse.urlunparse(new_parts)
227
+
228
+
229
+ def scopes_to_string(scopes):
230
+ """Converts scope value to a string suitable for sending to OAuth 2.0
231
+ authorization servers.
232
+
233
+ Args:
234
+ scopes (Sequence[str]): The sequence of scopes to convert.
235
+
236
+ Returns:
237
+ str: The scopes formatted as a single string.
238
+ """
239
+ return " ".join(scopes)
240
+
241
+
242
+ def string_to_scopes(scopes):
243
+ """Converts stringifed scopes value to a list.
244
+
245
+ Args:
246
+ scopes (Union[Sequence, str]): The string of space-separated scopes
247
+ to convert.
248
+ Returns:
249
+ Sequence(str): The separated scopes.
250
+ """
251
+ if not scopes:
252
+ return []
253
+
254
+ return scopes.split(" ")
255
+
256
+
257
+ def padded_urlsafe_b64decode(value):
258
+ """Decodes base64 strings lacking padding characters.
259
+
260
+ Google infrastructure tends to omit the base64 padding characters.
261
+
262
+ Args:
263
+ value (Union[str, bytes]): The encoded value.
264
+
265
+ Returns:
266
+ bytes: The decoded value
267
+ """
268
+ b64string = to_bytes(value)
269
+ padded = b64string + b"=" * (-len(b64string) % 4)
270
+ return base64.urlsafe_b64decode(padded)
271
+
272
+
273
+ def unpadded_urlsafe_b64encode(value):
274
+ """Encodes base64 strings removing any padding characters.
275
+
276
+ `rfc 7515`_ defines Base64url to NOT include any padding
277
+ characters, but the stdlib doesn't do that by default.
278
+
279
+ _rfc7515: https://tools.ietf.org/html/rfc7515#page-6
280
+
281
+ Args:
282
+ value (Union[str|bytes]): The bytes-like value to encode
283
+
284
+ Returns:
285
+ Union[str|bytes]: The encoded value
286
+ """
287
+ return base64.urlsafe_b64encode(value).rstrip(b"=")
288
+
289
+
290
+ def is_python_3():
291
+ """Check if the Python interpreter is Python 2 or 3.
292
+
293
+ Returns:
294
+ bool: True if the Python interpreter is Python 3 and False otherwise.
295
+ """
296
+ return sys.version_info > (3, 0)
297
+
298
+
299
+ def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]:
300
+ """
301
+ Hashes sensitive information within a dictionary.
302
+
303
+ Args:
304
+ data: The dictionary containing data to be processed.
305
+
306
+ Returns:
307
+ A new dictionary with sensitive values replaced by their SHA512 hashes.
308
+ If the input is a list, returns a list with each element recursively processed.
309
+ If the input is neither a dict nor a list, returns the type of the input as a string.
310
+
311
+ """
312
+ if isinstance(data, dict):
313
+ hashed_data: Dict[Any, Union[Optional[str], dict, list]] = {}
314
+ for key, value in data.items():
315
+ if key in _SENSITIVE_FIELDS and not isinstance(value, (dict, list)):
316
+ hashed_data[key] = _hash_value(value, key)
317
+ elif isinstance(value, (dict, list)):
318
+ hashed_data[key] = _hash_sensitive_info(value)
319
+ else:
320
+ hashed_data[key] = value
321
+ return hashed_data
322
+ elif isinstance(data, list):
323
+ hashed_list = []
324
+ for val in data:
325
+ hashed_list.append(_hash_sensitive_info(val))
326
+ return hashed_list
327
+ else:
328
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1701):
329
+ # Investigate and hash sensitive info before logging when the data type is
330
+ # not a dict or a list.
331
+ return str(type(data))
332
+
333
+
334
+ def _hash_value(value, field_name: str) -> Optional[str]:
335
+ """Hashes a value and returns a formatted hash string."""
336
+ if value is None:
337
+ return None
338
+ encoded_value = str(value).encode("utf-8")
339
+ hash_object = hashlib.sha512()
340
+ hash_object.update(encoded_value)
341
+ hex_digest = hash_object.hexdigest()
342
+ return f"hashed_{field_name}-{hex_digest}"
343
+
344
+
345
+ def _logger_configured(logger: logging.Logger) -> bool:
346
+ """Determines whether `logger` has non-default configuration
347
+
348
+ Args:
349
+ logger: The logger to check.
350
+
351
+ Returns:
352
+ bool: Whether the logger has any non-default configuration.
353
+ """
354
+ return (
355
+ logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
356
+ )
357
+
358
+
359
+ def is_logging_enabled(logger: logging.Logger) -> bool:
360
+ """
361
+ Checks if debug logging is enabled for the given logger.
362
+
363
+ Args:
364
+ logger: The logging.Logger instance to check.
365
+
366
+ Returns:
367
+ True if debug logging is enabled, False otherwise.
368
+ """
369
+ # NOTE: Log propagation to the root logger is disabled unless
370
+ # the base logger i.e. logging.getLogger("google") is
371
+ # explicitly configured by the end user. Ideally this
372
+ # needs to happen in the client layer (already does for GAPICs).
373
+ # However, this is implemented here to avoid logging
374
+ # (if a root logger is configured) when a version of google-auth
375
+ # which supports logging is used with:
376
+ # - an older version of a GAPIC which does not support logging.
377
+ # - Apiary client which does not support logging.
378
+ global _LOGGING_INITIALIZED
379
+ if not _LOGGING_INITIALIZED:
380
+ base_logger = logging.getLogger(_BASE_LOGGER_NAME)
381
+ if not _logger_configured(base_logger):
382
+ base_logger.propagate = False
383
+ _LOGGING_INITIALIZED = True
384
+
385
+ return logger.isEnabledFor(logging.DEBUG)
386
+
387
+
388
+ def request_log(
389
+ logger: logging.Logger,
390
+ method: str,
391
+ url: str,
392
+ body: Optional[bytes],
393
+ headers: Optional[Mapping[str, str]],
394
+ ) -> None:
395
+ """
396
+ Logs an HTTP request at the DEBUG level if logging is enabled.
397
+
398
+ Args:
399
+ logger: The logging.Logger instance to use.
400
+ method: The HTTP method (e.g., "GET", "POST").
401
+ url: The URL of the request.
402
+ body: The request body (can be None).
403
+ headers: The request headers (can be None).
404
+ """
405
+ if is_logging_enabled(logger):
406
+ content_type = (
407
+ headers["Content-Type"] if headers and "Content-Type" in headers else ""
408
+ )
409
+ json_body = _parse_request_body(body, content_type=content_type)
410
+ logged_body = _hash_sensitive_info(json_body)
411
+ logger.debug(
412
+ "Making request...",
413
+ extra={
414
+ "httpRequest": {
415
+ "method": method,
416
+ "url": url,
417
+ "body": logged_body,
418
+ "headers": headers,
419
+ }
420
+ },
421
+ )
422
+
423
+
424
+ def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any:
425
+ """
426
+ Parses a request body, handling bytes and string types, and different content types.
427
+
428
+ Args:
429
+ body (Optional[bytes]): The request body.
430
+ content_type (str): The content type of the request body, e.g., "application/json",
431
+ "application/x-www-form-urlencoded", or "text/plain". If empty, attempts
432
+ to parse as JSON.
433
+
434
+ Returns:
435
+ Parsed body (dict, str, or None).
436
+ - JSON: Decodes if content_type is "application/json" or None (fallback).
437
+ - URL-encoded: Parses if content_type is "application/x-www-form-urlencoded".
438
+ - Plain text: Returns string if content_type is "text/plain".
439
+ - None: Returns if body is None, UTF-8 decode fails, or content_type is unknown.
440
+ """
441
+ if body is None:
442
+ return None
443
+ try:
444
+ body_str = body.decode("utf-8")
445
+ except (UnicodeDecodeError, AttributeError):
446
+ return None
447
+ content_type = content_type.lower()
448
+ if not content_type or "application/json" in content_type:
449
+ try:
450
+ return json.loads(body_str)
451
+ except (json.JSONDecodeError, TypeError):
452
+ return body_str
453
+ if "application/x-www-form-urlencoded" in content_type:
454
+ parsed_query = urllib.parse.parse_qs(body_str)
455
+ result = {k: v[0] for k, v in parsed_query.items()}
456
+ return result
457
+ if "text/plain" in content_type:
458
+ return body_str
459
+ return None
460
+
461
+
462
+ def _parse_response(response: Any) -> Any:
463
+ """
464
+ Parses a response, attempting to decode JSON.
465
+
466
+ Args:
467
+ response: The response object to parse. This can be any type, but
468
+ it is expected to have a `json()` method if it contains JSON.
469
+
470
+ Returns:
471
+ The parsed response. If the response contains valid JSON, the
472
+ decoded JSON object (e.g., a dictionary or list) is returned.
473
+ If the response does not have a `json()` method or if the JSON
474
+ decoding fails, None is returned.
475
+ """
476
+ try:
477
+ json_response = response.json()
478
+ return json_response
479
+ except Exception:
480
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1744):
481
+ # Parse and return response payload as json based on different content types.
482
+ return None
483
+
484
+
485
+ def _response_log_base(logger: logging.Logger, parsed_response: Any) -> None:
486
+ """
487
+ Logs a parsed HTTP response at the DEBUG level.
488
+
489
+ This internal helper function takes a parsed response and logs it
490
+ using the provided logger. It also applies a hashing function to
491
+ potentially sensitive information before logging.
492
+
493
+ Args:
494
+ logger: The logging.Logger instance to use for logging.
495
+ parsed_response: The parsed HTTP response object (e.g., a dictionary,
496
+ list, or the original response if parsing failed).
497
+ """
498
+
499
+ logged_response = _hash_sensitive_info(parsed_response)
500
+ logger.debug("Response received...", extra={"httpResponse": logged_response})
501
+
502
+
503
+ def response_log(logger: logging.Logger, response: Any) -> None:
504
+ """
505
+ Logs an HTTP response at the DEBUG level if logging is enabled.
506
+
507
+ Args:
508
+ logger: The logging.Logger instance to use.
509
+ response: The HTTP response object to log.
510
+ """
511
+ if is_logging_enabled(logger):
512
+ json_response = _parse_response(response)
513
+ _response_log_base(logger, json_response)
lib/python3.10/site-packages/google/auth/_jwt_async.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """JSON Web Tokens
16
+
17
+ Provides support for creating (encoding) and verifying (decoding) JWTs,
18
+ especially JWTs generated and consumed by Google infrastructure.
19
+
20
+ See `rfc7519`_ for more details on JWTs.
21
+
22
+ To encode a JWT use :func:`encode`::
23
+
24
+ from google.auth import crypt
25
+ from google.auth import jwt_async
26
+
27
+ signer = crypt.Signer(private_key)
28
+ payload = {'some': 'payload'}
29
+ encoded = jwt_async.encode(signer, payload)
30
+
31
+ To decode a JWT and verify claims use :func:`decode`::
32
+
33
+ claims = jwt_async.decode(encoded, certs=public_certs)
34
+
35
+ You can also skip verification::
36
+
37
+ claims = jwt_async.decode(encoded, verify=False)
38
+
39
+ .. _rfc7519: https://tools.ietf.org/html/rfc7519
40
+
41
+
42
+ NOTE: This async support is experimental and marked internal. This surface may
43
+ change in minor releases.
44
+ """
45
+
46
+ from google.auth import _credentials_async
47
+ from google.auth import jwt
48
+
49
+
50
+ def encode(signer, payload, header=None, key_id=None):
51
+ """Make a signed JWT.
52
+
53
+ Args:
54
+ signer (google.auth.crypt.Signer): The signer used to sign the JWT.
55
+ payload (Mapping[str, str]): The JWT payload.
56
+ header (Mapping[str, str]): Additional JWT header payload.
57
+ key_id (str): The key id to add to the JWT header. If the
58
+ signer has a key id it will be used as the default. If this is
59
+ specified it will override the signer's key id.
60
+
61
+ Returns:
62
+ bytes: The encoded JWT.
63
+ """
64
+ return jwt.encode(signer, payload, header, key_id)
65
+
66
+
67
+ def decode(token, certs=None, verify=True, audience=None):
68
+ """Decode and verify a JWT.
69
+
70
+ Args:
71
+ token (str): The encoded JWT.
72
+ certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
73
+ certificate used to validate the JWT signature. If bytes or string,
74
+ it must the the public key certificate in PEM format. If a mapping,
75
+ it must be a mapping of key IDs to public key certificates in PEM
76
+ format. The mapping must contain the same key ID that's specified
77
+ in the token's header.
78
+ verify (bool): Whether to perform signature and claim validation.
79
+ Verification is done by default.
80
+ audience (str): The audience claim, 'aud', that this JWT should
81
+ contain. If None then the JWT's 'aud' parameter is not verified.
82
+
83
+ Returns:
84
+ Mapping[str, str]: The deserialized JSON payload in the JWT.
85
+
86
+ Raises:
87
+ ValueError: if any verification checks failed.
88
+ """
89
+
90
+ return jwt.decode(token, certs, verify, audience)
91
+
92
+
93
+ class Credentials(
94
+ jwt.Credentials, _credentials_async.Signing, _credentials_async.Credentials
95
+ ):
96
+ """Credentials that use a JWT as the bearer token.
97
+
98
+ These credentials require an "audience" claim. This claim identifies the
99
+ intended recipient of the bearer token.
100
+
101
+ The constructor arguments determine the claims for the JWT that is
102
+ sent with requests. Usually, you'll construct these credentials with
103
+ one of the helper constructors as shown in the next section.
104
+
105
+ To create JWT credentials using a Google service account private key
106
+ JSON file::
107
+
108
+ audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
109
+ credentials = jwt_async.Credentials.from_service_account_file(
110
+ 'service-account.json',
111
+ audience=audience)
112
+
113
+ If you already have the service account file loaded and parsed::
114
+
115
+ service_account_info = json.load(open('service_account.json'))
116
+ credentials = jwt_async.Credentials.from_service_account_info(
117
+ service_account_info,
118
+ audience=audience)
119
+
120
+ Both helper methods pass on arguments to the constructor, so you can
121
+ specify the JWT claims::
122
+
123
+ credentials = jwt_async.Credentials.from_service_account_file(
124
+ 'service-account.json',
125
+ audience=audience,
126
+ additional_claims={'meta': 'data'})
127
+
128
+ You can also construct the credentials directly if you have a
129
+ :class:`~google.auth.crypt.Signer` instance::
130
+
131
+ credentials = jwt_async.Credentials(
132
+ signer,
133
+ issuer='your-issuer',
134
+ subject='your-subject',
135
+ audience=audience)
136
+
137
+ The claims are considered immutable. If you want to modify the claims,
138
+ you can easily create another instance using :meth:`with_claims`::
139
+
140
+ new_audience = (
141
+ 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
142
+ new_credentials = credentials.with_claims(audience=new_audience)
143
+ """
144
+
145
+
146
+ class OnDemandCredentials(
147
+ jwt.OnDemandCredentials, _credentials_async.Signing, _credentials_async.Credentials
148
+ ):
149
+ """On-demand JWT credentials.
150
+
151
+ Like :class:`Credentials`, this class uses a JWT as the bearer token for
152
+ authentication. However, this class does not require the audience at
153
+ construction time. Instead, it will generate a new token on-demand for
154
+ each request using the request URI as the audience. It caches tokens
155
+ so that multiple requests to the same URI do not incur the overhead
156
+ of generating a new token every time.
157
+
158
+ This behavior is especially useful for `gRPC`_ clients. A gRPC service may
159
+ have multiple audience and gRPC clients may not know all of the audiences
160
+ required for accessing a particular service. With these credentials,
161
+ no knowledge of the audiences is required ahead of time.
162
+
163
+ .. _grpc: http://www.grpc.io/
164
+ """
lib/python3.10/site-packages/google/auth/_oauth2client.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for transitioning from oauth2client to google-auth.
16
+
17
+ .. warning::
18
+ This module is private as it is intended to assist first-party downstream
19
+ clients with the transition from oauth2client to google-auth.
20
+ """
21
+
22
+ from __future__ import absolute_import
23
+
24
+ from google.auth import _helpers
25
+ import google.auth.app_engine
26
+ import google.auth.compute_engine
27
+ import google.oauth2.credentials
28
+ import google.oauth2.service_account
29
+
30
+ try:
31
+ import oauth2client.client # type: ignore
32
+ import oauth2client.contrib.gce # type: ignore
33
+ import oauth2client.service_account # type: ignore
34
+ except ImportError as caught_exc:
35
+ raise ImportError("oauth2client is not installed.") from caught_exc
36
+
37
+ try:
38
+ import oauth2client.contrib.appengine # type: ignore
39
+
40
+ _HAS_APPENGINE = True
41
+ except ImportError:
42
+ _HAS_APPENGINE = False
43
+
44
+
45
+ _CONVERT_ERROR_TMPL = "Unable to convert {} to a google-auth credentials class."
46
+
47
+
48
+ def _convert_oauth2_credentials(credentials):
49
+ """Converts to :class:`google.oauth2.credentials.Credentials`.
50
+
51
+ Args:
52
+ credentials (Union[oauth2client.client.OAuth2Credentials,
53
+ oauth2client.client.GoogleCredentials]): The credentials to
54
+ convert.
55
+
56
+ Returns:
57
+ google.oauth2.credentials.Credentials: The converted credentials.
58
+ """
59
+ new_credentials = google.oauth2.credentials.Credentials(
60
+ token=credentials.access_token,
61
+ refresh_token=credentials.refresh_token,
62
+ token_uri=credentials.token_uri,
63
+ client_id=credentials.client_id,
64
+ client_secret=credentials.client_secret,
65
+ scopes=credentials.scopes,
66
+ )
67
+
68
+ new_credentials._expires = credentials.token_expiry
69
+
70
+ return new_credentials
71
+
72
+
73
+ def _convert_service_account_credentials(credentials):
74
+ """Converts to :class:`google.oauth2.service_account.Credentials`.
75
+
76
+ Args:
77
+ credentials (Union[
78
+ oauth2client.service_account.ServiceAccountCredentials,
79
+ oauth2client.service_account._JWTAccessCredentials]): The
80
+ credentials to convert.
81
+
82
+ Returns:
83
+ google.oauth2.service_account.Credentials: The converted credentials.
84
+ """
85
+ info = credentials.serialization_data.copy()
86
+ info["token_uri"] = credentials.token_uri
87
+ return google.oauth2.service_account.Credentials.from_service_account_info(info)
88
+
89
+
90
+ def _convert_gce_app_assertion_credentials(credentials):
91
+ """Converts to :class:`google.auth.compute_engine.Credentials`.
92
+
93
+ Args:
94
+ credentials (oauth2client.contrib.gce.AppAssertionCredentials): The
95
+ credentials to convert.
96
+
97
+ Returns:
98
+ google.oauth2.service_account.Credentials: The converted credentials.
99
+ """
100
+ return google.auth.compute_engine.Credentials(
101
+ service_account_email=credentials.service_account_email
102
+ )
103
+
104
+
105
+ def _convert_appengine_app_assertion_credentials(credentials):
106
+ """Converts to :class:`google.auth.app_engine.Credentials`.
107
+
108
+ Args:
109
+ credentials (oauth2client.contrib.app_engine.AppAssertionCredentials):
110
+ The credentials to convert.
111
+
112
+ Returns:
113
+ google.oauth2.service_account.Credentials: The converted credentials.
114
+ """
115
+ # pylint: disable=invalid-name
116
+ return google.auth.app_engine.Credentials(
117
+ scopes=_helpers.string_to_scopes(credentials.scope),
118
+ service_account_id=credentials.service_account_id,
119
+ )
120
+
121
+
122
+ _CLASS_CONVERSION_MAP = {
123
+ oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials,
124
+ oauth2client.client.GoogleCredentials: _convert_oauth2_credentials,
125
+ oauth2client.service_account.ServiceAccountCredentials: _convert_service_account_credentials,
126
+ oauth2client.service_account._JWTAccessCredentials: _convert_service_account_credentials,
127
+ oauth2client.contrib.gce.AppAssertionCredentials: _convert_gce_app_assertion_credentials,
128
+ }
129
+
130
+ if _HAS_APPENGINE:
131
+ _CLASS_CONVERSION_MAP[
132
+ oauth2client.contrib.appengine.AppAssertionCredentials
133
+ ] = _convert_appengine_app_assertion_credentials
134
+
135
+
136
+ def convert(credentials):
137
+ """Convert oauth2client credentials to google-auth credentials.
138
+
139
+ This class converts:
140
+
141
+ - :class:`oauth2client.client.OAuth2Credentials` to
142
+ :class:`google.oauth2.credentials.Credentials`.
143
+ - :class:`oauth2client.client.GoogleCredentials` to
144
+ :class:`google.oauth2.credentials.Credentials`.
145
+ - :class:`oauth2client.service_account.ServiceAccountCredentials` to
146
+ :class:`google.oauth2.service_account.Credentials`.
147
+ - :class:`oauth2client.service_account._JWTAccessCredentials` to
148
+ :class:`google.oauth2.service_account.Credentials`.
149
+ - :class:`oauth2client.contrib.gce.AppAssertionCredentials` to
150
+ :class:`google.auth.compute_engine.Credentials`.
151
+ - :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to
152
+ :class:`google.auth.app_engine.Credentials`.
153
+
154
+ Returns:
155
+ google.auth.credentials.Credentials: The converted credentials.
156
+
157
+ Raises:
158
+ ValueError: If the credentials could not be converted.
159
+ """
160
+
161
+ credentials_class = type(credentials)
162
+
163
+ try:
164
+ return _CLASS_CONVERSION_MAP[credentials_class](credentials)
165
+ except KeyError as caught_exc:
166
+ new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class))
167
+ raise new_exc from caught_exc
lib/python3.10/site-packages/google/auth/_refresh_worker.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import copy
16
+ import logging
17
+ import threading
18
+
19
+ import google.auth.exceptions as e
20
+
21
+ _LOGGER = logging.getLogger(__name__)
22
+
23
+
24
+ class RefreshThreadManager:
25
+ """
26
+ Organizes exactly one background job that refresh a token.
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initializes the manager."""
31
+
32
+ self._worker = None
33
+ self._lock = threading.Lock() # protects access to worker threads.
34
+
35
+ def start_refresh(self, cred, request):
36
+ """Starts a refresh thread for the given credentials.
37
+ The credentials are refreshed using the request parameter.
38
+ request and cred MUST not be None
39
+
40
+ Returns True if a background refresh was kicked off. False otherwise.
41
+
42
+ Args:
43
+ cred: A credentials object.
44
+ request: A request object.
45
+ Returns:
46
+ bool
47
+ """
48
+ if cred is None or request is None:
49
+ raise e.InvalidValue(
50
+ "Unable to start refresh. cred and request must be valid and instantiated objects."
51
+ )
52
+
53
+ with self._lock:
54
+ if self._worker is not None and self._worker._error_info is not None:
55
+ return False
56
+
57
+ if self._worker is None or not self._worker.is_alive(): # pragma: NO COVER
58
+ self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request))
59
+ self._worker.start()
60
+ return True
61
+
62
+ def clear_error(self):
63
+ """
64
+ Removes any errors that were stored from previous background refreshes.
65
+ """
66
+ with self._lock:
67
+ if self._worker:
68
+ self._worker._error_info = None
69
+
70
+ def __getstate__(self):
71
+ """Pickle helper that serializes the _lock attribute."""
72
+ state = self.__dict__.copy()
73
+ state["_lock"] = None
74
+ return state
75
+
76
+ def __setstate__(self, state):
77
+ """Pickle helper that deserializes the _lock attribute."""
78
+ state["_lock"] = threading.Lock()
79
+ self.__dict__.update(state)
80
+
81
+
82
+ class RefreshThread(threading.Thread):
83
+ """
84
+ Thread that refreshes credentials.
85
+ """
86
+
87
+ def __init__(self, cred, request, **kwargs):
88
+ """Initializes the thread.
89
+
90
+ Args:
91
+ cred: A Credential object to refresh.
92
+ request: A Request object used to perform a credential refresh.
93
+ **kwargs: Additional keyword arguments.
94
+ """
95
+
96
+ super().__init__(**kwargs)
97
+ self._cred = cred
98
+ self._request = request
99
+ self._error_info = None
100
+
101
+ def run(self):
102
+ """
103
+ Perform the credential refresh.
104
+ """
105
+ try:
106
+ self._cred.refresh(self._request)
107
+ except Exception as err: # pragma: NO COVER
108
+ _LOGGER.error(f"Background refresh failed due to: {err}")
109
+ self._error_info = err
lib/python3.10/site-packages/google/auth/_service_account_info.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helper functions for loading data from a Google service account file."""
16
+
17
+ import io
18
+ import json
19
+
20
+ from google.auth import crypt
21
+ from google.auth import exceptions
22
+
23
+
24
+ def from_dict(data, require=None, use_rsa_signer=True):
25
+ """Validates a dictionary containing Google service account data.
26
+
27
+ Creates and returns a :class:`google.auth.crypt.Signer` instance from the
28
+ private key specified in the data.
29
+
30
+ Args:
31
+ data (Mapping[str, str]): The service account data
32
+ require (Sequence[str]): List of keys required to be present in the
33
+ info.
34
+ use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer.
35
+ We use RSA signer by default.
36
+
37
+ Returns:
38
+ google.auth.crypt.Signer: A signer created from the private key in the
39
+ service account file.
40
+
41
+ Raises:
42
+ MalformedError: if the data was in the wrong format, or if one of the
43
+ required keys is missing.
44
+ """
45
+ keys_needed = set(require if require is not None else [])
46
+
47
+ missing = keys_needed.difference(data.keys())
48
+
49
+ if missing:
50
+ raise exceptions.MalformedError(
51
+ "Service account info was not in the expected format, missing "
52
+ "fields {}.".format(", ".join(missing))
53
+ )
54
+
55
+ # Create a signer.
56
+ if use_rsa_signer:
57
+ signer = crypt.RSASigner.from_service_account_info(data)
58
+ else:
59
+ signer = crypt.ES256Signer.from_service_account_info(data)
60
+
61
+ return signer
62
+
63
+
64
+ def from_filename(filename, require=None, use_rsa_signer=True):
65
+ """Reads a Google service account JSON file and returns its parsed info.
66
+
67
+ Args:
68
+ filename (str): The path to the service account .json file.
69
+ require (Sequence[str]): List of keys required to be present in the
70
+ info.
71
+ use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer.
72
+ We use RSA signer by default.
73
+
74
+ Returns:
75
+ Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified
76
+ info and a signer instance.
77
+ """
78
+ with io.open(filename, "r", encoding="utf-8") as json_file:
79
+ data = json.load(json_file)
80
+ return data, from_dict(data, require=require, use_rsa_signer=use_rsa_signer)
lib/python3.10/site-packages/google/auth/aio/__init__.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google Auth AIO Library for Python."""
16
+
17
+ import logging
18
+
19
+ from google.auth import version as google_auth_version
20
+
21
+
22
+ __version__ = google_auth_version.__version__
23
+
24
+ # Set default logging handler to avoid "No handler found" warnings.
25
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
lib/python3.10/site-packages/google/auth/aio/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (430 Bytes). View file
 
lib/python3.10/site-packages/google/auth/aio/__pycache__/_helpers.cpython-310.pyc ADDED
Binary file (1.51 kB). View file
 
lib/python3.10/site-packages/google/auth/aio/__pycache__/credentials.cpython-310.pyc ADDED
Binary file (5.7 kB). View file
 
lib/python3.10/site-packages/google/auth/aio/_helpers.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helper functions for commonly used utilities."""
16
+
17
+
18
+ import logging
19
+ from typing import Any
20
+
21
+ from google.auth import _helpers
22
+
23
+
24
+ async def _parse_response_async(response: Any) -> Any:
25
+ """
26
+ Parses an async response, attempting to decode JSON.
27
+
28
+ Args:
29
+ response: The response object to parse. This can be any type, but
30
+ it is expected to have a `json()` method if it contains JSON.
31
+
32
+ Returns:
33
+ The parsed response. If the response contains valid JSON, the
34
+ decoded JSON object (e.g., a dictionary) is returned.
35
+ If the response does not have a `json()` method or if the JSON
36
+ decoding fails, None is returned.
37
+ """
38
+ try:
39
+ json_response = await response.json()
40
+ return json_response
41
+ except Exception:
42
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1745):
43
+ # Parse and return response payload as json based on different content types.
44
+ return None
45
+
46
+
47
+ async def response_log_async(logger: logging.Logger, response: Any) -> None:
48
+ """
49
+ Logs an Async HTTP response at the DEBUG level if logging is enabled.
50
+
51
+ Args:
52
+ logger: The logging.Logger instance to use.
53
+ response: The HTTP response object to log.
54
+ """
55
+ if _helpers.is_logging_enabled(logger):
56
+ # TODO(https://github.com/googleapis/google-auth-library-python/issues/1755):
57
+ # Parsing the response for async streaming logging results in
58
+ # the stream to be empty downstream. For now, we will not be logging
59
+ # the response for async responses until we investigate further.
60
+ # json_response = await _parse_response_async(response)
61
+ json_response = None
62
+ _helpers._response_log_base(logger, json_response)
lib/python3.10/site-packages/google/auth/aio/credentials.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Interfaces for asynchronous credentials."""
17
+
18
+
19
+ from google.auth import _helpers
20
+ from google.auth import exceptions
21
+ from google.auth._credentials_base import _BaseCredentials
22
+
23
+
24
+ class Credentials(_BaseCredentials):
25
+ """Base class for all asynchronous credentials.
26
+
27
+ All credentials have a :attr:`token` that is used for authentication and
28
+ may also optionally set an :attr:`expiry` to indicate when the token will
29
+ no longer be valid.
30
+
31
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
32
+ Credentials can do this automatically before the first HTTP request in
33
+ :meth:`before_request`.
34
+
35
+ Although the token and expiration will change as the credentials are
36
+ :meth:`refreshed <refresh>` and used, credentials should be considered
37
+ immutable. Various credentials will accept configuration such as private
38
+ keys, scopes, and other options. These options are not changeable after
39
+ construction. Some classes will provide mechanisms to copy the credentials
40
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
41
+ """
42
+
43
+ def __init__(self):
44
+ super(Credentials, self).__init__()
45
+
46
+ async def apply(self, headers, token=None):
47
+ """Apply the token to the authentication header.
48
+
49
+ Args:
50
+ headers (Mapping): The HTTP request headers.
51
+ token (Optional[str]): If specified, overrides the current access
52
+ token.
53
+ """
54
+ self._apply(headers, token=token)
55
+
56
+ async def refresh(self, request):
57
+ """Refreshes the access token.
58
+
59
+ Args:
60
+ request (google.auth.aio.transport.Request): The object used to make
61
+ HTTP requests.
62
+
63
+ Raises:
64
+ google.auth.exceptions.RefreshError: If the credentials could
65
+ not be refreshed.
66
+ """
67
+ raise NotImplementedError("Refresh must be implemented")
68
+
69
+ async def before_request(self, request, method, url, headers):
70
+ """Performs credential-specific before request logic.
71
+
72
+ Refreshes the credentials if necessary, then calls :meth:`apply` to
73
+ apply the token to the authentication header.
74
+
75
+ Args:
76
+ request (google.auth.aio.transport.Request): The object used to make
77
+ HTTP requests.
78
+ method (str): The request's HTTP method or the RPC method being
79
+ invoked.
80
+ url (str): The request's URI or the RPC service's URI.
81
+ headers (Mapping): The request's headers.
82
+ """
83
+ await self.apply(headers)
84
+
85
+
86
+ class StaticCredentials(Credentials):
87
+ """Asynchronous Credentials representing an immutable access token.
88
+
89
+ The credentials are considered immutable except the tokens which can be
90
+ configured in the constructor ::
91
+
92
+ credentials = StaticCredentials(token="token123")
93
+
94
+ StaticCredentials does not support :meth `refresh` and assumes that the configured
95
+ token is valid and not expired. StaticCredentials will never attempt to
96
+ refresh the token.
97
+ """
98
+
99
+ def __init__(self, token):
100
+ """
101
+ Args:
102
+ token (str): The access token.
103
+ """
104
+ super(StaticCredentials, self).__init__()
105
+ self.token = token
106
+
107
+ @_helpers.copy_docstring(Credentials)
108
+ async def refresh(self, request):
109
+ raise exceptions.InvalidOperation("Static credentials cannot be refreshed.")
110
+
111
+ # Note: before_request should never try to refresh access tokens.
112
+ # StaticCredentials intentionally does not support it.
113
+ @_helpers.copy_docstring(Credentials)
114
+ async def before_request(self, request, method, url, headers):
115
+ await self.apply(headers)
116
+
117
+
118
+ class AnonymousCredentials(Credentials):
119
+ """Asynchronous Credentials that do not provide any authentication information.
120
+
121
+ These are useful in the case of services that support anonymous access or
122
+ local service emulators that do not use credentials.
123
+ """
124
+
125
+ async def refresh(self, request):
126
+ """Raises :class:``InvalidOperation``, anonymous credentials cannot be
127
+ refreshed."""
128
+ raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.")
129
+
130
+ async def apply(self, headers, token=None):
131
+ """Anonymous credentials do nothing to the request.
132
+
133
+ The optional ``token`` argument is not supported.
134
+
135
+ Raises:
136
+ google.auth.exceptions.InvalidValue: If a token was specified.
137
+ """
138
+ if token is not None:
139
+ raise exceptions.InvalidValue("Anonymous credentials don't support tokens.")
140
+
141
+ async def before_request(self, request, method, url, headers):
142
+ """Anonymous credentials do nothing to the request."""
143
+ pass
lib/python3.10/site-packages/google/auth/aio/transport/__init__.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Transport - Asynchronous HTTP client library support.
16
+
17
+ :mod:`google.auth.aio` is designed to work with various asynchronous client libraries such
18
+ as aiohttp. In order to work across these libraries with different
19
+ interfaces some abstraction is needed.
20
+
21
+ This module provides two interfaces that are implemented by transport adapters
22
+ to support HTTP libraries. :class:`Request` defines the interface expected by
23
+ :mod:`google.auth` to make asynchronous requests. :class:`Response` defines the interface
24
+ for the return value of :class:`Request`.
25
+ """
26
+
27
+ import abc
28
+ from typing import AsyncGenerator, Mapping, Optional
29
+
30
+ import google.auth.transport
31
+
32
+
33
+ _DEFAULT_TIMEOUT_SECONDS = 180
34
+
35
+ DEFAULT_RETRYABLE_STATUS_CODES = google.auth.transport.DEFAULT_RETRYABLE_STATUS_CODES
36
+ """Sequence[int]: HTTP status codes indicating a request can be retried.
37
+ """
38
+
39
+
40
+ DEFAULT_MAX_RETRY_ATTEMPTS = 3
41
+ """int: How many times to retry a request."""
42
+
43
+
44
+ class Response(metaclass=abc.ABCMeta):
45
+ """Asynchronous HTTP Response Interface."""
46
+
47
+ @property
48
+ @abc.abstractmethod
49
+ def status_code(self) -> int:
50
+ """
51
+ The HTTP response status code.
52
+
53
+ Returns:
54
+ int: The HTTP response status code.
55
+
56
+ """
57
+ raise NotImplementedError("status_code must be implemented.")
58
+
59
+ @property
60
+ @abc.abstractmethod
61
+ def headers(self) -> Mapping[str, str]:
62
+ """The HTTP response headers.
63
+
64
+ Returns:
65
+ Mapping[str, str]: The HTTP response headers.
66
+ """
67
+ raise NotImplementedError("headers must be implemented.")
68
+
69
+ @abc.abstractmethod
70
+ async def content(self, chunk_size: int) -> AsyncGenerator[bytes, None]:
71
+ """The raw response content.
72
+
73
+ Args:
74
+ chunk_size (int): The size of each chunk.
75
+
76
+ Yields:
77
+ AsyncGenerator[bytes, None]: An asynchronous generator yielding
78
+ response chunks as bytes.
79
+ """
80
+ raise NotImplementedError("content must be implemented.")
81
+
82
+ @abc.abstractmethod
83
+ async def read(self) -> bytes:
84
+ """Read the entire response content as bytes.
85
+
86
+ Returns:
87
+ bytes: The entire response content.
88
+ """
89
+ raise NotImplementedError("read must be implemented.")
90
+
91
+ @abc.abstractmethod
92
+ async def close(self):
93
+ """Close the response after it is fully consumed to resource."""
94
+ raise NotImplementedError("close must be implemented.")
95
+
96
+
97
+ class Request(metaclass=abc.ABCMeta):
98
+ """Interface for a callable that makes HTTP requests.
99
+
100
+ Specific transport implementations should provide an implementation of
101
+ this that adapts their specific request / response API.
102
+
103
+ .. automethod:: __call__
104
+ """
105
+
106
+ @abc.abstractmethod
107
+ async def __call__(
108
+ self,
109
+ url: str,
110
+ method: str,
111
+ body: Optional[bytes],
112
+ headers: Optional[Mapping[str, str]],
113
+ timeout: float,
114
+ **kwargs
115
+ ) -> Response:
116
+ """Make an HTTP request.
117
+
118
+ Args:
119
+ url (str): The URI to be requested.
120
+ method (str): The HTTP method to use for the request. Defaults
121
+ to 'GET'.
122
+ body (Optional[bytes]): The payload / body in HTTP request.
123
+ headers (Mapping[str, str]): Request headers.
124
+ timeout (float): The number of seconds to wait for a
125
+ response from the server. If not specified or if None, the
126
+ transport-specific default timeout will be used.
127
+ kwargs: Additional arguments passed on to the transport's
128
+ request method.
129
+
130
+ Returns:
131
+ google.auth.aio.transport.Response: The HTTP response.
132
+
133
+ Raises:
134
+ google.auth.exceptions.TransportError: If any exception occurred.
135
+ """
136
+ # pylint: disable=redundant-returns-doc, missing-raises-doc
137
+ # (pylint doesn't play well with abstract docstrings.)
138
+ raise NotImplementedError("__call__ must be implemented.")
139
+
140
+ async def close(self) -> None:
141
+ """
142
+ Close the underlying session.
143
+ """
144
+ raise NotImplementedError("close must be implemented.")
lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (4.56 kB). View file
 
lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/aiohttp.cpython-310.pyc ADDED
Binary file (6.78 kB). View file
 
lib/python3.10/site-packages/google/auth/aio/transport/__pycache__/sessions.cpython-310.pyc ADDED
Binary file (8.4 kB). View file
 
lib/python3.10/site-packages/google/auth/aio/transport/aiohttp.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Transport adapter for Asynchronous HTTP Requests based on aiohttp.
16
+ """
17
+
18
+ import asyncio
19
+ import logging
20
+ from typing import AsyncGenerator, Mapping, Optional
21
+
22
+ try:
23
+ import aiohttp # type: ignore
24
+ except ImportError as caught_exc: # pragma: NO COVER
25
+ raise ImportError(
26
+ "The aiohttp library is not installed from please install the aiohttp package to use the aiohttp transport."
27
+ ) from caught_exc
28
+
29
+ from google.auth import _helpers
30
+ from google.auth import exceptions
31
+ from google.auth.aio import _helpers as _helpers_async
32
+ from google.auth.aio import transport
33
+
34
+ _LOGGER = logging.getLogger(__name__)
35
+
36
+
37
+ class Response(transport.Response):
38
+ """
39
+ Represents an HTTP response and its data. It is returned by ``google.auth.aio.transport.sessions.AsyncAuthorizedSession``.
40
+
41
+ Args:
42
+ response (aiohttp.ClientResponse): An instance of aiohttp.ClientResponse.
43
+
44
+ Attributes:
45
+ status_code (int): The HTTP status code of the response.
46
+ headers (Mapping[str, str]): The HTTP headers of the response.
47
+ """
48
+
49
+ def __init__(self, response: aiohttp.ClientResponse):
50
+ self._response = response
51
+
52
+ @property
53
+ @_helpers.copy_docstring(transport.Response)
54
+ def status_code(self) -> int:
55
+ return self._response.status
56
+
57
+ @property
58
+ @_helpers.copy_docstring(transport.Response)
59
+ def headers(self) -> Mapping[str, str]:
60
+ return {key: value for key, value in self._response.headers.items()}
61
+
62
+ @_helpers.copy_docstring(transport.Response)
63
+ async def content(self, chunk_size: int = 1024) -> AsyncGenerator[bytes, None]:
64
+ try:
65
+ async for chunk in self._response.content.iter_chunked(
66
+ chunk_size
67
+ ): # pragma: no branch
68
+ yield chunk
69
+ except aiohttp.ClientPayloadError as exc:
70
+ raise exceptions.ResponseError(
71
+ "Failed to read from the payload stream."
72
+ ) from exc
73
+
74
+ @_helpers.copy_docstring(transport.Response)
75
+ async def read(self) -> bytes:
76
+ try:
77
+ return await self._response.read()
78
+ except aiohttp.ClientResponseError as exc:
79
+ raise exceptions.ResponseError("Failed to read the response body.") from exc
80
+
81
+ @_helpers.copy_docstring(transport.Response)
82
+ async def close(self):
83
+ self._response.close()
84
+
85
+
86
+ class Request(transport.Request):
87
+ """Asynchronous Requests request adapter.
88
+
89
+ This class is used internally for making requests using aiohttp
90
+ in a consistent way. If you use :class:`google.auth.aio.transport.sessions.AsyncAuthorizedSession`
91
+ you do not need to construct or use this class directly.
92
+
93
+ This class can be useful if you want to configure a Request callable
94
+ with a custom ``aiohttp.ClientSession`` in :class:`AuthorizedSession` or if
95
+ you want to manually refresh a :class:`~google.auth.aio.credentials.Credentials` instance::
96
+
97
+ import aiohttp
98
+ import google.auth.aio.transport.aiohttp
99
+
100
+ # Default example:
101
+ request = google.auth.aio.transport.aiohttp.Request()
102
+ await credentials.refresh(request)
103
+
104
+ # Custom aiohttp Session Example:
105
+ session = session=aiohttp.ClientSession(auto_decompress=False)
106
+ request = google.auth.aio.transport.aiohttp.Request(session=session)
107
+ auth_sesion = google.auth.aio.transport.sessions.AsyncAuthorizedSession(auth_request=request)
108
+
109
+ Args:
110
+ session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used
111
+ to make HTTP requests. If not specified, a session will be created.
112
+
113
+ .. automethod:: __call__
114
+ """
115
+
116
+ def __init__(self, session: aiohttp.ClientSession = None):
117
+ self._session = session
118
+ self._closed = False
119
+
120
+ async def __call__(
121
+ self,
122
+ url: str,
123
+ method: str = "GET",
124
+ body: Optional[bytes] = None,
125
+ headers: Optional[Mapping[str, str]] = None,
126
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
127
+ **kwargs,
128
+ ) -> transport.Response:
129
+ """
130
+ Make an HTTP request using aiohttp.
131
+
132
+ Args:
133
+ url (str): The URL to be requested.
134
+ method (Optional[str]):
135
+ The HTTP method to use for the request. Defaults to 'GET'.
136
+ body (Optional[bytes]):
137
+ The payload or body in HTTP request.
138
+ headers (Optional[Mapping[str, str]]):
139
+ Request headers.
140
+ timeout (float): The number of seconds to wait for a
141
+ response from the server. If not specified or if None, the
142
+ requests default timeout will be used.
143
+ kwargs: Additional arguments passed through to the underlying
144
+ aiohttp :meth:`aiohttp.Session.request` method.
145
+
146
+ Returns:
147
+ google.auth.aio.transport.Response: The HTTP response.
148
+
149
+ Raises:
150
+ - google.auth.exceptions.TransportError: If the request fails or if the session is closed.
151
+ - google.auth.exceptions.TimeoutError: If the request times out.
152
+ """
153
+
154
+ try:
155
+ if self._closed:
156
+ raise exceptions.TransportError("session is closed.")
157
+
158
+ if not self._session:
159
+ self._session = aiohttp.ClientSession()
160
+
161
+ client_timeout = aiohttp.ClientTimeout(total=timeout)
162
+ _helpers.request_log(_LOGGER, method, url, body, headers)
163
+ response = await self._session.request(
164
+ method,
165
+ url,
166
+ data=body,
167
+ headers=headers,
168
+ timeout=client_timeout,
169
+ **kwargs,
170
+ )
171
+ await _helpers_async.response_log_async(_LOGGER, response)
172
+ return Response(response)
173
+
174
+ except aiohttp.ClientError as caught_exc:
175
+ client_exc = exceptions.TransportError(f"Failed to send request to {url}.")
176
+ raise client_exc from caught_exc
177
+
178
+ except asyncio.TimeoutError as caught_exc:
179
+ timeout_exc = exceptions.TimeoutError(
180
+ f"Request timed out after {timeout} seconds."
181
+ )
182
+ raise timeout_exc from caught_exc
183
+
184
+ async def close(self) -> None:
185
+ """
186
+ Close the underlying aiohttp session to release the acquired resources.
187
+ """
188
+ if not self._closed and self._session:
189
+ await self._session.close()
190
+ self._closed = True
lib/python3.10/site-packages/google/auth/aio/transport/sessions.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ from contextlib import asynccontextmanager
17
+ import functools
18
+ import time
19
+ from typing import Mapping, Optional
20
+
21
+ from google.auth import _exponential_backoff, exceptions
22
+ from google.auth.aio import transport
23
+ from google.auth.aio.credentials import Credentials
24
+ from google.auth.exceptions import TimeoutError
25
+
26
+ try:
27
+ from google.auth.aio.transport.aiohttp import Request as AiohttpRequest
28
+
29
+ AIOHTTP_INSTALLED = True
30
+ except ImportError: # pragma: NO COVER
31
+ AIOHTTP_INSTALLED = False
32
+
33
+
34
+ @asynccontextmanager
35
+ async def timeout_guard(timeout):
36
+ """
37
+ timeout_guard is an asynchronous context manager to apply a timeout to an asynchronous block of code.
38
+
39
+ Args:
40
+ timeout (float): The time in seconds before the context manager times out.
41
+
42
+ Raises:
43
+ google.auth.exceptions.TimeoutError: If the code within the context exceeds the provided timeout.
44
+
45
+ Usage:
46
+ async with timeout_guard(10) as with_timeout:
47
+ await with_timeout(async_function())
48
+ """
49
+ start = time.monotonic()
50
+ total_timeout = timeout
51
+
52
+ def _remaining_time():
53
+ elapsed = time.monotonic() - start
54
+ remaining = total_timeout - elapsed
55
+ if remaining <= 0:
56
+ raise TimeoutError(
57
+ f"Context manager exceeded the configured timeout of {total_timeout}s."
58
+ )
59
+ return remaining
60
+
61
+ async def with_timeout(coro):
62
+ try:
63
+ remaining = _remaining_time()
64
+ response = await asyncio.wait_for(coro, remaining)
65
+ return response
66
+ except (asyncio.TimeoutError, TimeoutError) as e:
67
+ raise TimeoutError(
68
+ f"The operation {coro} exceeded the configured timeout of {total_timeout}s."
69
+ ) from e
70
+
71
+ try:
72
+ yield with_timeout
73
+
74
+ finally:
75
+ _remaining_time()
76
+
77
+
78
+ class AsyncAuthorizedSession:
79
+ """This is an asynchronous implementation of :class:`google.auth.requests.AuthorizedSession` class.
80
+ We utilize an instance of a class that implements :class:`google.auth.aio.transport.Request` configured
81
+ by the caller or otherwise default to `google.auth.aio.transport.aiohttp.Request` if the external aiohttp
82
+ package is installed.
83
+
84
+ A Requests Session class with credentials.
85
+
86
+ This class is used to perform asynchronous requests to API endpoints that require
87
+ authorization::
88
+
89
+ import aiohttp
90
+ from google.auth.aio.transport import sessions
91
+
92
+ async with sessions.AsyncAuthorizedSession(credentials) as authed_session:
93
+ response = await authed_session.request(
94
+ 'GET', 'https://www.googleapis.com/storage/v1/b')
95
+
96
+ The underlying :meth:`request` implementation handles adding the
97
+ credentials' headers to the request and refreshing credentials as needed.
98
+
99
+ Args:
100
+ credentials (google.auth.aio.credentials.Credentials):
101
+ The credentials to add to the request.
102
+ auth_request (Optional[google.auth.aio.transport.Request]):
103
+ An instance of a class that implements
104
+ :class:`~google.auth.aio.transport.Request` used to make requests
105
+ and refresh credentials. If not passed,
106
+ an instance of :class:`~google.auth.aio.transport.aiohttp.Request`
107
+ is created.
108
+
109
+ Raises:
110
+ - google.auth.exceptions.TransportError: If `auth_request` is `None`
111
+ and the external package `aiohttp` is not installed.
112
+ - google.auth.exceptions.InvalidType: If the provided credentials are
113
+ not of type `google.auth.aio.credentials.Credentials`.
114
+ """
115
+
116
+ def __init__(
117
+ self, credentials: Credentials, auth_request: Optional[transport.Request] = None
118
+ ):
119
+ if not isinstance(credentials, Credentials):
120
+ raise exceptions.InvalidType(
121
+ f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`"
122
+ )
123
+ self._credentials = credentials
124
+ _auth_request = auth_request
125
+ if not _auth_request and AIOHTTP_INSTALLED:
126
+ _auth_request = AiohttpRequest()
127
+ if _auth_request is None:
128
+ raise exceptions.TransportError(
129
+ "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value."
130
+ )
131
+ self._auth_request = _auth_request
132
+
133
+ async def request(
134
+ self,
135
+ method: str,
136
+ url: str,
137
+ data: Optional[bytes] = None,
138
+ headers: Optional[Mapping[str, str]] = None,
139
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
140
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
141
+ **kwargs,
142
+ ) -> transport.Response:
143
+ """
144
+ Args:
145
+ method (str): The http method used to make the request.
146
+ url (str): The URI to be requested.
147
+ data (Optional[bytes]): The payload or body in HTTP request.
148
+ headers (Optional[Mapping[str, str]]): Request headers.
149
+ timeout (float):
150
+ The amount of time in seconds to wait for the server response
151
+ with each individual request.
152
+ max_allowed_time (float):
153
+ If the method runs longer than this, a ``Timeout`` exception is
154
+ automatically raised. Unlike the ``timeout`` parameter, this
155
+ value applies to the total method execution time, even if
156
+ multiple requests are made under the hood.
157
+
158
+ Mind that it is not guaranteed that the timeout error is raised
159
+ at ``max_allowed_time``. It might take longer, for example, if
160
+ an underlying request takes a lot of time, but the request
161
+ itself does not timeout, e.g. if a large file is being
162
+ transmitted. The timout error will be raised after such
163
+ request completes.
164
+
165
+ Returns:
166
+ google.auth.aio.transport.Response: The HTTP response.
167
+
168
+ Raises:
169
+ google.auth.exceptions.TimeoutError: If the method does not complete within
170
+ the configured `max_allowed_time` or the request exceeds the configured
171
+ `timeout`.
172
+ """
173
+
174
+ retries = _exponential_backoff.AsyncExponentialBackoff(
175
+ total_attempts=transport.DEFAULT_MAX_RETRY_ATTEMPTS
176
+ )
177
+ async with timeout_guard(max_allowed_time) as with_timeout:
178
+ await with_timeout(
179
+ # Note: before_request will attempt to refresh credentials if expired.
180
+ self._credentials.before_request(
181
+ self._auth_request, method, url, headers
182
+ )
183
+ )
184
+ # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch`
185
+ # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372
186
+ async for _ in retries: # pragma: no branch
187
+ response = await with_timeout(
188
+ self._auth_request(url, method, data, headers, timeout, **kwargs)
189
+ )
190
+ if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES:
191
+ break
192
+ return response
193
+
194
+ @functools.wraps(request)
195
+ async def get(
196
+ self,
197
+ url: str,
198
+ data: Optional[bytes] = None,
199
+ headers: Optional[Mapping[str, str]] = None,
200
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
201
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
202
+ **kwargs,
203
+ ) -> transport.Response:
204
+ return await self.request(
205
+ "GET", url, data, headers, max_allowed_time, timeout, **kwargs
206
+ )
207
+
208
+ @functools.wraps(request)
209
+ async def post(
210
+ self,
211
+ url: str,
212
+ data: Optional[bytes] = None,
213
+ headers: Optional[Mapping[str, str]] = None,
214
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
215
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
216
+ **kwargs,
217
+ ) -> transport.Response:
218
+ return await self.request(
219
+ "POST", url, data, headers, max_allowed_time, timeout, **kwargs
220
+ )
221
+
222
+ @functools.wraps(request)
223
+ async def put(
224
+ self,
225
+ url: str,
226
+ data: Optional[bytes] = None,
227
+ headers: Optional[Mapping[str, str]] = None,
228
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
229
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
230
+ **kwargs,
231
+ ) -> transport.Response:
232
+ return await self.request(
233
+ "PUT", url, data, headers, max_allowed_time, timeout, **kwargs
234
+ )
235
+
236
+ @functools.wraps(request)
237
+ async def patch(
238
+ self,
239
+ url: str,
240
+ data: Optional[bytes] = None,
241
+ headers: Optional[Mapping[str, str]] = None,
242
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
243
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
244
+ **kwargs,
245
+ ) -> transport.Response:
246
+ return await self.request(
247
+ "PATCH", url, data, headers, max_allowed_time, timeout, **kwargs
248
+ )
249
+
250
+ @functools.wraps(request)
251
+ async def delete(
252
+ self,
253
+ url: str,
254
+ data: Optional[bytes] = None,
255
+ headers: Optional[Mapping[str, str]] = None,
256
+ max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS,
257
+ timeout: float = transport._DEFAULT_TIMEOUT_SECONDS,
258
+ **kwargs,
259
+ ) -> transport.Response:
260
+ return await self.request(
261
+ "DELETE", url, data, headers, max_allowed_time, timeout, **kwargs
262
+ )
263
+
264
+ async def close(self) -> None:
265
+ """
266
+ Close the underlying auth request session.
267
+ """
268
+ await self._auth_request.close()
lib/python3.10/site-packages/google/auth/api_key.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google API key support.
16
+ This module provides authentication using the `API key`_.
17
+ .. _API key:
18
+ https://cloud.google.com/docs/authentication/api-keys/
19
+ """
20
+
21
+ from google.auth import _helpers
22
+ from google.auth import credentials
23
+ from google.auth import exceptions
24
+
25
+
26
+ class Credentials(credentials.Credentials):
27
+ """API key credentials.
28
+ These credentials use API key to provide authorization to applications.
29
+ """
30
+
31
+ def __init__(self, token):
32
+ """
33
+ Args:
34
+ token (str): API key string
35
+ Raises:
36
+ ValueError: If the provided API key is not a non-empty string.
37
+ """
38
+ super(Credentials, self).__init__()
39
+ if not token:
40
+ raise exceptions.InvalidValue("Token must be a non-empty API key string")
41
+ self.token = token
42
+
43
+ @property
44
+ def expired(self):
45
+ return False
46
+
47
+ @property
48
+ def valid(self):
49
+ return True
50
+
51
+ @_helpers.copy_docstring(credentials.Credentials)
52
+ def refresh(self, request):
53
+ return
54
+
55
+ def apply(self, headers, token=None):
56
+ """Apply the API key token to the x-goog-api-key header.
57
+ Args:
58
+ headers (Mapping): The HTTP request headers.
59
+ token (Optional[str]): If specified, overrides the current access
60
+ token.
61
+ """
62
+ headers["x-goog-api-key"] = token or self.token
63
+
64
+ def before_request(self, request, method, url, headers):
65
+ """Performs credential-specific before request logic.
66
+ Refreshes the credentials if necessary, then calls :meth:`apply` to
67
+ apply the token to the x-goog-api-key header.
68
+ Args:
69
+ request (google.auth.transport.Request): The object used to make
70
+ HTTP requests.
71
+ method (str): The request's HTTP method or the RPC method being
72
+ invoked.
73
+ url (str): The request's URI or the RPC service's URI.
74
+ headers (Mapping): The request's headers.
75
+ """
76
+ self.apply(headers)
lib/python3.10/site-packages/google/auth/app_engine.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google App Engine standard environment support.
16
+
17
+ This module provides authentication and signing for applications running on App
18
+ Engine in the standard environment using the `App Identity API`_.
19
+
20
+
21
+ .. _App Identity API:
22
+ https://cloud.google.com/appengine/docs/python/appidentity/
23
+ """
24
+
25
+ import datetime
26
+
27
+ from google.auth import _helpers
28
+ from google.auth import credentials
29
+ from google.auth import crypt
30
+ from google.auth import exceptions
31
+
32
+ # pytype: disable=import-error
33
+ try:
34
+ from google.appengine.api import app_identity # type: ignore
35
+ except ImportError:
36
+ app_identity = None # type: ignore
37
+ # pytype: enable=import-error
38
+
39
+
40
+ class Signer(crypt.Signer):
41
+ """Signs messages using the App Engine App Identity service.
42
+
43
+ This can be used in place of :class:`google.auth.crypt.Signer` when
44
+ running in the App Engine standard environment.
45
+ """
46
+
47
+ @property
48
+ def key_id(self):
49
+ """Optional[str]: The key ID used to identify this private key.
50
+
51
+ .. warning::
52
+ This is always ``None``. The key ID used by App Engine can not
53
+ be reliably determined ahead of time.
54
+ """
55
+ return None
56
+
57
+ @_helpers.copy_docstring(crypt.Signer)
58
+ def sign(self, message):
59
+ message = _helpers.to_bytes(message)
60
+ _, signature = app_identity.sign_blob(message)
61
+ return signature
62
+
63
+
64
+ def get_project_id():
65
+ """Gets the project ID for the current App Engine application.
66
+
67
+ Returns:
68
+ str: The project ID
69
+
70
+ Raises:
71
+ google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
72
+ """
73
+ # pylint: disable=missing-raises-doc
74
+ # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
75
+ # realize it's a valid alias.
76
+ if app_identity is None:
77
+ raise exceptions.OSError("The App Engine APIs are not available.")
78
+ return app_identity.get_application_id()
79
+
80
+
81
+ class Credentials(
82
+ credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject
83
+ ):
84
+ """App Engine standard environment credentials.
85
+
86
+ These credentials use the App Engine App Identity API to obtain access
87
+ tokens.
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ scopes=None,
93
+ default_scopes=None,
94
+ service_account_id=None,
95
+ quota_project_id=None,
96
+ ):
97
+ """
98
+ Args:
99
+ scopes (Sequence[str]): Scopes to request from the App Identity
100
+ API.
101
+ default_scopes (Sequence[str]): Default scopes passed by a
102
+ Google client library. Use 'scopes' for user-defined scopes.
103
+ service_account_id (str): The service account ID passed into
104
+ :func:`google.appengine.api.app_identity.get_access_token`.
105
+ If not specified, the default application service account
106
+ ID will be used.
107
+ quota_project_id (Optional[str]): The project ID used for quota
108
+ and billing.
109
+
110
+ Raises:
111
+ google.auth.exceptions.OSError: If the App Engine APIs are unavailable.
112
+ """
113
+ # pylint: disable=missing-raises-doc
114
+ # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't
115
+ # realize it's a valid alias.
116
+ if app_identity is None:
117
+ raise exceptions.OSError("The App Engine APIs are not available.")
118
+
119
+ super(Credentials, self).__init__()
120
+ self._scopes = scopes
121
+ self._default_scopes = default_scopes
122
+ self._service_account_id = service_account_id
123
+ self._signer = Signer()
124
+ self._quota_project_id = quota_project_id
125
+
126
+ @_helpers.copy_docstring(credentials.Credentials)
127
+ def refresh(self, request):
128
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
129
+ # pylint: disable=unused-argument
130
+ token, ttl = app_identity.get_access_token(scopes, self._service_account_id)
131
+ expiry = datetime.datetime.utcfromtimestamp(ttl)
132
+
133
+ self.token, self.expiry = token, expiry
134
+
135
+ @property
136
+ def service_account_email(self):
137
+ """The service account email."""
138
+ if self._service_account_id is None:
139
+ self._service_account_id = app_identity.get_service_account_name()
140
+ return self._service_account_id
141
+
142
+ @property
143
+ def requires_scopes(self):
144
+ """Checks if the credentials requires scopes.
145
+
146
+ Returns:
147
+ bool: True if there are no scopes set otherwise False.
148
+ """
149
+ return not self._scopes and not self._default_scopes
150
+
151
+ @_helpers.copy_docstring(credentials.Scoped)
152
+ def with_scopes(self, scopes, default_scopes=None):
153
+ return self.__class__(
154
+ scopes=scopes,
155
+ default_scopes=default_scopes,
156
+ service_account_id=self._service_account_id,
157
+ quota_project_id=self.quota_project_id,
158
+ )
159
+
160
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
161
+ def with_quota_project(self, quota_project_id):
162
+ return self.__class__(
163
+ scopes=self._scopes,
164
+ service_account_id=self._service_account_id,
165
+ quota_project_id=quota_project_id,
166
+ )
167
+
168
+ @_helpers.copy_docstring(credentials.Signing)
169
+ def sign_bytes(self, message):
170
+ return self._signer.sign(message)
171
+
172
+ @property # type: ignore
173
+ @_helpers.copy_docstring(credentials.Signing)
174
+ def signer_email(self):
175
+ return self.service_account_email
176
+
177
+ @property # type: ignore
178
+ @_helpers.copy_docstring(credentials.Signing)
179
+ def signer(self):
180
+ return self._signer
lib/python3.10/site-packages/google/auth/aws.py ADDED
@@ -0,0 +1,861 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """AWS Credentials and AWS Signature V4 Request Signer.
16
+
17
+ This module provides credentials to access Google Cloud resources from Amazon
18
+ Web Services (AWS) workloads. These credentials are recommended over the
19
+ use of service account credentials in AWS as they do not involve the management
20
+ of long-live service account private keys.
21
+
22
+ AWS Credentials are initialized using external_account arguments which are
23
+ typically loaded from the external credentials JSON file.
24
+
25
+ This module also provides a definition for an abstract AWS security credentials supplier.
26
+ This supplier can be implemented to return valid AWS security credentials and an AWS region
27
+ and used to create AWS credentials. The credentials will then call the
28
+ supplier instead of using pre-defined methods such as calling the EC2 metadata endpoints.
29
+
30
+ This module also provides a basic implementation of the
31
+ `AWS Signature Version 4`_ request signing algorithm.
32
+
33
+ AWS Credentials use serialized signed requests to the
34
+ `AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens
35
+ via the GCP STS endpoint.
36
+
37
+ .. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
38
+ .. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
39
+ """
40
+
41
+ import abc
42
+ from dataclasses import dataclass
43
+ import hashlib
44
+ import hmac
45
+ import http.client as http_client
46
+ import json
47
+ import os
48
+ import posixpath
49
+ import re
50
+ from typing import Optional
51
+ import urllib
52
+ from urllib.parse import urljoin
53
+
54
+ from google.auth import _helpers
55
+ from google.auth import environment_vars
56
+ from google.auth import exceptions
57
+ from google.auth import external_account
58
+
59
+ # AWS Signature Version 4 signing algorithm identifier.
60
+ _AWS_ALGORITHM = "AWS4-HMAC-SHA256"
61
+ # The termination string for the AWS credential scope value as defined in
62
+ # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
63
+ _AWS_REQUEST_TYPE = "aws4_request"
64
+ # The AWS authorization header name for the security session token if available.
65
+ _AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token"
66
+ # The AWS authorization header name for the auto-generated date.
67
+ _AWS_DATE_HEADER = "x-amz-date"
68
+ # The default AWS regional credential verification URL.
69
+ _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = (
70
+ "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
71
+ )
72
+ # IMDSV2 session token lifetime. This is set to a low value because the session token is used immediately.
73
+ _IMDSV2_SESSION_TOKEN_TTL_SECONDS = "300"
74
+
75
+
76
+ class RequestSigner(object):
77
+ """Implements an AWS request signer based on the AWS Signature Version 4 signing
78
+ process.
79
+ https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
80
+ """
81
+
82
+ def __init__(self, region_name):
83
+ """Instantiates an AWS request signer used to compute authenticated signed
84
+ requests to AWS APIs based on the AWS Signature Version 4 signing process.
85
+
86
+ Args:
87
+ region_name (str): The AWS region to use.
88
+ """
89
+
90
+ self._region_name = region_name
91
+
92
+ def get_request_options(
93
+ self,
94
+ aws_security_credentials,
95
+ url,
96
+ method,
97
+ request_payload="",
98
+ additional_headers={},
99
+ ):
100
+ """Generates the signed request for the provided HTTP request for calling
101
+ an AWS API. This follows the steps described at:
102
+ https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
103
+
104
+ Args:
105
+ aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.
106
+ url (str): The AWS service URL containing the canonical URI and
107
+ query string.
108
+ method (str): The HTTP method used to call this API.
109
+ request_payload (Optional[str]): The optional request payload if
110
+ available.
111
+ additional_headers (Optional[Mapping[str, str]]): The optional
112
+ additional headers needed for the requested AWS API.
113
+
114
+ Returns:
115
+ Mapping[str, str]: The AWS signed request dictionary object.
116
+ """
117
+
118
+ additional_headers = additional_headers or {}
119
+
120
+ uri = urllib.parse.urlparse(url)
121
+ # Normalize the URL path. This is needed for the canonical_uri.
122
+ # os.path.normpath can't be used since it normalizes "/" paths
123
+ # to "\\" in Windows OS.
124
+ normalized_uri = urllib.parse.urlparse(
125
+ urljoin(url, posixpath.normpath(uri.path))
126
+ )
127
+ # Validate provided URL.
128
+ if not uri.hostname or uri.scheme != "https":
129
+ raise exceptions.InvalidResource("Invalid AWS service URL")
130
+
131
+ header_map = _generate_authentication_header_map(
132
+ host=uri.hostname,
133
+ canonical_uri=normalized_uri.path or "/",
134
+ canonical_querystring=_get_canonical_querystring(uri.query),
135
+ method=method,
136
+ region=self._region_name,
137
+ aws_security_credentials=aws_security_credentials,
138
+ request_payload=request_payload,
139
+ additional_headers=additional_headers,
140
+ )
141
+ headers = {
142
+ "Authorization": header_map.get("authorization_header"),
143
+ "host": uri.hostname,
144
+ }
145
+ # Add x-amz-date if available.
146
+ if "amz_date" in header_map:
147
+ headers[_AWS_DATE_HEADER] = header_map.get("amz_date")
148
+ # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.
149
+ for key in additional_headers:
150
+ headers[key] = additional_headers[key]
151
+
152
+ # Add session token if available.
153
+ if aws_security_credentials.session_token is not None:
154
+ headers[_AWS_SECURITY_TOKEN_HEADER] = aws_security_credentials.session_token
155
+
156
+ signed_request = {"url": url, "method": method, "headers": headers}
157
+ if request_payload:
158
+ signed_request["data"] = request_payload
159
+ return signed_request
160
+
161
+
162
+ def _get_canonical_querystring(query):
163
+ """Generates the canonical query string given a raw query string.
164
+ Logic is based on
165
+ https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
166
+
167
+ Args:
168
+ query (str): The raw query string.
169
+
170
+ Returns:
171
+ str: The canonical query string.
172
+ """
173
+ # Parse raw query string.
174
+ querystring = urllib.parse.parse_qs(query)
175
+ querystring_encoded_map = {}
176
+ for key in querystring:
177
+ quote_key = urllib.parse.quote(key, safe="-_.~")
178
+ # URI encode key.
179
+ querystring_encoded_map[quote_key] = []
180
+ for item in querystring[key]:
181
+ # For each key, URI encode all values for that key.
182
+ querystring_encoded_map[quote_key].append(
183
+ urllib.parse.quote(item, safe="-_.~")
184
+ )
185
+ # Sort values for each key.
186
+ querystring_encoded_map[quote_key].sort()
187
+ # Sort keys.
188
+ sorted_keys = list(querystring_encoded_map.keys())
189
+ sorted_keys.sort()
190
+ # Reconstruct the query string. Preserve keys with multiple values.
191
+ querystring_encoded_pairs = []
192
+ for key in sorted_keys:
193
+ for item in querystring_encoded_map[key]:
194
+ querystring_encoded_pairs.append("{}={}".format(key, item))
195
+ return "&".join(querystring_encoded_pairs)
196
+
197
+
198
+ def _sign(key, msg):
199
+ """Creates the HMAC-SHA256 hash of the provided message using the provided
200
+ key.
201
+
202
+ Args:
203
+ key (str): The HMAC-SHA256 key to use.
204
+ msg (str): The message to hash.
205
+
206
+ Returns:
207
+ str: The computed hash bytes.
208
+ """
209
+ return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
210
+
211
+
212
+ def _get_signing_key(key, date_stamp, region_name, service_name):
213
+ """Calculates the signing key used to calculate the signature for
214
+ AWS Signature Version 4 based on:
215
+ https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
216
+
217
+ Args:
218
+ key (str): The AWS secret access key.
219
+ date_stamp (str): The '%Y%m%d' date format.
220
+ region_name (str): The AWS region.
221
+ service_name (str): The AWS service name, eg. sts.
222
+
223
+ Returns:
224
+ str: The signing key bytes.
225
+ """
226
+ k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp)
227
+ k_region = _sign(k_date, region_name)
228
+ k_service = _sign(k_region, service_name)
229
+ k_signing = _sign(k_service, "aws4_request")
230
+ return k_signing
231
+
232
+
233
+ def _generate_authentication_header_map(
234
+ host,
235
+ canonical_uri,
236
+ canonical_querystring,
237
+ method,
238
+ region,
239
+ aws_security_credentials,
240
+ request_payload="",
241
+ additional_headers={},
242
+ ):
243
+ """Generates the authentication header map needed for generating the AWS
244
+ Signature Version 4 signed request.
245
+
246
+ Args:
247
+ host (str): The AWS service URL hostname.
248
+ canonical_uri (str): The AWS service URL path name.
249
+ canonical_querystring (str): The AWS service URL query string.
250
+ method (str): The HTTP method used to call this API.
251
+ region (str): The AWS region.
252
+ aws_security_credentials (AWSSecurityCredentials): The AWS security credentials.
253
+ request_payload (Optional[str]): The optional request payload if
254
+ available.
255
+ additional_headers (Optional[Mapping[str, str]]): The optional
256
+ additional headers needed for the requested AWS API.
257
+
258
+ Returns:
259
+ Mapping[str, str]: The AWS authentication header dictionary object.
260
+ This contains the x-amz-date and authorization header information.
261
+ """
262
+ # iam.amazonaws.com host => iam service.
263
+ # sts.us-east-2.amazonaws.com host => sts service.
264
+ service_name = host.split(".")[0]
265
+
266
+ current_time = _helpers.utcnow()
267
+ amz_date = current_time.strftime("%Y%m%dT%H%M%SZ")
268
+ date_stamp = current_time.strftime("%Y%m%d")
269
+
270
+ # Change all additional headers to be lower case.
271
+ full_headers = {}
272
+ for key in additional_headers:
273
+ full_headers[key.lower()] = additional_headers[key]
274
+ # Add AWS session token if available.
275
+ if aws_security_credentials.session_token is not None:
276
+ full_headers[
277
+ _AWS_SECURITY_TOKEN_HEADER
278
+ ] = aws_security_credentials.session_token
279
+
280
+ # Required headers
281
+ full_headers["host"] = host
282
+ # Do not use generated x-amz-date if the date header is provided.
283
+ # Previously the date was not fixed with x-amz- and could be provided
284
+ # manually.
285
+ # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
286
+ if "date" not in full_headers:
287
+ full_headers[_AWS_DATE_HEADER] = amz_date
288
+
289
+ # Header keys need to be sorted alphabetically.
290
+ canonical_headers = ""
291
+ header_keys = list(full_headers.keys())
292
+ header_keys.sort()
293
+ for key in header_keys:
294
+ canonical_headers = "{}{}:{}\n".format(
295
+ canonical_headers, key, full_headers[key]
296
+ )
297
+ signed_headers = ";".join(header_keys)
298
+
299
+ payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest()
300
+
301
+ # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
302
+ canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format(
303
+ method,
304
+ canonical_uri,
305
+ canonical_querystring,
306
+ canonical_headers,
307
+ signed_headers,
308
+ payload_hash,
309
+ )
310
+
311
+ credential_scope = "{}/{}/{}/{}".format(
312
+ date_stamp, region, service_name, _AWS_REQUEST_TYPE
313
+ )
314
+
315
+ # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
316
+ string_to_sign = "{}\n{}\n{}\n{}".format(
317
+ _AWS_ALGORITHM,
318
+ amz_date,
319
+ credential_scope,
320
+ hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
321
+ )
322
+
323
+ # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
324
+ signing_key = _get_signing_key(
325
+ aws_security_credentials.secret_access_key, date_stamp, region, service_name
326
+ )
327
+ signature = hmac.new(
328
+ signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
329
+ ).hexdigest()
330
+
331
+ # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
332
+ authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format(
333
+ _AWS_ALGORITHM,
334
+ aws_security_credentials.access_key_id,
335
+ credential_scope,
336
+ signed_headers,
337
+ signature,
338
+ )
339
+
340
+ authentication_header = {"authorization_header": authorization_header}
341
+ # Do not use generated x-amz-date if the date header is provided.
342
+ if "date" not in full_headers:
343
+ authentication_header["amz_date"] = amz_date
344
+ return authentication_header
345
+
346
+
347
+ @dataclass
348
+ class AwsSecurityCredentials:
349
+ """A class that models AWS security credentials with an optional session token.
350
+
351
+ Attributes:
352
+ access_key_id (str): The AWS security credentials access key id.
353
+ secret_access_key (str): The AWS security credentials secret access key.
354
+ session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials.
355
+ """
356
+
357
+ access_key_id: str
358
+ secret_access_key: str
359
+ session_token: Optional[str] = None
360
+
361
+
362
+ class AwsSecurityCredentialsSupplier(metaclass=abc.ABCMeta):
363
+ """Base class for AWS security credential suppliers. This can be implemented with custom logic to retrieve
364
+ AWS security credentials to exchange for a Google Cloud access token. The AWS external account credential does
365
+ not cache the AWS security credentials, so caching logic should be added in the implementation.
366
+ """
367
+
368
+ @abc.abstractmethod
369
+ def get_aws_security_credentials(self, context, request):
370
+ """Returns the AWS security credentials for the requested context.
371
+
372
+ .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier.
373
+
374
+ Args:
375
+ context (google.auth.externalaccount.SupplierContext): The context object
376
+ containing information about the requested audience and subject token type.
377
+ request (google.auth.transport.Request): The object used to make
378
+ HTTP requests.
379
+
380
+ Raises:
381
+ google.auth.exceptions.RefreshError: If an error is encountered during
382
+ security credential retrieval logic.
383
+
384
+ Returns:
385
+ AwsSecurityCredentials: The requested AWS security credentials.
386
+ """
387
+ raise NotImplementedError("")
388
+
389
+ @abc.abstractmethod
390
+ def get_aws_region(self, context, request):
391
+ """Returns the AWS region for the requested context.
392
+
393
+ Args:
394
+ context (google.auth.externalaccount.SupplierContext): The context object
395
+ containing information about the requested audience and subject token type.
396
+ request (google.auth.transport.Request): The object used to make
397
+ HTTP requests.
398
+
399
+ Raises:
400
+ google.auth.exceptions.RefreshError: If an error is encountered during
401
+ region retrieval logic.
402
+
403
+ Returns:
404
+ str: The AWS region.
405
+ """
406
+ raise NotImplementedError("")
407
+
408
+
409
+ class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier):
410
+ """Default implementation of AWS security credentials supplier. Supports retrieving
411
+ credentials and region via EC2 metadata endpoints and environment variables.
412
+ """
413
+
414
+ def __init__(self, credential_source):
415
+ self._region_url = credential_source.get("region_url")
416
+ self._security_credentials_url = credential_source.get("url")
417
+ self._imdsv2_session_token_url = credential_source.get(
418
+ "imdsv2_session_token_url"
419
+ )
420
+
421
+ @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)
422
+ def get_aws_security_credentials(self, context, request):
423
+
424
+ # Check environment variables for permanent credentials first.
425
+ # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
426
+ env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID)
427
+ env_aws_secret_access_key = os.environ.get(
428
+ environment_vars.AWS_SECRET_ACCESS_KEY
429
+ )
430
+ # This is normally not available for permanent credentials.
431
+ env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN)
432
+ if env_aws_access_key_id and env_aws_secret_access_key:
433
+ return AwsSecurityCredentials(
434
+ env_aws_access_key_id, env_aws_secret_access_key, env_aws_session_token
435
+ )
436
+
437
+ imdsv2_session_token = self._get_imdsv2_session_token(request)
438
+ role_name = self._get_metadata_role_name(request, imdsv2_session_token)
439
+
440
+ # Get security credentials.
441
+ credentials = self._get_metadata_security_credentials(
442
+ request, role_name, imdsv2_session_token
443
+ )
444
+
445
+ return AwsSecurityCredentials(
446
+ credentials.get("AccessKeyId"),
447
+ credentials.get("SecretAccessKey"),
448
+ credentials.get("Token"),
449
+ )
450
+
451
+ @_helpers.copy_docstring(AwsSecurityCredentialsSupplier)
452
+ def get_aws_region(self, context, request):
453
+ # The AWS metadata server is not available in some AWS environments
454
+ # such as AWS lambda. Instead, it is available via environment
455
+ # variable.
456
+ env_aws_region = os.environ.get(environment_vars.AWS_REGION)
457
+ if env_aws_region is not None:
458
+ return env_aws_region
459
+
460
+ env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION)
461
+ if env_aws_region is not None:
462
+ return env_aws_region
463
+
464
+ if not self._region_url:
465
+ raise exceptions.RefreshError("Unable to determine AWS region")
466
+
467
+ headers = None
468
+ imdsv2_session_token = self._get_imdsv2_session_token(request)
469
+ if imdsv2_session_token is not None:
470
+ headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}
471
+
472
+ response = request(url=self._region_url, method="GET", headers=headers)
473
+
474
+ # Support both string and bytes type response.data.
475
+ response_body = (
476
+ response.data.decode("utf-8")
477
+ if hasattr(response.data, "decode")
478
+ else response.data
479
+ )
480
+
481
+ if response.status != http_client.OK:
482
+ raise exceptions.RefreshError(
483
+ "Unable to retrieve AWS region: {}".format(response_body)
484
+ )
485
+
486
+ # This endpoint will return the region in format: us-east-2b.
487
+ # Only the us-east-2 part should be used.
488
+ return response_body[:-1]
489
+
490
+ def _get_imdsv2_session_token(self, request):
491
+ if request is not None and self._imdsv2_session_token_url is not None:
492
+ headers = {
493
+ "X-aws-ec2-metadata-token-ttl-seconds": _IMDSV2_SESSION_TOKEN_TTL_SECONDS
494
+ }
495
+
496
+ imdsv2_session_token_response = request(
497
+ url=self._imdsv2_session_token_url, method="PUT", headers=headers
498
+ )
499
+
500
+ if imdsv2_session_token_response.status != http_client.OK:
501
+ raise exceptions.RefreshError(
502
+ "Unable to retrieve AWS Session Token: {}".format(
503
+ imdsv2_session_token_response.data
504
+ )
505
+ )
506
+
507
+ return imdsv2_session_token_response.data
508
+ else:
509
+ return None
510
+
511
+ def _get_metadata_security_credentials(
512
+ self, request, role_name, imdsv2_session_token
513
+ ):
514
+ """Retrieves the AWS security credentials required for signing AWS
515
+ requests from the AWS metadata server.
516
+
517
+ Args:
518
+ request (google.auth.transport.Request): A callable used to make
519
+ HTTP requests.
520
+ role_name (str): The AWS role name required by the AWS metadata
521
+ server security_credentials endpoint in order to return the
522
+ credentials.
523
+ imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a
524
+ header in the requests to AWS metadata endpoint.
525
+
526
+ Returns:
527
+ Mapping[str, str]: The AWS metadata server security credentials
528
+ response.
529
+
530
+ Raises:
531
+ google.auth.exceptions.RefreshError: If an error occurs while
532
+ retrieving the AWS security credentials.
533
+ """
534
+ headers = {"Content-Type": "application/json"}
535
+ if imdsv2_session_token is not None:
536
+ headers["X-aws-ec2-metadata-token"] = imdsv2_session_token
537
+
538
+ response = request(
539
+ url="{}/{}".format(self._security_credentials_url, role_name),
540
+ method="GET",
541
+ headers=headers,
542
+ )
543
+
544
+ # support both string and bytes type response.data
545
+ response_body = (
546
+ response.data.decode("utf-8")
547
+ if hasattr(response.data, "decode")
548
+ else response.data
549
+ )
550
+
551
+ if response.status != http_client.OK:
552
+ raise exceptions.RefreshError(
553
+ "Unable to retrieve AWS security credentials: {}".format(response_body)
554
+ )
555
+
556
+ credentials_response = json.loads(response_body)
557
+
558
+ return credentials_response
559
+
560
+ def _get_metadata_role_name(self, request, imdsv2_session_token):
561
+ """Retrieves the AWS role currently attached to the current AWS
562
+ workload by querying the AWS metadata server. This is needed for the
563
+ AWS metadata server security credentials endpoint in order to retrieve
564
+ the AWS security credentials needed to sign requests to AWS APIs.
565
+
566
+ Args:
567
+ request (google.auth.transport.Request): A callable used to make
568
+ HTTP requests.
569
+ imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a
570
+ header in the requests to AWS metadata endpoint.
571
+
572
+ Returns:
573
+ str: The AWS role name.
574
+
575
+ Raises:
576
+ google.auth.exceptions.RefreshError: If an error occurs while
577
+ retrieving the AWS role name.
578
+ """
579
+ if self._security_credentials_url is None:
580
+ raise exceptions.RefreshError(
581
+ "Unable to determine the AWS metadata server security credentials endpoint"
582
+ )
583
+
584
+ headers = None
585
+ if imdsv2_session_token is not None:
586
+ headers = {"X-aws-ec2-metadata-token": imdsv2_session_token}
587
+
588
+ response = request(
589
+ url=self._security_credentials_url, method="GET", headers=headers
590
+ )
591
+
592
+ # support both string and bytes type response.data
593
+ response_body = (
594
+ response.data.decode("utf-8")
595
+ if hasattr(response.data, "decode")
596
+ else response.data
597
+ )
598
+
599
+ if response.status != http_client.OK:
600
+ raise exceptions.RefreshError(
601
+ "Unable to retrieve AWS role name {}".format(response_body)
602
+ )
603
+
604
+ return response_body
605
+
606
+
607
+ class Credentials(external_account.Credentials):
608
+ """AWS external account credentials.
609
+ This is used to exchange serialized AWS signature v4 signed requests to
610
+ AWS STS GetCallerIdentity service for Google access tokens.
611
+ """
612
+
613
+ def __init__(
614
+ self,
615
+ audience,
616
+ subject_token_type,
617
+ token_url=external_account._DEFAULT_TOKEN_URL,
618
+ credential_source=None,
619
+ aws_security_credentials_supplier=None,
620
+ *args,
621
+ **kwargs
622
+ ):
623
+ """Instantiates an AWS workload external account credentials object.
624
+
625
+ Args:
626
+ audience (str): The STS audience field.
627
+ subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.
628
+ Expected values include::
629
+
630
+ “urn:ietf:params:aws:token-type:aws4_request”
631
+
632
+ token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token".
633
+ credential_source (Optional [Mapping]): The credential source dictionary used
634
+ to provide instructions on how to retrieve external credential to be exchanged for Google access tokens.
635
+ Either a credential source or an AWS security credentials supplier must be provided.
636
+
637
+ Example credential_source for AWS credential::
638
+
639
+ {
640
+ "environment_id": "aws1",
641
+ "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
642
+ "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
643
+ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
644
+ imdsv2_session_token_url": "http://169.254.169.254/latest/api/token"
645
+ }
646
+
647
+ aws_security_credentials_supplier (Optional [AwsSecurityCredentialsSupplier]): Optional AWS security credentials supplier.
648
+ This will be called to supply valid AWS security credentails which will then
649
+ be exchanged for Google access tokens. Either an AWS security credentials supplier
650
+ or a credential source must be provided.
651
+ args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
652
+ kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method.
653
+
654
+ Raises:
655
+ google.auth.exceptions.RefreshError: If an error is encountered during
656
+ access token retrieval logic.
657
+ ValueError: For invalid parameters.
658
+
659
+ .. note:: Typically one of the helper constructors
660
+ :meth:`from_file` or
661
+ :meth:`from_info` are used instead of calling the constructor directly.
662
+ """
663
+ super(Credentials, self).__init__(
664
+ audience=audience,
665
+ subject_token_type=subject_token_type,
666
+ token_url=token_url,
667
+ credential_source=credential_source,
668
+ *args,
669
+ **kwargs
670
+ )
671
+ if credential_source is None and aws_security_credentials_supplier is None:
672
+ raise exceptions.InvalidValue(
673
+ "A valid credential source or AWS security credentials supplier must be provided."
674
+ )
675
+ if (
676
+ credential_source is not None
677
+ and aws_security_credentials_supplier is not None
678
+ ):
679
+ raise exceptions.InvalidValue(
680
+ "AWS credential cannot have both a credential source and an AWS security credentials supplier."
681
+ )
682
+
683
+ if aws_security_credentials_supplier:
684
+ self._aws_security_credentials_supplier = aws_security_credentials_supplier
685
+ # The regional cred verification URL would normally be provided through the credential source. So set it to the default one here.
686
+ self._cred_verification_url = (
687
+ _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL
688
+ )
689
+ else:
690
+ environment_id = credential_source.get("environment_id") or ""
691
+ self._aws_security_credentials_supplier = _DefaultAwsSecurityCredentialsSupplier(
692
+ credential_source
693
+ )
694
+ self._cred_verification_url = credential_source.get(
695
+ "regional_cred_verification_url"
696
+ )
697
+
698
+ # Get the environment ID, i.e. "aws1". Currently, only one version supported (1).
699
+ matches = re.match(r"^(aws)([\d]+)$", environment_id)
700
+ if matches:
701
+ env_id, env_version = matches.groups()
702
+ else:
703
+ env_id, env_version = (None, None)
704
+
705
+ if env_id != "aws" or self._cred_verification_url is None:
706
+ raise exceptions.InvalidResource(
707
+ "No valid AWS 'credential_source' provided"
708
+ )
709
+ elif env_version is None or int(env_version) != 1:
710
+ raise exceptions.InvalidValue(
711
+ "aws version '{}' is not supported in the current build.".format(
712
+ env_version
713
+ )
714
+ )
715
+
716
+ self._target_resource = audience
717
+ self._request_signer = None
718
+
719
+ def retrieve_subject_token(self, request):
720
+ """Retrieves the subject token using the credential_source object.
721
+ The subject token is a serialized `AWS GetCallerIdentity signed request`_.
722
+
723
+ The logic is summarized as:
724
+
725
+ Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION
726
+ environment variable or from the AWS metadata server availability-zone
727
+ if not found in the environment variable.
728
+
729
+ Check AWS credentials in environment variables. If not found, retrieve
730
+ from the AWS metadata server security-credentials endpoint.
731
+
732
+ When retrieving AWS credentials from the metadata server
733
+ security-credentials endpoint, the AWS role needs to be determined by
734
+ calling the security-credentials endpoint without any argument. Then the
735
+ credentials can be retrieved via: security-credentials/role_name
736
+
737
+ Generate the signed request to AWS STS GetCallerIdentity action.
738
+
739
+ Inject x-goog-cloud-target-resource into header and serialize the
740
+ signed request. This will be the subject-token to pass to GCP STS.
741
+
742
+ .. _AWS GetCallerIdentity signed request:
743
+ https://cloud.google.com/iam/docs/access-resources-aws#exchange-token
744
+
745
+ Args:
746
+ request (google.auth.transport.Request): A callable used to make
747
+ HTTP requests.
748
+ Returns:
749
+ str: The retrieved subject token.
750
+ """
751
+
752
+ # Initialize the request signer if not yet initialized after determining
753
+ # the current AWS region.
754
+ if self._request_signer is None:
755
+ self._region = self._aws_security_credentials_supplier.get_aws_region(
756
+ self._supplier_context, request
757
+ )
758
+ self._request_signer = RequestSigner(self._region)
759
+
760
+ # Retrieve the AWS security credentials needed to generate the signed
761
+ # request.
762
+ aws_security_credentials = self._aws_security_credentials_supplier.get_aws_security_credentials(
763
+ self._supplier_context, request
764
+ )
765
+ # Generate the signed request to AWS STS GetCallerIdentity API.
766
+ # Use the required regional endpoint. Otherwise, the request will fail.
767
+ request_options = self._request_signer.get_request_options(
768
+ aws_security_credentials,
769
+ self._cred_verification_url.replace("{region}", self._region),
770
+ "POST",
771
+ )
772
+ # The GCP STS endpoint expects the headers to be formatted as:
773
+ # [
774
+ # {key: 'x-amz-date', value: '...'},
775
+ # {key: 'Authorization', value: '...'},
776
+ # ...
777
+ # ]
778
+ # And then serialized as:
779
+ # quote(json.dumps({
780
+ # url: '...',
781
+ # method: 'POST',
782
+ # headers: [{key: 'x-amz-date', value: '...'}, ...]
783
+ # }))
784
+ request_headers = request_options.get("headers")
785
+ # The full, canonical resource name of the workload identity pool
786
+ # provider, with or without the HTTPS prefix.
787
+ # Including this header as part of the signature is recommended to
788
+ # ensure data integrity.
789
+ request_headers["x-goog-cloud-target-resource"] = self._target_resource
790
+
791
+ # Serialize AWS signed request.
792
+ aws_signed_req = {}
793
+ aws_signed_req["url"] = request_options.get("url")
794
+ aws_signed_req["method"] = request_options.get("method")
795
+ aws_signed_req["headers"] = []
796
+ # Reformat header to GCP STS expected format.
797
+ for key in request_headers.keys():
798
+ aws_signed_req["headers"].append(
799
+ {"key": key, "value": request_headers[key]}
800
+ )
801
+
802
+ return urllib.parse.quote(
803
+ json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True)
804
+ )
805
+
806
+ def _create_default_metrics_options(self):
807
+ metrics_options = super(Credentials, self)._create_default_metrics_options()
808
+ metrics_options["source"] = "aws"
809
+ if self._has_custom_supplier():
810
+ metrics_options["source"] = "programmatic"
811
+ return metrics_options
812
+
813
+ def _has_custom_supplier(self):
814
+ return self._credential_source is None
815
+
816
+ def _constructor_args(self):
817
+ args = super(Credentials, self)._constructor_args()
818
+ # If a custom supplier was used, append it to the args dict.
819
+ if self._has_custom_supplier():
820
+ args.update(
821
+ {
822
+ "aws_security_credentials_supplier": self._aws_security_credentials_supplier
823
+ }
824
+ )
825
+ return args
826
+
827
+ @classmethod
828
+ def from_info(cls, info, **kwargs):
829
+ """Creates an AWS Credentials instance from parsed external account info.
830
+
831
+ Args:
832
+ info (Mapping[str, str]): The AWS external account info in Google
833
+ format.
834
+ kwargs: Additional arguments to pass to the constructor.
835
+
836
+ Returns:
837
+ google.auth.aws.Credentials: The constructed credentials.
838
+
839
+ Raises:
840
+ ValueError: For invalid parameters.
841
+ """
842
+ aws_security_credentials_supplier = info.get(
843
+ "aws_security_credentials_supplier"
844
+ )
845
+ kwargs.update(
846
+ {"aws_security_credentials_supplier": aws_security_credentials_supplier}
847
+ )
848
+ return super(Credentials, cls).from_info(info, **kwargs)
849
+
850
+ @classmethod
851
+ def from_file(cls, filename, **kwargs):
852
+ """Creates an AWS Credentials instance from an external account json file.
853
+
854
+ Args:
855
+ filename (str): The path to the AWS external account json file.
856
+ kwargs: Additional arguments to pass to the constructor.
857
+
858
+ Returns:
859
+ google.auth.aws.Credentials: The constructed credentials.
860
+ """
861
+ return super(Credentials, cls).from_file(filename, **kwargs)
lib/python3.10/site-packages/google/auth/compute_engine/_metadata.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Provides helper methods for talking to the Compute Engine metadata server.
16
+
17
+ See https://cloud.google.com/compute/docs/metadata for more details.
18
+ """
19
+
20
+ import datetime
21
+ import http.client as http_client
22
+ import json
23
+ import logging
24
+ import os
25
+ from urllib.parse import urljoin
26
+
27
+ from google.auth import _helpers
28
+ from google.auth import environment_vars
29
+ from google.auth import exceptions
30
+ from google.auth import metrics
31
+ from google.auth import transport
32
+ from google.auth._exponential_backoff import ExponentialBackoff
33
+
34
+ _LOGGER = logging.getLogger(__name__)
35
+
36
+ # Environment variable GCE_METADATA_HOST is originally named
37
+ # GCE_METADATA_ROOT. For compatibility reasons, here it checks
38
+ # the new variable first; if not set, the system falls back
39
+ # to the old variable.
40
+ _GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None)
41
+ if not _GCE_METADATA_HOST:
42
+ _GCE_METADATA_HOST = os.getenv(
43
+ environment_vars.GCE_METADATA_ROOT, "metadata.google.internal"
44
+ )
45
+ _METADATA_ROOT = "http://{}/computeMetadata/v1/".format(_GCE_METADATA_HOST)
46
+
47
+ # This is used to ping the metadata server, it avoids the cost of a DNS
48
+ # lookup.
49
+ _METADATA_IP_ROOT = "http://{}".format(
50
+ os.getenv(environment_vars.GCE_METADATA_IP, "169.254.169.254")
51
+ )
52
+ _METADATA_FLAVOR_HEADER = "metadata-flavor"
53
+ _METADATA_FLAVOR_VALUE = "Google"
54
+ _METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE}
55
+
56
+ # Timeout in seconds to wait for the GCE metadata server when detecting the
57
+ # GCE environment.
58
+ try:
59
+ _METADATA_DEFAULT_TIMEOUT = int(os.getenv("GCE_METADATA_TIMEOUT", 3))
60
+ except ValueError: # pragma: NO COVER
61
+ _METADATA_DEFAULT_TIMEOUT = 3
62
+
63
+ # Detect GCE Residency
64
+ _GOOGLE = "Google"
65
+ _GCE_PRODUCT_NAME_FILE = "/sys/class/dmi/id/product_name"
66
+
67
+
68
+ def is_on_gce(request):
69
+ """Checks to see if the code runs on Google Compute Engine
70
+
71
+ Args:
72
+ request (google.auth.transport.Request): A callable used to make
73
+ HTTP requests.
74
+
75
+ Returns:
76
+ bool: True if the code runs on Google Compute Engine, False otherwise.
77
+ """
78
+ if ping(request):
79
+ return True
80
+
81
+ if os.name == "nt":
82
+ # TODO: implement GCE residency detection on Windows
83
+ return False
84
+
85
+ # Detect GCE residency on Linux
86
+ return detect_gce_residency_linux()
87
+
88
+
89
+ def detect_gce_residency_linux():
90
+ """Detect Google Compute Engine residency by smbios check on Linux
91
+
92
+ Returns:
93
+ bool: True if the GCE product name file is detected, False otherwise.
94
+ """
95
+ try:
96
+ with open(_GCE_PRODUCT_NAME_FILE, "r") as file_obj:
97
+ content = file_obj.read().strip()
98
+
99
+ except Exception:
100
+ return False
101
+
102
+ return content.startswith(_GOOGLE)
103
+
104
+
105
+ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3):
106
+ """Checks to see if the metadata server is available.
107
+
108
+ Args:
109
+ request (google.auth.transport.Request): A callable used to make
110
+ HTTP requests.
111
+ timeout (int): How long to wait for the metadata server to respond.
112
+ retry_count (int): How many times to attempt connecting to metadata
113
+ server using above timeout.
114
+
115
+ Returns:
116
+ bool: True if the metadata server is reachable, False otherwise.
117
+ """
118
+ # NOTE: The explicit ``timeout`` is a workaround. The underlying
119
+ # issue is that resolving an unknown host on some networks will take
120
+ # 20-30 seconds; making this timeout short fixes the issue, but
121
+ # could lead to false negatives in the event that we are on GCE, but
122
+ # the metadata resolution was particularly slow. The latter case is
123
+ # "unlikely".
124
+ headers = _METADATA_HEADERS.copy()
125
+ headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping()
126
+
127
+ backoff = ExponentialBackoff(total_attempts=retry_count)
128
+
129
+ for attempt in backoff:
130
+ try:
131
+ response = request(
132
+ url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout
133
+ )
134
+
135
+ metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER)
136
+ return (
137
+ response.status == http_client.OK
138
+ and metadata_flavor == _METADATA_FLAVOR_VALUE
139
+ )
140
+
141
+ except exceptions.TransportError as e:
142
+ _LOGGER.warning(
143
+ "Compute Engine Metadata server unavailable on "
144
+ "attempt %s of %s. Reason: %s",
145
+ attempt,
146
+ retry_count,
147
+ e,
148
+ )
149
+
150
+ return False
151
+
152
+
153
+ def get(
154
+ request,
155
+ path,
156
+ root=_METADATA_ROOT,
157
+ params=None,
158
+ recursive=False,
159
+ retry_count=5,
160
+ headers=None,
161
+ return_none_for_not_found_error=False,
162
+ timeout=_METADATA_DEFAULT_TIMEOUT,
163
+ ):
164
+ """Fetch a resource from the metadata server.
165
+
166
+ Args:
167
+ request (google.auth.transport.Request): A callable used to make
168
+ HTTP requests.
169
+ path (str): The resource to retrieve. For example,
170
+ ``'instance/service-accounts/default'``.
171
+ root (str): The full path to the metadata server root.
172
+ params (Optional[Mapping[str, str]]): A mapping of query parameter
173
+ keys to values.
174
+ recursive (bool): Whether to do a recursive query of metadata. See
175
+ https://cloud.google.com/compute/docs/metadata#aggcontents for more
176
+ details.
177
+ retry_count (int): How many times to attempt connecting to metadata
178
+ server using above timeout.
179
+ headers (Optional[Mapping[str, str]]): Headers for the request.
180
+ return_none_for_not_found_error (Optional[bool]): If True, returns None
181
+ for 404 error instead of throwing an exception.
182
+ timeout (int): How long to wait, in seconds for the metadata server to respond.
183
+
184
+ Returns:
185
+ Union[Mapping, str]: If the metadata server returns JSON, a mapping of
186
+ the decoded JSON is returned. Otherwise, the response content is
187
+ returned as a string.
188
+
189
+ Raises:
190
+ google.auth.exceptions.TransportError: if an error occurred while
191
+ retrieving metadata.
192
+ """
193
+ base_url = urljoin(root, path)
194
+ query_params = {} if params is None else params
195
+
196
+ headers_to_use = _METADATA_HEADERS.copy()
197
+ if headers:
198
+ headers_to_use.update(headers)
199
+
200
+ if recursive:
201
+ query_params["recursive"] = "true"
202
+
203
+ url = _helpers.update_query(base_url, query_params)
204
+
205
+ backoff = ExponentialBackoff(total_attempts=retry_count)
206
+ failure_reason = None
207
+ for attempt in backoff:
208
+ try:
209
+ response = request(
210
+ url=url, method="GET", headers=headers_to_use, timeout=timeout
211
+ )
212
+ if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES:
213
+ _LOGGER.warning(
214
+ "Compute Engine Metadata server unavailable on "
215
+ "attempt %s of %s. Response status: %s",
216
+ attempt,
217
+ retry_count,
218
+ response.status,
219
+ )
220
+ failure_reason = (
221
+ response.data.decode("utf-8")
222
+ if hasattr(response.data, "decode")
223
+ else response.data
224
+ )
225
+ continue
226
+ else:
227
+ break
228
+
229
+ except exceptions.TransportError as e:
230
+ _LOGGER.warning(
231
+ "Compute Engine Metadata server unavailable on "
232
+ "attempt %s of %s. Reason: %s",
233
+ attempt,
234
+ retry_count,
235
+ e,
236
+ )
237
+ failure_reason = e
238
+ else:
239
+ raise exceptions.TransportError(
240
+ "Failed to retrieve {} from the Google Compute Engine "
241
+ "metadata service. Compute Engine Metadata server unavailable due to {}".format(
242
+ url, failure_reason
243
+ )
244
+ )
245
+
246
+ content = _helpers.from_bytes(response.data)
247
+
248
+ if response.status == http_client.NOT_FOUND and return_none_for_not_found_error:
249
+ return None
250
+
251
+ if response.status == http_client.OK:
252
+ if (
253
+ _helpers.parse_content_type(response.headers["content-type"])
254
+ == "application/json"
255
+ ):
256
+ try:
257
+ return json.loads(content)
258
+ except ValueError as caught_exc:
259
+ new_exc = exceptions.TransportError(
260
+ "Received invalid JSON from the Google Compute Engine "
261
+ "metadata service: {:.20}".format(content)
262
+ )
263
+ raise new_exc from caught_exc
264
+ else:
265
+ return content
266
+
267
+ raise exceptions.TransportError(
268
+ "Failed to retrieve {} from the Google Compute Engine "
269
+ "metadata service. Status: {} Response:\n{}".format(
270
+ url, response.status, response.data
271
+ ),
272
+ response,
273
+ )
274
+
275
+
276
+ def get_project_id(request):
277
+ """Get the Google Cloud Project ID from the metadata server.
278
+
279
+ Args:
280
+ request (google.auth.transport.Request): A callable used to make
281
+ HTTP requests.
282
+
283
+ Returns:
284
+ str: The project ID
285
+
286
+ Raises:
287
+ google.auth.exceptions.TransportError: if an error occurred while
288
+ retrieving metadata.
289
+ """
290
+ return get(request, "project/project-id")
291
+
292
+
293
+ def get_universe_domain(request):
294
+ """Get the universe domain value from the metadata server.
295
+
296
+ Args:
297
+ request (google.auth.transport.Request): A callable used to make
298
+ HTTP requests.
299
+
300
+ Returns:
301
+ str: The universe domain value. If the universe domain endpoint is not
302
+ not found, return the default value, which is googleapis.com
303
+
304
+ Raises:
305
+ google.auth.exceptions.TransportError: if an error other than
306
+ 404 occurs while retrieving metadata.
307
+ """
308
+ universe_domain = get(
309
+ request, "universe/universe-domain", return_none_for_not_found_error=True
310
+ )
311
+ if not universe_domain:
312
+ return "googleapis.com"
313
+ return universe_domain
314
+
315
+
316
+ def get_service_account_info(request, service_account="default"):
317
+ """Get information about a service account from the metadata server.
318
+
319
+ Args:
320
+ request (google.auth.transport.Request): A callable used to make
321
+ HTTP requests.
322
+ service_account (str): The string 'default' or a service account email
323
+ address. The determines which service account for which to acquire
324
+ information.
325
+
326
+ Returns:
327
+ Mapping: The service account's information, for example::
328
+
329
+ {
330
+ 'email': '...',
331
+ 'scopes': ['scope', ...],
332
+ 'aliases': ['default', '...']
333
+ }
334
+
335
+ Raises:
336
+ google.auth.exceptions.TransportError: if an error occurred while
337
+ retrieving metadata.
338
+ """
339
+ path = "instance/service-accounts/{0}/".format(service_account)
340
+ # See https://cloud.google.com/compute/docs/metadata#aggcontents
341
+ # for more on the use of 'recursive'.
342
+ return get(request, path, params={"recursive": "true"})
343
+
344
+
345
+ def get_service_account_token(request, service_account="default", scopes=None):
346
+ """Get the OAuth 2.0 access token for a service account.
347
+
348
+ Args:
349
+ request (google.auth.transport.Request): A callable used to make
350
+ HTTP requests.
351
+ service_account (str): The string 'default' or a service account email
352
+ address. The determines which service account for which to acquire
353
+ an access token.
354
+ scopes (Optional[Union[str, List[str]]]): Optional string or list of
355
+ strings with auth scopes.
356
+ Returns:
357
+ Tuple[str, datetime]: The access token and its expiration.
358
+
359
+ Raises:
360
+ google.auth.exceptions.TransportError: if an error occurred while
361
+ retrieving metadata.
362
+ """
363
+ if scopes:
364
+ if not isinstance(scopes, str):
365
+ scopes = ",".join(scopes)
366
+ params = {"scopes": scopes}
367
+ else:
368
+ params = None
369
+
370
+ metrics_header = {
371
+ metrics.API_CLIENT_HEADER: metrics.token_request_access_token_mds()
372
+ }
373
+
374
+ path = "instance/service-accounts/{0}/token".format(service_account)
375
+ token_json = get(request, path, params=params, headers=metrics_header)
376
+ token_expiry = _helpers.utcnow() + datetime.timedelta(
377
+ seconds=token_json["expires_in"]
378
+ )
379
+ return token_json["access_token"], token_expiry
lib/python3.10/site-packages/google/auth/compute_engine/credentials.py ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google Compute Engine credentials.
16
+
17
+ This module provides authentication for an application running on Google
18
+ Compute Engine using the Compute Engine metadata server.
19
+
20
+ """
21
+
22
+ import datetime
23
+
24
+ from google.auth import _helpers
25
+ from google.auth import credentials
26
+ from google.auth import exceptions
27
+ from google.auth import iam
28
+ from google.auth import jwt
29
+ from google.auth import metrics
30
+ from google.auth.compute_engine import _metadata
31
+ from google.oauth2 import _client
32
+
33
+
34
+ class Credentials(
35
+ credentials.Scoped,
36
+ credentials.CredentialsWithQuotaProject,
37
+ credentials.CredentialsWithUniverseDomain,
38
+ ):
39
+ """Compute Engine Credentials.
40
+
41
+ These credentials use the Google Compute Engine metadata server to obtain
42
+ OAuth 2.0 access tokens associated with the instance's service account,
43
+ and are also used for Cloud Run, Flex and App Engine (except for the Python
44
+ 2.7 runtime, which is supported only on older versions of this library).
45
+
46
+ For more information about Compute Engine authentication, including how
47
+ to configure scopes, see the `Compute Engine authentication
48
+ documentation`_.
49
+
50
+ .. note:: On Compute Engine the metadata server ignores requested scopes.
51
+ On Cloud Run, Flex and App Engine the server honours requested scopes.
52
+
53
+ .. _Compute Engine authentication documentation:
54
+ https://cloud.google.com/compute/docs/authentication#using
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ service_account_email="default",
60
+ quota_project_id=None,
61
+ scopes=None,
62
+ default_scopes=None,
63
+ universe_domain=None,
64
+ ):
65
+ """
66
+ Args:
67
+ service_account_email (str): The service account email to use, or
68
+ 'default'. A Compute Engine instance may have multiple service
69
+ accounts.
70
+ quota_project_id (Optional[str]): The project ID used for quota and
71
+ billing.
72
+ scopes (Optional[Sequence[str]]): The list of scopes for the credentials.
73
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
74
+ Google client library. Use 'scopes' for user-defined scopes.
75
+ universe_domain (Optional[str]): The universe domain. If not
76
+ provided or None, credential will attempt to fetch the value
77
+ from metadata server. If metadata server doesn't have universe
78
+ domain endpoint, then the default googleapis.com will be used.
79
+ """
80
+ super(Credentials, self).__init__()
81
+ self._service_account_email = service_account_email
82
+ self._quota_project_id = quota_project_id
83
+ self._scopes = scopes
84
+ self._default_scopes = default_scopes
85
+ self._universe_domain_cached = False
86
+ if universe_domain:
87
+ self._universe_domain = universe_domain
88
+ self._universe_domain_cached = True
89
+
90
+ def _retrieve_info(self, request):
91
+ """Retrieve information about the service account.
92
+
93
+ Updates the scopes and retrieves the full service account email.
94
+
95
+ Args:
96
+ request (google.auth.transport.Request): The object used to make
97
+ HTTP requests.
98
+ """
99
+ info = _metadata.get_service_account_info(
100
+ request, service_account=self._service_account_email
101
+ )
102
+
103
+ self._service_account_email = info["email"]
104
+
105
+ # Don't override scopes requested by the user.
106
+ if self._scopes is None:
107
+ self._scopes = info["scopes"]
108
+
109
+ def _metric_header_for_usage(self):
110
+ return metrics.CRED_TYPE_SA_MDS
111
+
112
+ def refresh(self, request):
113
+ """Refresh the access token and scopes.
114
+
115
+ Args:
116
+ request (google.auth.transport.Request): The object used to make
117
+ HTTP requests.
118
+
119
+ Raises:
120
+ google.auth.exceptions.RefreshError: If the Compute Engine metadata
121
+ service can't be reached if if the instance has not
122
+ credentials.
123
+ """
124
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
125
+ try:
126
+ self._retrieve_info(request)
127
+ # Always fetch token with default service account email.
128
+ self.token, self.expiry = _metadata.get_service_account_token(
129
+ request, service_account="default", scopes=scopes
130
+ )
131
+ except exceptions.TransportError as caught_exc:
132
+ new_exc = exceptions.RefreshError(caught_exc)
133
+ raise new_exc from caught_exc
134
+
135
+ @property
136
+ def service_account_email(self):
137
+ """The service account email.
138
+
139
+ .. note:: This is not guaranteed to be set until :meth:`refresh` has been
140
+ called.
141
+ """
142
+ return self._service_account_email
143
+
144
+ @property
145
+ def requires_scopes(self):
146
+ return not self._scopes
147
+
148
+ @property
149
+ def universe_domain(self):
150
+ if self._universe_domain_cached:
151
+ return self._universe_domain
152
+
153
+ from google.auth.transport import requests as google_auth_requests
154
+
155
+ self._universe_domain = _metadata.get_universe_domain(
156
+ google_auth_requests.Request()
157
+ )
158
+ self._universe_domain_cached = True
159
+ return self._universe_domain
160
+
161
+ @_helpers.copy_docstring(credentials.Credentials)
162
+ def get_cred_info(self):
163
+ return {
164
+ "credential_source": "metadata server",
165
+ "credential_type": "VM credentials",
166
+ "principal": self.service_account_email,
167
+ }
168
+
169
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
170
+ def with_quota_project(self, quota_project_id):
171
+ creds = self.__class__(
172
+ service_account_email=self._service_account_email,
173
+ quota_project_id=quota_project_id,
174
+ scopes=self._scopes,
175
+ default_scopes=self._default_scopes,
176
+ )
177
+ creds._universe_domain = self._universe_domain
178
+ creds._universe_domain_cached = self._universe_domain_cached
179
+ return creds
180
+
181
+ @_helpers.copy_docstring(credentials.Scoped)
182
+ def with_scopes(self, scopes, default_scopes=None):
183
+ # Compute Engine credentials can not be scoped (the metadata service
184
+ # ignores the scopes parameter). App Engine, Cloud Run and Flex support
185
+ # requesting scopes.
186
+ creds = self.__class__(
187
+ scopes=scopes,
188
+ default_scopes=default_scopes,
189
+ service_account_email=self._service_account_email,
190
+ quota_project_id=self._quota_project_id,
191
+ )
192
+ creds._universe_domain = self._universe_domain
193
+ creds._universe_domain_cached = self._universe_domain_cached
194
+ return creds
195
+
196
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
197
+ def with_universe_domain(self, universe_domain):
198
+ return self.__class__(
199
+ scopes=self._scopes,
200
+ default_scopes=self._default_scopes,
201
+ service_account_email=self._service_account_email,
202
+ quota_project_id=self._quota_project_id,
203
+ universe_domain=universe_domain,
204
+ )
205
+
206
+
207
+ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
208
+ _DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"
209
+
210
+
211
+ class IDTokenCredentials(
212
+ credentials.CredentialsWithQuotaProject,
213
+ credentials.Signing,
214
+ credentials.CredentialsWithTokenUri,
215
+ ):
216
+ """Open ID Connect ID Token-based service account credentials.
217
+
218
+ These credentials relies on the default service account of a GCE instance.
219
+
220
+ ID token can be requested from `GCE metadata server identity endpoint`_, IAM
221
+ token endpoint or other token endpoints you specify. If metadata server
222
+ identity endpoint is not used, the GCE instance must have been started with
223
+ a service account that has access to the IAM Cloud API.
224
+
225
+ .. _GCE metadata server identity endpoint:
226
+ https://cloud.google.com/compute/docs/instances/verifying-instance-identity
227
+ """
228
+
229
+ def __init__(
230
+ self,
231
+ request,
232
+ target_audience,
233
+ token_uri=None,
234
+ additional_claims=None,
235
+ service_account_email=None,
236
+ signer=None,
237
+ use_metadata_identity_endpoint=False,
238
+ quota_project_id=None,
239
+ ):
240
+ """
241
+ Args:
242
+ request (google.auth.transport.Request): The object used to make
243
+ HTTP requests.
244
+ target_audience (str): The intended audience for these credentials,
245
+ used when requesting the ID Token. The ID Token's ``aud`` claim
246
+ will be set to this string.
247
+ token_uri (str): The OAuth 2.0 Token URI.
248
+ additional_claims (Mapping[str, str]): Any additional claims for
249
+ the JWT assertion used in the authorization grant.
250
+ service_account_email (str): Optional explicit service account to
251
+ use to sign JWT tokens.
252
+ By default, this is the default GCE service account.
253
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
254
+ In case the signer is specified, the request argument will be
255
+ ignored.
256
+ use_metadata_identity_endpoint (bool): Whether to use GCE metadata
257
+ identity endpoint. For backward compatibility the default value
258
+ is False. If set to True, ``token_uri``, ``additional_claims``,
259
+ ``service_account_email``, ``signer`` argument should not be set;
260
+ otherwise ValueError will be raised.
261
+ quota_project_id (Optional[str]): The project ID used for quota and
262
+ billing.
263
+
264
+ Raises:
265
+ ValueError:
266
+ If ``use_metadata_identity_endpoint`` is set to True, and one of
267
+ ``token_uri``, ``additional_claims``, ``service_account_email``,
268
+ ``signer`` arguments is set.
269
+ """
270
+ super(IDTokenCredentials, self).__init__()
271
+
272
+ self._quota_project_id = quota_project_id
273
+ self._use_metadata_identity_endpoint = use_metadata_identity_endpoint
274
+ self._target_audience = target_audience
275
+
276
+ if use_metadata_identity_endpoint:
277
+ if token_uri or additional_claims or service_account_email or signer:
278
+ raise exceptions.MalformedError(
279
+ "If use_metadata_identity_endpoint is set, token_uri, "
280
+ "additional_claims, service_account_email, signer arguments"
281
+ " must not be set"
282
+ )
283
+ self._token_uri = None
284
+ self._additional_claims = None
285
+ self._signer = None
286
+
287
+ if service_account_email is None:
288
+ sa_info = _metadata.get_service_account_info(request)
289
+ self._service_account_email = sa_info["email"]
290
+ else:
291
+ self._service_account_email = service_account_email
292
+
293
+ if not use_metadata_identity_endpoint:
294
+ if signer is None:
295
+ signer = iam.Signer(
296
+ request=request,
297
+ credentials=Credentials(),
298
+ service_account_email=self._service_account_email,
299
+ )
300
+ self._signer = signer
301
+ self._token_uri = token_uri or _DEFAULT_TOKEN_URI
302
+
303
+ if additional_claims is not None:
304
+ self._additional_claims = additional_claims
305
+ else:
306
+ self._additional_claims = {}
307
+
308
+ def with_target_audience(self, target_audience):
309
+ """Create a copy of these credentials with the specified target
310
+ audience.
311
+ Args:
312
+ target_audience (str): The intended audience for these credentials,
313
+ used when requesting the ID Token.
314
+ Returns:
315
+ google.auth.service_account.IDTokenCredentials: A new credentials
316
+ instance.
317
+ """
318
+ # since the signer is already instantiated,
319
+ # the request is not needed
320
+ if self._use_metadata_identity_endpoint:
321
+ return self.__class__(
322
+ None,
323
+ target_audience=target_audience,
324
+ use_metadata_identity_endpoint=True,
325
+ quota_project_id=self._quota_project_id,
326
+ )
327
+ else:
328
+ return self.__class__(
329
+ None,
330
+ service_account_email=self._service_account_email,
331
+ token_uri=self._token_uri,
332
+ target_audience=target_audience,
333
+ additional_claims=self._additional_claims.copy(),
334
+ signer=self.signer,
335
+ use_metadata_identity_endpoint=False,
336
+ quota_project_id=self._quota_project_id,
337
+ )
338
+
339
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
340
+ def with_quota_project(self, quota_project_id):
341
+
342
+ # since the signer is already instantiated,
343
+ # the request is not needed
344
+ if self._use_metadata_identity_endpoint:
345
+ return self.__class__(
346
+ None,
347
+ target_audience=self._target_audience,
348
+ use_metadata_identity_endpoint=True,
349
+ quota_project_id=quota_project_id,
350
+ )
351
+ else:
352
+ return self.__class__(
353
+ None,
354
+ service_account_email=self._service_account_email,
355
+ token_uri=self._token_uri,
356
+ target_audience=self._target_audience,
357
+ additional_claims=self._additional_claims.copy(),
358
+ signer=self.signer,
359
+ use_metadata_identity_endpoint=False,
360
+ quota_project_id=quota_project_id,
361
+ )
362
+
363
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
364
+ def with_token_uri(self, token_uri):
365
+
366
+ # since the signer is already instantiated,
367
+ # the request is not needed
368
+ if self._use_metadata_identity_endpoint:
369
+ raise exceptions.MalformedError(
370
+ "If use_metadata_identity_endpoint is set, token_uri" " must not be set"
371
+ )
372
+ else:
373
+ return self.__class__(
374
+ None,
375
+ service_account_email=self._service_account_email,
376
+ token_uri=token_uri,
377
+ target_audience=self._target_audience,
378
+ additional_claims=self._additional_claims.copy(),
379
+ signer=self.signer,
380
+ use_metadata_identity_endpoint=False,
381
+ quota_project_id=self.quota_project_id,
382
+ )
383
+
384
+ def _make_authorization_grant_assertion(self):
385
+ """Create the OAuth 2.0 assertion.
386
+ This assertion is used during the OAuth 2.0 grant to acquire an
387
+ ID token.
388
+ Returns:
389
+ bytes: The authorization grant assertion.
390
+ """
391
+ now = _helpers.utcnow()
392
+ lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
393
+ expiry = now + lifetime
394
+
395
+ payload = {
396
+ "iat": _helpers.datetime_to_secs(now),
397
+ "exp": _helpers.datetime_to_secs(expiry),
398
+ # The issuer must be the service account email.
399
+ "iss": self.service_account_email,
400
+ # The audience must be the auth token endpoint's URI
401
+ "aud": self._token_uri,
402
+ # The target audience specifies which service the ID token is
403
+ # intended for.
404
+ "target_audience": self._target_audience,
405
+ }
406
+
407
+ payload.update(self._additional_claims)
408
+
409
+ token = jwt.encode(self._signer, payload)
410
+
411
+ return token
412
+
413
+ def _call_metadata_identity_endpoint(self, request):
414
+ """Request ID token from metadata identity endpoint.
415
+
416
+ Args:
417
+ request (google.auth.transport.Request): The object used to make
418
+ HTTP requests.
419
+
420
+ Returns:
421
+ Tuple[str, datetime.datetime]: The ID token and the expiry of the ID token.
422
+
423
+ Raises:
424
+ google.auth.exceptions.RefreshError: If the Compute Engine metadata
425
+ service can't be reached or if the instance has no credentials.
426
+ ValueError: If extracting expiry from the obtained ID token fails.
427
+ """
428
+ try:
429
+ path = "instance/service-accounts/default/identity"
430
+ params = {"audience": self._target_audience, "format": "full"}
431
+ metrics_header = {
432
+ metrics.API_CLIENT_HEADER: metrics.token_request_id_token_mds()
433
+ }
434
+ id_token = _metadata.get(
435
+ request, path, params=params, headers=metrics_header
436
+ )
437
+ except exceptions.TransportError as caught_exc:
438
+ new_exc = exceptions.RefreshError(caught_exc)
439
+ raise new_exc from caught_exc
440
+
441
+ _, payload, _, _ = jwt._unverified_decode(id_token)
442
+ return id_token, datetime.datetime.utcfromtimestamp(payload["exp"])
443
+
444
+ def refresh(self, request):
445
+ """Refreshes the ID token.
446
+
447
+ Args:
448
+ request (google.auth.transport.Request): The object used to make
449
+ HTTP requests.
450
+
451
+ Raises:
452
+ google.auth.exceptions.RefreshError: If the credentials could
453
+ not be refreshed.
454
+ ValueError: If extracting expiry from the obtained ID token fails.
455
+ """
456
+ if self._use_metadata_identity_endpoint:
457
+ self.token, self.expiry = self._call_metadata_identity_endpoint(request)
458
+ else:
459
+ assertion = self._make_authorization_grant_assertion()
460
+ access_token, expiry, _ = _client.id_token_jwt_grant(
461
+ request, self._token_uri, assertion
462
+ )
463
+ self.token = access_token
464
+ self.expiry = expiry
465
+
466
+ @property # type: ignore
467
+ @_helpers.copy_docstring(credentials.Signing)
468
+ def signer(self):
469
+ return self._signer
470
+
471
+ def sign_bytes(self, message):
472
+ """Signs the given message.
473
+
474
+ Args:
475
+ message (bytes): The message to sign.
476
+
477
+ Returns:
478
+ bytes: The message's cryptographic signature.
479
+
480
+ Raises:
481
+ ValueError:
482
+ Signer is not available if metadata identity endpoint is used.
483
+ """
484
+ if self._use_metadata_identity_endpoint:
485
+ raise exceptions.InvalidOperation(
486
+ "Signer is not available if metadata identity endpoint is used"
487
+ )
488
+ return self._signer.sign(message)
489
+
490
+ @property
491
+ def service_account_email(self):
492
+ """The service account email."""
493
+ return self._service_account_email
494
+
495
+ @property
496
+ def signer_email(self):
497
+ return self._service_account_email
lib/python3.10/site-packages/google/auth/credentials.py ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Interfaces for credentials."""
17
+
18
+ import abc
19
+ from enum import Enum
20
+ import os
21
+
22
+ from google.auth import _helpers, environment_vars
23
+ from google.auth import exceptions
24
+ from google.auth import metrics
25
+ from google.auth._credentials_base import _BaseCredentials
26
+ from google.auth._refresh_worker import RefreshThreadManager
27
+
28
+ DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
29
+
30
+
31
+ class Credentials(_BaseCredentials):
32
+ """Base class for all credentials.
33
+
34
+ All credentials have a :attr:`token` that is used for authentication and
35
+ may also optionally set an :attr:`expiry` to indicate when the token will
36
+ no longer be valid.
37
+
38
+ Most credentials will be :attr:`invalid` until :meth:`refresh` is called.
39
+ Credentials can do this automatically before the first HTTP request in
40
+ :meth:`before_request`.
41
+
42
+ Although the token and expiration will change as the credentials are
43
+ :meth:`refreshed <refresh>` and used, credentials should be considered
44
+ immutable. Various credentials will accept configuration such as private
45
+ keys, scopes, and other options. These options are not changeable after
46
+ construction. Some classes will provide mechanisms to copy the credentials
47
+ with modifications such as :meth:`ScopedCredentials.with_scopes`.
48
+ """
49
+
50
+ def __init__(self):
51
+ super(Credentials, self).__init__()
52
+
53
+ self.expiry = None
54
+ """Optional[datetime]: When the token expires and is no longer valid.
55
+ If this is None, the token is assumed to never expire."""
56
+ self._quota_project_id = None
57
+ """Optional[str]: Project to use for quota and billing purposes."""
58
+ self._trust_boundary = None
59
+ """Optional[dict]: Cache of a trust boundary response which has a list
60
+ of allowed regions and an encoded string representation of credentials
61
+ trust boundary."""
62
+ self._universe_domain = DEFAULT_UNIVERSE_DOMAIN
63
+ """Optional[str]: The universe domain value, default is googleapis.com
64
+ """
65
+
66
+ self._use_non_blocking_refresh = False
67
+ self._refresh_worker = RefreshThreadManager()
68
+
69
+ @property
70
+ def expired(self):
71
+ """Checks if the credentials are expired.
72
+
73
+ Note that credentials can be invalid but not expired because
74
+ Credentials with :attr:`expiry` set to None is considered to never
75
+ expire.
76
+
77
+ .. deprecated:: v2.24.0
78
+ Prefer checking :attr:`token_state` instead.
79
+ """
80
+ if not self.expiry:
81
+ return False
82
+ # Remove some threshold from expiry to err on the side of reporting
83
+ # expiration early so that we avoid the 401-refresh-retry loop.
84
+ skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
85
+ return _helpers.utcnow() >= skewed_expiry
86
+
87
+ @property
88
+ def valid(self):
89
+ """Checks the validity of the credentials.
90
+
91
+ This is True if the credentials have a :attr:`token` and the token
92
+ is not :attr:`expired`.
93
+
94
+ .. deprecated:: v2.24.0
95
+ Prefer checking :attr:`token_state` instead.
96
+ """
97
+ return self.token is not None and not self.expired
98
+
99
+ @property
100
+ def token_state(self):
101
+ """
102
+ See `:obj:`TokenState`
103
+ """
104
+ if self.token is None:
105
+ return TokenState.INVALID
106
+
107
+ # Credentials that can't expire are always treated as fresh.
108
+ if self.expiry is None:
109
+ return TokenState.FRESH
110
+
111
+ expired = _helpers.utcnow() >= self.expiry
112
+ if expired:
113
+ return TokenState.INVALID
114
+
115
+ is_stale = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD)
116
+ if is_stale:
117
+ return TokenState.STALE
118
+
119
+ return TokenState.FRESH
120
+
121
+ @property
122
+ def quota_project_id(self):
123
+ """Project to use for quota and billing purposes."""
124
+ return self._quota_project_id
125
+
126
+ @property
127
+ def universe_domain(self):
128
+ """The universe domain value."""
129
+ return self._universe_domain
130
+
131
+ def get_cred_info(self):
132
+ """The credential information JSON.
133
+
134
+ The credential information will be added to auth related error messages
135
+ by client library.
136
+
137
+ Returns:
138
+ Mapping[str, str]: The credential information JSON.
139
+ """
140
+ return None
141
+
142
+ @abc.abstractmethod
143
+ def refresh(self, request):
144
+ """Refreshes the access token.
145
+
146
+ Args:
147
+ request (google.auth.transport.Request): The object used to make
148
+ HTTP requests.
149
+
150
+ Raises:
151
+ google.auth.exceptions.RefreshError: If the credentials could
152
+ not be refreshed.
153
+ """
154
+ # pylint: disable=missing-raises-doc
155
+ # (pylint doesn't recognize that this is abstract)
156
+ raise NotImplementedError("Refresh must be implemented")
157
+
158
+ def _metric_header_for_usage(self):
159
+ """The x-goog-api-client header for token usage metric.
160
+
161
+ This header will be added to the API service requests in before_request
162
+ method. For example, "cred-type/sa-jwt" means service account self
163
+ signed jwt access token is used in the API service request
164
+ authorization header. Children credentials classes need to override
165
+ this method to provide the header value, if the token usage metric is
166
+ needed.
167
+
168
+ Returns:
169
+ str: The x-goog-api-client header value.
170
+ """
171
+ return None
172
+
173
+ def apply(self, headers, token=None):
174
+ """Apply the token to the authentication header.
175
+
176
+ Args:
177
+ headers (Mapping): The HTTP request headers.
178
+ token (Optional[str]): If specified, overrides the current access
179
+ token.
180
+ """
181
+ self._apply(headers, token=token)
182
+ """Trust boundary value will be a cached value from global lookup.
183
+
184
+ The response of trust boundary will be a list of regions and a hex
185
+ encoded representation.
186
+
187
+ An example of global lookup response:
188
+ {
189
+ "locations": [
190
+ "us-central1", "us-east1", "europe-west1", "asia-east1"
191
+ ]
192
+ "encoded_locations": "0xA30"
193
+ }
194
+ """
195
+ if self._trust_boundary is not None:
196
+ headers["x-allowed-locations"] = self._trust_boundary["encoded_locations"]
197
+ if self.quota_project_id:
198
+ headers["x-goog-user-project"] = self.quota_project_id
199
+
200
+ def _blocking_refresh(self, request):
201
+ if not self.valid:
202
+ self.refresh(request)
203
+
204
+ def _non_blocking_refresh(self, request):
205
+ use_blocking_refresh_fallback = False
206
+
207
+ if self.token_state == TokenState.STALE:
208
+ use_blocking_refresh_fallback = not self._refresh_worker.start_refresh(
209
+ self, request
210
+ )
211
+
212
+ if self.token_state == TokenState.INVALID or use_blocking_refresh_fallback:
213
+ self.refresh(request)
214
+ # If the blocking refresh succeeds then we can clear the error info
215
+ # on the background refresh worker, and perform refreshes in a
216
+ # background thread.
217
+ self._refresh_worker.clear_error()
218
+
219
+ def before_request(self, request, method, url, headers):
220
+ """Performs credential-specific before request logic.
221
+
222
+ Refreshes the credentials if necessary, then calls :meth:`apply` to
223
+ apply the token to the authentication header.
224
+
225
+ Args:
226
+ request (google.auth.transport.Request): The object used to make
227
+ HTTP requests.
228
+ method (str): The request's HTTP method or the RPC method being
229
+ invoked.
230
+ url (str): The request's URI or the RPC service's URI.
231
+ headers (Mapping): The request's headers.
232
+ """
233
+ # pylint: disable=unused-argument
234
+ # (Subclasses may use these arguments to ascertain information about
235
+ # the http request.)
236
+ if self._use_non_blocking_refresh:
237
+ self._non_blocking_refresh(request)
238
+ else:
239
+ self._blocking_refresh(request)
240
+
241
+ metrics.add_metric_header(headers, self._metric_header_for_usage())
242
+ self.apply(headers)
243
+
244
+ def with_non_blocking_refresh(self):
245
+ self._use_non_blocking_refresh = True
246
+
247
+
248
+ class CredentialsWithQuotaProject(Credentials):
249
+ """Abstract base for credentials supporting ``with_quota_project`` factory"""
250
+
251
+ def with_quota_project(self, quota_project_id):
252
+ """Returns a copy of these credentials with a modified quota project.
253
+
254
+ Args:
255
+ quota_project_id (str): The project to use for quota and
256
+ billing purposes
257
+
258
+ Returns:
259
+ google.auth.credentials.Credentials: A new credentials instance.
260
+ """
261
+ raise NotImplementedError("This credential does not support quota project.")
262
+
263
+ def with_quota_project_from_environment(self):
264
+ quota_from_env = os.environ.get(environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT)
265
+ if quota_from_env:
266
+ return self.with_quota_project(quota_from_env)
267
+ return self
268
+
269
+
270
+ class CredentialsWithTokenUri(Credentials):
271
+ """Abstract base for credentials supporting ``with_token_uri`` factory"""
272
+
273
+ def with_token_uri(self, token_uri):
274
+ """Returns a copy of these credentials with a modified token uri.
275
+
276
+ Args:
277
+ token_uri (str): The uri to use for fetching/exchanging tokens
278
+
279
+ Returns:
280
+ google.auth.credentials.Credentials: A new credentials instance.
281
+ """
282
+ raise NotImplementedError("This credential does not use token uri.")
283
+
284
+
285
+ class CredentialsWithUniverseDomain(Credentials):
286
+ """Abstract base for credentials supporting ``with_universe_domain`` factory"""
287
+
288
+ def with_universe_domain(self, universe_domain):
289
+ """Returns a copy of these credentials with a modified universe domain.
290
+
291
+ Args:
292
+ universe_domain (str): The universe domain to use
293
+
294
+ Returns:
295
+ google.auth.credentials.Credentials: A new credentials instance.
296
+ """
297
+ raise NotImplementedError(
298
+ "This credential does not support with_universe_domain."
299
+ )
300
+
301
+
302
+ class AnonymousCredentials(Credentials):
303
+ """Credentials that do not provide any authentication information.
304
+
305
+ These are useful in the case of services that support anonymous access or
306
+ local service emulators that do not use credentials.
307
+ """
308
+
309
+ @property
310
+ def expired(self):
311
+ """Returns `False`, anonymous credentials never expire."""
312
+ return False
313
+
314
+ @property
315
+ def valid(self):
316
+ """Returns `True`, anonymous credentials are always valid."""
317
+ return True
318
+
319
+ def refresh(self, request):
320
+ """Raises :class:``InvalidOperation``, anonymous credentials cannot be
321
+ refreshed."""
322
+ raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.")
323
+
324
+ def apply(self, headers, token=None):
325
+ """Anonymous credentials do nothing to the request.
326
+
327
+ The optional ``token`` argument is not supported.
328
+
329
+ Raises:
330
+ google.auth.exceptions.InvalidValue: If a token was specified.
331
+ """
332
+ if token is not None:
333
+ raise exceptions.InvalidValue("Anonymous credentials don't support tokens.")
334
+
335
+ def before_request(self, request, method, url, headers):
336
+ """Anonymous credentials do nothing to the request."""
337
+
338
+
339
+ class ReadOnlyScoped(metaclass=abc.ABCMeta):
340
+ """Interface for credentials whose scopes can be queried.
341
+
342
+ OAuth 2.0-based credentials allow limiting access using scopes as described
343
+ in `RFC6749 Section 3.3`_.
344
+ If a credential class implements this interface then the credentials either
345
+ use scopes in their implementation.
346
+
347
+ Some credentials require scopes in order to obtain a token. You can check
348
+ if scoping is necessary with :attr:`requires_scopes`::
349
+
350
+ if credentials.requires_scopes:
351
+ # Scoping is required.
352
+ credentials = credentials.with_scopes(scopes=['one', 'two'])
353
+
354
+ Credentials that require scopes must either be constructed with scopes::
355
+
356
+ credentials = SomeScopedCredentials(scopes=['one', 'two'])
357
+
358
+ Or must copy an existing instance using :meth:`with_scopes`::
359
+
360
+ scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
361
+
362
+ Some credentials have scopes but do not allow or require scopes to be set,
363
+ these credentials can be used as-is.
364
+
365
+ .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
366
+ """
367
+
368
+ def __init__(self):
369
+ super(ReadOnlyScoped, self).__init__()
370
+ self._scopes = None
371
+ self._default_scopes = None
372
+
373
+ @property
374
+ def scopes(self):
375
+ """Sequence[str]: the credentials' current set of scopes."""
376
+ return self._scopes
377
+
378
+ @property
379
+ def default_scopes(self):
380
+ """Sequence[str]: the credentials' current set of default scopes."""
381
+ return self._default_scopes
382
+
383
+ @abc.abstractproperty
384
+ def requires_scopes(self):
385
+ """True if these credentials require scopes to obtain an access token.
386
+ """
387
+ return False
388
+
389
+ def has_scopes(self, scopes):
390
+ """Checks if the credentials have the given scopes.
391
+
392
+ .. warning: This method is not guaranteed to be accurate if the
393
+ credentials are :attr:`~Credentials.invalid`.
394
+
395
+ Args:
396
+ scopes (Sequence[str]): The list of scopes to check.
397
+
398
+ Returns:
399
+ bool: True if the credentials have the given scopes.
400
+ """
401
+ credential_scopes = (
402
+ self._scopes if self._scopes is not None else self._default_scopes
403
+ )
404
+ return set(scopes).issubset(set(credential_scopes or []))
405
+
406
+
407
+ class Scoped(ReadOnlyScoped):
408
+ """Interface for credentials whose scopes can be replaced while copying.
409
+
410
+ OAuth 2.0-based credentials allow limiting access using scopes as described
411
+ in `RFC6749 Section 3.3`_.
412
+ If a credential class implements this interface then the credentials either
413
+ use scopes in their implementation.
414
+
415
+ Some credentials require scopes in order to obtain a token. You can check
416
+ if scoping is necessary with :attr:`requires_scopes`::
417
+
418
+ if credentials.requires_scopes:
419
+ # Scoping is required.
420
+ credentials = credentials.create_scoped(['one', 'two'])
421
+
422
+ Credentials that require scopes must either be constructed with scopes::
423
+
424
+ credentials = SomeScopedCredentials(scopes=['one', 'two'])
425
+
426
+ Or must copy an existing instance using :meth:`with_scopes`::
427
+
428
+ scoped_credentials = credentials.with_scopes(scopes=['one', 'two'])
429
+
430
+ Some credentials have scopes but do not allow or require scopes to be set,
431
+ these credentials can be used as-is.
432
+
433
+ .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3
434
+ """
435
+
436
+ @abc.abstractmethod
437
+ def with_scopes(self, scopes, default_scopes=None):
438
+ """Create a copy of these credentials with the specified scopes.
439
+
440
+ Args:
441
+ scopes (Sequence[str]): The list of scopes to attach to the
442
+ current credentials.
443
+
444
+ Raises:
445
+ NotImplementedError: If the credentials' scopes can not be changed.
446
+ This can be avoided by checking :attr:`requires_scopes` before
447
+ calling this method.
448
+ """
449
+ raise NotImplementedError("This class does not require scoping.")
450
+
451
+
452
+ def with_scopes_if_required(credentials, scopes, default_scopes=None):
453
+ """Creates a copy of the credentials with scopes if scoping is required.
454
+
455
+ This helper function is useful when you do not know (or care to know) the
456
+ specific type of credentials you are using (such as when you use
457
+ :func:`google.auth.default`). This function will call
458
+ :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if
459
+ the credentials require scoping. Otherwise, it will return the credentials
460
+ as-is.
461
+
462
+ Args:
463
+ credentials (google.auth.credentials.Credentials): The credentials to
464
+ scope if necessary.
465
+ scopes (Sequence[str]): The list of scopes to use.
466
+ default_scopes (Sequence[str]): Default scopes passed by a
467
+ Google client library. Use 'scopes' for user-defined scopes.
468
+
469
+ Returns:
470
+ google.auth.credentials.Credentials: Either a new set of scoped
471
+ credentials, or the passed in credentials instance if no scoping
472
+ was required.
473
+ """
474
+ if isinstance(credentials, Scoped) and credentials.requires_scopes:
475
+ return credentials.with_scopes(scopes, default_scopes=default_scopes)
476
+ else:
477
+ return credentials
478
+
479
+
480
+ class Signing(metaclass=abc.ABCMeta):
481
+ """Interface for credentials that can cryptographically sign messages."""
482
+
483
+ @abc.abstractmethod
484
+ def sign_bytes(self, message):
485
+ """Signs the given message.
486
+
487
+ Args:
488
+ message (bytes): The message to sign.
489
+
490
+ Returns:
491
+ bytes: The message's cryptographic signature.
492
+ """
493
+ # pylint: disable=missing-raises-doc,redundant-returns-doc
494
+ # (pylint doesn't recognize that this is abstract)
495
+ raise NotImplementedError("Sign bytes must be implemented.")
496
+
497
+ @abc.abstractproperty
498
+ def signer_email(self):
499
+ """Optional[str]: An email address that identifies the signer."""
500
+ # pylint: disable=missing-raises-doc
501
+ # (pylint doesn't recognize that this is abstract)
502
+ raise NotImplementedError("Signer email must be implemented.")
503
+
504
+ @abc.abstractproperty
505
+ def signer(self):
506
+ """google.auth.crypt.Signer: The signer used to sign bytes."""
507
+ # pylint: disable=missing-raises-doc
508
+ # (pylint doesn't recognize that this is abstract)
509
+ raise NotImplementedError("Signer must be implemented.")
510
+
511
+
512
+ class TokenState(Enum):
513
+ """
514
+ Tracks the state of a token.
515
+ FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry.
516
+ STALE: The token is close to expired, and should be refreshed. The token can be used normally.
517
+ INVALID: The token is expired or invalid. The token cannot be used for a normal operation.
518
+ """
519
+
520
+ FRESH = 1
521
+ STALE = 2
522
+ INVALID = 3
lib/python3.10/site-packages/google/auth/downscoped.py ADDED
@@ -0,0 +1,512 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2021 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Downscoping with Credential Access Boundaries
16
+
17
+ This module provides the ability to downscope credentials using
18
+ `Downscoping with Credential Access Boundaries`_. This is useful to restrict the
19
+ Identity and Access Management (IAM) permissions that a short-lived credential
20
+ can use.
21
+
22
+ To downscope permissions of a source credential, a Credential Access Boundary
23
+ that specifies which resources the new credential can access, as well as
24
+ an upper bound on the permissions that are available on each resource, has to
25
+ be defined. A downscoped credential can then be instantiated using the source
26
+ credential and the Credential Access Boundary.
27
+
28
+ The common pattern of usage is to have a token broker with elevated access
29
+ generate these downscoped credentials from higher access source credentials and
30
+ pass the downscoped short-lived access tokens to a token consumer via some
31
+ secure authenticated channel for limited access to Google Cloud Storage
32
+ resources.
33
+
34
+ For example, a token broker can be set up on a server in a private network.
35
+ Various workloads (token consumers) in the same network will send authenticated
36
+ requests to that broker for downscoped tokens to access or modify specific google
37
+ cloud storage buckets.
38
+
39
+ The broker will instantiate downscoped credentials instances that can be used to
40
+ generate short lived downscoped access tokens that can be passed to the token
41
+ consumer. These downscoped access tokens can be injected by the consumer into
42
+ google.oauth2.Credentials and used to initialize a storage client instance to
43
+ access Google Cloud Storage resources with restricted access.
44
+
45
+ Note: Only Cloud Storage supports Credential Access Boundaries. Other Google
46
+ Cloud services do not support this feature.
47
+
48
+ .. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials
49
+ """
50
+
51
+ import datetime
52
+
53
+ from google.auth import _helpers
54
+ from google.auth import credentials
55
+ from google.auth import exceptions
56
+ from google.oauth2 import sts
57
+
58
+ # The maximum number of access boundary rules a Credential Access Boundary can
59
+ # contain.
60
+ _MAX_ACCESS_BOUNDARY_RULES_COUNT = 10
61
+ # The token exchange grant_type used for exchanging credentials.
62
+ _STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
63
+ # The token exchange requested_token_type. This is always an access_token.
64
+ _STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
65
+ # The STS token URL used to exchanged a short lived access token for a downscoped one.
66
+ _STS_TOKEN_URL_PATTERN = "https://sts.{}/v1/token"
67
+ # The subject token type to use when exchanging a short lived access token for a
68
+ # downscoped token.
69
+ _STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
70
+
71
+
72
+ class CredentialAccessBoundary(object):
73
+ """Defines a Credential Access Boundary which contains a list of access boundary
74
+ rules. Each rule contains information on the resource that the rule applies to,
75
+ the upper bound of the permissions that are available on that resource and an
76
+ optional condition to further restrict permissions.
77
+ """
78
+
79
+ def __init__(self, rules=[]):
80
+ """Instantiates a Credential Access Boundary. A Credential Access Boundary
81
+ can contain up to 10 access boundary rules.
82
+
83
+ Args:
84
+ rules (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of
85
+ access boundary rules limiting the access that a downscoped credential
86
+ will have.
87
+ Raises:
88
+ InvalidType: If any of the rules are not a valid type.
89
+ InvalidValue: If the provided rules exceed the maximum allowed.
90
+ """
91
+ self.rules = rules
92
+
93
+ @property
94
+ def rules(self):
95
+ """Returns the list of access boundary rules defined on the Credential
96
+ Access Boundary.
97
+
98
+ Returns:
99
+ Tuple[google.auth.downscoped.AccessBoundaryRule, ...]: The list of access
100
+ boundary rules defined on the Credential Access Boundary. These are returned
101
+ as an immutable tuple to prevent modification.
102
+ """
103
+ return tuple(self._rules)
104
+
105
+ @rules.setter
106
+ def rules(self, value):
107
+ """Updates the current rules on the Credential Access Boundary. This will overwrite
108
+ the existing set of rules.
109
+
110
+ Args:
111
+ value (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of
112
+ access boundary rules limiting the access that a downscoped credential
113
+ will have.
114
+ Raises:
115
+ InvalidType: If any of the rules are not a valid type.
116
+ InvalidValue: If the provided rules exceed the maximum allowed.
117
+ """
118
+ if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT:
119
+ raise exceptions.InvalidValue(
120
+ "Credential access boundary rules can have a maximum of {} rules.".format(
121
+ _MAX_ACCESS_BOUNDARY_RULES_COUNT
122
+ )
123
+ )
124
+ for access_boundary_rule in value:
125
+ if not isinstance(access_boundary_rule, AccessBoundaryRule):
126
+ raise exceptions.InvalidType(
127
+ "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."
128
+ )
129
+ # Make a copy of the original list.
130
+ self._rules = list(value)
131
+
132
+ def add_rule(self, rule):
133
+ """Adds a single access boundary rule to the existing rules.
134
+
135
+ Args:
136
+ rule (google.auth.downscoped.AccessBoundaryRule): The access boundary rule,
137
+ limiting the access that a downscoped credential will have, to be added to
138
+ the existing rules.
139
+ Raises:
140
+ InvalidType: If any of the rules are not a valid type.
141
+ InvalidValue: If the provided rules exceed the maximum allowed.
142
+ """
143
+ if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT:
144
+ raise exceptions.InvalidValue(
145
+ "Credential access boundary rules can have a maximum of {} rules.".format(
146
+ _MAX_ACCESS_BOUNDARY_RULES_COUNT
147
+ )
148
+ )
149
+ if not isinstance(rule, AccessBoundaryRule):
150
+ raise exceptions.InvalidType(
151
+ "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'."
152
+ )
153
+ self._rules.append(rule)
154
+
155
+ def to_json(self):
156
+ """Generates the dictionary representation of the Credential Access Boundary.
157
+ This uses the format expected by the Security Token Service API as documented in
158
+ `Defining a Credential Access Boundary`_.
159
+
160
+ .. _Defining a Credential Access Boundary:
161
+ https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary
162
+
163
+ Returns:
164
+ Mapping: Credential Access Boundary Rule represented in a dictionary object.
165
+ """
166
+ rules = []
167
+ for access_boundary_rule in self.rules:
168
+ rules.append(access_boundary_rule.to_json())
169
+
170
+ return {"accessBoundary": {"accessBoundaryRules": rules}}
171
+
172
+
173
+ class AccessBoundaryRule(object):
174
+ """Defines an access boundary rule which contains information on the resource that
175
+ the rule applies to, the upper bound of the permissions that are available on that
176
+ resource and an optional condition to further restrict permissions.
177
+ """
178
+
179
+ def __init__(
180
+ self, available_resource, available_permissions, availability_condition=None
181
+ ):
182
+ """Instantiates a single access boundary rule.
183
+
184
+ Args:
185
+ available_resource (str): The full resource name of the Cloud Storage bucket
186
+ that the rule applies to. Use the format
187
+ "//storage.googleapis.com/projects/_/buckets/bucket-name".
188
+ available_permissions (Sequence[str]): A list defining the upper bound that
189
+ the downscoped token will have on the available permissions for the
190
+ resource. Each value is the identifier for an IAM predefined role or
191
+ custom role, with the prefix "inRole:". For example:
192
+ "inRole:roles/storage.objectViewer".
193
+ Only the permissions in these roles will be available.
194
+ availability_condition (Optional[google.auth.downscoped.AvailabilityCondition]):
195
+ Optional condition that restricts the availability of permissions to
196
+ specific Cloud Storage objects.
197
+
198
+ Raises:
199
+ InvalidType: If any of the parameters are not of the expected types.
200
+ InvalidValue: If any of the parameters are not of the expected values.
201
+ """
202
+ self.available_resource = available_resource
203
+ self.available_permissions = available_permissions
204
+ self.availability_condition = availability_condition
205
+
206
+ @property
207
+ def available_resource(self):
208
+ """Returns the current available resource.
209
+
210
+ Returns:
211
+ str: The current available resource.
212
+ """
213
+ return self._available_resource
214
+
215
+ @available_resource.setter
216
+ def available_resource(self, value):
217
+ """Updates the current available resource.
218
+
219
+ Args:
220
+ value (str): The updated value of the available resource.
221
+
222
+ Raises:
223
+ google.auth.exceptions.InvalidType: If the value is not a string.
224
+ """
225
+ if not isinstance(value, str):
226
+ raise exceptions.InvalidType(
227
+ "The provided available_resource is not a string."
228
+ )
229
+ self._available_resource = value
230
+
231
+ @property
232
+ def available_permissions(self):
233
+ """Returns the current available permissions.
234
+
235
+ Returns:
236
+ Tuple[str, ...]: The current available permissions. These are returned
237
+ as an immutable tuple to prevent modification.
238
+ """
239
+ return tuple(self._available_permissions)
240
+
241
+ @available_permissions.setter
242
+ def available_permissions(self, value):
243
+ """Updates the current available permissions.
244
+
245
+ Args:
246
+ value (Sequence[str]): The updated value of the available permissions.
247
+
248
+ Raises:
249
+ InvalidType: If the value is not a list of strings.
250
+ InvalidValue: If the value is not valid.
251
+ """
252
+ for available_permission in value:
253
+ if not isinstance(available_permission, str):
254
+ raise exceptions.InvalidType(
255
+ "Provided available_permissions are not a list of strings."
256
+ )
257
+ if available_permission.find("inRole:") != 0:
258
+ raise exceptions.InvalidValue(
259
+ "available_permissions must be prefixed with 'inRole:'."
260
+ )
261
+ # Make a copy of the original list.
262
+ self._available_permissions = list(value)
263
+
264
+ @property
265
+ def availability_condition(self):
266
+ """Returns the current availability condition.
267
+
268
+ Returns:
269
+ Optional[google.auth.downscoped.AvailabilityCondition]: The current
270
+ availability condition.
271
+ """
272
+ return self._availability_condition
273
+
274
+ @availability_condition.setter
275
+ def availability_condition(self, value):
276
+ """Updates the current availability condition.
277
+
278
+ Args:
279
+ value (Optional[google.auth.downscoped.AvailabilityCondition]): The updated
280
+ value of the availability condition.
281
+
282
+ Raises:
283
+ google.auth.exceptions.InvalidType: If the value is not of type google.auth.downscoped.AvailabilityCondition
284
+ or None.
285
+ """
286
+ if not isinstance(value, AvailabilityCondition) and value is not None:
287
+ raise exceptions.InvalidType(
288
+ "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None."
289
+ )
290
+ self._availability_condition = value
291
+
292
+ def to_json(self):
293
+ """Generates the dictionary representation of the access boundary rule.
294
+ This uses the format expected by the Security Token Service API as documented in
295
+ `Defining a Credential Access Boundary`_.
296
+
297
+ .. _Defining a Credential Access Boundary:
298
+ https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary
299
+
300
+ Returns:
301
+ Mapping: The access boundary rule represented in a dictionary object.
302
+ """
303
+ json = {
304
+ "availablePermissions": list(self.available_permissions),
305
+ "availableResource": self.available_resource,
306
+ }
307
+ if self.availability_condition:
308
+ json["availabilityCondition"] = self.availability_condition.to_json()
309
+ return json
310
+
311
+
312
+ class AvailabilityCondition(object):
313
+ """An optional condition that can be used as part of a Credential Access Boundary
314
+ to further restrict permissions."""
315
+
316
+ def __init__(self, expression, title=None, description=None):
317
+ """Instantiates an availability condition using the provided expression and
318
+ optional title or description.
319
+
320
+ Args:
321
+ expression (str): A condition expression that specifies the Cloud Storage
322
+ objects where permissions are available. For example, this expression
323
+ makes permissions available for objects whose name starts with "customer-a":
324
+ "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')"
325
+ title (Optional[str]): An optional short string that identifies the purpose of
326
+ the condition.
327
+ description (Optional[str]): Optional details about the purpose of the condition.
328
+
329
+ Raises:
330
+ InvalidType: If any of the parameters are not of the expected types.
331
+ InvalidValue: If any of the parameters are not of the expected values.
332
+ """
333
+ self.expression = expression
334
+ self.title = title
335
+ self.description = description
336
+
337
+ @property
338
+ def expression(self):
339
+ """Returns the current condition expression.
340
+
341
+ Returns:
342
+ str: The current conditon expression.
343
+ """
344
+ return self._expression
345
+
346
+ @expression.setter
347
+ def expression(self, value):
348
+ """Updates the current condition expression.
349
+
350
+ Args:
351
+ value (str): The updated value of the condition expression.
352
+
353
+ Raises:
354
+ google.auth.exceptions.InvalidType: If the value is not of type string.
355
+ """
356
+ if not isinstance(value, str):
357
+ raise exceptions.InvalidType("The provided expression is not a string.")
358
+ self._expression = value
359
+
360
+ @property
361
+ def title(self):
362
+ """Returns the current title.
363
+
364
+ Returns:
365
+ Optional[str]: The current title.
366
+ """
367
+ return self._title
368
+
369
+ @title.setter
370
+ def title(self, value):
371
+ """Updates the current title.
372
+
373
+ Args:
374
+ value (Optional[str]): The updated value of the title.
375
+
376
+ Raises:
377
+ google.auth.exceptions.InvalidType: If the value is not of type string or None.
378
+ """
379
+ if not isinstance(value, str) and value is not None:
380
+ raise exceptions.InvalidType("The provided title is not a string or None.")
381
+ self._title = value
382
+
383
+ @property
384
+ def description(self):
385
+ """Returns the current description.
386
+
387
+ Returns:
388
+ Optional[str]: The current description.
389
+ """
390
+ return self._description
391
+
392
+ @description.setter
393
+ def description(self, value):
394
+ """Updates the current description.
395
+
396
+ Args:
397
+ value (Optional[str]): The updated value of the description.
398
+
399
+ Raises:
400
+ google.auth.exceptions.InvalidType: If the value is not of type string or None.
401
+ """
402
+ if not isinstance(value, str) and value is not None:
403
+ raise exceptions.InvalidType(
404
+ "The provided description is not a string or None."
405
+ )
406
+ self._description = value
407
+
408
+ def to_json(self):
409
+ """Generates the dictionary representation of the availability condition.
410
+ This uses the format expected by the Security Token Service API as documented in
411
+ `Defining a Credential Access Boundary`_.
412
+
413
+ .. _Defining a Credential Access Boundary:
414
+ https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary
415
+
416
+ Returns:
417
+ Mapping[str, str]: The availability condition represented in a dictionary
418
+ object.
419
+ """
420
+ json = {"expression": self.expression}
421
+ if self.title:
422
+ json["title"] = self.title
423
+ if self.description:
424
+ json["description"] = self.description
425
+ return json
426
+
427
+
428
+ class Credentials(credentials.CredentialsWithQuotaProject):
429
+ """Defines a set of Google credentials that are downscoped from an existing set
430
+ of Google OAuth2 credentials. This is useful to restrict the Identity and Access
431
+ Management (IAM) permissions that a short-lived credential can use.
432
+ The common pattern of usage is to have a token broker with elevated access
433
+ generate these downscoped credentials from higher access source credentials and
434
+ pass the downscoped short-lived access tokens to a token consumer via some
435
+ secure authenticated channel for limited access to Google Cloud Storage
436
+ resources.
437
+ """
438
+
439
+ def __init__(
440
+ self,
441
+ source_credentials,
442
+ credential_access_boundary,
443
+ quota_project_id=None,
444
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
445
+ ):
446
+ """Instantiates a downscoped credentials object using the provided source
447
+ credentials and credential access boundary rules.
448
+ To downscope permissions of a source credential, a Credential Access Boundary
449
+ that specifies which resources the new credential can access, as well as an
450
+ upper bound on the permissions that are available on each resource, has to be
451
+ defined. A downscoped credential can then be instantiated using the source
452
+ credential and the Credential Access Boundary.
453
+
454
+ Args:
455
+ source_credentials (google.auth.credentials.Credentials): The source credentials
456
+ to be downscoped based on the provided Credential Access Boundary rules.
457
+ credential_access_boundary (google.auth.downscoped.CredentialAccessBoundary):
458
+ The Credential Access Boundary which contains a list of access boundary
459
+ rules. Each rule contains information on the resource that the rule applies to,
460
+ the upper bound of the permissions that are available on that resource and an
461
+ optional condition to further restrict permissions.
462
+ quota_project_id (Optional[str]): The optional quota project ID.
463
+ universe_domain (Optional[str]): The universe domain value, default is googleapis.com
464
+ Raises:
465
+ google.auth.exceptions.RefreshError: If the source credentials
466
+ return an error on token refresh.
467
+ google.auth.exceptions.OAuthError: If the STS token exchange
468
+ endpoint returned an error during downscoped token generation.
469
+ """
470
+
471
+ super(Credentials, self).__init__()
472
+ self._source_credentials = source_credentials
473
+ self._credential_access_boundary = credential_access_boundary
474
+ self._quota_project_id = quota_project_id
475
+ self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
476
+ self._sts_client = sts.Client(
477
+ _STS_TOKEN_URL_PATTERN.format(self.universe_domain)
478
+ )
479
+
480
+ @_helpers.copy_docstring(credentials.Credentials)
481
+ def refresh(self, request):
482
+ # Generate an access token from the source credentials.
483
+ self._source_credentials.refresh(request)
484
+ now = _helpers.utcnow()
485
+ # Exchange the access token for a downscoped access token.
486
+ response_data = self._sts_client.exchange_token(
487
+ request=request,
488
+ grant_type=_STS_GRANT_TYPE,
489
+ subject_token=self._source_credentials.token,
490
+ subject_token_type=_STS_SUBJECT_TOKEN_TYPE,
491
+ requested_token_type=_STS_REQUESTED_TOKEN_TYPE,
492
+ additional_options=self._credential_access_boundary.to_json(),
493
+ )
494
+ self.token = response_data.get("access_token")
495
+ # For downscoping CAB flow, the STS endpoint may not return the expiration
496
+ # field for some flows. The generated downscoped token should always have
497
+ # the same expiration time as the source credentials. When no expires_in
498
+ # field is returned in the response, we can just get the expiration time
499
+ # from the source credentials.
500
+ if response_data.get("expires_in"):
501
+ lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))
502
+ self.expiry = now + lifetime
503
+ else:
504
+ self.expiry = self._source_credentials.expiry
505
+
506
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
507
+ def with_quota_project(self, quota_project_id):
508
+ return self.__class__(
509
+ self._source_credentials,
510
+ self._credential_access_boundary,
511
+ quota_project_id=quota_project_id,
512
+ )
lib/python3.10/site-packages/google/auth/environment_vars.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Environment variables used by :mod:`google.auth`."""
16
+
17
+
18
+ PROJECT = "GOOGLE_CLOUD_PROJECT"
19
+ """Environment variable defining default project.
20
+
21
+ This used by :func:`google.auth.default` to explicitly set a project ID. This
22
+ environment variable is also used by the Google Cloud Python Library.
23
+ """
24
+
25
+ LEGACY_PROJECT = "GCLOUD_PROJECT"
26
+ """Previously used environment variable defining the default project.
27
+
28
+ This environment variable is used instead of the current one in some
29
+ situations (such as Google App Engine).
30
+ """
31
+
32
+ GOOGLE_CLOUD_QUOTA_PROJECT = "GOOGLE_CLOUD_QUOTA_PROJECT"
33
+ """Environment variable defining the project to be used for
34
+ quota and billing."""
35
+
36
+ CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
37
+ """Environment variable defining the location of Google application default
38
+ credentials."""
39
+
40
+ # The environment variable name which can replace ~/.config if set.
41
+ CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG"
42
+ """Environment variable defines the location of Google Cloud SDK's config
43
+ files."""
44
+
45
+ # These two variables allow for customization of the addresses used when
46
+ # contacting the GCE metadata service.
47
+ GCE_METADATA_HOST = "GCE_METADATA_HOST"
48
+ """Environment variable providing an alternate hostname or host:port to be
49
+ used for GCE metadata requests.
50
+
51
+ This environment variable was originally named GCE_METADATA_ROOT. The system will
52
+ check this environemnt variable first; should there be no value present,
53
+ the system will fall back to the old variable.
54
+ """
55
+
56
+ GCE_METADATA_ROOT = "GCE_METADATA_ROOT"
57
+ """Old environment variable for GCE_METADATA_HOST."""
58
+
59
+ GCE_METADATA_IP = "GCE_METADATA_IP"
60
+ """Environment variable providing an alternate ip:port to be used for ip-only
61
+ GCE metadata requests."""
62
+
63
+ GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
64
+ """Environment variable controlling whether to use client certificate or not.
65
+
66
+ The default value is false. Users have to explicitly set this value to true
67
+ in order to use client certificate to establish a mutual TLS channel."""
68
+
69
+ LEGACY_APPENGINE_RUNTIME = "APPENGINE_RUNTIME"
70
+ """Gen1 environment variable defining the App Engine Runtime.
71
+
72
+ Used to distinguish between GAE gen1 and GAE gen2+.
73
+ """
74
+
75
+ # AWS environment variables used with AWS workload identity pools to retrieve
76
+ # AWS security credentials and the AWS region needed to create a serialized
77
+ # signed requests to the AWS STS GetCalledIdentity API that can be exchanged
78
+ # for a Google access tokens via the GCP STS endpoint.
79
+ # When not available the AWS metadata server is used to retrieve these values.
80
+ AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
81
+ AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
82
+ AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN"
83
+ AWS_REGION = "AWS_REGION"
84
+ AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION"
lib/python3.10/site-packages/google/auth/exceptions.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Exceptions used in the google.auth package."""
16
+
17
+
18
+ class GoogleAuthError(Exception):
19
+ """Base class for all google.auth errors."""
20
+
21
+ def __init__(self, *args, **kwargs):
22
+ super(GoogleAuthError, self).__init__(*args)
23
+ retryable = kwargs.get("retryable", False)
24
+ self._retryable = retryable
25
+
26
+ @property
27
+ def retryable(self):
28
+ return self._retryable
29
+
30
+
31
+ class TransportError(GoogleAuthError):
32
+ """Used to indicate an error occurred during an HTTP request."""
33
+
34
+
35
+ class RefreshError(GoogleAuthError):
36
+ """Used to indicate that an refreshing the credentials' access token
37
+ failed."""
38
+
39
+
40
+ class UserAccessTokenError(GoogleAuthError):
41
+ """Used to indicate ``gcloud auth print-access-token`` command failed."""
42
+
43
+
44
+ class DefaultCredentialsError(GoogleAuthError):
45
+ """Used to indicate that acquiring default credentials failed."""
46
+
47
+
48
+ class MutualTLSChannelError(GoogleAuthError):
49
+ """Used to indicate that mutual TLS channel creation is failed, or mutual
50
+ TLS channel credentials is missing or invalid."""
51
+
52
+
53
+ class ClientCertError(GoogleAuthError):
54
+ """Used to indicate that client certificate is missing or invalid."""
55
+
56
+ @property
57
+ def retryable(self):
58
+ return False
59
+
60
+
61
+ class OAuthError(GoogleAuthError):
62
+ """Used to indicate an error occurred during an OAuth related HTTP
63
+ request."""
64
+
65
+
66
+ class ReauthFailError(RefreshError):
67
+ """An exception for when reauth failed."""
68
+
69
+ def __init__(self, message=None, **kwargs):
70
+ super(ReauthFailError, self).__init__(
71
+ "Reauthentication failed. {0}".format(message), **kwargs
72
+ )
73
+
74
+
75
+ class ReauthSamlChallengeFailError(ReauthFailError):
76
+ """An exception for SAML reauth challenge failures."""
77
+
78
+
79
+ class MalformedError(DefaultCredentialsError, ValueError):
80
+ """An exception for malformed data."""
81
+
82
+
83
+ class InvalidResource(DefaultCredentialsError, ValueError):
84
+ """An exception for URL error."""
85
+
86
+
87
+ class InvalidOperation(DefaultCredentialsError, ValueError):
88
+ """An exception for invalid operation."""
89
+
90
+
91
+ class InvalidValue(DefaultCredentialsError, ValueError):
92
+ """Used to wrap general ValueError of python."""
93
+
94
+
95
+ class InvalidType(DefaultCredentialsError, TypeError):
96
+ """Used to wrap general TypeError of python."""
97
+
98
+
99
+ class OSError(DefaultCredentialsError, EnvironmentError):
100
+ """Used to wrap EnvironmentError(OSError after python3.3)."""
101
+
102
+
103
+ class TimeoutError(GoogleAuthError):
104
+ """Used to indicate a timeout error occurred during an HTTP request."""
105
+
106
+
107
+ class ResponseError(GoogleAuthError):
108
+ """Used to indicate an error occurred when reading an HTTP response."""
lib/python3.10/site-packages/google/auth/external_account.py ADDED
@@ -0,0 +1,628 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """External Account Credentials.
16
+
17
+ This module provides credentials that exchange workload identity pool external
18
+ credentials for Google access tokens. This facilitates accessing Google Cloud
19
+ Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS,
20
+ Microsoft Azure, OIDC identity providers), using native credentials retrieved
21
+ from the current environment without the need to copy, save and manage
22
+ long-lived service account credentials.
23
+
24
+ Specifically, this is intended to use access tokens acquired using the GCP STS
25
+ token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec.
26
+
27
+ .. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693
28
+ """
29
+
30
+ import abc
31
+ import copy
32
+ from dataclasses import dataclass
33
+ import datetime
34
+ import functools
35
+ import io
36
+ import json
37
+ import re
38
+
39
+ from google.auth import _helpers
40
+ from google.auth import credentials
41
+ from google.auth import exceptions
42
+ from google.auth import impersonated_credentials
43
+ from google.auth import metrics
44
+ from google.oauth2 import sts
45
+ from google.oauth2 import utils
46
+
47
+ # External account JSON type identifier.
48
+ _EXTERNAL_ACCOUNT_JSON_TYPE = "external_account"
49
+ # The token exchange grant_type used for exchanging credentials.
50
+ _STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
51
+ # The token exchange requested_token_type. This is always an access_token.
52
+ _STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
53
+ # Cloud resource manager URL used to retrieve project information.
54
+ _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/"
55
+ # Default Google sts token url.
56
+ _DEFAULT_TOKEN_URL = "https://sts.{universe_domain}/v1/token"
57
+
58
+
59
+ @dataclass
60
+ class SupplierContext:
61
+ """A context class that contains information about the requested third party credential that is passed
62
+ to AWS security credential and subject token suppliers.
63
+
64
+ Attributes:
65
+ subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec.
66
+ Expected values include::
67
+
68
+ “urn:ietf:params:oauth:token-type:jwt”
69
+ “urn:ietf:params:oauth:token-type:id-token”
70
+ “urn:ietf:params:oauth:token-type:saml2”
71
+ “urn:ietf:params:aws:token-type:aws4_request”
72
+
73
+ audience (str): The requested audience for the subject token.
74
+ """
75
+
76
+ subject_token_type: str
77
+ audience: str
78
+
79
+
80
+ class Credentials(
81
+ credentials.Scoped,
82
+ credentials.CredentialsWithQuotaProject,
83
+ credentials.CredentialsWithTokenUri,
84
+ metaclass=abc.ABCMeta,
85
+ ):
86
+ """Base class for all external account credentials.
87
+
88
+ This is used to instantiate Credentials for exchanging external account
89
+ credentials for Google access token and authorizing requests to Google APIs.
90
+ The base class implements the common logic for exchanging external account
91
+ credentials for Google access tokens.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ audience,
97
+ subject_token_type,
98
+ token_url,
99
+ credential_source,
100
+ service_account_impersonation_url=None,
101
+ service_account_impersonation_options=None,
102
+ client_id=None,
103
+ client_secret=None,
104
+ token_info_url=None,
105
+ quota_project_id=None,
106
+ scopes=None,
107
+ default_scopes=None,
108
+ workforce_pool_user_project=None,
109
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
110
+ trust_boundary=None,
111
+ ):
112
+ """Instantiates an external account credentials object.
113
+
114
+ Args:
115
+ audience (str): The STS audience field.
116
+ subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec.
117
+ Expected values include::
118
+
119
+ “urn:ietf:params:oauth:token-type:jwt”
120
+ “urn:ietf:params:oauth:token-type:id-token”
121
+ “urn:ietf:params:oauth:token-type:saml2”
122
+ “urn:ietf:params:aws:token-type:aws4_request”
123
+
124
+ token_url (str): The STS endpoint URL.
125
+ credential_source (Mapping): The credential source dictionary.
126
+ service_account_impersonation_url (Optional[str]): The optional service account
127
+ impersonation generateAccessToken URL.
128
+ client_id (Optional[str]): The optional client ID.
129
+ client_secret (Optional[str]): The optional client secret.
130
+ token_info_url (str): The optional STS endpoint URL for token introspection.
131
+ quota_project_id (Optional[str]): The optional quota project ID.
132
+ scopes (Optional[Sequence[str]]): Optional scopes to request during the
133
+ authorization grant.
134
+ default_scopes (Optional[Sequence[str]]): Default scopes passed by a
135
+ Google client library. Use 'scopes' for user-defined scopes.
136
+ workforce_pool_user_project (Optona[str]): The optional workforce pool user
137
+ project number when the credential corresponds to a workforce pool and not
138
+ a workload identity pool. The underlying principal must still have
139
+ serviceusage.services.use IAM permission to use the project for
140
+ billing/quota.
141
+ universe_domain (str): The universe domain. The default universe
142
+ domain is googleapis.com.
143
+ trust_boundary (str): String representation of trust boundary meta.
144
+ Raises:
145
+ google.auth.exceptions.RefreshError: If the generateAccessToken
146
+ endpoint returned an error.
147
+ """
148
+ super(Credentials, self).__init__()
149
+ self._audience = audience
150
+ self._subject_token_type = subject_token_type
151
+ self._universe_domain = universe_domain
152
+ self._token_url = token_url
153
+ if self._token_url == _DEFAULT_TOKEN_URL:
154
+ self._token_url = self._token_url.replace(
155
+ "{universe_domain}", self._universe_domain
156
+ )
157
+ self._token_info_url = token_info_url
158
+ self._credential_source = credential_source
159
+ self._service_account_impersonation_url = service_account_impersonation_url
160
+ self._service_account_impersonation_options = (
161
+ service_account_impersonation_options or {}
162
+ )
163
+ self._client_id = client_id
164
+ self._client_secret = client_secret
165
+ self._quota_project_id = quota_project_id
166
+ self._scopes = scopes
167
+ self._default_scopes = default_scopes
168
+ self._workforce_pool_user_project = workforce_pool_user_project
169
+ self._trust_boundary = {
170
+ "locations": [],
171
+ "encoded_locations": "0x0",
172
+ } # expose a placeholder trust boundary value.
173
+
174
+ if self._client_id:
175
+ self._client_auth = utils.ClientAuthentication(
176
+ utils.ClientAuthType.basic, self._client_id, self._client_secret
177
+ )
178
+ else:
179
+ self._client_auth = None
180
+ self._sts_client = sts.Client(self._token_url, self._client_auth)
181
+
182
+ self._metrics_options = self._create_default_metrics_options()
183
+
184
+ self._impersonated_credentials = None
185
+ self._project_id = None
186
+ self._supplier_context = SupplierContext(
187
+ self._subject_token_type, self._audience
188
+ )
189
+ self._cred_file_path = None
190
+
191
+ if not self.is_workforce_pool and self._workforce_pool_user_project:
192
+ # Workload identity pools do not support workforce pool user projects.
193
+ raise exceptions.InvalidValue(
194
+ "workforce_pool_user_project should not be set for non-workforce pool "
195
+ "credentials"
196
+ )
197
+
198
+ @property
199
+ def info(self):
200
+ """Generates the dictionary representation of the current credentials.
201
+
202
+ Returns:
203
+ Mapping: The dictionary representation of the credentials. This is the
204
+ reverse of "from_info" defined on the subclasses of this class. It is
205
+ useful for serializing the current credentials so it can deserialized
206
+ later.
207
+ """
208
+ config_info = self._constructor_args()
209
+ config_info.update(
210
+ type=_EXTERNAL_ACCOUNT_JSON_TYPE,
211
+ service_account_impersonation=config_info.pop(
212
+ "service_account_impersonation_options", None
213
+ ),
214
+ )
215
+ config_info.pop("scopes", None)
216
+ config_info.pop("default_scopes", None)
217
+ return {key: value for key, value in config_info.items() if value is not None}
218
+
219
+ def _constructor_args(self):
220
+ args = {
221
+ "audience": self._audience,
222
+ "subject_token_type": self._subject_token_type,
223
+ "token_url": self._token_url,
224
+ "token_info_url": self._token_info_url,
225
+ "service_account_impersonation_url": self._service_account_impersonation_url,
226
+ "service_account_impersonation_options": copy.deepcopy(
227
+ self._service_account_impersonation_options
228
+ )
229
+ or None,
230
+ "credential_source": copy.deepcopy(self._credential_source),
231
+ "quota_project_id": self._quota_project_id,
232
+ "client_id": self._client_id,
233
+ "client_secret": self._client_secret,
234
+ "workforce_pool_user_project": self._workforce_pool_user_project,
235
+ "scopes": self._scopes,
236
+ "default_scopes": self._default_scopes,
237
+ "universe_domain": self._universe_domain,
238
+ }
239
+ if not self.is_workforce_pool:
240
+ args.pop("workforce_pool_user_project")
241
+ return args
242
+
243
+ @property
244
+ def service_account_email(self):
245
+ """Returns the service account email if service account impersonation is used.
246
+
247
+ Returns:
248
+ Optional[str]: The service account email if impersonation is used. Otherwise
249
+ None is returned.
250
+ """
251
+ if self._service_account_impersonation_url:
252
+ # Parse email from URL. The formal looks as follows:
253
+ # https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/name@project-id.iam.gserviceaccount.com:generateAccessToken
254
+ url = self._service_account_impersonation_url
255
+ start_index = url.rfind("/")
256
+ end_index = url.find(":generateAccessToken")
257
+ if start_index != -1 and end_index != -1 and start_index < end_index:
258
+ start_index = start_index + 1
259
+ return url[start_index:end_index]
260
+ return None
261
+
262
+ @property
263
+ def is_user(self):
264
+ """Returns whether the credentials represent a user (True) or workload (False).
265
+ Workloads behave similarly to service accounts. Currently workloads will use
266
+ service account impersonation but will eventually not require impersonation.
267
+ As a result, this property is more reliable than the service account email
268
+ property in determining if the credentials represent a user or workload.
269
+
270
+ Returns:
271
+ bool: True if the credentials represent a user. False if they represent a
272
+ workload.
273
+ """
274
+ # If service account impersonation is used, the credentials will always represent a
275
+ # service account.
276
+ if self._service_account_impersonation_url:
277
+ return False
278
+ return self.is_workforce_pool
279
+
280
+ @property
281
+ def is_workforce_pool(self):
282
+ """Returns whether the credentials represent a workforce pool (True) or
283
+ workload (False) based on the credentials' audience.
284
+
285
+ This will also return True for impersonated workforce pool credentials.
286
+
287
+ Returns:
288
+ bool: True if the credentials represent a workforce pool. False if they
289
+ represent a workload.
290
+ """
291
+ # Workforce pools representing users have the following audience format:
292
+ # //iam.googleapis.com/locations/$location/workforcePools/$poolId/providers/$providerId
293
+ p = re.compile(r"//iam\.googleapis\.com/locations/[^/]+/workforcePools/")
294
+ return p.match(self._audience or "") is not None
295
+
296
+ @property
297
+ def requires_scopes(self):
298
+ """Checks if the credentials requires scopes.
299
+
300
+ Returns:
301
+ bool: True if there are no scopes set otherwise False.
302
+ """
303
+ return not self._scopes and not self._default_scopes
304
+
305
+ @property
306
+ def project_number(self):
307
+ """Optional[str]: The project number corresponding to the workload identity pool."""
308
+
309
+ # STS audience pattern:
310
+ # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...
311
+ components = self._audience.split("/")
312
+ try:
313
+ project_index = components.index("projects")
314
+ if project_index + 1 < len(components):
315
+ return components[project_index + 1] or None
316
+ except ValueError:
317
+ return None
318
+
319
+ @property
320
+ def token_info_url(self):
321
+ """Optional[str]: The STS token introspection endpoint."""
322
+
323
+ return self._token_info_url
324
+
325
+ @_helpers.copy_docstring(credentials.Credentials)
326
+ def get_cred_info(self):
327
+ if self._cred_file_path:
328
+ cred_info_json = {
329
+ "credential_source": self._cred_file_path,
330
+ "credential_type": "external account credentials",
331
+ }
332
+ if self.service_account_email:
333
+ cred_info_json["principal"] = self.service_account_email
334
+ return cred_info_json
335
+ return None
336
+
337
+ @_helpers.copy_docstring(credentials.Scoped)
338
+ def with_scopes(self, scopes, default_scopes=None):
339
+ kwargs = self._constructor_args()
340
+ kwargs.update(scopes=scopes, default_scopes=default_scopes)
341
+ scoped = self.__class__(**kwargs)
342
+ scoped._cred_file_path = self._cred_file_path
343
+ scoped._metrics_options = self._metrics_options
344
+ return scoped
345
+
346
+ @abc.abstractmethod
347
+ def retrieve_subject_token(self, request):
348
+ """Retrieves the subject token using the credential_source object.
349
+
350
+ Args:
351
+ request (google.auth.transport.Request): A callable used to make
352
+ HTTP requests.
353
+ Returns:
354
+ str: The retrieved subject token.
355
+ """
356
+ # pylint: disable=missing-raises-doc
357
+ # (pylint doesn't recognize that this is abstract)
358
+ raise NotImplementedError("retrieve_subject_token must be implemented")
359
+
360
+ def get_project_id(self, request):
361
+ """Retrieves the project ID corresponding to the workload identity or workforce pool.
362
+ For workforce pool credentials, it returns the project ID corresponding to
363
+ the workforce_pool_user_project.
364
+
365
+ When not determinable, None is returned.
366
+
367
+ This is introduced to support the current pattern of using the Auth library:
368
+
369
+ credentials, project_id = google.auth.default()
370
+
371
+ The resource may not have permission (resourcemanager.projects.get) to
372
+ call this API or the required scopes may not be selected:
373
+ https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
374
+
375
+ Args:
376
+ request (google.auth.transport.Request): A callable used to make
377
+ HTTP requests.
378
+ Returns:
379
+ Optional[str]: The project ID corresponding to the workload identity pool
380
+ or workforce pool if determinable.
381
+ """
382
+ if self._project_id:
383
+ # If already retrieved, return the cached project ID value.
384
+ return self._project_id
385
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
386
+ # Scopes are required in order to retrieve a valid access token.
387
+ project_number = self.project_number or self._workforce_pool_user_project
388
+ if project_number and scopes:
389
+ headers = {}
390
+ url = _CLOUD_RESOURCE_MANAGER + project_number
391
+ self.before_request(request, "GET", url, headers)
392
+ response = request(url=url, method="GET", headers=headers)
393
+
394
+ response_body = (
395
+ response.data.decode("utf-8")
396
+ if hasattr(response.data, "decode")
397
+ else response.data
398
+ )
399
+ response_data = json.loads(response_body)
400
+
401
+ if response.status == 200:
402
+ # Cache result as this field is immutable.
403
+ self._project_id = response_data.get("projectId")
404
+ return self._project_id
405
+
406
+ return None
407
+
408
+ @_helpers.copy_docstring(credentials.Credentials)
409
+ def refresh(self, request):
410
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
411
+
412
+ # Inject client certificate into request.
413
+ if self._mtls_required():
414
+ request = functools.partial(
415
+ request, cert=self._get_mtls_cert_and_key_paths()
416
+ )
417
+
418
+ if self._should_initialize_impersonated_credentials():
419
+ self._impersonated_credentials = self._initialize_impersonated_credentials()
420
+
421
+ if self._impersonated_credentials:
422
+ self._impersonated_credentials.refresh(request)
423
+ self.token = self._impersonated_credentials.token
424
+ self.expiry = self._impersonated_credentials.expiry
425
+ else:
426
+ now = _helpers.utcnow()
427
+ additional_options = None
428
+ # Do not pass workforce_pool_user_project when client authentication
429
+ # is used. The client ID is sufficient for determining the user project.
430
+ if self._workforce_pool_user_project and not self._client_id:
431
+ additional_options = {"userProject": self._workforce_pool_user_project}
432
+ additional_headers = {
433
+ metrics.API_CLIENT_HEADER: metrics.byoid_metrics_header(
434
+ self._metrics_options
435
+ )
436
+ }
437
+ response_data = self._sts_client.exchange_token(
438
+ request=request,
439
+ grant_type=_STS_GRANT_TYPE,
440
+ subject_token=self.retrieve_subject_token(request),
441
+ subject_token_type=self._subject_token_type,
442
+ audience=self._audience,
443
+ scopes=scopes,
444
+ requested_token_type=_STS_REQUESTED_TOKEN_TYPE,
445
+ additional_options=additional_options,
446
+ additional_headers=additional_headers,
447
+ )
448
+ self.token = response_data.get("access_token")
449
+ expires_in = response_data.get("expires_in")
450
+ # Some services do not respect the OAUTH2.0 RFC and send expires_in as a
451
+ # JSON String.
452
+ if isinstance(expires_in, str):
453
+ expires_in = int(expires_in)
454
+
455
+ lifetime = datetime.timedelta(seconds=expires_in)
456
+
457
+ self.expiry = now + lifetime
458
+
459
+ def _make_copy(self):
460
+ kwargs = self._constructor_args()
461
+ new_cred = self.__class__(**kwargs)
462
+ new_cred._cred_file_path = self._cred_file_path
463
+ new_cred._metrics_options = self._metrics_options
464
+ return new_cred
465
+
466
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
467
+ def with_quota_project(self, quota_project_id):
468
+ # Return copy of instance with the provided quota project ID.
469
+ cred = self._make_copy()
470
+ cred._quota_project_id = quota_project_id
471
+ return cred
472
+
473
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
474
+ def with_token_uri(self, token_uri):
475
+ cred = self._make_copy()
476
+ cred._token_url = token_uri
477
+ return cred
478
+
479
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
480
+ def with_universe_domain(self, universe_domain):
481
+ cred = self._make_copy()
482
+ cred._universe_domain = universe_domain
483
+ return cred
484
+
485
+ def _should_initialize_impersonated_credentials(self):
486
+ return (
487
+ self._service_account_impersonation_url is not None
488
+ and self._impersonated_credentials is None
489
+ )
490
+
491
+ def _initialize_impersonated_credentials(self):
492
+ """Generates an impersonated credentials.
493
+
494
+ For more details, see `projects.serviceAccounts.generateAccessToken`_.
495
+
496
+ .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
497
+
498
+ Returns:
499
+ impersonated_credentials.Credential: The impersonated credentials
500
+ object.
501
+
502
+ Raises:
503
+ google.auth.exceptions.RefreshError: If the generateAccessToken
504
+ endpoint returned an error.
505
+ """
506
+ # Return copy of instance with no service account impersonation.
507
+ kwargs = self._constructor_args()
508
+ kwargs.update(
509
+ service_account_impersonation_url=None,
510
+ service_account_impersonation_options={},
511
+ )
512
+ source_credentials = self.__class__(**kwargs)
513
+ source_credentials._metrics_options = self._metrics_options
514
+
515
+ # Determine target_principal.
516
+ target_principal = self.service_account_email
517
+ if not target_principal:
518
+ raise exceptions.RefreshError(
519
+ "Unable to determine target principal from service account impersonation URL."
520
+ )
521
+
522
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
523
+ # Initialize and return impersonated credentials.
524
+ return impersonated_credentials.Credentials(
525
+ source_credentials=source_credentials,
526
+ target_principal=target_principal,
527
+ target_scopes=scopes,
528
+ quota_project_id=self._quota_project_id,
529
+ iam_endpoint_override=self._service_account_impersonation_url,
530
+ lifetime=self._service_account_impersonation_options.get(
531
+ "token_lifetime_seconds"
532
+ ),
533
+ )
534
+
535
+ def _create_default_metrics_options(self):
536
+ metrics_options = {}
537
+ if self._service_account_impersonation_url:
538
+ metrics_options["sa-impersonation"] = "true"
539
+ else:
540
+ metrics_options["sa-impersonation"] = "false"
541
+ if self._service_account_impersonation_options.get("token_lifetime_seconds"):
542
+ metrics_options["config-lifetime"] = "true"
543
+ else:
544
+ metrics_options["config-lifetime"] = "false"
545
+
546
+ return metrics_options
547
+
548
+ def _mtls_required(self):
549
+ """Returns a boolean representing whether the current credential is configured
550
+ for mTLS and should add a certificate to the outgoing calls to the sts and service
551
+ account impersonation endpoint.
552
+
553
+ Returns:
554
+ bool: True if the credential is configured for mTLS, False if it is not.
555
+ """
556
+ return False
557
+
558
+ def _get_mtls_cert_and_key_paths(self):
559
+ """Gets the file locations for a certificate and private key file
560
+ to be used for configuring mTLS for the sts and service account
561
+ impersonation calls. Currently only expected to return a value when using
562
+ X509 workload identity federation.
563
+
564
+ Returns:
565
+ Tuple[str, str]: The cert and key file locations as strings in a tuple.
566
+
567
+ Raises:
568
+ NotImplementedError: When the current credential is not configured for
569
+ mTLS.
570
+ """
571
+ raise NotImplementedError(
572
+ "_get_mtls_cert_and_key_location must be implemented."
573
+ )
574
+
575
+ @classmethod
576
+ def from_info(cls, info, **kwargs):
577
+ """Creates a Credentials instance from parsed external account info.
578
+
579
+ Args:
580
+ info (Mapping[str, str]): The external account info in Google
581
+ format.
582
+ kwargs: Additional arguments to pass to the constructor.
583
+
584
+ Returns:
585
+ google.auth.identity_pool.Credentials: The constructed
586
+ credentials.
587
+
588
+ Raises:
589
+ InvalidValue: For invalid parameters.
590
+ """
591
+ return cls(
592
+ audience=info.get("audience"),
593
+ subject_token_type=info.get("subject_token_type"),
594
+ token_url=info.get("token_url"),
595
+ token_info_url=info.get("token_info_url"),
596
+ service_account_impersonation_url=info.get(
597
+ "service_account_impersonation_url"
598
+ ),
599
+ service_account_impersonation_options=info.get(
600
+ "service_account_impersonation"
601
+ )
602
+ or {},
603
+ client_id=info.get("client_id"),
604
+ client_secret=info.get("client_secret"),
605
+ credential_source=info.get("credential_source"),
606
+ quota_project_id=info.get("quota_project_id"),
607
+ workforce_pool_user_project=info.get("workforce_pool_user_project"),
608
+ universe_domain=info.get(
609
+ "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN
610
+ ),
611
+ **kwargs
612
+ )
613
+
614
+ @classmethod
615
+ def from_file(cls, filename, **kwargs):
616
+ """Creates a Credentials instance from an external account json file.
617
+
618
+ Args:
619
+ filename (str): The path to the external account json file.
620
+ kwargs: Additional arguments to pass to the constructor.
621
+
622
+ Returns:
623
+ google.auth.identity_pool.Credentials: The constructed
624
+ credentials.
625
+ """
626
+ with io.open(filename, "r", encoding="utf-8") as json_file:
627
+ data = json.load(json_file)
628
+ return cls.from_info(data, **kwargs)
lib/python3.10/site-packages/google/auth/external_account_authorized_user.py ADDED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """External Account Authorized User Credentials.
16
+ This module provides credentials based on OAuth 2.0 access and refresh tokens.
17
+ These credentials usually access resources on behalf of a user (resource
18
+ owner).
19
+
20
+ Specifically, these are sourced using external identities via Workforce Identity Federation.
21
+
22
+ Obtaining the initial access and refresh token can be done through the Google Cloud CLI.
23
+
24
+ Example credential:
25
+ {
26
+ "type": "external_account_authorized_user",
27
+ "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID",
28
+ "refresh_token": "refreshToken",
29
+ "token_url": "https://sts.googleapis.com/v1/oauth/token",
30
+ "token_info_url": "https://sts.googleapis.com/v1/instrospect",
31
+ "client_id": "clientId",
32
+ "client_secret": "clientSecret"
33
+ }
34
+ """
35
+
36
+ import datetime
37
+ import io
38
+ import json
39
+
40
+ from google.auth import _helpers
41
+ from google.auth import credentials
42
+ from google.auth import exceptions
43
+ from google.oauth2 import sts
44
+ from google.oauth2 import utils
45
+
46
+ _EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user"
47
+
48
+
49
+ class Credentials(
50
+ credentials.CredentialsWithQuotaProject,
51
+ credentials.ReadOnlyScoped,
52
+ credentials.CredentialsWithTokenUri,
53
+ ):
54
+ """Credentials for External Account Authorized Users.
55
+
56
+ This is used to instantiate Credentials for exchanging refresh tokens from
57
+ authorized users for Google access token and authorizing requests to Google
58
+ APIs.
59
+
60
+ The credentials are considered immutable. If you want to modify the
61
+ quota project, use `with_quota_project` and if you want to modify the token
62
+ uri, use `with_token_uri`.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ token=None,
68
+ expiry=None,
69
+ refresh_token=None,
70
+ audience=None,
71
+ client_id=None,
72
+ client_secret=None,
73
+ token_url=None,
74
+ token_info_url=None,
75
+ revoke_url=None,
76
+ scopes=None,
77
+ quota_project_id=None,
78
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
79
+ ):
80
+ """Instantiates a external account authorized user credentials object.
81
+
82
+ Args:
83
+ token (str): The OAuth 2.0 access token. Can be None if refresh information
84
+ is provided.
85
+ expiry (datetime.datetime): The optional expiration datetime of the OAuth 2.0 access
86
+ token.
87
+ refresh_token (str): The optional OAuth 2.0 refresh token. If specified,
88
+ credentials can be refreshed.
89
+ audience (str): The optional STS audience which contains the resource name for the workforce
90
+ pool and the provider identifier in that pool.
91
+ client_id (str): The OAuth 2.0 client ID. Must be specified for refresh, can be left as
92
+ None if the token can not be refreshed.
93
+ client_secret (str): The OAuth 2.0 client secret. Must be specified for refresh, can be
94
+ left as None if the token can not be refreshed.
95
+ token_url (str): The optional STS token exchange endpoint for refresh. Must be specified for
96
+ refresh, can be left as None if the token can not be refreshed.
97
+ token_info_url (str): The optional STS endpoint URL for token introspection.
98
+ revoke_url (str): The optional STS endpoint URL for revoking tokens.
99
+ quota_project_id (str): The optional project ID used for quota and billing.
100
+ This project may be different from the project used to
101
+ create the credentials.
102
+ universe_domain (Optional[str]): The universe domain. The default value
103
+ is googleapis.com.
104
+
105
+ Returns:
106
+ google.auth.external_account_authorized_user.Credentials: The
107
+ constructed credentials.
108
+ """
109
+ super(Credentials, self).__init__()
110
+
111
+ self.token = token
112
+ self.expiry = expiry
113
+ self._audience = audience
114
+ self._refresh_token = refresh_token
115
+ self._token_url = token_url
116
+ self._token_info_url = token_info_url
117
+ self._client_id = client_id
118
+ self._client_secret = client_secret
119
+ self._revoke_url = revoke_url
120
+ self._quota_project_id = quota_project_id
121
+ self._scopes = scopes
122
+ self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
123
+ self._cred_file_path = None
124
+
125
+ if not self.valid and not self.can_refresh:
126
+ raise exceptions.InvalidOperation(
127
+ "Token should be created with fields to make it valid (`token` and "
128
+ "`expiry`), or fields to allow it to refresh (`refresh_token`, "
129
+ "`token_url`, `client_id`, `client_secret`)."
130
+ )
131
+
132
+ self._client_auth = None
133
+ if self._client_id:
134
+ self._client_auth = utils.ClientAuthentication(
135
+ utils.ClientAuthType.basic, self._client_id, self._client_secret
136
+ )
137
+ self._sts_client = sts.Client(self._token_url, self._client_auth)
138
+
139
+ @property
140
+ def info(self):
141
+ """Generates the serializable dictionary representation of the current
142
+ credentials.
143
+
144
+ Returns:
145
+ Mapping: The dictionary representation of the credentials. This is the
146
+ reverse of the "from_info" method defined in this class. It is
147
+ useful for serializing the current credentials so it can deserialized
148
+ later.
149
+ """
150
+ config_info = self.constructor_args()
151
+ config_info.update(type=_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE)
152
+ if config_info["expiry"]:
153
+ config_info["expiry"] = config_info["expiry"].isoformat() + "Z"
154
+
155
+ return {key: value for key, value in config_info.items() if value is not None}
156
+
157
+ def constructor_args(self):
158
+ return {
159
+ "audience": self._audience,
160
+ "refresh_token": self._refresh_token,
161
+ "token_url": self._token_url,
162
+ "token_info_url": self._token_info_url,
163
+ "client_id": self._client_id,
164
+ "client_secret": self._client_secret,
165
+ "token": self.token,
166
+ "expiry": self.expiry,
167
+ "revoke_url": self._revoke_url,
168
+ "scopes": self._scopes,
169
+ "quota_project_id": self._quota_project_id,
170
+ "universe_domain": self._universe_domain,
171
+ }
172
+
173
+ @property
174
+ def scopes(self):
175
+ """Optional[str]: The OAuth 2.0 permission scopes."""
176
+ return self._scopes
177
+
178
+ @property
179
+ def requires_scopes(self):
180
+ """ False: OAuth 2.0 credentials have their scopes set when
181
+ the initial token is requested and can not be changed."""
182
+ return False
183
+
184
+ @property
185
+ def client_id(self):
186
+ """Optional[str]: The OAuth 2.0 client ID."""
187
+ return self._client_id
188
+
189
+ @property
190
+ def client_secret(self):
191
+ """Optional[str]: The OAuth 2.0 client secret."""
192
+ return self._client_secret
193
+
194
+ @property
195
+ def audience(self):
196
+ """Optional[str]: The STS audience which contains the resource name for the
197
+ workforce pool and the provider identifier in that pool."""
198
+ return self._audience
199
+
200
+ @property
201
+ def refresh_token(self):
202
+ """Optional[str]: The OAuth 2.0 refresh token."""
203
+ return self._refresh_token
204
+
205
+ @property
206
+ def token_url(self):
207
+ """Optional[str]: The STS token exchange endpoint for refresh."""
208
+ return self._token_url
209
+
210
+ @property
211
+ def token_info_url(self):
212
+ """Optional[str]: The STS endpoint for token info."""
213
+ return self._token_info_url
214
+
215
+ @property
216
+ def revoke_url(self):
217
+ """Optional[str]: The STS endpoint for token revocation."""
218
+ return self._revoke_url
219
+
220
+ @property
221
+ def is_user(self):
222
+ """ True: This credential always represents a user."""
223
+ return True
224
+
225
+ @property
226
+ def can_refresh(self):
227
+ return all(
228
+ (self._refresh_token, self._token_url, self._client_id, self._client_secret)
229
+ )
230
+
231
+ def get_project_id(self, request=None):
232
+ """Retrieves the project ID corresponding to the workload identity or workforce pool.
233
+ For workforce pool credentials, it returns the project ID corresponding to
234
+ the workforce_pool_user_project.
235
+
236
+ When not determinable, None is returned.
237
+
238
+ Args:
239
+ request (google.auth.transport.requests.Request): Request object.
240
+ Unused here, but passed from _default.default().
241
+
242
+ Return:
243
+ str: project ID is not determinable for this credential type so it returns None
244
+ """
245
+
246
+ return None
247
+
248
+ def to_json(self, strip=None):
249
+ """Utility function that creates a JSON representation of this
250
+ credential.
251
+ Args:
252
+ strip (Sequence[str]): Optional list of members to exclude from the
253
+ generated JSON.
254
+ Returns:
255
+ str: A JSON representation of this instance. When converted into
256
+ a dictionary, it can be passed to from_info()
257
+ to create a new instance.
258
+ """
259
+ strip = strip if strip else []
260
+ return json.dumps({k: v for (k, v) in self.info.items() if k not in strip})
261
+
262
+ def refresh(self, request):
263
+ """Refreshes the access token.
264
+
265
+ Args:
266
+ request (google.auth.transport.Request): The object used to make
267
+ HTTP requests.
268
+
269
+ Raises:
270
+ google.auth.exceptions.RefreshError: If the credentials could
271
+ not be refreshed.
272
+ """
273
+ if not self.can_refresh:
274
+ raise exceptions.RefreshError(
275
+ "The credentials do not contain the necessary fields need to "
276
+ "refresh the access token. You must specify refresh_token, "
277
+ "token_url, client_id, and client_secret."
278
+ )
279
+
280
+ now = _helpers.utcnow()
281
+ response_data = self._make_sts_request(request)
282
+
283
+ self.token = response_data.get("access_token")
284
+
285
+ lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))
286
+ self.expiry = now + lifetime
287
+
288
+ if "refresh_token" in response_data:
289
+ self._refresh_token = response_data["refresh_token"]
290
+
291
+ def _make_sts_request(self, request):
292
+ return self._sts_client.refresh_token(request, self._refresh_token)
293
+
294
+ @_helpers.copy_docstring(credentials.Credentials)
295
+ def get_cred_info(self):
296
+ if self._cred_file_path:
297
+ return {
298
+ "credential_source": self._cred_file_path,
299
+ "credential_type": "external account authorized user credentials",
300
+ }
301
+ return None
302
+
303
+ def _make_copy(self):
304
+ kwargs = self.constructor_args()
305
+ cred = self.__class__(**kwargs)
306
+ cred._cred_file_path = self._cred_file_path
307
+ return cred
308
+
309
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
310
+ def with_quota_project(self, quota_project_id):
311
+ cred = self._make_copy()
312
+ cred._quota_project_id = quota_project_id
313
+ return cred
314
+
315
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
316
+ def with_token_uri(self, token_uri):
317
+ cred = self._make_copy()
318
+ cred._token_url = token_uri
319
+ return cred
320
+
321
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
322
+ def with_universe_domain(self, universe_domain):
323
+ cred = self._make_copy()
324
+ cred._universe_domain = universe_domain
325
+ return cred
326
+
327
+ @classmethod
328
+ def from_info(cls, info, **kwargs):
329
+ """Creates a Credentials instance from parsed external account info.
330
+
331
+ Args:
332
+ info (Mapping[str, str]): The external account info in Google
333
+ format.
334
+ kwargs: Additional arguments to pass to the constructor.
335
+
336
+ Returns:
337
+ google.auth.external_account_authorized_user.Credentials: The
338
+ constructed credentials.
339
+
340
+ Raises:
341
+ ValueError: For invalid parameters.
342
+ """
343
+ expiry = info.get("expiry")
344
+ if expiry:
345
+ expiry = datetime.datetime.strptime(
346
+ expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
347
+ )
348
+ return cls(
349
+ audience=info.get("audience"),
350
+ refresh_token=info.get("refresh_token"),
351
+ token_url=info.get("token_url"),
352
+ token_info_url=info.get("token_info_url"),
353
+ client_id=info.get("client_id"),
354
+ client_secret=info.get("client_secret"),
355
+ token=info.get("token"),
356
+ expiry=expiry,
357
+ revoke_url=info.get("revoke_url"),
358
+ quota_project_id=info.get("quota_project_id"),
359
+ scopes=info.get("scopes"),
360
+ universe_domain=info.get(
361
+ "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN
362
+ ),
363
+ **kwargs
364
+ )
365
+
366
+ @classmethod
367
+ def from_file(cls, filename, **kwargs):
368
+ """Creates a Credentials instance from an external account json file.
369
+
370
+ Args:
371
+ filename (str): The path to the external account json file.
372
+ kwargs: Additional arguments to pass to the constructor.
373
+
374
+ Returns:
375
+ google.auth.external_account_authorized_user.Credentials: The
376
+ constructed credentials.
377
+ """
378
+ with io.open(filename, "r", encoding="utf-8") as json_file:
379
+ data = json.load(json_file)
380
+ return cls.from_info(data, **kwargs)
lib/python3.10/site-packages/google/auth/iam.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Tools for using the Google `Cloud Identity and Access Management (IAM)
16
+ API`_'s auth-related functionality.
17
+
18
+ .. _Cloud Identity and Access Management (IAM) API:
19
+ https://cloud.google.com/iam/docs/
20
+ """
21
+
22
+ import base64
23
+ import http.client as http_client
24
+ import json
25
+
26
+ from google.auth import _exponential_backoff
27
+ from google.auth import _helpers
28
+ from google.auth import credentials
29
+ from google.auth import crypt
30
+ from google.auth import exceptions
31
+
32
+ IAM_RETRY_CODES = {
33
+ http_client.INTERNAL_SERVER_ERROR,
34
+ http_client.BAD_GATEWAY,
35
+ http_client.SERVICE_UNAVAILABLE,
36
+ http_client.GATEWAY_TIMEOUT,
37
+ }
38
+
39
+ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"]
40
+
41
+ _IAM_ENDPOINT = (
42
+ "https://iamcredentials.googleapis.com/v1/projects/-"
43
+ + "/serviceAccounts/{}:generateAccessToken"
44
+ )
45
+
46
+ _IAM_SIGN_ENDPOINT = (
47
+ "https://iamcredentials.googleapis.com/v1/projects/-"
48
+ + "/serviceAccounts/{}:signBlob"
49
+ )
50
+
51
+ _IAM_SIGNJWT_ENDPOINT = (
52
+ "https://iamcredentials.googleapis.com/v1/projects/-"
53
+ + "/serviceAccounts/{}:signJwt"
54
+ )
55
+
56
+ _IAM_IDTOKEN_ENDPOINT = (
57
+ "https://iamcredentials.googleapis.com/v1/"
58
+ + "projects/-/serviceAccounts/{}:generateIdToken"
59
+ )
60
+
61
+
62
+ class Signer(crypt.Signer):
63
+ """Signs messages using the IAM `signBlob API`_.
64
+
65
+ This is useful when you need to sign bytes but do not have access to the
66
+ credential's private key file.
67
+
68
+ .. _signBlob API:
69
+ https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts
70
+ /signBlob
71
+ """
72
+
73
+ def __init__(self, request, credentials, service_account_email):
74
+ """
75
+ Args:
76
+ request (google.auth.transport.Request): The object used to make
77
+ HTTP requests.
78
+ credentials (google.auth.credentials.Credentials): The credentials
79
+ that will be used to authenticate the request to the IAM API.
80
+ The credentials must have of one the following scopes:
81
+
82
+ - https://www.googleapis.com/auth/iam
83
+ - https://www.googleapis.com/auth/cloud-platform
84
+ service_account_email (str): The service account email identifying
85
+ which service account to use to sign bytes. Often, this can
86
+ be the same as the service account email in the given
87
+ credentials.
88
+ """
89
+ self._request = request
90
+ self._credentials = credentials
91
+ self._service_account_email = service_account_email
92
+
93
+ def _make_signing_request(self, message):
94
+ """Makes a request to the API signBlob API."""
95
+ message = _helpers.to_bytes(message)
96
+
97
+ method = "POST"
98
+ url = _IAM_SIGN_ENDPOINT.replace(
99
+ credentials.DEFAULT_UNIVERSE_DOMAIN, self._credentials.universe_domain
100
+ ).format(self._service_account_email)
101
+ headers = {"Content-Type": "application/json"}
102
+ body = json.dumps(
103
+ {"payload": base64.b64encode(message).decode("utf-8")}
104
+ ).encode("utf-8")
105
+
106
+ retries = _exponential_backoff.ExponentialBackoff()
107
+ for _ in retries:
108
+ self._credentials.before_request(self._request, method, url, headers)
109
+
110
+ response = self._request(url=url, method=method, body=body, headers=headers)
111
+
112
+ if response.status in IAM_RETRY_CODES:
113
+ continue
114
+
115
+ if response.status != http_client.OK:
116
+ raise exceptions.TransportError(
117
+ "Error calling the IAM signBlob API: {}".format(response.data)
118
+ )
119
+
120
+ return json.loads(response.data.decode("utf-8"))
121
+ raise exceptions.TransportError("exhausted signBlob endpoint retries")
122
+
123
+ @property
124
+ def key_id(self):
125
+ """Optional[str]: The key ID used to identify this private key.
126
+
127
+ .. warning::
128
+ This is always ``None``. The key ID used by IAM can not
129
+ be reliably determined ahead of time.
130
+ """
131
+ return None
132
+
133
+ @_helpers.copy_docstring(crypt.Signer)
134
+ def sign(self, message):
135
+ response = self._make_signing_request(message)
136
+ return base64.b64decode(response["signedBlob"])