koichi12 commited on
Commit
761e0a7
·
verified ·
1 Parent(s): fdaa370

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. .gitattributes +5 -0
  2. .venv/lib/python3.11/site-packages/google/auth/__init__.py +53 -0
  3. .venv/lib/python3.11/site-packages/google/auth/_cloud_sdk.py +153 -0
  4. .venv/lib/python3.11/site-packages/google/auth/_credentials_async.py +171 -0
  5. .venv/lib/python3.11/site-packages/google/auth/_exponential_backoff.py +164 -0
  6. .venv/lib/python3.11/site-packages/google/auth/api_key.py +76 -0
  7. .venv/lib/python3.11/site-packages/google/auth/aws.py +861 -0
  8. .venv/lib/python3.11/site-packages/google/auth/compute_engine/__pycache__/credentials.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/google/auth/downscoped.py +512 -0
  10. .venv/lib/python3.11/site-packages/google/auth/external_account_authorized_user.py +380 -0
  11. .venv/lib/python3.11/site-packages/google/auth/transport/_custom_tls_signer.py +283 -0
  12. .venv/lib/python3.11/site-packages/google/auth/transport/_requests_base.py +53 -0
  13. .venv/lib/python3.11/site-packages/google/auth/transport/grpc.py +343 -0
  14. .venv/lib/python3.11/site-packages/google/oauth2/__init__.py +36 -0
  15. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/__init__.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_client.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_client_async.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_credentials_async.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_id_token_async.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_reauth_async.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_service_account_async.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/challenges.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/credentials.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/gdch_credentials.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/id_token.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/reauth.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/service_account.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/sts.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/utils.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_handler.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_handler_factory.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_types.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/google/oauth2/_client.py +508 -0
  34. .venv/lib/python3.11/site-packages/google/oauth2/_client_async.py +286 -0
  35. .venv/lib/python3.11/site-packages/google/oauth2/_credentials_async.py +118 -0
  36. .venv/lib/python3.11/site-packages/google/oauth2/_id_token_async.py +285 -0
  37. .venv/lib/python3.11/site-packages/google/oauth2/_reauth_async.py +328 -0
  38. .venv/lib/python3.11/site-packages/google/oauth2/_service_account_async.py +132 -0
  39. .venv/lib/python3.11/site-packages/google/oauth2/challenges.py +281 -0
  40. .venv/lib/python3.11/site-packages/google/oauth2/credentials.py +614 -0
  41. .venv/lib/python3.11/site-packages/google/oauth2/gdch_credentials.py +251 -0
  42. .venv/lib/python3.11/site-packages/google/oauth2/id_token.py +358 -0
  43. .venv/lib/python3.11/site-packages/google/oauth2/py.typed +2 -0
  44. .venv/lib/python3.11/site-packages/google/oauth2/reauth.py +369 -0
  45. .venv/lib/python3.11/site-packages/google/oauth2/service_account.py +847 -0
  46. .venv/lib/python3.11/site-packages/google/oauth2/sts.py +176 -0
  47. .venv/lib/python3.11/site-packages/google/oauth2/utils.py +168 -0
  48. .venv/lib/python3.11/site-packages/google/oauth2/webauthn_handler.py +82 -0
  49. .venv/lib/python3.11/site-packages/google/oauth2/webauthn_types.py +156 -0
  50. .venv/lib/python3.11/site-packages/google/type/calendar_period_pb2.py +44 -0
.gitattributes CHANGED
@@ -186,3 +186,8 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_
186
  .venv/lib/python3.11/site-packages/ray/scripts/__pycache__/scripts.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
187
  .venv/lib/python3.11/site-packages/cpuinfo/__pycache__/cpuinfo.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
188
  .venv/lib/python3.11/site-packages/yaml/_yaml.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
186
  .venv/lib/python3.11/site-packages/ray/scripts/__pycache__/scripts.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
187
  .venv/lib/python3.11/site-packages/cpuinfo/__pycache__/cpuinfo.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
188
  .venv/lib/python3.11/site-packages/yaml/_yaml.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
189
+ .venv/lib/python3.11/site-packages/pillow.libs/libbrotlicommon-3ecfe81c.so.1 filter=lfs diff=lfs merge=lfs -text
190
+ .venv/lib/python3.11/site-packages/pillow.libs/libwebp-2fd3cdca.so.7.1.9 filter=lfs diff=lfs merge=lfs -text
191
+ .venv/lib/python3.11/site-packages/pillow.libs/libfreetype-be14bf51.so.6.20.1 filter=lfs diff=lfs merge=lfs -text
192
+ .venv/lib/python3.11/site-packages/pillow.libs/libpng16-58efbb84.so.16.43.0 filter=lfs diff=lfs merge=lfs -text
193
+ .venv/lib/python3.11/site-packages/pillow.libs/libjpeg-77ae51ab.so.62.4.0 filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/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())
.venv/lib/python3.11/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
.venv/lib/python3.11/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."""
.venv/lib/python3.11/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
.venv/lib/python3.11/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)
.venv/lib/python3.11/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)
.venv/lib/python3.11/site-packages/google/auth/compute_engine/__pycache__/credentials.cpython-311.pyc ADDED
Binary file (19.9 kB). View file
 
.venv/lib/python3.11/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
+ )
.venv/lib/python3.11/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)
.venv/lib/python3.11/site-packages/google/auth/transport/_custom_tls_signer.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """
16
+ Code for configuring client side TLS to offload the signing operation to
17
+ signing libraries.
18
+ """
19
+
20
+ import ctypes
21
+ import json
22
+ import logging
23
+ import os
24
+ import sys
25
+
26
+ import cffi # type: ignore
27
+
28
+ from google.auth import exceptions
29
+
30
+ _LOGGER = logging.getLogger(__name__)
31
+
32
+ # C++ offload lib requires google-auth lib to provide the following callback:
33
+ # using SignFunc = int (*)(unsigned char *sig, size_t *sig_len,
34
+ # const unsigned char *tbs, size_t tbs_len)
35
+ # The bytes to be signed and the length are provided via `tbs` and `tbs_len`,
36
+ # the callback computes the signature, and write the signature and its length
37
+ # into `sig` and `sig_len`.
38
+ # If the signing is successful, the callback returns 1, otherwise it returns 0.
39
+ SIGN_CALLBACK_CTYPE = ctypes.CFUNCTYPE(
40
+ ctypes.c_int, # return type
41
+ ctypes.POINTER(ctypes.c_ubyte), # sig
42
+ ctypes.POINTER(ctypes.c_size_t), # sig_len
43
+ ctypes.POINTER(ctypes.c_ubyte), # tbs
44
+ ctypes.c_size_t, # tbs_len
45
+ )
46
+
47
+
48
+ # Cast SSL_CTX* to void*
49
+ def _cast_ssl_ctx_to_void_p_pyopenssl(ssl_ctx):
50
+ return ctypes.cast(int(cffi.FFI().cast("intptr_t", ssl_ctx)), ctypes.c_void_p)
51
+
52
+
53
+ # Cast SSL_CTX* to void*
54
+ def _cast_ssl_ctx_to_void_p_stdlib(context):
55
+ return ctypes.c_void_p.from_address(
56
+ id(context) + ctypes.sizeof(ctypes.c_void_p) * 2
57
+ )
58
+
59
+
60
+ # Load offload library and set up the function types.
61
+ def load_offload_lib(offload_lib_path):
62
+ _LOGGER.debug("loading offload library from %s", offload_lib_path)
63
+
64
+ # winmode parameter is only available for python 3.8+.
65
+ lib = (
66
+ ctypes.CDLL(offload_lib_path, winmode=0)
67
+ if sys.version_info >= (3, 8) and os.name == "nt"
68
+ else ctypes.CDLL(offload_lib_path)
69
+ )
70
+
71
+ # Set up types for:
72
+ # int ConfigureSslContext(SignFunc sign_func, const char *cert, SSL_CTX *ctx)
73
+ lib.ConfigureSslContext.argtypes = [
74
+ SIGN_CALLBACK_CTYPE,
75
+ ctypes.c_char_p,
76
+ ctypes.c_void_p,
77
+ ]
78
+ lib.ConfigureSslContext.restype = ctypes.c_int
79
+
80
+ return lib
81
+
82
+
83
+ # Load signer library and set up the function types.
84
+ # See: https://github.com/googleapis/enterprise-certificate-proxy/blob/main/cshared/main.go
85
+ def load_signer_lib(signer_lib_path):
86
+ _LOGGER.debug("loading signer library from %s", signer_lib_path)
87
+
88
+ # winmode parameter is only available for python 3.8+.
89
+ lib = (
90
+ ctypes.CDLL(signer_lib_path, winmode=0)
91
+ if sys.version_info >= (3, 8) and os.name == "nt"
92
+ else ctypes.CDLL(signer_lib_path)
93
+ )
94
+
95
+ # Set up types for:
96
+ # func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int)
97
+ lib.GetCertPemForPython.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
98
+ # Returns: certLen
99
+ lib.GetCertPemForPython.restype = ctypes.c_int
100
+
101
+ # Set up types for:
102
+ # func SignForPython(configFilePath *C.char, digest *byte, digestLen int,
103
+ # sigHolder *byte, sigHolderLen int)
104
+ lib.SignForPython.argtypes = [
105
+ ctypes.c_char_p,
106
+ ctypes.c_char_p,
107
+ ctypes.c_int,
108
+ ctypes.c_char_p,
109
+ ctypes.c_int,
110
+ ]
111
+ # Returns: the signature length
112
+ lib.SignForPython.restype = ctypes.c_int
113
+
114
+ return lib
115
+
116
+
117
+ def load_provider_lib(provider_lib_path):
118
+ _LOGGER.debug("loading provider library from %s", provider_lib_path)
119
+
120
+ # winmode parameter is only available for python 3.8+.
121
+ lib = (
122
+ ctypes.CDLL(provider_lib_path, winmode=0)
123
+ if sys.version_info >= (3, 8) and os.name == "nt"
124
+ else ctypes.CDLL(provider_lib_path)
125
+ )
126
+
127
+ lib.ECP_attach_to_ctx.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
128
+ lib.ECP_attach_to_ctx.restype = ctypes.c_int
129
+
130
+ return lib
131
+
132
+
133
+ # Computes SHA256 hash.
134
+ def _compute_sha256_digest(to_be_signed, to_be_signed_len):
135
+ from cryptography.hazmat.primitives import hashes
136
+
137
+ data = ctypes.string_at(to_be_signed, to_be_signed_len)
138
+ hash = hashes.Hash(hashes.SHA256())
139
+ hash.update(data)
140
+ return hash.finalize()
141
+
142
+
143
+ # Create the signing callback. The actual signing work is done by the
144
+ # `SignForPython` method from the signer lib.
145
+ def get_sign_callback(signer_lib, config_file_path):
146
+ def sign_callback(sig, sig_len, tbs, tbs_len):
147
+ _LOGGER.debug("calling sign callback...")
148
+
149
+ digest = _compute_sha256_digest(tbs, tbs_len)
150
+ digestArray = ctypes.c_char * len(digest)
151
+
152
+ # reserve 2000 bytes for the signature, shoud be more then enough.
153
+ # RSA signature is 256 bytes, EC signature is 70~72.
154
+ sig_holder_len = 2000
155
+ sig_holder = ctypes.create_string_buffer(sig_holder_len)
156
+
157
+ signature_len = signer_lib.SignForPython(
158
+ config_file_path.encode(), # configFilePath
159
+ digestArray.from_buffer(bytearray(digest)), # digest
160
+ len(digest), # digestLen
161
+ sig_holder, # sigHolder
162
+ sig_holder_len, # sigHolderLen
163
+ )
164
+
165
+ if signature_len == 0:
166
+ # signing failed, return 0
167
+ return 0
168
+
169
+ sig_len[0] = signature_len
170
+ bs = bytearray(sig_holder)
171
+ for i in range(signature_len):
172
+ sig[i] = bs[i]
173
+
174
+ return 1
175
+
176
+ return SIGN_CALLBACK_CTYPE(sign_callback)
177
+
178
+
179
+ # Obtain the certificate bytes by calling the `GetCertPemForPython` method from
180
+ # the signer lib. The method is called twice, the first time is to compute the
181
+ # cert length, then we create a buffer to hold the cert, and call it again to
182
+ # fill the buffer.
183
+ def get_cert(signer_lib, config_file_path):
184
+ # First call to calculate the cert length
185
+ cert_len = signer_lib.GetCertPemForPython(
186
+ config_file_path.encode(), # configFilePath
187
+ None, # certHolder
188
+ 0, # certHolderLen
189
+ )
190
+ if cert_len == 0:
191
+ raise exceptions.MutualTLSChannelError("failed to get certificate")
192
+
193
+ # Then we create an array to hold the cert, and call again to fill the cert
194
+ cert_holder = ctypes.create_string_buffer(cert_len)
195
+ signer_lib.GetCertPemForPython(
196
+ config_file_path.encode(), # configFilePath
197
+ cert_holder, # certHolder
198
+ cert_len, # certHolderLen
199
+ )
200
+ return bytes(cert_holder)
201
+
202
+
203
+ class CustomTlsSigner(object):
204
+ def __init__(self, enterprise_cert_file_path):
205
+ """
206
+ This class loads the offload and signer library, and calls APIs from
207
+ these libraries to obtain the cert and a signing callback, and attach
208
+ them to SSL context. The cert and the signing callback will be used
209
+ for client authentication in TLS handshake.
210
+
211
+ Args:
212
+ enterprise_cert_file_path (str): the path to a enterprise cert JSON
213
+ file. The file should contain the following field:
214
+
215
+ {
216
+ "libs": {
217
+ "ecp_client": "...",
218
+ "tls_offload": "..."
219
+ }
220
+ }
221
+ """
222
+ self._enterprise_cert_file_path = enterprise_cert_file_path
223
+ self._cert = None
224
+ self._sign_callback = None
225
+ self._provider_lib = None
226
+
227
+ def load_libraries(self):
228
+ with open(self._enterprise_cert_file_path, "r") as f:
229
+ enterprise_cert_json = json.load(f)
230
+ libs = enterprise_cert_json.get("libs", {})
231
+
232
+ signer_library = libs.get("ecp_client", None)
233
+ offload_library = libs.get("tls_offload", None)
234
+ provider_library = libs.get("ecp_provider", None)
235
+
236
+ # Using newer provider implementation. This is mutually exclusive to the
237
+ # offload implementation.
238
+ if provider_library:
239
+ self._provider_lib = load_provider_lib(provider_library)
240
+ return
241
+
242
+ # Using old offload implementation
243
+ if offload_library and signer_library:
244
+ self._offload_lib = load_offload_lib(offload_library)
245
+ self._signer_lib = load_signer_lib(signer_library)
246
+ self.set_up_custom_key()
247
+ return
248
+
249
+ raise exceptions.MutualTLSChannelError("enterprise cert file is invalid")
250
+
251
+ def set_up_custom_key(self):
252
+ # We need to keep a reference of the cert and sign callback so it won't
253
+ # be garbage collected, otherwise it will crash when used by signer lib.
254
+ self._cert = get_cert(self._signer_lib, self._enterprise_cert_file_path)
255
+ self._sign_callback = get_sign_callback(
256
+ self._signer_lib, self._enterprise_cert_file_path
257
+ )
258
+
259
+ def should_use_provider(self):
260
+ if self._provider_lib:
261
+ return True
262
+ return False
263
+
264
+ def attach_to_ssl_context(self, ctx):
265
+ if self.should_use_provider():
266
+ if not self._provider_lib.ECP_attach_to_ctx(
267
+ _cast_ssl_ctx_to_void_p_stdlib(ctx),
268
+ self._enterprise_cert_file_path.encode("ascii"),
269
+ ):
270
+ raise exceptions.MutualTLSChannelError(
271
+ "failed to configure ECP Provider SSL context"
272
+ )
273
+ elif self._offload_lib and self._signer_lib:
274
+ if not self._offload_lib.ConfigureSslContext(
275
+ self._sign_callback,
276
+ ctypes.c_char_p(self._cert),
277
+ _cast_ssl_ctx_to_void_p_pyopenssl(ctx._ctx._context),
278
+ ):
279
+ raise exceptions.MutualTLSChannelError(
280
+ "failed to configure ECP Offload SSL context"
281
+ )
282
+ else:
283
+ raise exceptions.MutualTLSChannelError("Invalid ECP configuration.")
.venv/lib/python3.11/site-packages/google/auth/transport/_requests_base.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Base Requests."""
16
+ # NOTE: The coverage for this file is temporarily disabled in `.coveragerc`
17
+ # since it is currently unused.
18
+
19
+ import abc
20
+
21
+
22
+ _DEFAULT_TIMEOUT = 120 # in second
23
+
24
+
25
+ class _BaseAuthorizedSession(metaclass=abc.ABCMeta):
26
+ """Base class for a Request Session with credentials. This class is intended to capture
27
+ the common logic between synchronous and asynchronous request sessions and is not intended to
28
+ be instantiated directly.
29
+
30
+ Args:
31
+ credentials (google.auth._credentials_base.BaseCredentials): The credentials to
32
+ add to the request.
33
+ """
34
+
35
+ def __init__(self, credentials):
36
+ self.credentials = credentials
37
+
38
+ @abc.abstractmethod
39
+ def request(
40
+ self,
41
+ method,
42
+ url,
43
+ data=None,
44
+ headers=None,
45
+ max_allowed_time=None,
46
+ timeout=_DEFAULT_TIMEOUT,
47
+ **kwargs
48
+ ):
49
+ raise NotImplementedError("Request must be implemented")
50
+
51
+ @abc.abstractmethod
52
+ def close(self):
53
+ raise NotImplementedError("Close must be implemented")
.venv/lib/python3.11/site-packages/google/auth/transport/grpc.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Authorization support for gRPC."""
16
+
17
+ from __future__ import absolute_import
18
+
19
+ import logging
20
+ import os
21
+
22
+ from google.auth import environment_vars
23
+ from google.auth import exceptions
24
+ from google.auth.transport import _mtls_helper
25
+ from google.oauth2 import service_account
26
+
27
+ try:
28
+ import grpc # type: ignore
29
+ except ImportError as caught_exc: # pragma: NO COVER
30
+ raise ImportError(
31
+ "gRPC is not installed from please install the grpcio package to use the gRPC transport."
32
+ ) from caught_exc
33
+
34
+ _LOGGER = logging.getLogger(__name__)
35
+
36
+
37
+ class AuthMetadataPlugin(grpc.AuthMetadataPlugin):
38
+ """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
39
+ request.
40
+
41
+ .. _gRPC AuthMetadataPlugin:
42
+ http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin
43
+
44
+ Args:
45
+ credentials (google.auth.credentials.Credentials): The credentials to
46
+ add to requests.
47
+ request (google.auth.transport.Request): A HTTP transport request
48
+ object used to refresh credentials as needed.
49
+ default_host (Optional[str]): A host like "pubsub.googleapis.com".
50
+ This is used when a self-signed JWT is created from service
51
+ account credentials.
52
+ """
53
+
54
+ def __init__(self, credentials, request, default_host=None):
55
+ # pylint: disable=no-value-for-parameter
56
+ # pylint doesn't realize that the super method takes no arguments
57
+ # because this class is the same name as the superclass.
58
+ super(AuthMetadataPlugin, self).__init__()
59
+ self._credentials = credentials
60
+ self._request = request
61
+ self._default_host = default_host
62
+
63
+ def _get_authorization_headers(self, context):
64
+ """Gets the authorization headers for a request.
65
+
66
+ Returns:
67
+ Sequence[Tuple[str, str]]: A list of request headers (key, value)
68
+ to add to the request.
69
+ """
70
+ headers = {}
71
+
72
+ # https://google.aip.dev/auth/4111
73
+ # Attempt to use self-signed JWTs when a service account is used.
74
+ # A default host must be explicitly provided since it cannot always
75
+ # be determined from the context.service_url.
76
+ if isinstance(self._credentials, service_account.Credentials):
77
+ self._credentials._create_self_signed_jwt(
78
+ "https://{}/".format(self._default_host) if self._default_host else None
79
+ )
80
+
81
+ self._credentials.before_request(
82
+ self._request, context.method_name, context.service_url, headers
83
+ )
84
+
85
+ return list(headers.items())
86
+
87
+ def __call__(self, context, callback):
88
+ """Passes authorization metadata into the given callback.
89
+
90
+ Args:
91
+ context (grpc.AuthMetadataContext): The RPC context.
92
+ callback (grpc.AuthMetadataPluginCallback): The callback that will
93
+ be invoked to pass in the authorization metadata.
94
+ """
95
+ callback(self._get_authorization_headers(context), None)
96
+
97
+
98
+ def secure_authorized_channel(
99
+ credentials,
100
+ request,
101
+ target,
102
+ ssl_credentials=None,
103
+ client_cert_callback=None,
104
+ **kwargs
105
+ ):
106
+ """Creates a secure authorized gRPC channel.
107
+
108
+ This creates a channel with SSL and :class:`AuthMetadataPlugin`. This
109
+ channel can be used to create a stub that can make authorized requests.
110
+ Users can configure client certificate or rely on device certificates to
111
+ establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
112
+ variable is explicitly set to `true`.
113
+
114
+ Example::
115
+
116
+ import google.auth
117
+ import google.auth.transport.grpc
118
+ import google.auth.transport.requests
119
+ from google.cloud.speech.v1 import cloud_speech_pb2
120
+
121
+ # Get credentials.
122
+ credentials, _ = google.auth.default()
123
+
124
+ # Get an HTTP request function to refresh credentials.
125
+ request = google.auth.transport.requests.Request()
126
+
127
+ # Create a channel.
128
+ channel = google.auth.transport.grpc.secure_authorized_channel(
129
+ credentials, regular_endpoint, request,
130
+ ssl_credentials=grpc.ssl_channel_credentials())
131
+
132
+ # Use the channel to create a stub.
133
+ cloud_speech.create_Speech_stub(channel)
134
+
135
+ Usage:
136
+
137
+ There are actually a couple of options to create a channel, depending on if
138
+ you want to create a regular or mutual TLS channel.
139
+
140
+ First let's list the endpoints (regular vs mutual TLS) to choose from::
141
+
142
+ regular_endpoint = 'speech.googleapis.com:443'
143
+ mtls_endpoint = 'speech.mtls.googleapis.com:443'
144
+
145
+ Option 1: create a regular (non-mutual) TLS channel by explicitly setting
146
+ the ssl_credentials::
147
+
148
+ regular_ssl_credentials = grpc.ssl_channel_credentials()
149
+
150
+ channel = google.auth.transport.grpc.secure_authorized_channel(
151
+ credentials, regular_endpoint, request,
152
+ ssl_credentials=regular_ssl_credentials)
153
+
154
+ Option 2: create a mutual TLS channel by calling a callback which returns
155
+ the client side certificate and the key (Note that
156
+ `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
157
+ set to `true`)::
158
+
159
+ def my_client_cert_callback():
160
+ code_to_load_client_cert_and_key()
161
+ if loaded:
162
+ return (pem_cert_bytes, pem_key_bytes)
163
+ raise MyClientCertFailureException()
164
+
165
+ try:
166
+ channel = google.auth.transport.grpc.secure_authorized_channel(
167
+ credentials, mtls_endpoint, request,
168
+ client_cert_callback=my_client_cert_callback)
169
+ except MyClientCertFailureException:
170
+ # handle the exception
171
+
172
+ Option 3: use application default SSL credentials. It searches and uses
173
+ the command in a context aware metadata file, which is available on devices
174
+ with endpoint verification support (Note that
175
+ `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
176
+ set to `true`).
177
+ See https://cloud.google.com/endpoint-verification/docs/overview::
178
+
179
+ try:
180
+ default_ssl_credentials = SslCredentials()
181
+ except:
182
+ # Exception can be raised if the context aware metadata is malformed.
183
+ # See :class:`SslCredentials` for the possible exceptions.
184
+
185
+ # Choose the endpoint based on the SSL credentials type.
186
+ if default_ssl_credentials.is_mtls:
187
+ endpoint_to_use = mtls_endpoint
188
+ else:
189
+ endpoint_to_use = regular_endpoint
190
+ channel = google.auth.transport.grpc.secure_authorized_channel(
191
+ credentials, endpoint_to_use, request,
192
+ ssl_credentials=default_ssl_credentials)
193
+
194
+ Option 4: not setting ssl_credentials and client_cert_callback. For devices
195
+ without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE`
196
+ environment variable is not `true`, a regular TLS channel is created;
197
+ otherwise, a mutual TLS channel is created, however, the call should be
198
+ wrapped in a try/except block in case of malformed context aware metadata.
199
+
200
+ The following code uses regular_endpoint, it works the same no matter the
201
+ created channle is regular or mutual TLS. Regular endpoint ignores client
202
+ certificate and key::
203
+
204
+ channel = google.auth.transport.grpc.secure_authorized_channel(
205
+ credentials, regular_endpoint, request)
206
+
207
+ The following code uses mtls_endpoint, if the created channle is regular,
208
+ and API mtls_endpoint is confgured to require client SSL credentials, API
209
+ calls using this channel will be rejected::
210
+
211
+ channel = google.auth.transport.grpc.secure_authorized_channel(
212
+ credentials, mtls_endpoint, request)
213
+
214
+ Args:
215
+ credentials (google.auth.credentials.Credentials): The credentials to
216
+ add to requests.
217
+ request (google.auth.transport.Request): A HTTP transport request
218
+ object used to refresh credentials as needed. Even though gRPC
219
+ is a separate transport, there's no way to refresh the credentials
220
+ without using a standard http transport.
221
+ target (str): The host and port of the service.
222
+ ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
223
+ credentials. This can be used to specify different certificates.
224
+ This argument is mutually exclusive with client_cert_callback;
225
+ providing both will raise an exception.
226
+ If ssl_credentials and client_cert_callback are None, application
227
+ default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE`
228
+ environment variable is explicitly set to `true`, otherwise one way TLS
229
+ SSL credentials are used.
230
+ client_cert_callback (Callable[[], (bytes, bytes)]): Optional
231
+ callback function to obtain client certicate and key for mutual TLS
232
+ connection. This argument is mutually exclusive with
233
+ ssl_credentials; providing both will raise an exception.
234
+ This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE`
235
+ environment variable is explicitly set to `true`.
236
+ kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.
237
+
238
+ Returns:
239
+ grpc.Channel: The created gRPC channel.
240
+
241
+ Raises:
242
+ google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
243
+ creation failed for any reason.
244
+ """
245
+ # Create the metadata plugin for inserting the authorization header.
246
+ metadata_plugin = AuthMetadataPlugin(credentials, request)
247
+
248
+ # Create a set of grpc.CallCredentials using the metadata plugin.
249
+ google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
250
+
251
+ if ssl_credentials and client_cert_callback:
252
+ raise exceptions.MalformedError(
253
+ "Received both ssl_credentials and client_cert_callback; "
254
+ "these are mutually exclusive."
255
+ )
256
+
257
+ # If SSL credentials are not explicitly set, try client_cert_callback and ADC.
258
+ if not ssl_credentials:
259
+ use_client_cert = os.getenv(
260
+ environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
261
+ )
262
+ if use_client_cert == "true" and client_cert_callback:
263
+ # Use the callback if provided.
264
+ cert, key = client_cert_callback()
265
+ ssl_credentials = grpc.ssl_channel_credentials(
266
+ certificate_chain=cert, private_key=key
267
+ )
268
+ elif use_client_cert == "true":
269
+ # Use application default SSL credentials.
270
+ adc_ssl_credentils = SslCredentials()
271
+ ssl_credentials = adc_ssl_credentils.ssl_credentials
272
+ else:
273
+ ssl_credentials = grpc.ssl_channel_credentials()
274
+
275
+ # Combine the ssl credentials and the authorization credentials.
276
+ composite_credentials = grpc.composite_channel_credentials(
277
+ ssl_credentials, google_auth_credentials
278
+ )
279
+
280
+ return grpc.secure_channel(target, composite_credentials, **kwargs)
281
+
282
+
283
+ class SslCredentials:
284
+ """Class for application default SSL credentials.
285
+
286
+ The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment
287
+ variable whose default value is `false`. Client certificate will not be used
288
+ unless the environment variable is explicitly set to `true`. See
289
+ https://google.aip.dev/auth/4114
290
+
291
+ If the environment variable is `true`, then for devices with endpoint verification
292
+ support, a device certificate will be automatically loaded and mutual TLS will
293
+ be established.
294
+ See https://cloud.google.com/endpoint-verification/docs/overview.
295
+ """
296
+
297
+ def __init__(self):
298
+ use_client_cert = os.getenv(
299
+ environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
300
+ )
301
+ if use_client_cert != "true":
302
+ self._is_mtls = False
303
+ else:
304
+ # Load client SSL credentials.
305
+ metadata_path = _mtls_helper._check_config_path(
306
+ _mtls_helper.CONTEXT_AWARE_METADATA_PATH
307
+ )
308
+ self._is_mtls = metadata_path is not None
309
+
310
+ @property
311
+ def ssl_credentials(self):
312
+ """Get the created SSL channel credentials.
313
+
314
+ For devices with endpoint verification support, if the device certificate
315
+ loading has any problems, corresponding exceptions will be raised. For
316
+ a device without endpoint verification support, no exceptions will be
317
+ raised.
318
+
319
+ Returns:
320
+ grpc.ChannelCredentials: The created grpc channel credentials.
321
+
322
+ Raises:
323
+ google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
324
+ creation failed for any reason.
325
+ """
326
+ if self._is_mtls:
327
+ try:
328
+ _, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
329
+ self._ssl_credentials = grpc.ssl_channel_credentials(
330
+ certificate_chain=cert, private_key=key
331
+ )
332
+ except exceptions.ClientCertError as caught_exc:
333
+ new_exc = exceptions.MutualTLSChannelError(caught_exc)
334
+ raise new_exc from caught_exc
335
+ else:
336
+ self._ssl_credentials = grpc.ssl_channel_credentials()
337
+
338
+ return self._ssl_credentials
339
+
340
+ @property
341
+ def is_mtls(self):
342
+ """Indicates if the created SSL channel credentials is mutual TLS."""
343
+ return self._is_mtls
.venv/lib/python3.11/site-packages/google/oauth2/__init__.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 OAuth 2.0 Library for Python."""
16
+
17
+ import sys
18
+ import warnings
19
+
20
+
21
+ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER
22
+ """
23
+ Deprecation warning raised when Python 3.7 runtime is detected.
24
+ Python 3.7 support will be dropped after January 1, 2024.
25
+ """
26
+
27
+ pass
28
+
29
+
30
+ # Checks if the current runtime is Python 3.7.
31
+ if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER
32
+ message = (
33
+ "After January 1, 2024, new releases of this library will drop support "
34
+ "for Python 3.7."
35
+ )
36
+ warnings.warn(message, Python37DeprecationWarning)
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (1.05 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_client.cpython-311.pyc ADDED
Binary file (18.1 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_client_async.cpython-311.pyc ADDED
Binary file (10.7 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_credentials_async.cpython-311.pyc ADDED
Binary file (4.93 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_id_token_async.cpython-311.pyc ADDED
Binary file (11 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_reauth_async.cpython-311.pyc ADDED
Binary file (12.3 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/_service_account_async.cpython-311.pyc ADDED
Binary file (5.62 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/challenges.cpython-311.pyc ADDED
Binary file (13.4 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/credentials.cpython-311.pyc ADDED
Binary file (28.3 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/gdch_credentials.cpython-311.pyc ADDED
Binary file (9.9 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/id_token.cpython-311.pyc ADDED
Binary file (13.4 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/reauth.cpython-311.pyc ADDED
Binary file (13.1 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/service_account.cpython-311.pyc ADDED
Binary file (35.4 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/sts.cpython-311.pyc ADDED
Binary file (6.85 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/utils.cpython-311.pyc ADDED
Binary file (7.37 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_handler.cpython-311.pyc ADDED
Binary file (4.75 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_handler_factory.cpython-311.pyc ADDED
Binary file (1.27 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/__pycache__/webauthn_types.cpython-311.pyc ADDED
Binary file (8.35 kB). View file
 
.venv/lib/python3.11/site-packages/google/oauth2/_client.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 client.
16
+
17
+ This is a client for interacting with an OAuth 2.0 authorization server's
18
+ token endpoint.
19
+
20
+ For more information about the token endpoint, see
21
+ `Section 3.1 of rfc6749`_
22
+
23
+ .. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
24
+ """
25
+
26
+ import datetime
27
+ import http.client as http_client
28
+ import json
29
+ import urllib
30
+
31
+ from google.auth import _exponential_backoff
32
+ from google.auth import _helpers
33
+ from google.auth import credentials
34
+ from google.auth import exceptions
35
+ from google.auth import jwt
36
+ from google.auth import metrics
37
+ from google.auth import transport
38
+
39
+ _URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
40
+ _JSON_CONTENT_TYPE = "application/json"
41
+ _JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
42
+ _REFRESH_GRANT_TYPE = "refresh_token"
43
+
44
+
45
+ def _handle_error_response(response_data, retryable_error):
46
+ """Translates an error response into an exception.
47
+
48
+ Args:
49
+ response_data (Mapping | str): The decoded response data.
50
+ retryable_error Optional[bool]: A boolean indicating if an error is retryable.
51
+ Defaults to False.
52
+
53
+ Raises:
54
+ google.auth.exceptions.RefreshError: The errors contained in response_data.
55
+ """
56
+
57
+ retryable_error = retryable_error if retryable_error else False
58
+
59
+ if isinstance(response_data, str):
60
+ raise exceptions.RefreshError(response_data, retryable=retryable_error)
61
+ try:
62
+ error_details = "{}: {}".format(
63
+ response_data["error"], response_data.get("error_description")
64
+ )
65
+ # If no details could be extracted, use the response data.
66
+ except (KeyError, ValueError):
67
+ error_details = json.dumps(response_data)
68
+
69
+ raise exceptions.RefreshError(
70
+ error_details, response_data, retryable=retryable_error
71
+ )
72
+
73
+
74
+ def _can_retry(status_code, response_data):
75
+ """Checks if a request can be retried by inspecting the status code
76
+ and response body of the request.
77
+
78
+ Args:
79
+ status_code (int): The response status code.
80
+ response_data (Mapping | str): The decoded response data.
81
+
82
+ Returns:
83
+ bool: True if the response is retryable. False otherwise.
84
+ """
85
+ if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES:
86
+ return True
87
+
88
+ try:
89
+ # For a failed response, response_body could be a string
90
+ error_desc = response_data.get("error_description") or ""
91
+ error_code = response_data.get("error") or ""
92
+
93
+ if not isinstance(error_code, str) or not isinstance(error_desc, str):
94
+ return False
95
+
96
+ # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1
97
+ # This is needed because a redirect will not return a 500 status code.
98
+ retryable_error_descriptions = {
99
+ "internal_failure",
100
+ "server_error",
101
+ "temporarily_unavailable",
102
+ }
103
+
104
+ if any(e in retryable_error_descriptions for e in (error_code, error_desc)):
105
+ return True
106
+
107
+ except AttributeError:
108
+ pass
109
+
110
+ return False
111
+
112
+
113
+ def _parse_expiry(response_data):
114
+ """Parses the expiry field from a response into a datetime.
115
+
116
+ Args:
117
+ response_data (Mapping): The JSON-parsed response data.
118
+
119
+ Returns:
120
+ Optional[datetime]: The expiration or ``None`` if no expiration was
121
+ specified.
122
+ """
123
+ expires_in = response_data.get("expires_in", None)
124
+
125
+ if expires_in is not None:
126
+ # Some services do not respect the OAUTH2.0 RFC and send expires_in as a
127
+ # JSON String.
128
+ if isinstance(expires_in, str):
129
+ expires_in = int(expires_in)
130
+
131
+ return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)
132
+ else:
133
+ return None
134
+
135
+
136
+ def _token_endpoint_request_no_throw(
137
+ request,
138
+ token_uri,
139
+ body,
140
+ access_token=None,
141
+ use_json=False,
142
+ can_retry=True,
143
+ headers=None,
144
+ **kwargs
145
+ ):
146
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
147
+ This function doesn't throw on response errors.
148
+
149
+ Args:
150
+ request (google.auth.transport.Request): A callable used to make
151
+ HTTP requests.
152
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
153
+ URI.
154
+ body (Mapping[str, str]): The parameters to send in the request body.
155
+ access_token (Optional(str)): The access token needed to make the request.
156
+ use_json (Optional(bool)): Use urlencoded format or json format for the
157
+ content type. The default value is False.
158
+ can_retry (bool): Enable or disable request retry behavior.
159
+ headers (Optional[Mapping[str, str]]): The headers for the request.
160
+ kwargs: Additional arguments passed on to the request method. The
161
+ kwargs will be passed to `requests.request` method, see:
162
+ https://docs.python-requests.org/en/latest/api/#requests.request.
163
+ For example, you can use `cert=("cert_pem_path", "key_pem_path")`
164
+ to set up client side SSL certificate, and use
165
+ `verify="ca_bundle_path"` to set up the CA certificates for sever
166
+ side SSL certificate verification.
167
+
168
+ Returns:
169
+ Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
170
+ if the request is successful, a mapping for the JSON-decoded response
171
+ data and in the case of an error a boolean indicating if the error
172
+ is retryable.
173
+ """
174
+ if use_json:
175
+ headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE}
176
+ body = json.dumps(body).encode("utf-8")
177
+ else:
178
+ headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE}
179
+ body = urllib.parse.urlencode(body).encode("utf-8")
180
+
181
+ if access_token:
182
+ headers_to_use["Authorization"] = "Bearer {}".format(access_token)
183
+
184
+ if headers:
185
+ headers_to_use.update(headers)
186
+
187
+ response_data = {}
188
+ retryable_error = False
189
+
190
+ retries = _exponential_backoff.ExponentialBackoff()
191
+ for _ in retries:
192
+ response = request(
193
+ method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs
194
+ )
195
+ response_body = (
196
+ response.data.decode("utf-8")
197
+ if hasattr(response.data, "decode")
198
+ else response.data
199
+ )
200
+
201
+ try:
202
+ # response_body should be a JSON
203
+ response_data = json.loads(response_body)
204
+ except ValueError:
205
+ response_data = response_body
206
+
207
+ if response.status == http_client.OK:
208
+ return True, response_data, None
209
+
210
+ retryable_error = _can_retry(
211
+ status_code=response.status, response_data=response_data
212
+ )
213
+
214
+ if not can_retry or not retryable_error:
215
+ return False, response_data, retryable_error
216
+
217
+ return False, response_data, retryable_error
218
+
219
+
220
+ def _token_endpoint_request(
221
+ request,
222
+ token_uri,
223
+ body,
224
+ access_token=None,
225
+ use_json=False,
226
+ can_retry=True,
227
+ headers=None,
228
+ **kwargs
229
+ ):
230
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
231
+
232
+ Args:
233
+ request (google.auth.transport.Request): A callable used to make
234
+ HTTP requests.
235
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
236
+ URI.
237
+ body (Mapping[str, str]): The parameters to send in the request body.
238
+ access_token (Optional(str)): The access token needed to make the request.
239
+ use_json (Optional(bool)): Use urlencoded format or json format for the
240
+ content type. The default value is False.
241
+ can_retry (bool): Enable or disable request retry behavior.
242
+ headers (Optional[Mapping[str, str]]): The headers for the request.
243
+ kwargs: Additional arguments passed on to the request method. The
244
+ kwargs will be passed to `requests.request` method, see:
245
+ https://docs.python-requests.org/en/latest/api/#requests.request.
246
+ For example, you can use `cert=("cert_pem_path", "key_pem_path")`
247
+ to set up client side SSL certificate, and use
248
+ `verify="ca_bundle_path"` to set up the CA certificates for sever
249
+ side SSL certificate verification.
250
+
251
+ Returns:
252
+ Mapping[str, str]: The JSON-decoded response data.
253
+
254
+ Raises:
255
+ google.auth.exceptions.RefreshError: If the token endpoint returned
256
+ an error.
257
+ """
258
+
259
+ response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw(
260
+ request,
261
+ token_uri,
262
+ body,
263
+ access_token=access_token,
264
+ use_json=use_json,
265
+ can_retry=can_retry,
266
+ headers=headers,
267
+ **kwargs
268
+ )
269
+ if not response_status_ok:
270
+ _handle_error_response(response_data, retryable_error)
271
+ return response_data
272
+
273
+
274
+ def jwt_grant(request, token_uri, assertion, can_retry=True):
275
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants.
276
+
277
+ For more details, see `rfc7523 section 4`_.
278
+
279
+ Args:
280
+ request (google.auth.transport.Request): A callable used to make
281
+ HTTP requests.
282
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
283
+ URI.
284
+ assertion (str): The OAuth 2.0 assertion.
285
+ can_retry (bool): Enable or disable request retry behavior.
286
+
287
+ Returns:
288
+ Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
289
+ expiration, and additional data returned by the token endpoint.
290
+
291
+ Raises:
292
+ google.auth.exceptions.RefreshError: If the token endpoint returned
293
+ an error.
294
+
295
+ .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
296
+ """
297
+ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
298
+
299
+ response_data = _token_endpoint_request(
300
+ request,
301
+ token_uri,
302
+ body,
303
+ can_retry=can_retry,
304
+ headers={
305
+ metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion()
306
+ },
307
+ )
308
+
309
+ try:
310
+ access_token = response_data["access_token"]
311
+ except KeyError as caught_exc:
312
+ new_exc = exceptions.RefreshError(
313
+ "No access token in response.", response_data, retryable=False
314
+ )
315
+ raise new_exc from caught_exc
316
+
317
+ expiry = _parse_expiry(response_data)
318
+
319
+ return access_token, expiry, response_data
320
+
321
+
322
+ def call_iam_generate_id_token_endpoint(
323
+ request,
324
+ iam_id_token_endpoint,
325
+ signer_email,
326
+ audience,
327
+ access_token,
328
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
329
+ ):
330
+ """Call iam.generateIdToken endpoint to get ID token.
331
+
332
+ Args:
333
+ request (google.auth.transport.Request): A callable used to make
334
+ HTTP requests.
335
+ iam_id_token_endpoint (str): The IAM ID token endpoint to use.
336
+ signer_email (str): The signer email used to form the IAM
337
+ generateIdToken endpoint.
338
+ audience (str): The audience for the ID token.
339
+ access_token (str): The access token used to call the IAM endpoint.
340
+
341
+ Returns:
342
+ Tuple[str, datetime]: The ID token and expiration.
343
+ """
344
+ body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"}
345
+
346
+ response_data = _token_endpoint_request(
347
+ request,
348
+ iam_id_token_endpoint.replace(
349
+ credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain
350
+ ).format(signer_email),
351
+ body,
352
+ access_token=access_token,
353
+ use_json=True,
354
+ )
355
+
356
+ try:
357
+ id_token = response_data["token"]
358
+ except KeyError as caught_exc:
359
+ new_exc = exceptions.RefreshError(
360
+ "No ID token in response.", response_data, retryable=False
361
+ )
362
+ raise new_exc from caught_exc
363
+
364
+ payload = jwt.decode(id_token, verify=False)
365
+ expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
366
+
367
+ return id_token, expiry
368
+
369
+
370
+ def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):
371
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
372
+ requests an OpenID Connect ID Token instead of an access token.
373
+
374
+ This is a variant on the standard JWT Profile that is currently unique
375
+ to Google. This was added for the benefit of authenticating to services
376
+ that require ID Tokens instead of access tokens or JWT bearer tokens.
377
+
378
+ Args:
379
+ request (google.auth.transport.Request): A callable used to make
380
+ HTTP requests.
381
+ token_uri (str): The OAuth 2.0 authorization server's token endpoint
382
+ URI.
383
+ assertion (str): JWT token signed by a service account. The token's
384
+ payload must include a ``target_audience`` claim.
385
+ can_retry (bool): Enable or disable request retry behavior.
386
+
387
+ Returns:
388
+ Tuple[str, Optional[datetime], Mapping[str, str]]:
389
+ The (encoded) Open ID Connect ID Token, expiration, and additional
390
+ data returned by the endpoint.
391
+
392
+ Raises:
393
+ google.auth.exceptions.RefreshError: If the token endpoint returned
394
+ an error.
395
+ """
396
+ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
397
+
398
+ response_data = _token_endpoint_request(
399
+ request,
400
+ token_uri,
401
+ body,
402
+ can_retry=can_retry,
403
+ headers={
404
+ metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion()
405
+ },
406
+ )
407
+
408
+ try:
409
+ id_token = response_data["id_token"]
410
+ except KeyError as caught_exc:
411
+ new_exc = exceptions.RefreshError(
412
+ "No ID token in response.", response_data, retryable=False
413
+ )
414
+ raise new_exc from caught_exc
415
+
416
+ payload = jwt.decode(id_token, verify=False)
417
+ expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
418
+
419
+ return id_token, expiry, response_data
420
+
421
+
422
+ def _handle_refresh_grant_response(response_data, refresh_token):
423
+ """Extract tokens from refresh grant response.
424
+
425
+ Args:
426
+ response_data (Mapping[str, str]): Refresh grant response data.
427
+ refresh_token (str): Current refresh token.
428
+
429
+ Returns:
430
+ Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token,
431
+ refresh token, expiration, and additional data returned by the token
432
+ endpoint. If response_data doesn't have refresh token, then the current
433
+ refresh token will be returned.
434
+
435
+ Raises:
436
+ google.auth.exceptions.RefreshError: If the token endpoint returned
437
+ an error.
438
+ """
439
+ try:
440
+ access_token = response_data["access_token"]
441
+ except KeyError as caught_exc:
442
+ new_exc = exceptions.RefreshError(
443
+ "No access token in response.", response_data, retryable=False
444
+ )
445
+ raise new_exc from caught_exc
446
+
447
+ refresh_token = response_data.get("refresh_token", refresh_token)
448
+ expiry = _parse_expiry(response_data)
449
+
450
+ return access_token, refresh_token, expiry, response_data
451
+
452
+
453
+ def refresh_grant(
454
+ request,
455
+ token_uri,
456
+ refresh_token,
457
+ client_id,
458
+ client_secret,
459
+ scopes=None,
460
+ rapt_token=None,
461
+ can_retry=True,
462
+ ):
463
+ """Implements the OAuth 2.0 refresh token grant.
464
+
465
+ For more details, see `rfc678 section 6`_.
466
+
467
+ Args:
468
+ request (google.auth.transport.Request): A callable used to make
469
+ HTTP requests.
470
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
471
+ URI.
472
+ refresh_token (str): The refresh token to use to get a new access
473
+ token.
474
+ client_id (str): The OAuth 2.0 application's client ID.
475
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
476
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
477
+ scopes must be authorized for the refresh token. Useful if refresh
478
+ token has a wild card scope (e.g.
479
+ 'https://www.googleapis.com/auth/any-api').
480
+ rapt_token (Optional(str)): The reauth Proof Token.
481
+ can_retry (bool): Enable or disable request retry behavior.
482
+
483
+ Returns:
484
+ Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access
485
+ token, new or current refresh token, expiration, and additional data
486
+ returned by the token endpoint.
487
+
488
+ Raises:
489
+ google.auth.exceptions.RefreshError: If the token endpoint returned
490
+ an error.
491
+
492
+ .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
493
+ """
494
+ body = {
495
+ "grant_type": _REFRESH_GRANT_TYPE,
496
+ "client_id": client_id,
497
+ "client_secret": client_secret,
498
+ "refresh_token": refresh_token,
499
+ }
500
+ if scopes:
501
+ body["scope"] = " ".join(scopes)
502
+ if rapt_token:
503
+ body["rapt"] = rapt_token
504
+
505
+ response_data = _token_endpoint_request(
506
+ request, token_uri, body, can_retry=can_retry
507
+ )
508
+ return _handle_refresh_grant_response(response_data, refresh_token)
.venv/lib/python3.11/site-packages/google/oauth2/_client_async.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 async client.
16
+
17
+ This is a client for interacting with an OAuth 2.0 authorization server's
18
+ token endpoint.
19
+
20
+ For more information about the token endpoint, see
21
+ `Section 3.1 of rfc6749`_
22
+
23
+ .. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
24
+ """
25
+
26
+ import datetime
27
+ import http.client as http_client
28
+ import json
29
+ import urllib
30
+
31
+ from google.auth import _exponential_backoff
32
+ from google.auth import exceptions
33
+ from google.auth import jwt
34
+ from google.oauth2 import _client as client
35
+
36
+
37
+ async def _token_endpoint_request_no_throw(
38
+ request, token_uri, body, access_token=None, use_json=False, can_retry=True
39
+ ):
40
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
41
+ This function doesn't throw on response errors.
42
+
43
+ Args:
44
+ request (google.auth.transport.Request): A callable used to make
45
+ HTTP requests.
46
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
47
+ URI.
48
+ body (Mapping[str, str]): The parameters to send in the request body.
49
+ access_token (Optional(str)): The access token needed to make the request.
50
+ use_json (Optional(bool)): Use urlencoded format or json format for the
51
+ content type. The default value is False.
52
+ can_retry (bool): Enable or disable request retry behavior.
53
+
54
+ Returns:
55
+ Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating
56
+ if the request is successful, a mapping for the JSON-decoded response
57
+ data and in the case of an error a boolean indicating if the error
58
+ is retryable.
59
+ """
60
+ if use_json:
61
+ headers = {"Content-Type": client._JSON_CONTENT_TYPE}
62
+ body = json.dumps(body).encode("utf-8")
63
+ else:
64
+ headers = {"Content-Type": client._URLENCODED_CONTENT_TYPE}
65
+ body = urllib.parse.urlencode(body).encode("utf-8")
66
+
67
+ if access_token:
68
+ headers["Authorization"] = "Bearer {}".format(access_token)
69
+
70
+ response_data = {}
71
+ retryable_error = False
72
+
73
+ retries = _exponential_backoff.ExponentialBackoff()
74
+ for _ in retries:
75
+ response = await request(
76
+ method="POST", url=token_uri, headers=headers, body=body
77
+ )
78
+
79
+ # Using data.read() resulted in zlib decompression errors. This may require future investigation.
80
+ response_body1 = await response.content()
81
+
82
+ response_body = (
83
+ response_body1.decode("utf-8")
84
+ if hasattr(response_body1, "decode")
85
+ else response_body1
86
+ )
87
+
88
+ try:
89
+ response_data = json.loads(response_body)
90
+ except ValueError:
91
+ response_data = response_body
92
+
93
+ if response.status == http_client.OK:
94
+ return True, response_data, None
95
+
96
+ retryable_error = client._can_retry(
97
+ status_code=response.status, response_data=response_data
98
+ )
99
+
100
+ if not can_retry or not retryable_error:
101
+ return False, response_data, retryable_error
102
+
103
+ return False, response_data, retryable_error
104
+
105
+
106
+ async def _token_endpoint_request(
107
+ request, token_uri, body, access_token=None, use_json=False, can_retry=True
108
+ ):
109
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
110
+
111
+ Args:
112
+ request (google.auth.transport.Request): A callable used to make
113
+ HTTP requests.
114
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
115
+ URI.
116
+ body (Mapping[str, str]): The parameters to send in the request body.
117
+ access_token (Optional(str)): The access token needed to make the request.
118
+ use_json (Optional(bool)): Use urlencoded format or json format for the
119
+ content type. The default value is False.
120
+ can_retry (bool): Enable or disable request retry behavior.
121
+
122
+ Returns:
123
+ Mapping[str, str]: The JSON-decoded response data.
124
+
125
+ Raises:
126
+ google.auth.exceptions.RefreshError: If the token endpoint returned
127
+ an error.
128
+ """
129
+
130
+ response_status_ok, response_data, retryable_error = await _token_endpoint_request_no_throw(
131
+ request,
132
+ token_uri,
133
+ body,
134
+ access_token=access_token,
135
+ use_json=use_json,
136
+ can_retry=can_retry,
137
+ )
138
+ if not response_status_ok:
139
+ client._handle_error_response(response_data, retryable_error)
140
+ return response_data
141
+
142
+
143
+ async def jwt_grant(request, token_uri, assertion, can_retry=True):
144
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants.
145
+
146
+ For more details, see `rfc7523 section 4`_.
147
+
148
+ Args:
149
+ request (google.auth.transport.Request): A callable used to make
150
+ HTTP requests.
151
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
152
+ URI.
153
+ assertion (str): The OAuth 2.0 assertion.
154
+ can_retry (bool): Enable or disable request retry behavior.
155
+
156
+ Returns:
157
+ Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
158
+ expiration, and additional data returned by the token endpoint.
159
+
160
+ Raises:
161
+ google.auth.exceptions.RefreshError: If the token endpoint returned
162
+ an error.
163
+
164
+ .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
165
+ """
166
+ body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}
167
+
168
+ response_data = await _token_endpoint_request(
169
+ request, token_uri, body, can_retry=can_retry
170
+ )
171
+
172
+ try:
173
+ access_token = response_data["access_token"]
174
+ except KeyError as caught_exc:
175
+ new_exc = exceptions.RefreshError(
176
+ "No access token in response.", response_data, retryable=False
177
+ )
178
+ raise new_exc from caught_exc
179
+
180
+ expiry = client._parse_expiry(response_data)
181
+
182
+ return access_token, expiry, response_data
183
+
184
+
185
+ async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True):
186
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
187
+ requests an OpenID Connect ID Token instead of an access token.
188
+
189
+ This is a variant on the standard JWT Profile that is currently unique
190
+ to Google. This was added for the benefit of authenticating to services
191
+ that require ID Tokens instead of access tokens or JWT bearer tokens.
192
+
193
+ Args:
194
+ request (google.auth.transport.Request): A callable used to make
195
+ HTTP requests.
196
+ token_uri (str): The OAuth 2.0 authorization server's token endpoint
197
+ URI.
198
+ assertion (str): JWT token signed by a service account. The token's
199
+ payload must include a ``target_audience`` claim.
200
+ can_retry (bool): Enable or disable request retry behavior.
201
+
202
+ Returns:
203
+ Tuple[str, Optional[datetime], Mapping[str, str]]:
204
+ The (encoded) Open ID Connect ID Token, expiration, and additional
205
+ data returned by the endpoint.
206
+
207
+ Raises:
208
+ google.auth.exceptions.RefreshError: If the token endpoint returned
209
+ an error.
210
+ """
211
+ body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE}
212
+
213
+ response_data = await _token_endpoint_request(
214
+ request, token_uri, body, can_retry=can_retry
215
+ )
216
+
217
+ try:
218
+ id_token = response_data["id_token"]
219
+ except KeyError as caught_exc:
220
+ new_exc = exceptions.RefreshError(
221
+ "No ID token in response.", response_data, retryable=False
222
+ )
223
+ raise new_exc from caught_exc
224
+
225
+ payload = jwt.decode(id_token, verify=False)
226
+ expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
227
+
228
+ return id_token, expiry, response_data
229
+
230
+
231
+ async def refresh_grant(
232
+ request,
233
+ token_uri,
234
+ refresh_token,
235
+ client_id,
236
+ client_secret,
237
+ scopes=None,
238
+ rapt_token=None,
239
+ can_retry=True,
240
+ ):
241
+ """Implements the OAuth 2.0 refresh token grant.
242
+
243
+ For more details, see `rfc678 section 6`_.
244
+
245
+ Args:
246
+ request (google.auth.transport.Request): A callable used to make
247
+ HTTP requests.
248
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
249
+ URI.
250
+ refresh_token (str): The refresh token to use to get a new access
251
+ token.
252
+ client_id (str): The OAuth 2.0 application's client ID.
253
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
254
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
255
+ scopes must be authorized for the refresh token. Useful if refresh
256
+ token has a wild card scope (e.g.
257
+ 'https://www.googleapis.com/auth/any-api').
258
+ rapt_token (Optional(str)): The reauth Proof Token.
259
+ can_retry (bool): Enable or disable request retry behavior.
260
+
261
+ Returns:
262
+ Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
263
+ access token, new or current refresh token, expiration, and additional data
264
+ returned by the token endpoint.
265
+
266
+ Raises:
267
+ google.auth.exceptions.RefreshError: If the token endpoint returned
268
+ an error.
269
+
270
+ .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
271
+ """
272
+ body = {
273
+ "grant_type": client._REFRESH_GRANT_TYPE,
274
+ "client_id": client_id,
275
+ "client_secret": client_secret,
276
+ "refresh_token": refresh_token,
277
+ }
278
+ if scopes:
279
+ body["scope"] = " ".join(scopes)
280
+ if rapt_token:
281
+ body["rapt"] = rapt_token
282
+
283
+ response_data = await _token_endpoint_request(
284
+ request, token_uri, body, can_retry=can_retry
285
+ )
286
+ return client._handle_refresh_grant_response(response_data, refresh_token)
.venv/lib/python3.11/site-packages/google/oauth2/_credentials_async.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 Async Credentials.
16
+
17
+ This module provides credentials based on OAuth 2.0 access and refresh tokens.
18
+ These credentials usually access resources on behalf of a user (resource
19
+ owner).
20
+
21
+ Specifically, this is intended to use access tokens acquired using the
22
+ `Authorization Code grant`_ and can refresh those tokens using a
23
+ optional `refresh token`_.
24
+
25
+ Obtaining the initial access and refresh token is outside of the scope of this
26
+ module. Consult `rfc6749 section 4.1`_ for complete details on the
27
+ Authorization Code grant flow.
28
+
29
+ .. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
30
+ .. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
31
+ .. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
32
+ """
33
+
34
+ from google.auth import _credentials_async as credentials
35
+ from google.auth import _helpers
36
+ from google.auth import exceptions
37
+ from google.oauth2 import _reauth_async as reauth
38
+ from google.oauth2 import credentials as oauth2_credentials
39
+
40
+
41
+ class Credentials(oauth2_credentials.Credentials):
42
+ """Credentials using OAuth 2.0 access and refresh tokens.
43
+
44
+ The credentials are considered immutable. If you want to modify the
45
+ quota project, use :meth:`with_quota_project` or ::
46
+
47
+ credentials = credentials.with_quota_project('myproject-123)
48
+ """
49
+
50
+ @_helpers.copy_docstring(credentials.Credentials)
51
+ async def refresh(self, request):
52
+ if (
53
+ self._refresh_token is None
54
+ or self._token_uri is None
55
+ or self._client_id is None
56
+ or self._client_secret is None
57
+ ):
58
+ raise exceptions.RefreshError(
59
+ "The credentials do not contain the necessary fields need to "
60
+ "refresh the access token. You must specify refresh_token, "
61
+ "token_uri, client_id, and client_secret."
62
+ )
63
+
64
+ (
65
+ access_token,
66
+ refresh_token,
67
+ expiry,
68
+ grant_response,
69
+ rapt_token,
70
+ ) = await reauth.refresh_grant(
71
+ request,
72
+ self._token_uri,
73
+ self._refresh_token,
74
+ self._client_id,
75
+ self._client_secret,
76
+ scopes=self._scopes,
77
+ rapt_token=self._rapt_token,
78
+ enable_reauth_refresh=self._enable_reauth_refresh,
79
+ )
80
+
81
+ self.token = access_token
82
+ self.expiry = expiry
83
+ self._refresh_token = refresh_token
84
+ self._id_token = grant_response.get("id_token")
85
+ self._rapt_token = rapt_token
86
+
87
+ if self._scopes and "scope" in grant_response:
88
+ requested_scopes = frozenset(self._scopes)
89
+ granted_scopes = frozenset(grant_response["scope"].split())
90
+ scopes_requested_but_not_granted = requested_scopes - granted_scopes
91
+ if scopes_requested_but_not_granted:
92
+ raise exceptions.RefreshError(
93
+ "Not all requested scopes were granted by the "
94
+ "authorization server, missing scopes {}.".format(
95
+ ", ".join(scopes_requested_but_not_granted)
96
+ )
97
+ )
98
+
99
+ @_helpers.copy_docstring(credentials.Credentials)
100
+ async def before_request(self, request, method, url, headers):
101
+ if not self.valid:
102
+ await self.refresh(request)
103
+ self.apply(headers)
104
+
105
+
106
+ class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials):
107
+ """Access token credentials for user account.
108
+
109
+ Obtain the access token for a given user account or the current active
110
+ user account with the ``gcloud auth print-access-token`` command.
111
+
112
+ Args:
113
+ account (Optional[str]): Account to get the access token for. If not
114
+ specified, the current active account will be used.
115
+ quota_project_id (Optional[str]): The project ID used for quota
116
+ and billing.
117
+
118
+ """
.venv/lib/python3.11/site-packages/google/oauth2/_id_token_async.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Google ID Token helpers.
16
+
17
+ Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
18
+ generated by Google infrastructure.
19
+
20
+ To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
21
+ server use :func:`verify_oauth2_token`. To verify an ID Token issued by
22
+ Firebase, use :func:`verify_firebase_token`.
23
+
24
+ A general purpose ID Token verifier is available as :func:`verify_token`.
25
+
26
+ Example::
27
+
28
+ from google.oauth2 import _id_token_async
29
+ from google.auth.transport import aiohttp_requests
30
+
31
+ request = aiohttp_requests.Request()
32
+
33
+ id_info = await _id_token_async.verify_oauth2_token(
34
+ token, request, 'my-client-id.example.com')
35
+
36
+ if id_info['iss'] != 'https://accounts.google.com':
37
+ raise ValueError('Wrong issuer.')
38
+
39
+ userid = id_info['sub']
40
+
41
+ By default, this will re-fetch certificates for each verification. Because
42
+ Google's public keys are only changed infrequently (on the order of once per
43
+ day), you may wish to take advantage of caching to reduce latency and the
44
+ potential for network errors. This can be accomplished using an external
45
+ library like `CacheControl`_ to create a cache-aware
46
+ :class:`google.auth.transport.Request`::
47
+
48
+ import cachecontrol
49
+ import google.auth.transport.requests
50
+ import requests
51
+
52
+ session = requests.session()
53
+ cached_session = cachecontrol.CacheControl(session)
54
+ request = google.auth.transport.requests.Request(session=cached_session)
55
+
56
+ .. _OpenID Connect ID Token:
57
+ http://openid.net/specs/openid-connect-core-1_0.html#IDToken
58
+ .. _CacheControl: https://cachecontrol.readthedocs.io
59
+ """
60
+
61
+ import http.client as http_client
62
+ import json
63
+ import os
64
+
65
+ from google.auth import environment_vars
66
+ from google.auth import exceptions
67
+ from google.auth import jwt
68
+ from google.auth.transport import requests
69
+ from google.oauth2 import id_token as sync_id_token
70
+
71
+
72
+ async def _fetch_certs(request, certs_url):
73
+ """Fetches certificates.
74
+
75
+ Google-style cerificate endpoints return JSON in the format of
76
+ ``{'key id': 'x509 certificate'}``.
77
+
78
+ Args:
79
+ request (google.auth.transport.Request): The object used to make
80
+ HTTP requests. This must be an aiohttp request.
81
+ certs_url (str): The certificate endpoint URL.
82
+
83
+ Returns:
84
+ Mapping[str, str]: A mapping of public key ID to x.509 certificate
85
+ data.
86
+ """
87
+ response = await request(certs_url, method="GET")
88
+
89
+ if response.status != http_client.OK:
90
+ raise exceptions.TransportError(
91
+ "Could not fetch certificates at {}".format(certs_url)
92
+ )
93
+
94
+ data = await response.content()
95
+
96
+ return json.loads(data)
97
+
98
+
99
+ async def verify_token(
100
+ id_token,
101
+ request,
102
+ audience=None,
103
+ certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
104
+ clock_skew_in_seconds=0,
105
+ ):
106
+ """Verifies an ID token and returns the decoded token.
107
+
108
+ Args:
109
+ id_token (Union[str, bytes]): The encoded token.
110
+ request (google.auth.transport.Request): The object used to make
111
+ HTTP requests. This must be an aiohttp request.
112
+ audience (str): The audience that this token is intended for. If None
113
+ then the audience is not verified.
114
+ certs_url (str): The URL that specifies the certificates to use to
115
+ verify the token. This URL should return JSON in the format of
116
+ ``{'key id': 'x509 certificate'}``.
117
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
118
+ validation.
119
+
120
+ Returns:
121
+ Mapping[str, Any]: The decoded token.
122
+ """
123
+ certs = await _fetch_certs(request, certs_url)
124
+
125
+ return jwt.decode(
126
+ id_token,
127
+ certs=certs,
128
+ audience=audience,
129
+ clock_skew_in_seconds=clock_skew_in_seconds,
130
+ )
131
+
132
+
133
+ async def verify_oauth2_token(
134
+ id_token, request, audience=None, clock_skew_in_seconds=0
135
+ ):
136
+ """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
137
+
138
+ Args:
139
+ id_token (Union[str, bytes]): The encoded token.
140
+ request (google.auth.transport.Request): The object used to make
141
+ HTTP requests. This must be an aiohttp request.
142
+ audience (str): The audience that this token is intended for. This is
143
+ typically your application's OAuth 2.0 client ID. If None then the
144
+ audience is not verified.
145
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
146
+ validation.
147
+
148
+ Returns:
149
+ Mapping[str, Any]: The decoded token.
150
+
151
+ Raises:
152
+ exceptions.GoogleAuthError: If the issuer is invalid.
153
+ """
154
+ idinfo = await verify_token(
155
+ id_token,
156
+ request,
157
+ audience=audience,
158
+ certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
159
+ clock_skew_in_seconds=clock_skew_in_seconds,
160
+ )
161
+
162
+ if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS:
163
+ raise exceptions.GoogleAuthError(
164
+ "Wrong issuer. 'iss' should be one of the following: {}".format(
165
+ sync_id_token._GOOGLE_ISSUERS
166
+ )
167
+ )
168
+
169
+ return idinfo
170
+
171
+
172
+ async def verify_firebase_token(
173
+ id_token, request, audience=None, clock_skew_in_seconds=0
174
+ ):
175
+ """Verifies an ID Token issued by Firebase Authentication.
176
+
177
+ Args:
178
+ id_token (Union[str, bytes]): The encoded token.
179
+ request (google.auth.transport.Request): The object used to make
180
+ HTTP requests. This must be an aiohttp request.
181
+ audience (str): The audience that this token is intended for. This is
182
+ typically your Firebase application ID. If None then the audience
183
+ is not verified.
184
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
185
+ validation.
186
+
187
+ Returns:
188
+ Mapping[str, Any]: The decoded token.
189
+ """
190
+ return await verify_token(
191
+ id_token,
192
+ request,
193
+ audience=audience,
194
+ certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
195
+ clock_skew_in_seconds=clock_skew_in_seconds,
196
+ )
197
+
198
+
199
+ async def fetch_id_token(request, audience):
200
+ """Fetch the ID Token from the current environment.
201
+
202
+ This function acquires ID token from the environment in the following order.
203
+ See https://google.aip.dev/auth/4110.
204
+
205
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
206
+ to the path of a valid service account JSON file, then ID token is
207
+ acquired using this service account credentials.
208
+ 2. If the application is running in Compute Engine, App Engine or Cloud Run,
209
+ then the ID token are obtained from the metadata server.
210
+ 3. If metadata server doesn't exist and no valid service account credentials
211
+ are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
212
+ be raised.
213
+
214
+ Example::
215
+
216
+ import google.oauth2._id_token_async
217
+ import google.auth.transport.aiohttp_requests
218
+
219
+ request = google.auth.transport.aiohttp_requests.Request()
220
+ target_audience = "https://pubsub.googleapis.com"
221
+
222
+ id_token = await google.oauth2._id_token_async.fetch_id_token(request, target_audience)
223
+
224
+ Args:
225
+ request (google.auth.transport.aiohttp_requests.Request): A callable used to make
226
+ HTTP requests.
227
+ audience (str): The audience that this ID token is intended for.
228
+
229
+ Returns:
230
+ str: The ID token.
231
+
232
+ Raises:
233
+ ~google.auth.exceptions.DefaultCredentialsError:
234
+ If metadata server doesn't exist and no valid service account
235
+ credentials are found.
236
+ """
237
+ # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
238
+ # variable.
239
+ credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
240
+ if credentials_filename:
241
+ if not (
242
+ os.path.exists(credentials_filename)
243
+ and os.path.isfile(credentials_filename)
244
+ ):
245
+ raise exceptions.DefaultCredentialsError(
246
+ "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
247
+ )
248
+
249
+ try:
250
+ with open(credentials_filename, "r") as f:
251
+ from google.oauth2 import _service_account_async as service_account
252
+
253
+ info = json.load(f)
254
+ if info.get("type") == "service_account":
255
+ credentials = service_account.IDTokenCredentials.from_service_account_info(
256
+ info, target_audience=audience
257
+ )
258
+ await credentials.refresh(request)
259
+ return credentials.token
260
+ except ValueError as caught_exc:
261
+ new_exc = exceptions.DefaultCredentialsError(
262
+ "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
263
+ caught_exc,
264
+ )
265
+ raise new_exc from caught_exc
266
+
267
+ # 2. Try to fetch ID token from metada server if it exists. The code works
268
+ # for GAE and Cloud Run metadata server as well.
269
+ try:
270
+ from google.auth import compute_engine
271
+ from google.auth.compute_engine import _metadata
272
+
273
+ request_new = requests.Request()
274
+ if _metadata.ping(request_new):
275
+ credentials = compute_engine.IDTokenCredentials(
276
+ request_new, audience, use_metadata_identity_endpoint=True
277
+ )
278
+ credentials.refresh(request_new)
279
+ return credentials.token
280
+ except (ImportError, exceptions.TransportError):
281
+ pass
282
+
283
+ raise exceptions.DefaultCredentialsError(
284
+ "Neither metadata server or valid service account credentials are found."
285
+ )
.venv/lib/python3.11/site-packages/google/oauth2/_reauth_async.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """A module that provides functions for handling rapt authentication.
16
+
17
+ Reauth is a process of obtaining additional authentication (such as password,
18
+ security token, etc.) while refreshing OAuth 2.0 credentials for a user.
19
+
20
+ Credentials that use the Reauth flow must have the reauth scope,
21
+ ``https://www.googleapis.com/auth/accounts.reauth``.
22
+
23
+ This module provides a high-level function for executing the Reauth process,
24
+ :func:`refresh_grant`, and lower-level helpers for doing the individual
25
+ steps of the reauth process.
26
+
27
+ Those steps are:
28
+
29
+ 1. Obtaining a list of challenges from the reauth server.
30
+ 2. Running through each challenge and sending the result back to the reauth
31
+ server.
32
+ 3. Refreshing the access token using the returned rapt token.
33
+ """
34
+
35
+ import sys
36
+
37
+ from google.auth import exceptions
38
+ from google.oauth2 import _client
39
+ from google.oauth2 import _client_async
40
+ from google.oauth2 import challenges
41
+ from google.oauth2 import reauth
42
+
43
+
44
+ async def _get_challenges(
45
+ request, supported_challenge_types, access_token, requested_scopes=None
46
+ ):
47
+ """Does initial request to reauth API to get the challenges.
48
+
49
+ Args:
50
+ request (google.auth.transport.Request): A callable used to make
51
+ HTTP requests. This must be an aiohttp request.
52
+ supported_challenge_types (Sequence[str]): list of challenge names
53
+ supported by the manager.
54
+ access_token (str): Access token with reauth scopes.
55
+ requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials.
56
+
57
+ Returns:
58
+ dict: The response from the reauth API.
59
+ """
60
+ body = {"supportedChallengeTypes": supported_challenge_types}
61
+ if requested_scopes:
62
+ body["oauthScopesForDomainPolicyLookup"] = requested_scopes
63
+
64
+ return await _client_async._token_endpoint_request(
65
+ request,
66
+ reauth._REAUTH_API + ":start",
67
+ body,
68
+ access_token=access_token,
69
+ use_json=True,
70
+ )
71
+
72
+
73
+ async def _send_challenge_result(
74
+ request, session_id, challenge_id, client_input, access_token
75
+ ):
76
+ """Attempt to refresh access token by sending next challenge result.
77
+
78
+ Args:
79
+ request (google.auth.transport.Request): A callable used to make
80
+ HTTP requests. This must be an aiohttp request.
81
+ session_id (str): session id returned by the initial reauth call.
82
+ challenge_id (str): challenge id returned by the initial reauth call.
83
+ client_input: dict with a challenge-specific client input. For example:
84
+ ``{'credential': password}`` for password challenge.
85
+ access_token (str): Access token with reauth scopes.
86
+
87
+ Returns:
88
+ dict: The response from the reauth API.
89
+ """
90
+ body = {
91
+ "sessionId": session_id,
92
+ "challengeId": challenge_id,
93
+ "action": "RESPOND",
94
+ "proposalResponse": client_input,
95
+ }
96
+
97
+ return await _client_async._token_endpoint_request(
98
+ request,
99
+ reauth._REAUTH_API + "/{}:continue".format(session_id),
100
+ body,
101
+ access_token=access_token,
102
+ use_json=True,
103
+ )
104
+
105
+
106
+ async def _run_next_challenge(msg, request, access_token):
107
+ """Get the next challenge from msg and run it.
108
+
109
+ Args:
110
+ msg (dict): Reauth API response body (either from the initial request to
111
+ https://reauth.googleapis.com/v2/sessions:start or from sending the
112
+ previous challenge response to
113
+ https://reauth.googleapis.com/v2/sessions/id:continue)
114
+ request (google.auth.transport.Request): A callable used to make
115
+ HTTP requests. This must be an aiohttp request.
116
+ access_token (str): reauth access token
117
+
118
+ Returns:
119
+ dict: The response from the reauth API.
120
+
121
+ Raises:
122
+ google.auth.exceptions.ReauthError: if reauth failed.
123
+ """
124
+ for challenge in msg["challenges"]:
125
+ if challenge["status"] != "READY":
126
+ # Skip non-activated challenges.
127
+ continue
128
+ c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None)
129
+ if not c:
130
+ raise exceptions.ReauthFailError(
131
+ "Unsupported challenge type {0}. Supported types: {1}".format(
132
+ challenge["challengeType"],
133
+ ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())),
134
+ )
135
+ )
136
+ if not c.is_locally_eligible:
137
+ raise exceptions.ReauthFailError(
138
+ "Challenge {0} is not locally eligible".format(
139
+ challenge["challengeType"]
140
+ )
141
+ )
142
+ client_input = c.obtain_challenge_input(challenge)
143
+ if not client_input:
144
+ return None
145
+ return await _send_challenge_result(
146
+ request,
147
+ msg["sessionId"],
148
+ challenge["challengeId"],
149
+ client_input,
150
+ access_token,
151
+ )
152
+ return None
153
+
154
+
155
+ async def _obtain_rapt(request, access_token, requested_scopes):
156
+ """Given an http request method and reauth access token, get rapt token.
157
+
158
+ Args:
159
+ request (google.auth.transport.Request): A callable used to make
160
+ HTTP requests. This must be an aiohttp request.
161
+ access_token (str): reauth access token
162
+ requested_scopes (Sequence[str]): scopes required by the client application
163
+
164
+ Returns:
165
+ str: The rapt token.
166
+
167
+ Raises:
168
+ google.auth.exceptions.ReauthError: if reauth failed
169
+ """
170
+ msg = await _get_challenges(
171
+ request,
172
+ list(challenges.AVAILABLE_CHALLENGES.keys()),
173
+ access_token,
174
+ requested_scopes,
175
+ )
176
+
177
+ if msg["status"] == reauth._AUTHENTICATED:
178
+ return msg["encodedProofOfReauthToken"]
179
+
180
+ for _ in range(0, reauth.RUN_CHALLENGE_RETRY_LIMIT):
181
+ if not (
182
+ msg["status"] == reauth._CHALLENGE_REQUIRED
183
+ or msg["status"] == reauth._CHALLENGE_PENDING
184
+ ):
185
+ raise exceptions.ReauthFailError(
186
+ "Reauthentication challenge failed due to API error: {}".format(
187
+ msg["status"]
188
+ )
189
+ )
190
+
191
+ if not reauth.is_interactive():
192
+ raise exceptions.ReauthFailError(
193
+ "Reauthentication challenge could not be answered because you are not"
194
+ " in an interactive session."
195
+ )
196
+
197
+ msg = await _run_next_challenge(msg, request, access_token)
198
+
199
+ if msg["status"] == reauth._AUTHENTICATED:
200
+ return msg["encodedProofOfReauthToken"]
201
+
202
+ # If we got here it means we didn't get authenticated.
203
+ raise exceptions.ReauthFailError("Failed to obtain rapt token.")
204
+
205
+
206
+ async def get_rapt_token(
207
+ request, client_id, client_secret, refresh_token, token_uri, scopes=None
208
+ ):
209
+ """Given an http request method and refresh_token, get rapt token.
210
+
211
+ Args:
212
+ request (google.auth.transport.Request): A callable used to make
213
+ HTTP requests. This must be an aiohttp request.
214
+ client_id (str): client id to get access token for reauth scope.
215
+ client_secret (str): client secret for the client_id
216
+ refresh_token (str): refresh token to refresh access token
217
+ token_uri (str): uri to refresh access token
218
+ scopes (Optional(Sequence[str])): scopes required by the client application
219
+
220
+ Returns:
221
+ str: The rapt token.
222
+ Raises:
223
+ google.auth.exceptions.RefreshError: If reauth failed.
224
+ """
225
+ sys.stderr.write("Reauthentication required.\n")
226
+
227
+ # Get access token for reauth.
228
+ access_token, _, _, _ = await _client_async.refresh_grant(
229
+ request=request,
230
+ client_id=client_id,
231
+ client_secret=client_secret,
232
+ refresh_token=refresh_token,
233
+ token_uri=token_uri,
234
+ scopes=[reauth._REAUTH_SCOPE],
235
+ )
236
+
237
+ # Get rapt token from reauth API.
238
+ rapt_token = await _obtain_rapt(request, access_token, requested_scopes=scopes)
239
+
240
+ return rapt_token
241
+
242
+
243
+ async def refresh_grant(
244
+ request,
245
+ token_uri,
246
+ refresh_token,
247
+ client_id,
248
+ client_secret,
249
+ scopes=None,
250
+ rapt_token=None,
251
+ enable_reauth_refresh=False,
252
+ ):
253
+ """Implements the reauthentication flow.
254
+
255
+ Args:
256
+ request (google.auth.transport.Request): A callable used to make
257
+ HTTP requests. This must be an aiohttp request.
258
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
259
+ URI.
260
+ refresh_token (str): The refresh token to use to get a new access
261
+ token.
262
+ client_id (str): The OAuth 2.0 application's client ID.
263
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
264
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
265
+ scopes must be authorized for the refresh token. Useful if refresh
266
+ token has a wild card scope (e.g.
267
+ 'https://www.googleapis.com/auth/any-api').
268
+ rapt_token (Optional(str)): The rapt token for reauth.
269
+ enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
270
+ should be used. The default value is False. This option is for
271
+ gcloud only, other users should use the default value.
272
+
273
+ Returns:
274
+ Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The
275
+ access token, new refresh token, expiration, the additional data
276
+ returned by the token endpoint, and the rapt token.
277
+
278
+ Raises:
279
+ google.auth.exceptions.RefreshError: If the token endpoint returned
280
+ an error.
281
+ """
282
+ body = {
283
+ "grant_type": _client._REFRESH_GRANT_TYPE,
284
+ "client_id": client_id,
285
+ "client_secret": client_secret,
286
+ "refresh_token": refresh_token,
287
+ }
288
+ if scopes:
289
+ body["scope"] = " ".join(scopes)
290
+ if rapt_token:
291
+ body["rapt"] = rapt_token
292
+
293
+ response_status_ok, response_data, retryable_error = await _client_async._token_endpoint_request_no_throw(
294
+ request, token_uri, body
295
+ )
296
+ if (
297
+ not response_status_ok
298
+ and response_data.get("error") == reauth._REAUTH_NEEDED_ERROR
299
+ and (
300
+ response_data.get("error_subtype")
301
+ == reauth._REAUTH_NEEDED_ERROR_INVALID_RAPT
302
+ or response_data.get("error_subtype")
303
+ == reauth._REAUTH_NEEDED_ERROR_RAPT_REQUIRED
304
+ )
305
+ ):
306
+ if not enable_reauth_refresh:
307
+ raise exceptions.RefreshError(
308
+ "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate."
309
+ )
310
+
311
+ rapt_token = await get_rapt_token(
312
+ request, client_id, client_secret, refresh_token, token_uri, scopes=scopes
313
+ )
314
+ body["rapt"] = rapt_token
315
+ (
316
+ response_status_ok,
317
+ response_data,
318
+ retryable_error,
319
+ ) = await _client_async._token_endpoint_request_no_throw(
320
+ request, token_uri, body
321
+ )
322
+
323
+ if not response_status_ok:
324
+ _client._handle_error_response(response_data, retryable_error)
325
+ refresh_response = _client._handle_refresh_grant_response(
326
+ response_data, refresh_token
327
+ )
328
+ return refresh_response + (rapt_token,)
.venv/lib/python3.11/site-packages/google/oauth2/_service_account_async.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
16
+
17
+ NOTE: This file adds asynchronous refresh methods to both credentials
18
+ classes, and therefore async/await syntax is required when calling this
19
+ method when using service account credentials with asynchronous functionality.
20
+ Otherwise, all other methods are inherited from the regular service account
21
+ credentials file google.oauth2.service_account
22
+
23
+ """
24
+
25
+ from google.auth import _credentials_async as credentials_async
26
+ from google.auth import _helpers
27
+ from google.oauth2 import _client_async
28
+ from google.oauth2 import service_account
29
+
30
+
31
+ class Credentials(
32
+ service_account.Credentials, credentials_async.Scoped, credentials_async.Credentials
33
+ ):
34
+ """Service account credentials
35
+
36
+ Usually, you'll create these credentials with one of the helper
37
+ constructors. To create credentials using a Google service account
38
+ private key JSON file::
39
+
40
+ credentials = _service_account_async.Credentials.from_service_account_file(
41
+ 'service-account.json')
42
+
43
+ Or if you already have the service account file loaded::
44
+
45
+ service_account_info = json.load(open('service_account.json'))
46
+ credentials = _service_account_async.Credentials.from_service_account_info(
47
+ service_account_info)
48
+
49
+ Both helper methods pass on arguments to the constructor, so you can
50
+ specify additional scopes and a subject if necessary::
51
+
52
+ credentials = _service_account_async.Credentials.from_service_account_file(
53
+ 'service-account.json',
54
+ scopes=['email'],
55
+ subject='user@example.com')
56
+
57
+ The credentials are considered immutable. If you want to modify the scopes
58
+ or the subject used for delegation, use :meth:`with_scopes` or
59
+ :meth:`with_subject`::
60
+
61
+ scoped_credentials = credentials.with_scopes(['email'])
62
+ delegated_credentials = credentials.with_subject(subject)
63
+
64
+ To add a quota project, use :meth:`with_quota_project`::
65
+
66
+ credentials = credentials.with_quota_project('myproject-123')
67
+ """
68
+
69
+ @_helpers.copy_docstring(credentials_async.Credentials)
70
+ async def refresh(self, request):
71
+ assertion = self._make_authorization_grant_assertion()
72
+ access_token, expiry, _ = await _client_async.jwt_grant(
73
+ request, self._token_uri, assertion
74
+ )
75
+ self.token = access_token
76
+ self.expiry = expiry
77
+
78
+
79
+ class IDTokenCredentials(
80
+ service_account.IDTokenCredentials,
81
+ credentials_async.Signing,
82
+ credentials_async.Credentials,
83
+ ):
84
+ """Open ID Connect ID Token-based service account credentials.
85
+
86
+ These credentials are largely similar to :class:`.Credentials`, but instead
87
+ of using an OAuth 2.0 Access Token as the bearer token, they use an Open
88
+ ID Connect ID Token as the bearer token. These credentials are useful when
89
+ communicating to services that require ID Tokens and can not accept access
90
+ tokens.
91
+
92
+ Usually, you'll create these credentials with one of the helper
93
+ constructors. To create credentials using a Google service account
94
+ private key JSON file::
95
+
96
+ credentials = (
97
+ _service_account_async.IDTokenCredentials.from_service_account_file(
98
+ 'service-account.json'))
99
+
100
+ Or if you already have the service account file loaded::
101
+
102
+ service_account_info = json.load(open('service_account.json'))
103
+ credentials = (
104
+ _service_account_async.IDTokenCredentials.from_service_account_info(
105
+ service_account_info))
106
+
107
+ Both helper methods pass on arguments to the constructor, so you can
108
+ specify additional scopes and a subject if necessary::
109
+
110
+ credentials = (
111
+ _service_account_async.IDTokenCredentials.from_service_account_file(
112
+ 'service-account.json',
113
+ scopes=['email'],
114
+ subject='user@example.com'))
115
+
116
+ The credentials are considered immutable. If you want to modify the scopes
117
+ or the subject used for delegation, use :meth:`with_scopes` or
118
+ :meth:`with_subject`::
119
+
120
+ scoped_credentials = credentials.with_scopes(['email'])
121
+ delegated_credentials = credentials.with_subject(subject)
122
+
123
+ """
124
+
125
+ @_helpers.copy_docstring(credentials_async.Credentials)
126
+ async def refresh(self, request):
127
+ assertion = self._make_authorization_grant_assertion()
128
+ access_token, expiry, _ = await _client_async.id_token_jwt_grant(
129
+ request, self._token_uri, assertion
130
+ )
131
+ self.token = access_token
132
+ self.expiry = expiry
.venv/lib/python3.11/site-packages/google/oauth2/challenges.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """ Challenges for reauthentication.
16
+ """
17
+
18
+ import abc
19
+ import base64
20
+ import getpass
21
+ import sys
22
+
23
+ from google.auth import _helpers
24
+ from google.auth import exceptions
25
+ from google.oauth2 import webauthn_handler_factory
26
+ from google.oauth2.webauthn_types import (
27
+ AuthenticationExtensionsClientInputs,
28
+ GetRequest,
29
+ PublicKeyCredentialDescriptor,
30
+ )
31
+
32
+
33
+ REAUTH_ORIGIN = "https://accounts.google.com"
34
+ SAML_CHALLENGE_MESSAGE = (
35
+ "Please run `gcloud auth login` to complete reauthentication with SAML."
36
+ )
37
+ WEBAUTHN_TIMEOUT_MS = 120000 # Two minute timeout
38
+
39
+
40
+ def get_user_password(text):
41
+ """Get password from user.
42
+
43
+ Override this function with a different logic if you are using this library
44
+ outside a CLI.
45
+
46
+ Args:
47
+ text (str): message for the password prompt.
48
+
49
+ Returns:
50
+ str: password string.
51
+ """
52
+ return getpass.getpass(text)
53
+
54
+
55
+ class ReauthChallenge(metaclass=abc.ABCMeta):
56
+ """Base class for reauth challenges."""
57
+
58
+ @property
59
+ @abc.abstractmethod
60
+ def name(self): # pragma: NO COVER
61
+ """Returns the name of the challenge."""
62
+ raise NotImplementedError("name property must be implemented")
63
+
64
+ @property
65
+ @abc.abstractmethod
66
+ def is_locally_eligible(self): # pragma: NO COVER
67
+ """Returns true if a challenge is supported locally on this machine."""
68
+ raise NotImplementedError("is_locally_eligible property must be implemented")
69
+
70
+ @abc.abstractmethod
71
+ def obtain_challenge_input(self, metadata): # pragma: NO COVER
72
+ """Performs logic required to obtain credentials and returns it.
73
+
74
+ Args:
75
+ metadata (Mapping): challenge metadata returned in the 'challenges' field in
76
+ the initial reauth request. Includes the 'challengeType' field
77
+ and other challenge-specific fields.
78
+
79
+ Returns:
80
+ response that will be send to the reauth service as the content of
81
+ the 'proposalResponse' field in the request body. Usually a dict
82
+ with the keys specific to the challenge. For example,
83
+ ``{'credential': password}`` for password challenge.
84
+ """
85
+ raise NotImplementedError("obtain_challenge_input method must be implemented")
86
+
87
+
88
+ class PasswordChallenge(ReauthChallenge):
89
+ """Challenge that asks for user's password."""
90
+
91
+ @property
92
+ def name(self):
93
+ return "PASSWORD"
94
+
95
+ @property
96
+ def is_locally_eligible(self):
97
+ return True
98
+
99
+ @_helpers.copy_docstring(ReauthChallenge)
100
+ def obtain_challenge_input(self, unused_metadata):
101
+ passwd = get_user_password("Please enter your password:")
102
+ if not passwd:
103
+ passwd = " " # avoid the server crashing in case of no password :D
104
+ return {"credential": passwd}
105
+
106
+
107
+ class SecurityKeyChallenge(ReauthChallenge):
108
+ """Challenge that asks for user's security key touch."""
109
+
110
+ @property
111
+ def name(self):
112
+ return "SECURITY_KEY"
113
+
114
+ @property
115
+ def is_locally_eligible(self):
116
+ return True
117
+
118
+ @_helpers.copy_docstring(ReauthChallenge)
119
+ def obtain_challenge_input(self, metadata):
120
+ # Check if there is an available Webauthn Handler, if not use pyu2f
121
+ try:
122
+ factory = webauthn_handler_factory.WebauthnHandlerFactory()
123
+ webauthn_handler = factory.get_handler()
124
+ if webauthn_handler is not None:
125
+ sys.stderr.write("Please insert and touch your security key\n")
126
+ return self._obtain_challenge_input_webauthn(metadata, webauthn_handler)
127
+ except Exception:
128
+ # Attempt pyu2f if exception in webauthn flow
129
+ pass
130
+
131
+ try:
132
+ import pyu2f.convenience.authenticator # type: ignore
133
+ import pyu2f.errors # type: ignore
134
+ import pyu2f.model # type: ignore
135
+ except ImportError:
136
+ raise exceptions.ReauthFailError(
137
+ "pyu2f dependency is required to use Security key reauth feature. "
138
+ "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`."
139
+ )
140
+ sk = metadata["securityKey"]
141
+ challenges = sk["challenges"]
142
+ # Read both 'applicationId' and 'relyingPartyId', if they are the same, use
143
+ # applicationId, if they are different, use relyingPartyId first and retry
144
+ # with applicationId
145
+ application_id = sk["applicationId"]
146
+ relying_party_id = sk["relyingPartyId"]
147
+
148
+ if application_id != relying_party_id:
149
+ application_parameters = [relying_party_id, application_id]
150
+ else:
151
+ application_parameters = [application_id]
152
+
153
+ challenge_data = []
154
+ for c in challenges:
155
+ kh = c["keyHandle"].encode("ascii")
156
+ key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh)))
157
+ challenge = c["challenge"].encode("ascii")
158
+ challenge = base64.urlsafe_b64decode(challenge)
159
+ challenge_data.append({"key": key, "challenge": challenge})
160
+
161
+ # Track number of tries to suppress error message until all application_parameters
162
+ # are tried.
163
+ tries = 0
164
+ for app_id in application_parameters:
165
+ try:
166
+ tries += 1
167
+ api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator(
168
+ REAUTH_ORIGIN
169
+ )
170
+ response = api.Authenticate(
171
+ app_id, challenge_data, print_callback=sys.stderr.write
172
+ )
173
+ return {"securityKey": response}
174
+ except pyu2f.errors.U2FError as e:
175
+ if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE:
176
+ # Only show error if all app_ids have been tried
177
+ if tries == len(application_parameters):
178
+ sys.stderr.write("Ineligible security key.\n")
179
+ return None
180
+ continue
181
+ if e.code == pyu2f.errors.U2FError.TIMEOUT:
182
+ sys.stderr.write(
183
+ "Timed out while waiting for security key touch.\n"
184
+ )
185
+ else:
186
+ raise e
187
+ except pyu2f.errors.PluginError as e:
188
+ sys.stderr.write("Plugin error: {}.\n".format(e))
189
+ continue
190
+ except pyu2f.errors.NoDeviceFoundError:
191
+ sys.stderr.write("No security key found.\n")
192
+ return None
193
+
194
+ def _obtain_challenge_input_webauthn(self, metadata, webauthn_handler):
195
+ sk = metadata.get("securityKey")
196
+ if sk is None:
197
+ raise exceptions.InvalidValue("securityKey is None")
198
+ challenges = sk.get("challenges")
199
+ application_id = sk.get("applicationId")
200
+ relying_party_id = sk.get("relyingPartyId")
201
+ if challenges is None or len(challenges) < 1:
202
+ raise exceptions.InvalidValue("challenges is None or empty")
203
+ if application_id is None:
204
+ raise exceptions.InvalidValue("application_id is None")
205
+ if relying_party_id is None:
206
+ raise exceptions.InvalidValue("relying_party_id is None")
207
+
208
+ allow_credentials = []
209
+ for challenge in challenges:
210
+ kh = challenge.get("keyHandle")
211
+ if kh is None:
212
+ raise exceptions.InvalidValue("keyHandle is None")
213
+ key_handle = self._unpadded_urlsafe_b64recode(kh)
214
+ allow_credentials.append(PublicKeyCredentialDescriptor(id=key_handle))
215
+
216
+ extension = AuthenticationExtensionsClientInputs(appid=application_id)
217
+
218
+ challenge = challenges[0].get("challenge")
219
+ if challenge is None:
220
+ raise exceptions.InvalidValue("challenge is None")
221
+
222
+ get_request = GetRequest(
223
+ origin=REAUTH_ORIGIN,
224
+ rpid=relying_party_id,
225
+ challenge=self._unpadded_urlsafe_b64recode(challenge),
226
+ timeout_ms=WEBAUTHN_TIMEOUT_MS,
227
+ allow_credentials=allow_credentials,
228
+ user_verification="required",
229
+ extensions=extension,
230
+ )
231
+
232
+ try:
233
+ get_response = webauthn_handler.get(get_request)
234
+ except Exception as e:
235
+ sys.stderr.write("Webauthn Error: {}.\n".format(e))
236
+ raise e
237
+
238
+ response = {
239
+ "clientData": get_response.response.client_data_json,
240
+ "authenticatorData": get_response.response.authenticator_data,
241
+ "signatureData": get_response.response.signature,
242
+ "applicationId": application_id,
243
+ "keyHandle": get_response.id,
244
+ "securityKeyReplyType": 2,
245
+ }
246
+ return {"securityKey": response}
247
+
248
+ def _unpadded_urlsafe_b64recode(self, s):
249
+ """Converts standard b64 encoded string to url safe b64 encoded string
250
+ with no padding."""
251
+ b = base64.urlsafe_b64decode(s)
252
+ return base64.urlsafe_b64encode(b).decode().rstrip("=")
253
+
254
+
255
+ class SamlChallenge(ReauthChallenge):
256
+ """Challenge that asks the users to browse to their ID Providers.
257
+
258
+ Currently SAML challenge is not supported. When obtaining the challenge
259
+ input, exception will be raised to instruct the users to run
260
+ `gcloud auth login` for reauthentication.
261
+ """
262
+
263
+ @property
264
+ def name(self):
265
+ return "SAML"
266
+
267
+ @property
268
+ def is_locally_eligible(self):
269
+ return True
270
+
271
+ def obtain_challenge_input(self, metadata):
272
+ # Magic Arch has not fully supported returning a proper dedirect URL
273
+ # for programmatic SAML users today. So we error our here and request
274
+ # users to use gcloud to complete a login.
275
+ raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE)
276
+
277
+
278
+ AVAILABLE_CHALLENGES = {
279
+ challenge.name: challenge
280
+ for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()]
281
+ }
.venv/lib/python3.11/site-packages/google/oauth2/credentials.py ADDED
@@ -0,0 +1,614 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 Credentials.
16
+
17
+ This module provides credentials based on OAuth 2.0 access and refresh tokens.
18
+ These credentials usually access resources on behalf of a user (resource
19
+ owner).
20
+
21
+ Specifically, this is intended to use access tokens acquired using the
22
+ `Authorization Code grant`_ and can refresh those tokens using a
23
+ optional `refresh token`_.
24
+
25
+ Obtaining the initial access and refresh token is outside of the scope of this
26
+ module. Consult `rfc6749 section 4.1`_ for complete details on the
27
+ Authorization Code grant flow.
28
+
29
+ .. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
30
+ .. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
31
+ .. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
32
+ """
33
+
34
+ from datetime import datetime
35
+ import io
36
+ import json
37
+ import logging
38
+ import warnings
39
+
40
+ from google.auth import _cloud_sdk
41
+ from google.auth import _helpers
42
+ from google.auth import credentials
43
+ from google.auth import exceptions
44
+ from google.auth import metrics
45
+ from google.oauth2 import reauth
46
+
47
+ _LOGGER = logging.getLogger(__name__)
48
+
49
+
50
+ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
51
+ _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
52
+
53
+ # The Google OAuth 2.0 token info endpoint. Used for getting token info JSON from access tokens.
54
+ _GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo"
55
+
56
+
57
+ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
58
+ """Credentials using OAuth 2.0 access and refresh tokens.
59
+
60
+ The credentials are considered immutable except the tokens and the token
61
+ expiry, which are updated after refresh. If you want to modify the quota
62
+ project, use :meth:`with_quota_project` or ::
63
+
64
+ credentials = credentials.with_quota_project('myproject-123')
65
+
66
+ Reauth is disabled by default. To enable reauth, set the
67
+ `enable_reauth_refresh` parameter to True in the constructor. Note that
68
+ reauth feature is intended for gcloud to use only.
69
+ If reauth is enabled, `pyu2f` dependency has to be installed in order to use security
70
+ key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install
71
+ google-auth[reauth]`.
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ token,
77
+ refresh_token=None,
78
+ id_token=None,
79
+ token_uri=None,
80
+ client_id=None,
81
+ client_secret=None,
82
+ scopes=None,
83
+ default_scopes=None,
84
+ quota_project_id=None,
85
+ expiry=None,
86
+ rapt_token=None,
87
+ refresh_handler=None,
88
+ enable_reauth_refresh=False,
89
+ granted_scopes=None,
90
+ trust_boundary=None,
91
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
92
+ account=None,
93
+ ):
94
+ """
95
+ Args:
96
+ token (Optional(str)): The OAuth 2.0 access token. Can be None
97
+ if refresh information is provided.
98
+ refresh_token (str): The OAuth 2.0 refresh token. If specified,
99
+ credentials can be refreshed.
100
+ id_token (str): The Open ID Connect ID Token.
101
+ token_uri (str): The OAuth 2.0 authorization server's token
102
+ endpoint URI. Must be specified for refresh, can be left as
103
+ None if the token can not be refreshed.
104
+ client_id (str): The OAuth 2.0 client ID. Must be specified for
105
+ refresh, can be left as None if the token can not be refreshed.
106
+ client_secret(str): The OAuth 2.0 client secret. Must be specified
107
+ for refresh, can be left as None if the token can not be
108
+ refreshed.
109
+ scopes (Sequence[str]): The scopes used to obtain authorization.
110
+ This parameter is used by :meth:`has_scopes`. OAuth 2.0
111
+ credentials can not request additional scopes after
112
+ authorization. The scopes must be derivable from the refresh
113
+ token if refresh information is provided (e.g. The refresh
114
+ token scopes are a superset of this or contain a wild card
115
+ scope like 'https://www.googleapis.com/auth/any-api').
116
+ default_scopes (Sequence[str]): Default scopes passed by a
117
+ Google client library. Use 'scopes' for user-defined scopes.
118
+ quota_project_id (Optional[str]): The project ID used for quota and billing.
119
+ This project may be different from the project used to
120
+ create the credentials.
121
+ rapt_token (Optional[str]): The reauth Proof Token.
122
+ refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
123
+ A callable which takes in the HTTP request callable and the list of
124
+ OAuth scopes and when called returns an access token string for the
125
+ requested scopes and its expiry datetime. This is useful when no
126
+ refresh tokens are provided and tokens are obtained by calling
127
+ some external process on demand. It is particularly useful for
128
+ retrieving downscoped tokens from a token broker.
129
+ enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
130
+ should be used. This flag is for gcloud to use only.
131
+ granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user.
132
+ This could be different from the requested scopes and it could be empty if granted
133
+ and requested scopes were same.
134
+ trust_boundary (str): String representation of trust boundary meta.
135
+ universe_domain (Optional[str]): The universe domain. The default
136
+ universe domain is googleapis.com.
137
+ account (Optional[str]): The account associated with the credential.
138
+ """
139
+ super(Credentials, self).__init__()
140
+ self.token = token
141
+ self.expiry = expiry
142
+ self._refresh_token = refresh_token
143
+ self._id_token = id_token
144
+ self._scopes = scopes
145
+ self._default_scopes = default_scopes
146
+ self._granted_scopes = granted_scopes
147
+ self._token_uri = token_uri
148
+ self._client_id = client_id
149
+ self._client_secret = client_secret
150
+ self._quota_project_id = quota_project_id
151
+ self._rapt_token = rapt_token
152
+ self.refresh_handler = refresh_handler
153
+ self._enable_reauth_refresh = enable_reauth_refresh
154
+ self._trust_boundary = trust_boundary
155
+ self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
156
+ self._account = account or ""
157
+ self._cred_file_path = None
158
+
159
+ def __getstate__(self):
160
+ """A __getstate__ method must exist for the __setstate__ to be called
161
+ This is identical to the default implementation.
162
+ See https://docs.python.org/3.7/library/pickle.html#object.__setstate__
163
+ """
164
+ state_dict = self.__dict__.copy()
165
+ # Remove _refresh_handler function as there are limitations pickling and
166
+ # unpickling certain callables (lambda, functools.partial instances)
167
+ # because they need to be importable.
168
+ # Instead, the refresh_handler setter should be used to repopulate this.
169
+ if "_refresh_handler" in state_dict:
170
+ del state_dict["_refresh_handler"]
171
+
172
+ if "_refresh_worker" in state_dict:
173
+ del state_dict["_refresh_worker"]
174
+ return state_dict
175
+
176
+ def __setstate__(self, d):
177
+ """Credentials pickled with older versions of the class do not have
178
+ all the attributes."""
179
+ self.token = d.get("token")
180
+ self.expiry = d.get("expiry")
181
+ self._refresh_token = d.get("_refresh_token")
182
+ self._id_token = d.get("_id_token")
183
+ self._scopes = d.get("_scopes")
184
+ self._default_scopes = d.get("_default_scopes")
185
+ self._granted_scopes = d.get("_granted_scopes")
186
+ self._token_uri = d.get("_token_uri")
187
+ self._client_id = d.get("_client_id")
188
+ self._client_secret = d.get("_client_secret")
189
+ self._quota_project_id = d.get("_quota_project_id")
190
+ self._rapt_token = d.get("_rapt_token")
191
+ self._enable_reauth_refresh = d.get("_enable_reauth_refresh")
192
+ self._trust_boundary = d.get("_trust_boundary")
193
+ self._universe_domain = (
194
+ d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN
195
+ )
196
+ self._cred_file_path = d.get("_cred_file_path")
197
+ # The refresh_handler setter should be used to repopulate this.
198
+ self._refresh_handler = None
199
+ self._refresh_worker = None
200
+ self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False)
201
+ self._account = d.get("_account", "")
202
+
203
+ @property
204
+ def refresh_token(self):
205
+ """Optional[str]: The OAuth 2.0 refresh token."""
206
+ return self._refresh_token
207
+
208
+ @property
209
+ def scopes(self):
210
+ """Optional[str]: The OAuth 2.0 permission scopes."""
211
+ return self._scopes
212
+
213
+ @property
214
+ def granted_scopes(self):
215
+ """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user."""
216
+ return self._granted_scopes
217
+
218
+ @property
219
+ def token_uri(self):
220
+ """Optional[str]: The OAuth 2.0 authorization server's token endpoint
221
+ URI."""
222
+ return self._token_uri
223
+
224
+ @property
225
+ def id_token(self):
226
+ """Optional[str]: The Open ID Connect ID Token.
227
+
228
+ Depending on the authorization server and the scopes requested, this
229
+ may be populated when credentials are obtained and updated when
230
+ :meth:`refresh` is called. This token is a JWT. It can be verified
231
+ and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
232
+ """
233
+ return self._id_token
234
+
235
+ @property
236
+ def client_id(self):
237
+ """Optional[str]: The OAuth 2.0 client ID."""
238
+ return self._client_id
239
+
240
+ @property
241
+ def client_secret(self):
242
+ """Optional[str]: The OAuth 2.0 client secret."""
243
+ return self._client_secret
244
+
245
+ @property
246
+ def requires_scopes(self):
247
+ """False: OAuth 2.0 credentials have their scopes set when
248
+ the initial token is requested and can not be changed."""
249
+ return False
250
+
251
+ @property
252
+ def rapt_token(self):
253
+ """Optional[str]: The reauth Proof Token."""
254
+ return self._rapt_token
255
+
256
+ @property
257
+ def refresh_handler(self):
258
+ """Returns the refresh handler if available.
259
+
260
+ Returns:
261
+ Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:
262
+ The current refresh handler.
263
+ """
264
+ return self._refresh_handler
265
+
266
+ @refresh_handler.setter
267
+ def refresh_handler(self, value):
268
+ """Updates the current refresh handler.
269
+
270
+ Args:
271
+ value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
272
+ The updated value of the refresh handler.
273
+
274
+ Raises:
275
+ TypeError: If the value is not a callable or None.
276
+ """
277
+ if not callable(value) and value is not None:
278
+ raise TypeError("The provided refresh_handler is not a callable or None.")
279
+ self._refresh_handler = value
280
+
281
+ @property
282
+ def account(self):
283
+ """str: The user account associated with the credential. If the account is unknown an empty string is returned."""
284
+ return self._account
285
+
286
+ def _make_copy(self):
287
+ cred = self.__class__(
288
+ self.token,
289
+ refresh_token=self.refresh_token,
290
+ id_token=self.id_token,
291
+ token_uri=self.token_uri,
292
+ client_id=self.client_id,
293
+ client_secret=self.client_secret,
294
+ scopes=self.scopes,
295
+ default_scopes=self.default_scopes,
296
+ granted_scopes=self.granted_scopes,
297
+ quota_project_id=self.quota_project_id,
298
+ rapt_token=self.rapt_token,
299
+ enable_reauth_refresh=self._enable_reauth_refresh,
300
+ trust_boundary=self._trust_boundary,
301
+ universe_domain=self._universe_domain,
302
+ account=self._account,
303
+ )
304
+ cred._cred_file_path = self._cred_file_path
305
+ return cred
306
+
307
+ @_helpers.copy_docstring(credentials.Credentials)
308
+ def get_cred_info(self):
309
+ if self._cred_file_path:
310
+ cred_info = {
311
+ "credential_source": self._cred_file_path,
312
+ "credential_type": "user credentials",
313
+ }
314
+ if self.account:
315
+ cred_info["principal"] = self.account
316
+ return cred_info
317
+ return None
318
+
319
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
320
+ def with_quota_project(self, quota_project_id):
321
+ cred = self._make_copy()
322
+ cred._quota_project_id = quota_project_id
323
+ return cred
324
+
325
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
326
+ def with_token_uri(self, token_uri):
327
+ cred = self._make_copy()
328
+ cred._token_uri = token_uri
329
+ return cred
330
+
331
+ def with_account(self, account):
332
+ """Returns a copy of these credentials with a modified account.
333
+
334
+ Args:
335
+ account (str): The account to set
336
+
337
+ Returns:
338
+ google.oauth2.credentials.Credentials: A new credentials instance.
339
+ """
340
+ cred = self._make_copy()
341
+ cred._account = account
342
+ return cred
343
+
344
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
345
+ def with_universe_domain(self, universe_domain):
346
+ cred = self._make_copy()
347
+ cred._universe_domain = universe_domain
348
+ return cred
349
+
350
+ def _metric_header_for_usage(self):
351
+ return metrics.CRED_TYPE_USER
352
+
353
+ @_helpers.copy_docstring(credentials.Credentials)
354
+ def refresh(self, request):
355
+ if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
356
+ raise exceptions.RefreshError(
357
+ "User credential refresh is only supported in the default "
358
+ "googleapis.com universe domain, but the current universe "
359
+ "domain is {}. If you created the credential with an access "
360
+ "token, it's likely that the provided token is expired now, "
361
+ "please update your code with a valid token.".format(
362
+ self._universe_domain
363
+ )
364
+ )
365
+
366
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
367
+ # Use refresh handler if available and no refresh token is
368
+ # available. This is useful in general when tokens are obtained by calling
369
+ # some external process on demand. It is particularly useful for retrieving
370
+ # downscoped tokens from a token broker.
371
+ if self._refresh_token is None and self.refresh_handler:
372
+ token, expiry = self.refresh_handler(request, scopes=scopes)
373
+ # Validate returned data.
374
+ if not isinstance(token, str):
375
+ raise exceptions.RefreshError(
376
+ "The refresh_handler returned token is not a string."
377
+ )
378
+ if not isinstance(expiry, datetime):
379
+ raise exceptions.RefreshError(
380
+ "The refresh_handler returned expiry is not a datetime object."
381
+ )
382
+ if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD:
383
+ raise exceptions.RefreshError(
384
+ "The credentials returned by the refresh_handler are "
385
+ "already expired."
386
+ )
387
+ self.token = token
388
+ self.expiry = expiry
389
+ return
390
+
391
+ if (
392
+ self._refresh_token is None
393
+ or self._token_uri is None
394
+ or self._client_id is None
395
+ or self._client_secret is None
396
+ ):
397
+ raise exceptions.RefreshError(
398
+ "The credentials do not contain the necessary fields need to "
399
+ "refresh the access token. You must specify refresh_token, "
400
+ "token_uri, client_id, and client_secret."
401
+ )
402
+
403
+ (
404
+ access_token,
405
+ refresh_token,
406
+ expiry,
407
+ grant_response,
408
+ rapt_token,
409
+ ) = reauth.refresh_grant(
410
+ request,
411
+ self._token_uri,
412
+ self._refresh_token,
413
+ self._client_id,
414
+ self._client_secret,
415
+ scopes=scopes,
416
+ rapt_token=self._rapt_token,
417
+ enable_reauth_refresh=self._enable_reauth_refresh,
418
+ )
419
+
420
+ self.token = access_token
421
+ self.expiry = expiry
422
+ self._refresh_token = refresh_token
423
+ self._id_token = grant_response.get("id_token")
424
+ self._rapt_token = rapt_token
425
+
426
+ if scopes and "scope" in grant_response:
427
+ requested_scopes = frozenset(scopes)
428
+ self._granted_scopes = grant_response["scope"].split()
429
+ granted_scopes = frozenset(self._granted_scopes)
430
+ scopes_requested_but_not_granted = requested_scopes - granted_scopes
431
+ if scopes_requested_but_not_granted:
432
+ # User might be presented with unbundled scopes at the time of
433
+ # consent. So it is a valid scenario to not have all the requested
434
+ # scopes as part of granted scopes but log a warning in case the
435
+ # developer wants to debug the scenario.
436
+ _LOGGER.warning(
437
+ "Not all requested scopes were granted by the "
438
+ "authorization server, missing scopes {}.".format(
439
+ ", ".join(scopes_requested_but_not_granted)
440
+ )
441
+ )
442
+
443
+ @classmethod
444
+ def from_authorized_user_info(cls, info, scopes=None):
445
+ """Creates a Credentials instance from parsed authorized user info.
446
+
447
+ Args:
448
+ info (Mapping[str, str]): The authorized user info in Google
449
+ format.
450
+ scopes (Sequence[str]): Optional list of scopes to include in the
451
+ credentials.
452
+
453
+ Returns:
454
+ google.oauth2.credentials.Credentials: The constructed
455
+ credentials.
456
+
457
+ Raises:
458
+ ValueError: If the info is not in the expected format.
459
+ """
460
+ keys_needed = set(("refresh_token", "client_id", "client_secret"))
461
+ missing = keys_needed.difference(info.keys())
462
+
463
+ if missing:
464
+ raise ValueError(
465
+ "Authorized user info was not in the expected format, missing "
466
+ "fields {}.".format(", ".join(missing))
467
+ )
468
+
469
+ # access token expiry (datetime obj); auto-expire if not saved
470
+ expiry = info.get("expiry")
471
+ if expiry:
472
+ expiry = datetime.strptime(
473
+ expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
474
+ )
475
+ else:
476
+ expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD
477
+
478
+ # process scopes, which needs to be a seq
479
+ if scopes is None and "scopes" in info:
480
+ scopes = info.get("scopes")
481
+ if isinstance(scopes, str):
482
+ scopes = scopes.split(" ")
483
+
484
+ return cls(
485
+ token=info.get("token"),
486
+ refresh_token=info.get("refresh_token"),
487
+ token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides
488
+ scopes=scopes,
489
+ client_id=info.get("client_id"),
490
+ client_secret=info.get("client_secret"),
491
+ quota_project_id=info.get("quota_project_id"), # may not exist
492
+ expiry=expiry,
493
+ rapt_token=info.get("rapt_token"), # may not exist
494
+ trust_boundary=info.get("trust_boundary"), # may not exist
495
+ universe_domain=info.get("universe_domain"), # may not exist
496
+ account=info.get("account", ""), # may not exist
497
+ )
498
+
499
+ @classmethod
500
+ def from_authorized_user_file(cls, filename, scopes=None):
501
+ """Creates a Credentials instance from an authorized user json file.
502
+
503
+ Args:
504
+ filename (str): The path to the authorized user json file.
505
+ scopes (Sequence[str]): Optional list of scopes to include in the
506
+ credentials.
507
+
508
+ Returns:
509
+ google.oauth2.credentials.Credentials: The constructed
510
+ credentials.
511
+
512
+ Raises:
513
+ ValueError: If the file is not in the expected format.
514
+ """
515
+ with io.open(filename, "r", encoding="utf-8") as json_file:
516
+ data = json.load(json_file)
517
+ return cls.from_authorized_user_info(data, scopes)
518
+
519
+ def to_json(self, strip=None):
520
+ """Utility function that creates a JSON representation of a Credentials
521
+ object.
522
+
523
+ Args:
524
+ strip (Sequence[str]): Optional list of members to exclude from the
525
+ generated JSON.
526
+
527
+ Returns:
528
+ str: A JSON representation of this instance. When converted into
529
+ a dictionary, it can be passed to from_authorized_user_info()
530
+ to create a new credential instance.
531
+ """
532
+ prep = {
533
+ "token": self.token,
534
+ "refresh_token": self.refresh_token,
535
+ "token_uri": self.token_uri,
536
+ "client_id": self.client_id,
537
+ "client_secret": self.client_secret,
538
+ "scopes": self.scopes,
539
+ "rapt_token": self.rapt_token,
540
+ "universe_domain": self._universe_domain,
541
+ "account": self._account,
542
+ }
543
+ if self.expiry: # flatten expiry timestamp
544
+ prep["expiry"] = self.expiry.isoformat() + "Z"
545
+
546
+ # Remove empty entries (those which are None)
547
+ prep = {k: v for k, v in prep.items() if v is not None}
548
+
549
+ # Remove entries that explicitely need to be removed
550
+ if strip is not None:
551
+ prep = {k: v for k, v in prep.items() if k not in strip}
552
+
553
+ return json.dumps(prep)
554
+
555
+
556
+ class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
557
+ """Access token credentials for user account.
558
+
559
+ Obtain the access token for a given user account or the current active
560
+ user account with the ``gcloud auth print-access-token`` command.
561
+
562
+ Args:
563
+ account (Optional[str]): Account to get the access token for. If not
564
+ specified, the current active account will be used.
565
+ quota_project_id (Optional[str]): The project ID used for quota
566
+ and billing.
567
+ """
568
+
569
+ def __init__(self, account=None, quota_project_id=None):
570
+ warnings.warn(
571
+ "UserAccessTokenCredentials is deprecated, please use "
572
+ "google.oauth2.credentials.Credentials instead. To use "
573
+ "that credential type, simply run "
574
+ "`gcloud auth application-default login` and let the "
575
+ "client libraries pick up the application default credentials."
576
+ )
577
+ super(UserAccessTokenCredentials, self).__init__()
578
+ self._account = account
579
+ self._quota_project_id = quota_project_id
580
+
581
+ def with_account(self, account):
582
+ """Create a new instance with the given account.
583
+
584
+ Args:
585
+ account (str): Account to get the access token for.
586
+
587
+ Returns:
588
+ google.oauth2.credentials.UserAccessTokenCredentials: The created
589
+ credentials with the given account.
590
+ """
591
+ return self.__class__(account=account, quota_project_id=self._quota_project_id)
592
+
593
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
594
+ def with_quota_project(self, quota_project_id):
595
+ return self.__class__(account=self._account, quota_project_id=quota_project_id)
596
+
597
+ def refresh(self, request):
598
+ """Refreshes the access token.
599
+
600
+ Args:
601
+ request (google.auth.transport.Request): This argument is required
602
+ by the base class interface but not used in this implementation,
603
+ so just set it to `None`.
604
+
605
+ Raises:
606
+ google.auth.exceptions.UserAccessTokenError: If the access token
607
+ refresh failed.
608
+ """
609
+ self.token = _cloud_sdk.get_auth_access_token(self._account)
610
+
611
+ @_helpers.copy_docstring(credentials.Credentials)
612
+ def before_request(self, request, method, url, headers):
613
+ self.refresh(request)
614
+ self.apply(headers)
.venv/lib/python3.11/site-packages/google/oauth2/gdch_credentials.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Experimental GDCH credentials support.
16
+ """
17
+
18
+ import datetime
19
+
20
+ from google.auth import _helpers
21
+ from google.auth import _service_account_info
22
+ from google.auth import credentials
23
+ from google.auth import exceptions
24
+ from google.auth import jwt
25
+ from google.oauth2 import _client
26
+
27
+
28
+ TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange"
29
+ ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
30
+ SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount"
31
+ JWT_LIFETIME = datetime.timedelta(seconds=3600) # 1 hour
32
+
33
+
34
+ class ServiceAccountCredentials(credentials.Credentials):
35
+ """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service
36
+ account users.
37
+
38
+ .. _Google Distributed Cloud Hosted:
39
+ https://cloud.google.com/blog/topics/hybrid-cloud/\
40
+ announcing-google-distributed-cloud-edge-and-hosted
41
+
42
+ To create a GDCH service account credential, first create a JSON file of
43
+ the following format::
44
+
45
+ {
46
+ "type": "gdch_service_account",
47
+ "format_version": "1",
48
+ "project": "<project name>",
49
+ "private_key_id": "<key id>",
50
+ "private_key": "-----BEGIN EC PRIVATE KEY-----\n<key bytes>\n-----END EC PRIVATE KEY-----\n",
51
+ "name": "<service identity name>",
52
+ "ca_cert_path": "<CA cert path>",
53
+ "token_uri": "https://service-identity.<Domain>/authenticate"
54
+ }
55
+
56
+ The "format_version" field stands for the format of the JSON file. For now
57
+ it is always "1". The `private_key_id` and `private_key` is used for signing.
58
+ The `ca_cert_path` is used for token server TLS certificate verification.
59
+
60
+ After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment
61
+ variable to the JSON file path, then use the following code to create the
62
+ credential::
63
+
64
+ import google.auth
65
+
66
+ credential, _ = google.auth.default()
67
+ credential = credential.with_gdch_audience("<the audience>")
68
+
69
+ We can also create the credential directly::
70
+
71
+ from google.oauth import gdch_credentials
72
+
73
+ credential = gdch_credentials.ServiceAccountCredentials.from_service_account_file("<the json file path>")
74
+ credential = credential.with_gdch_audience("<the audience>")
75
+
76
+ The token is obtained in the following way. This class first creates a
77
+ self signed JWT. It uses the `name` value as the `iss` and `sub` claim, and
78
+ the `token_uri` as the `aud` claim, and signs the JWT with the `private_key`.
79
+ It then sends the JWT to the `token_uri` to exchange a final token for
80
+ `audience`.
81
+ """
82
+
83
+ def __init__(
84
+ self, signer, service_identity_name, project, audience, token_uri, ca_cert_path
85
+ ):
86
+ """
87
+ Args:
88
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
89
+ service_identity_name (str): The service identity name. It will be
90
+ used as the `iss` and `sub` claim in the self signed JWT.
91
+ project (str): The project.
92
+ audience (str): The audience for the final token.
93
+ token_uri (str): The token server uri.
94
+ ca_cert_path (str): The CA cert path for token server side TLS
95
+ certificate verification. If the token server uses well known
96
+ CA, then this parameter can be `None`.
97
+ """
98
+ super(ServiceAccountCredentials, self).__init__()
99
+ self._signer = signer
100
+ self._service_identity_name = service_identity_name
101
+ self._project = project
102
+ self._audience = audience
103
+ self._token_uri = token_uri
104
+ self._ca_cert_path = ca_cert_path
105
+
106
+ def _create_jwt(self):
107
+ now = _helpers.utcnow()
108
+ expiry = now + JWT_LIFETIME
109
+ iss_sub_value = "system:serviceaccount:{}:{}".format(
110
+ self._project, self._service_identity_name
111
+ )
112
+
113
+ payload = {
114
+ "iss": iss_sub_value,
115
+ "sub": iss_sub_value,
116
+ "aud": self._token_uri,
117
+ "iat": _helpers.datetime_to_secs(now),
118
+ "exp": _helpers.datetime_to_secs(expiry),
119
+ }
120
+
121
+ return _helpers.from_bytes(jwt.encode(self._signer, payload))
122
+
123
+ @_helpers.copy_docstring(credentials.Credentials)
124
+ def refresh(self, request):
125
+ import google.auth.transport.requests
126
+
127
+ if not isinstance(request, google.auth.transport.requests.Request):
128
+ raise exceptions.RefreshError(
129
+ "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object"
130
+ )
131
+
132
+ # Create a self signed JWT, and do token exchange.
133
+ jwt_token = self._create_jwt()
134
+ request_body = {
135
+ "grant_type": TOKEN_EXCHANGE_TYPE,
136
+ "audience": self._audience,
137
+ "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE,
138
+ "subject_token": jwt_token,
139
+ "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE,
140
+ }
141
+ response_data = _client._token_endpoint_request(
142
+ request,
143
+ self._token_uri,
144
+ request_body,
145
+ access_token=None,
146
+ use_json=True,
147
+ verify=self._ca_cert_path,
148
+ )
149
+
150
+ self.token, _, self.expiry, _ = _client._handle_refresh_grant_response(
151
+ response_data, None
152
+ )
153
+
154
+ def with_gdch_audience(self, audience):
155
+ """Create a copy of GDCH credentials with the specified audience.
156
+
157
+ Args:
158
+ audience (str): The intended audience for GDCH credentials.
159
+ """
160
+ return self.__class__(
161
+ self._signer,
162
+ self._service_identity_name,
163
+ self._project,
164
+ audience,
165
+ self._token_uri,
166
+ self._ca_cert_path,
167
+ )
168
+
169
+ @classmethod
170
+ def _from_signer_and_info(cls, signer, info):
171
+ """Creates a Credentials instance from a signer and service account
172
+ info.
173
+
174
+ Args:
175
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
176
+ info (Mapping[str, str]): The service account info.
177
+
178
+ Returns:
179
+ google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
180
+ credentials.
181
+
182
+ Raises:
183
+ ValueError: If the info is not in the expected format.
184
+ """
185
+ if info["format_version"] != "1":
186
+ raise ValueError("Only format version 1 is supported")
187
+
188
+ return cls(
189
+ signer,
190
+ info["name"], # service_identity_name
191
+ info["project"],
192
+ None, # audience
193
+ info["token_uri"],
194
+ info.get("ca_cert_path", None),
195
+ )
196
+
197
+ @classmethod
198
+ def from_service_account_info(cls, info):
199
+ """Creates a Credentials instance from parsed service account info.
200
+
201
+ Args:
202
+ info (Mapping[str, str]): The service account info in Google
203
+ format.
204
+ kwargs: Additional arguments to pass to the constructor.
205
+
206
+ Returns:
207
+ google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
208
+ credentials.
209
+
210
+ Raises:
211
+ ValueError: If the info is not in the expected format.
212
+ """
213
+ signer = _service_account_info.from_dict(
214
+ info,
215
+ require=[
216
+ "format_version",
217
+ "private_key_id",
218
+ "private_key",
219
+ "name",
220
+ "project",
221
+ "token_uri",
222
+ ],
223
+ use_rsa_signer=False,
224
+ )
225
+ return cls._from_signer_and_info(signer, info)
226
+
227
+ @classmethod
228
+ def from_service_account_file(cls, filename):
229
+ """Creates a Credentials instance from a service account json file.
230
+
231
+ Args:
232
+ filename (str): The path to the service account json file.
233
+ kwargs: Additional arguments to pass to the constructor.
234
+
235
+ Returns:
236
+ google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed
237
+ credentials.
238
+ """
239
+ info, signer = _service_account_info.from_filename(
240
+ filename,
241
+ require=[
242
+ "format_version",
243
+ "private_key_id",
244
+ "private_key",
245
+ "name",
246
+ "project",
247
+ "token_uri",
248
+ ],
249
+ use_rsa_signer=False,
250
+ )
251
+ return cls._from_signer_and_info(signer, info)
.venv/lib/python3.11/site-packages/google/oauth2/id_token.py ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ID Token helpers.
16
+
17
+ Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
18
+ generated by Google infrastructure.
19
+
20
+ To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
21
+ server use :func:`verify_oauth2_token`. To verify an ID Token issued by
22
+ Firebase, use :func:`verify_firebase_token`.
23
+
24
+ A general purpose ID Token verifier is available as :func:`verify_token`.
25
+
26
+ Example::
27
+
28
+ from google.oauth2 import id_token
29
+ from google.auth.transport import requests
30
+
31
+ request = requests.Request()
32
+
33
+ id_info = id_token.verify_oauth2_token(
34
+ token, request, 'my-client-id.example.com')
35
+
36
+ userid = id_info['sub']
37
+
38
+ By default, this will re-fetch certificates for each verification. Because
39
+ Google's public keys are only changed infrequently (on the order of once per
40
+ day), you may wish to take advantage of caching to reduce latency and the
41
+ potential for network errors. This can be accomplished using an external
42
+ library like `CacheControl`_ to create a cache-aware
43
+ :class:`google.auth.transport.Request`::
44
+
45
+ import cachecontrol
46
+ import google.auth.transport.requests
47
+ import requests
48
+
49
+ session = requests.session()
50
+ cached_session = cachecontrol.CacheControl(session)
51
+ request = google.auth.transport.requests.Request(session=cached_session)
52
+
53
+ .. _OpenID Connect ID Tokens:
54
+ http://openid.net/specs/openid-connect-core-1_0.html#IDToken
55
+ .. _CacheControl: https://cachecontrol.readthedocs.io
56
+ """
57
+
58
+ import http.client as http_client
59
+ import json
60
+ import os
61
+
62
+ from google.auth import environment_vars
63
+ from google.auth import exceptions
64
+ from google.auth import jwt
65
+
66
+
67
+ # The URL that provides public certificates for verifying ID tokens issued
68
+ # by Google's OAuth 2.0 authorization server.
69
+ _GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
70
+
71
+ # The URL that provides public certificates for verifying ID tokens issued
72
+ # by Firebase and the Google APIs infrastructure
73
+ _GOOGLE_APIS_CERTS_URL = (
74
+ "https://www.googleapis.com/robot/v1/metadata/x509"
75
+ "/securetoken@system.gserviceaccount.com"
76
+ )
77
+
78
+ _GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
79
+
80
+
81
+ def _fetch_certs(request, certs_url):
82
+ """Fetches certificates.
83
+
84
+ Google-style cerificate endpoints return JSON in the format of
85
+ ``{'key id': 'x509 certificate'}`` or a certificate array according
86
+ to the JWK spec (see https://tools.ietf.org/html/rfc7517).
87
+
88
+ Args:
89
+ request (google.auth.transport.Request): The object used to make
90
+ HTTP requests.
91
+ certs_url (str): The certificate endpoint URL.
92
+
93
+ Returns:
94
+ Mapping[str, str] | Mapping[str, list]: A mapping of public keys
95
+ in x.509 or JWK spec.
96
+ """
97
+ response = request(certs_url, method="GET")
98
+
99
+ if response.status != http_client.OK:
100
+ raise exceptions.TransportError(
101
+ "Could not fetch certificates at {}".format(certs_url)
102
+ )
103
+
104
+ return json.loads(response.data.decode("utf-8"))
105
+
106
+
107
+ def verify_token(
108
+ id_token,
109
+ request,
110
+ audience=None,
111
+ certs_url=_GOOGLE_OAUTH2_CERTS_URL,
112
+ clock_skew_in_seconds=0,
113
+ ):
114
+ """Verifies an ID token and returns the decoded token.
115
+
116
+ Args:
117
+ id_token (Union[str, bytes]): The encoded token.
118
+ request (google.auth.transport.Request): The object used to make
119
+ HTTP requests.
120
+ audience (str or list): The audience or audiences that this token is
121
+ intended for. If None then the audience is not verified.
122
+ certs_url (str): The URL that specifies the certificates to use to
123
+ verify the token. This URL should return JSON in the format of
124
+ ``{'key id': 'x509 certificate'}`` or a certificate array according to
125
+ the JWK spec (see https://tools.ietf.org/html/rfc7517).
126
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
127
+ validation.
128
+
129
+ Returns:
130
+ Mapping[str, Any]: The decoded token.
131
+ """
132
+ certs = _fetch_certs(request, certs_url)
133
+
134
+ if "keys" in certs:
135
+ try:
136
+ import jwt as jwt_lib # type: ignore
137
+ except ImportError as caught_exc: # pragma: NO COVER
138
+ raise ImportError(
139
+ "The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format."
140
+ ) from caught_exc
141
+ jwks_client = jwt_lib.PyJWKClient(certs_url)
142
+ signing_key = jwks_client.get_signing_key_from_jwt(id_token)
143
+ return jwt_lib.decode(
144
+ id_token,
145
+ signing_key.key,
146
+ algorithms=[signing_key.algorithm_name],
147
+ audience=audience,
148
+ )
149
+ else:
150
+ return jwt.decode(
151
+ id_token,
152
+ certs=certs,
153
+ audience=audience,
154
+ clock_skew_in_seconds=clock_skew_in_seconds,
155
+ )
156
+
157
+
158
+ def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):
159
+ """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
160
+
161
+ Args:
162
+ id_token (Union[str, bytes]): The encoded token.
163
+ request (google.auth.transport.Request): The object used to make
164
+ HTTP requests.
165
+ audience (str): The audience that this token is intended for. This is
166
+ typically your application's OAuth 2.0 client ID. If None then the
167
+ audience is not verified.
168
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
169
+ validation.
170
+
171
+ Returns:
172
+ Mapping[str, Any]: The decoded token.
173
+
174
+ Raises:
175
+ exceptions.GoogleAuthError: If the issuer is invalid.
176
+ ValueError: If token verification fails
177
+ """
178
+ idinfo = verify_token(
179
+ id_token,
180
+ request,
181
+ audience=audience,
182
+ certs_url=_GOOGLE_OAUTH2_CERTS_URL,
183
+ clock_skew_in_seconds=clock_skew_in_seconds,
184
+ )
185
+
186
+ if idinfo["iss"] not in _GOOGLE_ISSUERS:
187
+ raise exceptions.GoogleAuthError(
188
+ "Wrong issuer. 'iss' should be one of the following: {}".format(
189
+ _GOOGLE_ISSUERS
190
+ )
191
+ )
192
+
193
+ return idinfo
194
+
195
+
196
+ def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):
197
+ """Verifies an ID Token issued by Firebase Authentication.
198
+
199
+ Args:
200
+ id_token (Union[str, bytes]): The encoded token.
201
+ request (google.auth.transport.Request): The object used to make
202
+ HTTP requests.
203
+ audience (str): The audience that this token is intended for. This is
204
+ typically your Firebase application ID. If None then the audience
205
+ is not verified.
206
+ clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
207
+ validation.
208
+
209
+ Returns:
210
+ Mapping[str, Any]: The decoded token.
211
+ """
212
+ return verify_token(
213
+ id_token,
214
+ request,
215
+ audience=audience,
216
+ certs_url=_GOOGLE_APIS_CERTS_URL,
217
+ clock_skew_in_seconds=clock_skew_in_seconds,
218
+ )
219
+
220
+
221
+ def fetch_id_token_credentials(audience, request=None):
222
+ """Create the ID Token credentials from the current environment.
223
+
224
+ This function acquires ID token from the environment in the following order.
225
+ See https://google.aip.dev/auth/4110.
226
+
227
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
228
+ to the path of a valid service account JSON file, then ID token is
229
+ acquired using this service account credentials.
230
+ 2. If the application is running in Compute Engine, App Engine or Cloud Run,
231
+ then the ID token are obtained from the metadata server.
232
+ 3. If metadata server doesn't exist and no valid service account credentials
233
+ are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
234
+ be raised.
235
+
236
+ Example::
237
+
238
+ import google.oauth2.id_token
239
+ import google.auth.transport.requests
240
+
241
+ request = google.auth.transport.requests.Request()
242
+ target_audience = "https://pubsub.googleapis.com"
243
+
244
+ # Create ID token credentials.
245
+ credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request)
246
+
247
+ # Refresh the credential to obtain an ID token.
248
+ credentials.refresh(request)
249
+
250
+ id_token = credentials.token
251
+ id_token_expiry = credentials.expiry
252
+
253
+ Args:
254
+ audience (str): The audience that this ID token is intended for.
255
+ request (Optional[google.auth.transport.Request]): A callable used to make
256
+ HTTP requests. A request object will be created if not provided.
257
+
258
+ Returns:
259
+ google.auth.credentials.Credentials: The ID token credentials.
260
+
261
+ Raises:
262
+ ~google.auth.exceptions.DefaultCredentialsError:
263
+ If metadata server doesn't exist and no valid service account
264
+ credentials are found.
265
+ """
266
+ # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
267
+ # variable.
268
+ credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
269
+ if credentials_filename:
270
+ if not (
271
+ os.path.exists(credentials_filename)
272
+ and os.path.isfile(credentials_filename)
273
+ ):
274
+ raise exceptions.DefaultCredentialsError(
275
+ "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
276
+ )
277
+
278
+ try:
279
+ with open(credentials_filename, "r") as f:
280
+ from google.oauth2 import service_account
281
+
282
+ info = json.load(f)
283
+ if info.get("type") == "service_account":
284
+ return service_account.IDTokenCredentials.from_service_account_info(
285
+ info, target_audience=audience
286
+ )
287
+ except ValueError as caught_exc:
288
+ new_exc = exceptions.DefaultCredentialsError(
289
+ "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
290
+ caught_exc,
291
+ )
292
+ raise new_exc from caught_exc
293
+
294
+ # 2. Try to fetch ID token from metada server if it exists. The code
295
+ # works for GAE and Cloud Run metadata server as well.
296
+ try:
297
+ from google.auth import compute_engine
298
+ from google.auth.compute_engine import _metadata
299
+
300
+ # Create a request object if not provided.
301
+ if not request:
302
+ import google.auth.transport.requests
303
+
304
+ request = google.auth.transport.requests.Request()
305
+
306
+ if _metadata.ping(request):
307
+ return compute_engine.IDTokenCredentials(
308
+ request, audience, use_metadata_identity_endpoint=True
309
+ )
310
+ except (ImportError, exceptions.TransportError):
311
+ pass
312
+
313
+ raise exceptions.DefaultCredentialsError(
314
+ "Neither metadata server or valid service account credentials are found."
315
+ )
316
+
317
+
318
+ def fetch_id_token(request, audience):
319
+ """Fetch the ID Token from the current environment.
320
+
321
+ This function acquires ID token from the environment in the following order.
322
+ See https://google.aip.dev/auth/4110.
323
+
324
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
325
+ to the path of a valid service account JSON file, then ID token is
326
+ acquired using this service account credentials.
327
+ 2. If the application is running in Compute Engine, App Engine or Cloud Run,
328
+ then the ID token are obtained from the metadata server.
329
+ 3. If metadata server doesn't exist and no valid service account credentials
330
+ are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
331
+ be raised.
332
+
333
+ Example::
334
+
335
+ import google.oauth2.id_token
336
+ import google.auth.transport.requests
337
+
338
+ request = google.auth.transport.requests.Request()
339
+ target_audience = "https://pubsub.googleapis.com"
340
+
341
+ id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
342
+
343
+ Args:
344
+ request (google.auth.transport.Request): A callable used to make
345
+ HTTP requests.
346
+ audience (str): The audience that this ID token is intended for.
347
+
348
+ Returns:
349
+ str: The ID token.
350
+
351
+ Raises:
352
+ ~google.auth.exceptions.DefaultCredentialsError:
353
+ If metadata server doesn't exist and no valid service account
354
+ credentials are found.
355
+ """
356
+ id_token_credentials = fetch_id_token_credentials(audience, request=request)
357
+ id_token_credentials.refresh(request)
358
+ return id_token_credentials.token
.venv/lib/python3.11/site-packages/google/oauth2/py.typed ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Marker file for PEP 561.
2
+ # The google-oauth2 package uses inline types.
.venv/lib/python3.11/site-packages/google/oauth2/reauth.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """A module that provides functions for handling rapt authentication.
16
+
17
+ Reauth is a process of obtaining additional authentication (such as password,
18
+ security token, etc.) while refreshing OAuth 2.0 credentials for a user.
19
+
20
+ Credentials that use the Reauth flow must have the reauth scope,
21
+ ``https://www.googleapis.com/auth/accounts.reauth``.
22
+
23
+ This module provides a high-level function for executing the Reauth process,
24
+ :func:`refresh_grant`, and lower-level helpers for doing the individual
25
+ steps of the reauth process.
26
+
27
+ Those steps are:
28
+
29
+ 1. Obtaining a list of challenges from the reauth server.
30
+ 2. Running through each challenge and sending the result back to the reauth
31
+ server.
32
+ 3. Refreshing the access token using the returned rapt token.
33
+ """
34
+
35
+ import sys
36
+
37
+ from google.auth import exceptions
38
+ from google.auth import metrics
39
+ from google.oauth2 import _client
40
+ from google.oauth2 import challenges
41
+
42
+
43
+ _REAUTH_SCOPE = "https://www.googleapis.com/auth/accounts.reauth"
44
+ _REAUTH_API = "https://reauth.googleapis.com/v2/sessions"
45
+
46
+ _REAUTH_NEEDED_ERROR = "invalid_grant"
47
+ _REAUTH_NEEDED_ERROR_INVALID_RAPT = "invalid_rapt"
48
+ _REAUTH_NEEDED_ERROR_RAPT_REQUIRED = "rapt_required"
49
+
50
+ _AUTHENTICATED = "AUTHENTICATED"
51
+ _CHALLENGE_REQUIRED = "CHALLENGE_REQUIRED"
52
+ _CHALLENGE_PENDING = "CHALLENGE_PENDING"
53
+
54
+
55
+ # Override this global variable to set custom max number of rounds of reauth
56
+ # challenges should be run.
57
+ RUN_CHALLENGE_RETRY_LIMIT = 5
58
+
59
+
60
+ def is_interactive():
61
+ """Check if we are in an interractive environment.
62
+
63
+ Override this function with a different logic if you are using this library
64
+ outside a CLI.
65
+
66
+ If the rapt token needs refreshing, the user needs to answer the challenges.
67
+ If the user is not in an interractive environment, the challenges can not
68
+ be answered and we just wait for timeout for no reason.
69
+
70
+ Returns:
71
+ bool: True if is interactive environment, False otherwise.
72
+ """
73
+
74
+ return sys.stdin.isatty()
75
+
76
+
77
+ def _get_challenges(
78
+ request, supported_challenge_types, access_token, requested_scopes=None
79
+ ):
80
+ """Does initial request to reauth API to get the challenges.
81
+
82
+ Args:
83
+ request (google.auth.transport.Request): A callable used to make
84
+ HTTP requests.
85
+ supported_challenge_types (Sequence[str]): list of challenge names
86
+ supported by the manager.
87
+ access_token (str): Access token with reauth scopes.
88
+ requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials.
89
+
90
+ Returns:
91
+ dict: The response from the reauth API.
92
+ """
93
+ body = {"supportedChallengeTypes": supported_challenge_types}
94
+ if requested_scopes:
95
+ body["oauthScopesForDomainPolicyLookup"] = requested_scopes
96
+ metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_start()}
97
+
98
+ return _client._token_endpoint_request(
99
+ request,
100
+ _REAUTH_API + ":start",
101
+ body,
102
+ access_token=access_token,
103
+ use_json=True,
104
+ headers=metrics_header,
105
+ )
106
+
107
+
108
+ def _send_challenge_result(
109
+ request, session_id, challenge_id, client_input, access_token
110
+ ):
111
+ """Attempt to refresh access token by sending next challenge result.
112
+
113
+ Args:
114
+ request (google.auth.transport.Request): A callable used to make
115
+ HTTP requests.
116
+ session_id (str): session id returned by the initial reauth call.
117
+ challenge_id (str): challenge id returned by the initial reauth call.
118
+ client_input: dict with a challenge-specific client input. For example:
119
+ ``{'credential': password}`` for password challenge.
120
+ access_token (str): Access token with reauth scopes.
121
+
122
+ Returns:
123
+ dict: The response from the reauth API.
124
+ """
125
+ body = {
126
+ "sessionId": session_id,
127
+ "challengeId": challenge_id,
128
+ "action": "RESPOND",
129
+ "proposalResponse": client_input,
130
+ }
131
+ metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_continue()}
132
+
133
+ return _client._token_endpoint_request(
134
+ request,
135
+ _REAUTH_API + "/{}:continue".format(session_id),
136
+ body,
137
+ access_token=access_token,
138
+ use_json=True,
139
+ headers=metrics_header,
140
+ )
141
+
142
+
143
+ def _run_next_challenge(msg, request, access_token):
144
+ """Get the next challenge from msg and run it.
145
+
146
+ Args:
147
+ msg (dict): Reauth API response body (either from the initial request to
148
+ https://reauth.googleapis.com/v2/sessions:start or from sending the
149
+ previous challenge response to
150
+ https://reauth.googleapis.com/v2/sessions/id:continue)
151
+ request (google.auth.transport.Request): A callable used to make
152
+ HTTP requests.
153
+ access_token (str): reauth access token
154
+
155
+ Returns:
156
+ dict: The response from the reauth API.
157
+
158
+ Raises:
159
+ google.auth.exceptions.ReauthError: if reauth failed.
160
+ """
161
+ for challenge in msg["challenges"]:
162
+ if challenge["status"] != "READY":
163
+ # Skip non-activated challenges.
164
+ continue
165
+ c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None)
166
+ if not c:
167
+ raise exceptions.ReauthFailError(
168
+ "Unsupported challenge type {0}. Supported types: {1}".format(
169
+ challenge["challengeType"],
170
+ ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())),
171
+ )
172
+ )
173
+ if not c.is_locally_eligible:
174
+ raise exceptions.ReauthFailError(
175
+ "Challenge {0} is not locally eligible".format(
176
+ challenge["challengeType"]
177
+ )
178
+ )
179
+ client_input = c.obtain_challenge_input(challenge)
180
+ if not client_input:
181
+ return None
182
+ return _send_challenge_result(
183
+ request,
184
+ msg["sessionId"],
185
+ challenge["challengeId"],
186
+ client_input,
187
+ access_token,
188
+ )
189
+ return None
190
+
191
+
192
+ def _obtain_rapt(request, access_token, requested_scopes):
193
+ """Given an http request method and reauth access token, get rapt token.
194
+
195
+ Args:
196
+ request (google.auth.transport.Request): A callable used to make
197
+ HTTP requests.
198
+ access_token (str): reauth access token
199
+ requested_scopes (Sequence[str]): scopes required by the client application
200
+
201
+ Returns:
202
+ str: The rapt token.
203
+
204
+ Raises:
205
+ google.auth.exceptions.ReauthError: if reauth failed
206
+ """
207
+ msg = _get_challenges(
208
+ request,
209
+ list(challenges.AVAILABLE_CHALLENGES.keys()),
210
+ access_token,
211
+ requested_scopes,
212
+ )
213
+
214
+ if msg["status"] == _AUTHENTICATED:
215
+ return msg["encodedProofOfReauthToken"]
216
+
217
+ for _ in range(0, RUN_CHALLENGE_RETRY_LIMIT):
218
+ if not (
219
+ msg["status"] == _CHALLENGE_REQUIRED or msg["status"] == _CHALLENGE_PENDING
220
+ ):
221
+ raise exceptions.ReauthFailError(
222
+ "Reauthentication challenge failed due to API error: {}".format(
223
+ msg["status"]
224
+ )
225
+ )
226
+
227
+ if not is_interactive():
228
+ raise exceptions.ReauthFailError(
229
+ "Reauthentication challenge could not be answered because you are not"
230
+ " in an interactive session."
231
+ )
232
+
233
+ msg = _run_next_challenge(msg, request, access_token)
234
+
235
+ if not msg:
236
+ raise exceptions.ReauthFailError("Failed to obtain rapt token.")
237
+ if msg["status"] == _AUTHENTICATED:
238
+ return msg["encodedProofOfReauthToken"]
239
+
240
+ # If we got here it means we didn't get authenticated.
241
+ raise exceptions.ReauthFailError("Failed to obtain rapt token.")
242
+
243
+
244
+ def get_rapt_token(
245
+ request, client_id, client_secret, refresh_token, token_uri, scopes=None
246
+ ):
247
+ """Given an http request method and refresh_token, get rapt token.
248
+
249
+ Args:
250
+ request (google.auth.transport.Request): A callable used to make
251
+ HTTP requests.
252
+ client_id (str): client id to get access token for reauth scope.
253
+ client_secret (str): client secret for the client_id
254
+ refresh_token (str): refresh token to refresh access token
255
+ token_uri (str): uri to refresh access token
256
+ scopes (Optional(Sequence[str])): scopes required by the client application
257
+
258
+ Returns:
259
+ str: The rapt token.
260
+ Raises:
261
+ google.auth.exceptions.RefreshError: If reauth failed.
262
+ """
263
+ sys.stderr.write("Reauthentication required.\n")
264
+
265
+ # Get access token for reauth.
266
+ access_token, _, _, _ = _client.refresh_grant(
267
+ request=request,
268
+ client_id=client_id,
269
+ client_secret=client_secret,
270
+ refresh_token=refresh_token,
271
+ token_uri=token_uri,
272
+ scopes=[_REAUTH_SCOPE],
273
+ )
274
+
275
+ # Get rapt token from reauth API.
276
+ rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes)
277
+ sys.stderr.write("Reauthentication successful.\n")
278
+
279
+ return rapt_token
280
+
281
+
282
+ def refresh_grant(
283
+ request,
284
+ token_uri,
285
+ refresh_token,
286
+ client_id,
287
+ client_secret,
288
+ scopes=None,
289
+ rapt_token=None,
290
+ enable_reauth_refresh=False,
291
+ ):
292
+ """Implements the reauthentication flow.
293
+
294
+ Args:
295
+ request (google.auth.transport.Request): A callable used to make
296
+ HTTP requests.
297
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
298
+ URI.
299
+ refresh_token (str): The refresh token to use to get a new access
300
+ token.
301
+ client_id (str): The OAuth 2.0 application's client ID.
302
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
303
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
304
+ scopes must be authorized for the refresh token. Useful if refresh
305
+ token has a wild card scope (e.g.
306
+ 'https://www.googleapis.com/auth/any-api').
307
+ rapt_token (Optional(str)): The rapt token for reauth.
308
+ enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow
309
+ should be used. The default value is False. This option is for
310
+ gcloud only, other users should use the default value.
311
+
312
+ Returns:
313
+ Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The
314
+ access token, new refresh token, expiration, the additional data
315
+ returned by the token endpoint, and the rapt token.
316
+
317
+ Raises:
318
+ google.auth.exceptions.RefreshError: If the token endpoint returned
319
+ an error.
320
+ """
321
+ body = {
322
+ "grant_type": _client._REFRESH_GRANT_TYPE,
323
+ "client_id": client_id,
324
+ "client_secret": client_secret,
325
+ "refresh_token": refresh_token,
326
+ }
327
+ if scopes:
328
+ body["scope"] = " ".join(scopes)
329
+ if rapt_token:
330
+ body["rapt"] = rapt_token
331
+ metrics_header = {metrics.API_CLIENT_HEADER: metrics.token_request_user()}
332
+
333
+ response_status_ok, response_data, retryable_error = _client._token_endpoint_request_no_throw(
334
+ request, token_uri, body, headers=metrics_header
335
+ )
336
+
337
+ if not response_status_ok and isinstance(response_data, str):
338
+ raise exceptions.RefreshError(response_data, retryable=False)
339
+
340
+ if (
341
+ not response_status_ok
342
+ and response_data.get("error") == _REAUTH_NEEDED_ERROR
343
+ and (
344
+ response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_INVALID_RAPT
345
+ or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED
346
+ )
347
+ ):
348
+ if not enable_reauth_refresh:
349
+ raise exceptions.RefreshError(
350
+ "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate."
351
+ )
352
+
353
+ rapt_token = get_rapt_token(
354
+ request, client_id, client_secret, refresh_token, token_uri, scopes=scopes
355
+ )
356
+ body["rapt"] = rapt_token
357
+ (
358
+ response_status_ok,
359
+ response_data,
360
+ retryable_error,
361
+ ) = _client._token_endpoint_request_no_throw(
362
+ request, token_uri, body, headers=metrics_header
363
+ )
364
+
365
+ if not response_status_ok:
366
+ _client._handle_error_response(response_data, retryable_error)
367
+ return _client._handle_refresh_grant_response(response_data, refresh_token) + (
368
+ rapt_token,
369
+ )
.venv/lib/python3.11/site-packages/google/oauth2/service_account.py ADDED
@@ -0,0 +1,847 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
16
+
17
+ This module implements the JWT Profile for OAuth 2.0 Authorization Grants
18
+ as defined by `RFC 7523`_ with particular support for how this RFC is
19
+ implemented in Google's infrastructure. Google refers to these credentials
20
+ as *Service Accounts*.
21
+
22
+ Service accounts are used for server-to-server communication, such as
23
+ interactions between a web application server and a Google service. The
24
+ service account belongs to your application instead of to an individual end
25
+ user. In contrast to other OAuth 2.0 profiles, no users are involved and your
26
+ application "acts" as the service account.
27
+
28
+ Typically an application uses a service account when the application uses
29
+ Google APIs to work with its own data rather than a user's data. For example,
30
+ an application that uses Google Cloud Datastore for data persistence would use
31
+ a service account to authenticate its calls to the Google Cloud Datastore API.
32
+ However, an application that needs to access a user's Drive documents would
33
+ use the normal OAuth 2.0 profile.
34
+
35
+ Additionally, Google Apps domain administrators can grant service accounts
36
+ `domain-wide delegation`_ authority to access user data on behalf of users in
37
+ the domain.
38
+
39
+ This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
40
+ in place of the usual authorization token returned during the standard
41
+ OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
42
+ the acquired access token is used as the bearer token when making requests
43
+ using these credentials.
44
+
45
+ This profile differs from normal OAuth 2.0 profile because no user consent
46
+ step is required. The use of the private key allows this profile to assert
47
+ identity directly.
48
+
49
+ This profile also differs from the :mod:`google.auth.jwt` authentication
50
+ because the JWT credentials use the JWT directly as the bearer token. This
51
+ profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
52
+ obtained OAuth 2.0 access token is used as the bearer token.
53
+
54
+ Domain-wide delegation
55
+ ----------------------
56
+
57
+ Domain-wide delegation allows a service account to access user data on
58
+ behalf of any user in a Google Apps domain without consent from the user.
59
+ For example, an application that uses the Google Calendar API to add events to
60
+ the calendars of all users in a Google Apps domain would use a service account
61
+ to access the Google Calendar API on behalf of users.
62
+
63
+ The Google Apps administrator must explicitly authorize the service account to
64
+ do this. This authorization step is referred to as "delegating domain-wide
65
+ authority" to a service account.
66
+
67
+ You can use domain-wise delegation by creating a set of credentials with a
68
+ specific subject using :meth:`~Credentials.with_subject`.
69
+
70
+ .. _RFC 7523: https://tools.ietf.org/html/rfc7523
71
+ """
72
+
73
+ import copy
74
+ import datetime
75
+
76
+ from google.auth import _helpers
77
+ from google.auth import _service_account_info
78
+ from google.auth import credentials
79
+ from google.auth import exceptions
80
+ from google.auth import iam
81
+ from google.auth import jwt
82
+ from google.auth import metrics
83
+ from google.oauth2 import _client
84
+
85
+ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
86
+ _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
87
+
88
+
89
+ class Credentials(
90
+ credentials.Signing,
91
+ credentials.Scoped,
92
+ credentials.CredentialsWithQuotaProject,
93
+ credentials.CredentialsWithTokenUri,
94
+ ):
95
+ """Service account credentials
96
+
97
+ Usually, you'll create these credentials with one of the helper
98
+ constructors. To create credentials using a Google service account
99
+ private key JSON file::
100
+
101
+ credentials = service_account.Credentials.from_service_account_file(
102
+ 'service-account.json')
103
+
104
+ Or if you already have the service account file loaded::
105
+
106
+ service_account_info = json.load(open('service_account.json'))
107
+ credentials = service_account.Credentials.from_service_account_info(
108
+ service_account_info)
109
+
110
+ Both helper methods pass on arguments to the constructor, so you can
111
+ specify additional scopes and a subject if necessary::
112
+
113
+ credentials = service_account.Credentials.from_service_account_file(
114
+ 'service-account.json',
115
+ scopes=['email'],
116
+ subject='user@example.com')
117
+
118
+ The credentials are considered immutable. If you want to modify the scopes
119
+ or the subject used for delegation, use :meth:`with_scopes` or
120
+ :meth:`with_subject`::
121
+
122
+ scoped_credentials = credentials.with_scopes(['email'])
123
+ delegated_credentials = credentials.with_subject(subject)
124
+
125
+ To add a quota project, use :meth:`with_quota_project`::
126
+
127
+ credentials = credentials.with_quota_project('myproject-123')
128
+ """
129
+
130
+ def __init__(
131
+ self,
132
+ signer,
133
+ service_account_email,
134
+ token_uri,
135
+ scopes=None,
136
+ default_scopes=None,
137
+ subject=None,
138
+ project_id=None,
139
+ quota_project_id=None,
140
+ additional_claims=None,
141
+ always_use_jwt_access=False,
142
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
143
+ trust_boundary=None,
144
+ ):
145
+ """
146
+ Args:
147
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
148
+ service_account_email (str): The service account's email.
149
+ scopes (Sequence[str]): User-defined scopes to request during the
150
+ authorization grant.
151
+ default_scopes (Sequence[str]): Default scopes passed by a
152
+ Google client library. Use 'scopes' for user-defined scopes.
153
+ token_uri (str): The OAuth 2.0 Token URI.
154
+ subject (str): For domain-wide delegation, the email address of the
155
+ user to for which to request delegated access.
156
+ project_id (str): Project ID associated with the service account
157
+ credential.
158
+ quota_project_id (Optional[str]): The project ID used for quota and
159
+ billing.
160
+ additional_claims (Mapping[str, str]): Any additional claims for
161
+ the JWT assertion used in the authorization grant.
162
+ always_use_jwt_access (Optional[bool]): Whether self signed JWT should
163
+ be always used.
164
+ universe_domain (str): The universe domain. The default
165
+ universe domain is googleapis.com. For default value self
166
+ signed jwt is used for token refresh.
167
+ trust_boundary (str): String representation of trust boundary meta.
168
+
169
+ .. note:: Typically one of the helper constructors
170
+ :meth:`from_service_account_file` or
171
+ :meth:`from_service_account_info` are used instead of calling the
172
+ constructor directly.
173
+ """
174
+ super(Credentials, self).__init__()
175
+
176
+ self._cred_file_path = None
177
+ self._scopes = scopes
178
+ self._default_scopes = default_scopes
179
+ self._signer = signer
180
+ self._service_account_email = service_account_email
181
+ self._subject = subject
182
+ self._project_id = project_id
183
+ self._quota_project_id = quota_project_id
184
+ self._token_uri = token_uri
185
+ self._always_use_jwt_access = always_use_jwt_access
186
+ self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN
187
+
188
+ if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
189
+ self._always_use_jwt_access = True
190
+
191
+ self._jwt_credentials = None
192
+
193
+ if additional_claims is not None:
194
+ self._additional_claims = additional_claims
195
+ else:
196
+ self._additional_claims = {}
197
+ self._trust_boundary = {"locations": [], "encoded_locations": "0x0"}
198
+
199
+ @classmethod
200
+ def _from_signer_and_info(cls, signer, info, **kwargs):
201
+ """Creates a Credentials instance from a signer and service account
202
+ info.
203
+
204
+ Args:
205
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
206
+ info (Mapping[str, str]): The service account info.
207
+ kwargs: Additional arguments to pass to the constructor.
208
+
209
+ Returns:
210
+ google.auth.jwt.Credentials: The constructed credentials.
211
+
212
+ Raises:
213
+ ValueError: If the info is not in the expected format.
214
+ """
215
+ return cls(
216
+ signer,
217
+ service_account_email=info["client_email"],
218
+ token_uri=info["token_uri"],
219
+ project_id=info.get("project_id"),
220
+ universe_domain=info.get(
221
+ "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN
222
+ ),
223
+ trust_boundary=info.get("trust_boundary"),
224
+ **kwargs,
225
+ )
226
+
227
+ @classmethod
228
+ def from_service_account_info(cls, info, **kwargs):
229
+ """Creates a Credentials instance from parsed service account info.
230
+
231
+ Args:
232
+ info (Mapping[str, str]): The service account info in Google
233
+ format.
234
+ kwargs: Additional arguments to pass to the constructor.
235
+
236
+ Returns:
237
+ google.auth.service_account.Credentials: The constructed
238
+ credentials.
239
+
240
+ Raises:
241
+ ValueError: If the info is not in the expected format.
242
+ """
243
+ signer = _service_account_info.from_dict(
244
+ info, require=["client_email", "token_uri"]
245
+ )
246
+ return cls._from_signer_and_info(signer, info, **kwargs)
247
+
248
+ @classmethod
249
+ def from_service_account_file(cls, filename, **kwargs):
250
+ """Creates a Credentials instance from a service account json file.
251
+
252
+ Args:
253
+ filename (str): The path to the service account json file.
254
+ kwargs: Additional arguments to pass to the constructor.
255
+
256
+ Returns:
257
+ google.auth.service_account.Credentials: The constructed
258
+ credentials.
259
+ """
260
+ info, signer = _service_account_info.from_filename(
261
+ filename, require=["client_email", "token_uri"]
262
+ )
263
+ return cls._from_signer_and_info(signer, info, **kwargs)
264
+
265
+ @property
266
+ def service_account_email(self):
267
+ """The service account email."""
268
+ return self._service_account_email
269
+
270
+ @property
271
+ def project_id(self):
272
+ """Project ID associated with this credential."""
273
+ return self._project_id
274
+
275
+ @property
276
+ def requires_scopes(self):
277
+ """Checks if the credentials requires scopes.
278
+
279
+ Returns:
280
+ bool: True if there are no scopes set otherwise False.
281
+ """
282
+ return True if not self._scopes else False
283
+
284
+ def _make_copy(self):
285
+ cred = self.__class__(
286
+ self._signer,
287
+ service_account_email=self._service_account_email,
288
+ scopes=copy.copy(self._scopes),
289
+ default_scopes=copy.copy(self._default_scopes),
290
+ token_uri=self._token_uri,
291
+ subject=self._subject,
292
+ project_id=self._project_id,
293
+ quota_project_id=self._quota_project_id,
294
+ additional_claims=self._additional_claims.copy(),
295
+ always_use_jwt_access=self._always_use_jwt_access,
296
+ universe_domain=self._universe_domain,
297
+ )
298
+ cred._cred_file_path = self._cred_file_path
299
+ return cred
300
+
301
+ @_helpers.copy_docstring(credentials.Scoped)
302
+ def with_scopes(self, scopes, default_scopes=None):
303
+ cred = self._make_copy()
304
+ cred._scopes = scopes
305
+ cred._default_scopes = default_scopes
306
+ return cred
307
+
308
+ def with_always_use_jwt_access(self, always_use_jwt_access):
309
+ """Create a copy of these credentials with the specified always_use_jwt_access value.
310
+
311
+ Args:
312
+ always_use_jwt_access (bool): Whether always use self signed JWT or not.
313
+
314
+ Returns:
315
+ google.auth.service_account.Credentials: A new credentials
316
+ instance.
317
+ Raises:
318
+ google.auth.exceptions.InvalidValue: If the universe domain is not
319
+ default and always_use_jwt_access is False.
320
+ """
321
+ cred = self._make_copy()
322
+ if (
323
+ cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
324
+ and not always_use_jwt_access
325
+ ):
326
+ raise exceptions.InvalidValue(
327
+ "always_use_jwt_access should be True for non-default universe domain"
328
+ )
329
+ cred._always_use_jwt_access = always_use_jwt_access
330
+ return cred
331
+
332
+ @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
333
+ def with_universe_domain(self, universe_domain):
334
+ cred = self._make_copy()
335
+ cred._universe_domain = universe_domain
336
+ if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
337
+ cred._always_use_jwt_access = True
338
+ return cred
339
+
340
+ def with_subject(self, subject):
341
+ """Create a copy of these credentials with the specified subject.
342
+
343
+ Args:
344
+ subject (str): The subject claim.
345
+
346
+ Returns:
347
+ google.auth.service_account.Credentials: A new credentials
348
+ instance.
349
+ """
350
+ cred = self._make_copy()
351
+ cred._subject = subject
352
+ return cred
353
+
354
+ def with_claims(self, additional_claims):
355
+ """Returns a copy of these credentials with modified claims.
356
+
357
+ Args:
358
+ additional_claims (Mapping[str, str]): Any additional claims for
359
+ the JWT payload. This will be merged with the current
360
+ additional claims.
361
+
362
+ Returns:
363
+ google.auth.service_account.Credentials: A new credentials
364
+ instance.
365
+ """
366
+ new_additional_claims = copy.deepcopy(self._additional_claims)
367
+ new_additional_claims.update(additional_claims or {})
368
+ cred = self._make_copy()
369
+ cred._additional_claims = new_additional_claims
370
+ return cred
371
+
372
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
373
+ def with_quota_project(self, quota_project_id):
374
+ cred = self._make_copy()
375
+ cred._quota_project_id = quota_project_id
376
+ return cred
377
+
378
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
379
+ def with_token_uri(self, token_uri):
380
+ cred = self._make_copy()
381
+ cred._token_uri = token_uri
382
+ return cred
383
+
384
+ def _make_authorization_grant_assertion(self):
385
+ """Create the OAuth 2.0 assertion.
386
+
387
+ This assertion is used during the OAuth 2.0 grant to acquire an
388
+ access token.
389
+
390
+ Returns:
391
+ bytes: The authorization grant assertion.
392
+ """
393
+ now = _helpers.utcnow()
394
+ lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
395
+ expiry = now + lifetime
396
+
397
+ payload = {
398
+ "iat": _helpers.datetime_to_secs(now),
399
+ "exp": _helpers.datetime_to_secs(expiry),
400
+ # The issuer must be the service account email.
401
+ "iss": self._service_account_email,
402
+ # The audience must be the auth token endpoint's URI
403
+ "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
404
+ "scope": _helpers.scopes_to_string(self._scopes or ()),
405
+ }
406
+
407
+ payload.update(self._additional_claims)
408
+
409
+ # The subject can be a user email for domain-wide delegation.
410
+ if self._subject:
411
+ payload.setdefault("sub", self._subject)
412
+
413
+ token = jwt.encode(self._signer, payload)
414
+
415
+ return token
416
+
417
+ def _use_self_signed_jwt(self):
418
+ # Since domain wide delegation doesn't work with self signed JWT. If
419
+ # subject exists, then we should not use self signed JWT.
420
+ return self._subject is None and self._jwt_credentials is not None
421
+
422
+ def _metric_header_for_usage(self):
423
+ if self._use_self_signed_jwt():
424
+ return metrics.CRED_TYPE_SA_JWT
425
+ return metrics.CRED_TYPE_SA_ASSERTION
426
+
427
+ @_helpers.copy_docstring(credentials.Credentials)
428
+ def refresh(self, request):
429
+ if self._always_use_jwt_access and not self._jwt_credentials:
430
+ # If self signed jwt should be used but jwt credential is not
431
+ # created, try to create one with scopes
432
+ self._create_self_signed_jwt(None)
433
+
434
+ if (
435
+ self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
436
+ and self._subject
437
+ ):
438
+ raise exceptions.RefreshError(
439
+ "domain wide delegation is not supported for non-default universe domain"
440
+ )
441
+
442
+ if self._use_self_signed_jwt():
443
+ self._jwt_credentials.refresh(request)
444
+ self.token = self._jwt_credentials.token.decode()
445
+ self.expiry = self._jwt_credentials.expiry
446
+ else:
447
+ assertion = self._make_authorization_grant_assertion()
448
+ access_token, expiry, _ = _client.jwt_grant(
449
+ request, self._token_uri, assertion
450
+ )
451
+ self.token = access_token
452
+ self.expiry = expiry
453
+
454
+ def _create_self_signed_jwt(self, audience):
455
+ """Create a self-signed JWT from the credentials if requirements are met.
456
+
457
+ Args:
458
+ audience (str): The service URL. ``https://[API_ENDPOINT]/``
459
+ """
460
+ # https://google.aip.dev/auth/4111
461
+ if self._always_use_jwt_access:
462
+ if self._scopes:
463
+ additional_claims = {"scope": " ".join(self._scopes)}
464
+ if (
465
+ self._jwt_credentials is None
466
+ or self._jwt_credentials.additional_claims != additional_claims
467
+ ):
468
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
469
+ self, None, additional_claims=additional_claims
470
+ )
471
+ elif audience:
472
+ if (
473
+ self._jwt_credentials is None
474
+ or self._jwt_credentials._audience != audience
475
+ ):
476
+
477
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
478
+ self, audience
479
+ )
480
+ elif self._default_scopes:
481
+ additional_claims = {"scope": " ".join(self._default_scopes)}
482
+ if (
483
+ self._jwt_credentials is None
484
+ or additional_claims != self._jwt_credentials.additional_claims
485
+ ):
486
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
487
+ self, None, additional_claims=additional_claims
488
+ )
489
+ elif not self._scopes and audience:
490
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
491
+ self, audience
492
+ )
493
+
494
+ @_helpers.copy_docstring(credentials.Signing)
495
+ def sign_bytes(self, message):
496
+ return self._signer.sign(message)
497
+
498
+ @property # type: ignore
499
+ @_helpers.copy_docstring(credentials.Signing)
500
+ def signer(self):
501
+ return self._signer
502
+
503
+ @property # type: ignore
504
+ @_helpers.copy_docstring(credentials.Signing)
505
+ def signer_email(self):
506
+ return self._service_account_email
507
+
508
+ @_helpers.copy_docstring(credentials.Credentials)
509
+ def get_cred_info(self):
510
+ if self._cred_file_path:
511
+ return {
512
+ "credential_source": self._cred_file_path,
513
+ "credential_type": "service account credentials",
514
+ "principal": self.service_account_email,
515
+ }
516
+ return None
517
+
518
+
519
+ class IDTokenCredentials(
520
+ credentials.Signing,
521
+ credentials.CredentialsWithQuotaProject,
522
+ credentials.CredentialsWithTokenUri,
523
+ ):
524
+ """Open ID Connect ID Token-based service account credentials.
525
+
526
+ These credentials are largely similar to :class:`.Credentials`, but instead
527
+ of using an OAuth 2.0 Access Token as the bearer token, they use an Open
528
+ ID Connect ID Token as the bearer token. These credentials are useful when
529
+ communicating to services that require ID Tokens and can not accept access
530
+ tokens.
531
+
532
+ Usually, you'll create these credentials with one of the helper
533
+ constructors. To create credentials using a Google service account
534
+ private key JSON file::
535
+
536
+ credentials = (
537
+ service_account.IDTokenCredentials.from_service_account_file(
538
+ 'service-account.json'))
539
+
540
+
541
+ Or if you already have the service account file loaded::
542
+
543
+ service_account_info = json.load(open('service_account.json'))
544
+ credentials = (
545
+ service_account.IDTokenCredentials.from_service_account_info(
546
+ service_account_info))
547
+
548
+
549
+ Both helper methods pass on arguments to the constructor, so you can
550
+ specify additional scopes and a subject if necessary::
551
+
552
+ credentials = (
553
+ service_account.IDTokenCredentials.from_service_account_file(
554
+ 'service-account.json',
555
+ scopes=['email'],
556
+ subject='user@example.com'))
557
+
558
+
559
+ The credentials are considered immutable. If you want to modify the scopes
560
+ or the subject used for delegation, use :meth:`with_scopes` or
561
+ :meth:`with_subject`::
562
+
563
+ scoped_credentials = credentials.with_scopes(['email'])
564
+ delegated_credentials = credentials.with_subject(subject)
565
+
566
+ """
567
+
568
+ def __init__(
569
+ self,
570
+ signer,
571
+ service_account_email,
572
+ token_uri,
573
+ target_audience,
574
+ additional_claims=None,
575
+ quota_project_id=None,
576
+ universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
577
+ ):
578
+ """
579
+ Args:
580
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
581
+ service_account_email (str): The service account's email.
582
+ token_uri (str): The OAuth 2.0 Token URI.
583
+ target_audience (str): The intended audience for these credentials,
584
+ used when requesting the ID Token. The ID Token's ``aud`` claim
585
+ will be set to this string.
586
+ additional_claims (Mapping[str, str]): Any additional claims for
587
+ the JWT assertion used in the authorization grant.
588
+ quota_project_id (Optional[str]): The project ID used for quota and billing.
589
+ universe_domain (str): The universe domain. The default
590
+ universe domain is googleapis.com. For default value IAM ID
591
+ token endponint is used for token refresh. Note that
592
+ iam.serviceAccountTokenCreator role is required to use the IAM
593
+ endpoint.
594
+ .. note:: Typically one of the helper constructors
595
+ :meth:`from_service_account_file` or
596
+ :meth:`from_service_account_info` are used instead of calling the
597
+ constructor directly.
598
+ """
599
+ super(IDTokenCredentials, self).__init__()
600
+ self._signer = signer
601
+ self._service_account_email = service_account_email
602
+ self._token_uri = token_uri
603
+ self._target_audience = target_audience
604
+ self._quota_project_id = quota_project_id
605
+ self._use_iam_endpoint = False
606
+
607
+ if not universe_domain:
608
+ self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN
609
+ else:
610
+ self._universe_domain = universe_domain
611
+ self._iam_id_token_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace(
612
+ "googleapis.com", self._universe_domain
613
+ )
614
+
615
+ if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
616
+ self._use_iam_endpoint = True
617
+
618
+ if additional_claims is not None:
619
+ self._additional_claims = additional_claims
620
+ else:
621
+ self._additional_claims = {}
622
+
623
+ @classmethod
624
+ def _from_signer_and_info(cls, signer, info, **kwargs):
625
+ """Creates a credentials instance from a signer and service account
626
+ info.
627
+
628
+ Args:
629
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
630
+ info (Mapping[str, str]): The service account info.
631
+ kwargs: Additional arguments to pass to the constructor.
632
+
633
+ Returns:
634
+ google.auth.jwt.IDTokenCredentials: The constructed credentials.
635
+
636
+ Raises:
637
+ ValueError: If the info is not in the expected format.
638
+ """
639
+ kwargs.setdefault("service_account_email", info["client_email"])
640
+ kwargs.setdefault("token_uri", info["token_uri"])
641
+ if "universe_domain" in info:
642
+ kwargs["universe_domain"] = info["universe_domain"]
643
+ return cls(signer, **kwargs)
644
+
645
+ @classmethod
646
+ def from_service_account_info(cls, info, **kwargs):
647
+ """Creates a credentials instance from parsed service account info.
648
+
649
+ Args:
650
+ info (Mapping[str, str]): The service account info in Google
651
+ format.
652
+ kwargs: Additional arguments to pass to the constructor.
653
+
654
+ Returns:
655
+ google.auth.service_account.IDTokenCredentials: The constructed
656
+ credentials.
657
+
658
+ Raises:
659
+ ValueError: If the info is not in the expected format.
660
+ """
661
+ signer = _service_account_info.from_dict(
662
+ info, require=["client_email", "token_uri"]
663
+ )
664
+ return cls._from_signer_and_info(signer, info, **kwargs)
665
+
666
+ @classmethod
667
+ def from_service_account_file(cls, filename, **kwargs):
668
+ """Creates a credentials instance from a service account json file.
669
+
670
+ Args:
671
+ filename (str): The path to the service account json file.
672
+ kwargs: Additional arguments to pass to the constructor.
673
+
674
+ Returns:
675
+ google.auth.service_account.IDTokenCredentials: The constructed
676
+ credentials.
677
+ """
678
+ info, signer = _service_account_info.from_filename(
679
+ filename, require=["client_email", "token_uri"]
680
+ )
681
+ return cls._from_signer_and_info(signer, info, **kwargs)
682
+
683
+ def _make_copy(self):
684
+ cred = self.__class__(
685
+ self._signer,
686
+ service_account_email=self._service_account_email,
687
+ token_uri=self._token_uri,
688
+ target_audience=self._target_audience,
689
+ additional_claims=self._additional_claims.copy(),
690
+ quota_project_id=self.quota_project_id,
691
+ universe_domain=self._universe_domain,
692
+ )
693
+ # _use_iam_endpoint is not exposed in the constructor
694
+ cred._use_iam_endpoint = self._use_iam_endpoint
695
+ return cred
696
+
697
+ def with_target_audience(self, target_audience):
698
+ """Create a copy of these credentials with the specified target
699
+ audience.
700
+
701
+ Args:
702
+ target_audience (str): The intended audience for these credentials,
703
+ used when requesting the ID Token.
704
+
705
+ Returns:
706
+ google.auth.service_account.IDTokenCredentials: A new credentials
707
+ instance.
708
+ """
709
+ cred = self._make_copy()
710
+ cred._target_audience = target_audience
711
+ return cred
712
+
713
+ def _with_use_iam_endpoint(self, use_iam_endpoint):
714
+ """Create a copy of these credentials with the use_iam_endpoint value.
715
+
716
+ Args:
717
+ use_iam_endpoint (bool): If True, IAM generateIdToken endpoint will
718
+ be used instead of the token_uri. Note that
719
+ iam.serviceAccountTokenCreator role is required to use the IAM
720
+ endpoint. The default value is False. This feature is currently
721
+ experimental and subject to change without notice.
722
+
723
+ Returns:
724
+ google.auth.service_account.IDTokenCredentials: A new credentials
725
+ instance.
726
+ Raises:
727
+ google.auth.exceptions.InvalidValue: If the universe domain is not
728
+ default and use_iam_endpoint is False.
729
+ """
730
+ cred = self._make_copy()
731
+ if (
732
+ cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN
733
+ and not use_iam_endpoint
734
+ ):
735
+ raise exceptions.InvalidValue(
736
+ "use_iam_endpoint should be True for non-default universe domain"
737
+ )
738
+ cred._use_iam_endpoint = use_iam_endpoint
739
+ return cred
740
+
741
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
742
+ def with_quota_project(self, quota_project_id):
743
+ cred = self._make_copy()
744
+ cred._quota_project_id = quota_project_id
745
+ return cred
746
+
747
+ @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
748
+ def with_token_uri(self, token_uri):
749
+ cred = self._make_copy()
750
+ cred._token_uri = token_uri
751
+ return cred
752
+
753
+ def _make_authorization_grant_assertion(self):
754
+ """Create the OAuth 2.0 assertion.
755
+
756
+ This assertion is used during the OAuth 2.0 grant to acquire an
757
+ ID token.
758
+
759
+ Returns:
760
+ bytes: The authorization grant assertion.
761
+ """
762
+ now = _helpers.utcnow()
763
+ lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
764
+ expiry = now + lifetime
765
+
766
+ payload = {
767
+ "iat": _helpers.datetime_to_secs(now),
768
+ "exp": _helpers.datetime_to_secs(expiry),
769
+ # The issuer must be the service account email.
770
+ "iss": self.service_account_email,
771
+ # The audience must be the auth token endpoint's URI
772
+ "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
773
+ # The target audience specifies which service the ID token is
774
+ # intended for.
775
+ "target_audience": self._target_audience,
776
+ }
777
+
778
+ payload.update(self._additional_claims)
779
+
780
+ token = jwt.encode(self._signer, payload)
781
+
782
+ return token
783
+
784
+ def _refresh_with_iam_endpoint(self, request):
785
+ """Use IAM generateIdToken endpoint to obtain an ID token.
786
+
787
+ It works as follows:
788
+
789
+ 1. First we create a self signed jwt with
790
+ https://www.googleapis.com/auth/iam being the scope.
791
+
792
+ 2. Next we use the self signed jwt as the access token, and make a POST
793
+ request to IAM generateIdToken endpoint. The request body is:
794
+ {
795
+ "audience": self._target_audience,
796
+ "includeEmail": "true",
797
+ "useEmailAzp": "true",
798
+ }
799
+
800
+ If the request is succesfully, it will return {"token":"the ID token"},
801
+ and we can extract the ID token and compute its expiry.
802
+ """
803
+ jwt_credentials = jwt.Credentials.from_signing_credentials(
804
+ self,
805
+ None,
806
+ additional_claims={"scope": "https://www.googleapis.com/auth/iam"},
807
+ )
808
+ jwt_credentials.refresh(request)
809
+ self.token, self.expiry = _client.call_iam_generate_id_token_endpoint(
810
+ request,
811
+ self._iam_id_token_endpoint,
812
+ self.signer_email,
813
+ self._target_audience,
814
+ jwt_credentials.token.decode(),
815
+ self._universe_domain,
816
+ )
817
+
818
+ @_helpers.copy_docstring(credentials.Credentials)
819
+ def refresh(self, request):
820
+ if self._use_iam_endpoint:
821
+ self._refresh_with_iam_endpoint(request)
822
+ else:
823
+ assertion = self._make_authorization_grant_assertion()
824
+ access_token, expiry, _ = _client.id_token_jwt_grant(
825
+ request, self._token_uri, assertion
826
+ )
827
+ self.token = access_token
828
+ self.expiry = expiry
829
+
830
+ @property
831
+ def service_account_email(self):
832
+ """The service account email."""
833
+ return self._service_account_email
834
+
835
+ @_helpers.copy_docstring(credentials.Signing)
836
+ def sign_bytes(self, message):
837
+ return self._signer.sign(message)
838
+
839
+ @property # type: ignore
840
+ @_helpers.copy_docstring(credentials.Signing)
841
+ def signer(self):
842
+ return self._signer
843
+
844
+ @property # type: ignore
845
+ @_helpers.copy_docstring(credentials.Signing)
846
+ def signer_email(self):
847
+ return self._service_account_email
.venv/lib/python3.11/site-packages/google/oauth2/sts.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 Token Exchange Spec.
16
+
17
+ This module defines a token exchange utility based on the `OAuth 2.0 Token
18
+ Exchange`_ spec. This will be mainly used to exchange external credentials
19
+ for GCP access tokens in workload identity pools to access Google APIs.
20
+
21
+ The implementation will support various types of client authentication as
22
+ allowed in the spec.
23
+
24
+ A deviation on the spec will be for additional Google specific options that
25
+ cannot be easily mapped to parameters defined in the RFC.
26
+
27
+ The returned dictionary response will be based on the `rfc8693 section 2.2.1`_
28
+ spec JSON response.
29
+
30
+ .. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693
31
+ .. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1
32
+ """
33
+
34
+ import http.client as http_client
35
+ import json
36
+ import urllib
37
+
38
+ from google.oauth2 import utils
39
+
40
+
41
+ _URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"}
42
+
43
+
44
+ class Client(utils.OAuthClientAuthHandler):
45
+ """Implements the OAuth 2.0 token exchange spec based on
46
+ https://tools.ietf.org/html/rfc8693.
47
+ """
48
+
49
+ def __init__(self, token_exchange_endpoint, client_authentication=None):
50
+ """Initializes an STS client instance.
51
+
52
+ Args:
53
+ token_exchange_endpoint (str): The token exchange endpoint.
54
+ client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)):
55
+ The optional OAuth client authentication credentials if available.
56
+ """
57
+ super(Client, self).__init__(client_authentication)
58
+ self._token_exchange_endpoint = token_exchange_endpoint
59
+
60
+ def _make_request(self, request, headers, request_body):
61
+ # Initialize request headers.
62
+ request_headers = _URLENCODED_HEADERS.copy()
63
+
64
+ # Inject additional headers.
65
+ if headers:
66
+ for k, v in dict(headers).items():
67
+ request_headers[k] = v
68
+
69
+ # Apply OAuth client authentication.
70
+ self.apply_client_authentication_options(request_headers, request_body)
71
+
72
+ # Execute request.
73
+ response = request(
74
+ url=self._token_exchange_endpoint,
75
+ method="POST",
76
+ headers=request_headers,
77
+ body=urllib.parse.urlencode(request_body).encode("utf-8"),
78
+ )
79
+
80
+ response_body = (
81
+ response.data.decode("utf-8")
82
+ if hasattr(response.data, "decode")
83
+ else response.data
84
+ )
85
+
86
+ # If non-200 response received, translate to OAuthError exception.
87
+ if response.status != http_client.OK:
88
+ utils.handle_error_response(response_body)
89
+
90
+ response_data = json.loads(response_body)
91
+
92
+ # Return successful response.
93
+ return response_data
94
+
95
+ def exchange_token(
96
+ self,
97
+ request,
98
+ grant_type,
99
+ subject_token,
100
+ subject_token_type,
101
+ resource=None,
102
+ audience=None,
103
+ scopes=None,
104
+ requested_token_type=None,
105
+ actor_token=None,
106
+ actor_token_type=None,
107
+ additional_options=None,
108
+ additional_headers=None,
109
+ ):
110
+ """Exchanges the provided token for another type of token based on the
111
+ rfc8693 spec.
112
+
113
+ Args:
114
+ request (google.auth.transport.Request): A callable used to make
115
+ HTTP requests.
116
+ grant_type (str): The OAuth 2.0 token exchange grant type.
117
+ subject_token (str): The OAuth 2.0 token exchange subject token.
118
+ subject_token_type (str): The OAuth 2.0 token exchange subject token type.
119
+ resource (Optional[str]): The optional OAuth 2.0 token exchange resource field.
120
+ audience (Optional[str]): The optional OAuth 2.0 token exchange audience field.
121
+ scopes (Optional[Sequence[str]]): The optional list of scopes to use.
122
+ requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested
123
+ token type.
124
+ actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token.
125
+ actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type.
126
+ additional_options (Optional[Mapping[str, str]]): The optional additional
127
+ non-standard Google specific options.
128
+ additional_headers (Optional[Mapping[str, str]]): The optional additional
129
+ headers to pass to the token exchange endpoint.
130
+
131
+ Returns:
132
+ Mapping[str, str]: The token exchange JSON-decoded response data containing
133
+ the requested token and its expiration time.
134
+
135
+ Raises:
136
+ google.auth.exceptions.OAuthError: If the token endpoint returned
137
+ an error.
138
+ """
139
+ # Initialize request body.
140
+ request_body = {
141
+ "grant_type": grant_type,
142
+ "resource": resource,
143
+ "audience": audience,
144
+ "scope": " ".join(scopes or []),
145
+ "requested_token_type": requested_token_type,
146
+ "subject_token": subject_token,
147
+ "subject_token_type": subject_token_type,
148
+ "actor_token": actor_token,
149
+ "actor_token_type": actor_token_type,
150
+ "options": None,
151
+ }
152
+ # Add additional non-standard options.
153
+ if additional_options:
154
+ request_body["options"] = urllib.parse.quote(json.dumps(additional_options))
155
+ # Remove empty fields in request body.
156
+ for k, v in dict(request_body).items():
157
+ if v is None or v == "":
158
+ del request_body[k]
159
+
160
+ return self._make_request(request, additional_headers, request_body)
161
+
162
+ def refresh_token(self, request, refresh_token):
163
+ """Exchanges a refresh token for an access token based on the
164
+ RFC6749 spec.
165
+
166
+ Args:
167
+ request (google.auth.transport.Request): A callable used to make
168
+ HTTP requests.
169
+ subject_token (str): The OAuth 2.0 refresh token.
170
+ """
171
+
172
+ return self._make_request(
173
+ request,
174
+ None,
175
+ {"grant_type": "refresh_token", "refresh_token": refresh_token},
176
+ )
.venv/lib/python3.11/site-packages/google/oauth2/utils.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OAuth 2.0 Utilities.
16
+
17
+ This module provides implementations for various OAuth 2.0 utilities.
18
+ This includes `OAuth error handling`_ and
19
+ `Client authentication for OAuth flows`_.
20
+
21
+ OAuth error handling
22
+ --------------------
23
+ This will define interfaces for handling OAuth related error responses as
24
+ stated in `RFC 6749 section 5.2`_.
25
+ This will include a common function to convert these HTTP error responses to a
26
+ :class:`google.auth.exceptions.OAuthError` exception.
27
+
28
+
29
+ Client authentication for OAuth flows
30
+ -------------------------------------
31
+ We introduce an interface for defining client authentication credentials based
32
+ on `RFC 6749 section 2.3.1`_. This will expose the following
33
+ capabilities:
34
+
35
+ * Ability to support basic authentication via request header.
36
+ * Ability to support bearer token authentication via request header.
37
+ * Ability to support client ID / secret authentication via request body.
38
+
39
+ .. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1
40
+ .. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2
41
+ """
42
+
43
+ import abc
44
+ import base64
45
+ import enum
46
+ import json
47
+
48
+ from google.auth import exceptions
49
+
50
+
51
+ # OAuth client authentication based on
52
+ # https://tools.ietf.org/html/rfc6749#section-2.3.
53
+ class ClientAuthType(enum.Enum):
54
+ basic = 1
55
+ request_body = 2
56
+
57
+
58
+ class ClientAuthentication(object):
59
+ """Defines the client authentication credentials for basic and request-body
60
+ types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
61
+ """
62
+
63
+ def __init__(self, client_auth_type, client_id, client_secret=None):
64
+ """Instantiates a client authentication object containing the client ID
65
+ and secret credentials for basic and response-body auth.
66
+
67
+ Args:
68
+ client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
69
+ client authentication type.
70
+ client_id (str): The client ID.
71
+ client_secret (Optional[str]): The client secret.
72
+ """
73
+ self.client_auth_type = client_auth_type
74
+ self.client_id = client_id
75
+ self.client_secret = client_secret
76
+
77
+
78
+ class OAuthClientAuthHandler(metaclass=abc.ABCMeta):
79
+ """Abstract class for handling client authentication in OAuth-based
80
+ operations.
81
+ """
82
+
83
+ def __init__(self, client_authentication=None):
84
+ """Instantiates an OAuth client authentication handler.
85
+
86
+ Args:
87
+ client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
88
+ The OAuth client authentication credentials if available.
89
+ """
90
+ super(OAuthClientAuthHandler, self).__init__()
91
+ self._client_authentication = client_authentication
92
+
93
+ def apply_client_authentication_options(
94
+ self, headers, request_body=None, bearer_token=None
95
+ ):
96
+ """Applies client authentication on the OAuth request's headers or POST
97
+ body.
98
+
99
+ Args:
100
+ headers (Mapping[str, str]): The HTTP request header.
101
+ request_body (Optional[Mapping[str, str]]): The HTTP request body
102
+ dictionary. For requests that do not support request body, this
103
+ is None and will be ignored.
104
+ bearer_token (Optional[str]): The optional bearer token.
105
+ """
106
+ # Inject authenticated header.
107
+ self._inject_authenticated_headers(headers, bearer_token)
108
+ # Inject authenticated request body.
109
+ if bearer_token is None:
110
+ self._inject_authenticated_request_body(request_body)
111
+
112
+ def _inject_authenticated_headers(self, headers, bearer_token=None):
113
+ if bearer_token is not None:
114
+ headers["Authorization"] = "Bearer %s" % bearer_token
115
+ elif (
116
+ self._client_authentication is not None
117
+ and self._client_authentication.client_auth_type is ClientAuthType.basic
118
+ ):
119
+ username = self._client_authentication.client_id
120
+ password = self._client_authentication.client_secret or ""
121
+
122
+ credentials = base64.b64encode(
123
+ ("%s:%s" % (username, password)).encode()
124
+ ).decode()
125
+ headers["Authorization"] = "Basic %s" % credentials
126
+
127
+ def _inject_authenticated_request_body(self, request_body):
128
+ if (
129
+ self._client_authentication is not None
130
+ and self._client_authentication.client_auth_type
131
+ is ClientAuthType.request_body
132
+ ):
133
+ if request_body is None:
134
+ raise exceptions.OAuthError(
135
+ "HTTP request does not support request-body"
136
+ )
137
+ else:
138
+ request_body["client_id"] = self._client_authentication.client_id
139
+ request_body["client_secret"] = (
140
+ self._client_authentication.client_secret or ""
141
+ )
142
+
143
+
144
+ def handle_error_response(response_body):
145
+ """Translates an error response from an OAuth operation into an
146
+ OAuthError exception.
147
+
148
+ Args:
149
+ response_body (str): The decoded response data.
150
+
151
+ Raises:
152
+ google.auth.exceptions.OAuthError
153
+ """
154
+ try:
155
+ error_components = []
156
+ error_data = json.loads(response_body)
157
+
158
+ error_components.append("Error code {}".format(error_data["error"]))
159
+ if "error_description" in error_data:
160
+ error_components.append(": {}".format(error_data["error_description"]))
161
+ if "error_uri" in error_data:
162
+ error_components.append(" - {}".format(error_data["error_uri"]))
163
+ error_details = "".join(error_components)
164
+ # If no details could be extracted, use the response data.
165
+ except (KeyError, ValueError):
166
+ error_details = response_body
167
+
168
+ raise exceptions.OAuthError(error_details, response_body)
.venv/lib/python3.11/site-packages/google/oauth2/webauthn_handler.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+ import os
3
+ import struct
4
+ import subprocess
5
+
6
+ from google.auth import exceptions
7
+ from google.oauth2.webauthn_types import GetRequest, GetResponse
8
+
9
+
10
+ class WebAuthnHandler(abc.ABC):
11
+ @abc.abstractmethod
12
+ def is_available(self) -> bool:
13
+ """Check whether this WebAuthn handler is available"""
14
+ raise NotImplementedError("is_available method must be implemented")
15
+
16
+ @abc.abstractmethod
17
+ def get(self, get_request: GetRequest) -> GetResponse:
18
+ """WebAuthn get (assertion)"""
19
+ raise NotImplementedError("get method must be implemented")
20
+
21
+
22
+ class PluginHandler(WebAuthnHandler):
23
+ """Offloads WebAuthn get reqeust to a pluggable command-line tool.
24
+
25
+ Offloads WebAuthn get to a plugin which takes the form of a
26
+ command-line tool. The command-line tool is configurable via the
27
+ PluginHandler._ENV_VAR environment variable.
28
+
29
+ The WebAuthn plugin should implement the following interface:
30
+
31
+ Communication occurs over stdin/stdout, and messages are both sent and
32
+ received in the form:
33
+
34
+ [4 bytes - payload size (little-endian)][variable bytes - json payload]
35
+ """
36
+
37
+ _ENV_VAR = "GOOGLE_AUTH_WEBAUTHN_PLUGIN"
38
+
39
+ def is_available(self) -> bool:
40
+ try:
41
+ self._find_plugin()
42
+ except Exception:
43
+ return False
44
+ else:
45
+ return True
46
+
47
+ def get(self, get_request: GetRequest) -> GetResponse:
48
+ request_json = get_request.to_json()
49
+ cmd = self._find_plugin()
50
+ response_json = self._call_plugin(cmd, request_json)
51
+ return GetResponse.from_json(response_json)
52
+
53
+ def _call_plugin(self, cmd: str, input_json: str) -> str:
54
+ # Calculate length of input
55
+ input_length = len(input_json)
56
+ length_bytes_le = struct.pack("<I", input_length)
57
+ request = length_bytes_le + input_json.encode()
58
+
59
+ # Call plugin
60
+ process_result = subprocess.run(
61
+ [cmd], input=request, capture_output=True, check=True
62
+ )
63
+
64
+ # Check length of response
65
+ response_len_le = process_result.stdout[:4]
66
+ response_len = struct.unpack("<I", response_len_le)[0]
67
+ response = process_result.stdout[4:]
68
+ if response_len != len(response):
69
+ raise exceptions.MalformedError(
70
+ "Plugin response length {} does not match data {}".format(
71
+ response_len, len(response)
72
+ )
73
+ )
74
+ return response.decode()
75
+
76
+ def _find_plugin(self) -> str:
77
+ plugin_cmd = os.environ.get(PluginHandler._ENV_VAR)
78
+ if plugin_cmd is None:
79
+ raise exceptions.InvalidResource(
80
+ "{} env var is not set".format(PluginHandler._ENV_VAR)
81
+ )
82
+ return plugin_cmd
.venv/lib/python3.11/site-packages/google/oauth2/webauthn_types.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ import json
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from google.auth import exceptions
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class PublicKeyCredentialDescriptor:
10
+ """Descriptor for a security key based credential.
11
+
12
+ https://www.w3.org/TR/webauthn-3/#dictionary-credential-descriptor
13
+
14
+ Args:
15
+ id: <url-safe base64-encoded> credential id (key handle).
16
+ transports: <'usb'|'nfc'|'ble'|'internal'> List of supported transports.
17
+ """
18
+
19
+ id: str
20
+ transports: Optional[List[str]] = None
21
+
22
+ def to_dict(self):
23
+ cred = {"type": "public-key", "id": self.id}
24
+ if self.transports:
25
+ cred["transports"] = self.transports
26
+ return cred
27
+
28
+
29
+ @dataclass
30
+ class AuthenticationExtensionsClientInputs:
31
+ """Client extensions inputs for WebAuthn extensions.
32
+
33
+ Args:
34
+ appid: app id that can be asserted with in addition to rpid.
35
+ https://www.w3.org/TR/webauthn-3/#sctn-appid-extension
36
+ """
37
+
38
+ appid: Optional[str] = None
39
+
40
+ def to_dict(self):
41
+ extensions = {}
42
+ if self.appid:
43
+ extensions["appid"] = self.appid
44
+ return extensions
45
+
46
+
47
+ @dataclass
48
+ class GetRequest:
49
+ """WebAuthn get request
50
+
51
+ Args:
52
+ origin: Origin where the WebAuthn get assertion takes place.
53
+ rpid: Relying Party ID.
54
+ challenge: <url-safe base64-encoded> raw challenge.
55
+ timeout_ms: Timeout number in millisecond.
56
+ allow_credentials: List of allowed credentials.
57
+ user_verification: <'required'|'preferred'|'discouraged'> User verification requirement.
58
+ extensions: WebAuthn authentication extensions inputs.
59
+ """
60
+
61
+ origin: str
62
+ rpid: str
63
+ challenge: str
64
+ timeout_ms: Optional[int] = None
65
+ allow_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None
66
+ user_verification: Optional[str] = None
67
+ extensions: Optional[AuthenticationExtensionsClientInputs] = None
68
+
69
+ def to_json(self) -> str:
70
+ req_options: Dict[str, Any] = {"rpid": self.rpid, "challenge": self.challenge}
71
+ if self.timeout_ms:
72
+ req_options["timeout"] = self.timeout_ms
73
+ if self.allow_credentials:
74
+ req_options["allowCredentials"] = [
75
+ c.to_dict() for c in self.allow_credentials
76
+ ]
77
+ if self.user_verification:
78
+ req_options["userVerification"] = self.user_verification
79
+ if self.extensions:
80
+ req_options["extensions"] = self.extensions.to_dict()
81
+ return json.dumps(
82
+ {"type": "get", "origin": self.origin, "requestData": req_options}
83
+ )
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class AuthenticatorAssertionResponse:
88
+ """Authenticator response to a WebAuthn get (assertion) request.
89
+
90
+ https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse
91
+
92
+ Args:
93
+ client_data_json: <url-safe base64-encoded> client data JSON.
94
+ authenticator_data: <url-safe base64-encoded> authenticator data.
95
+ signature: <url-safe base64-encoded> signature.
96
+ user_handle: <url-safe base64-encoded> user handle.
97
+ """
98
+
99
+ client_data_json: str
100
+ authenticator_data: str
101
+ signature: str
102
+ user_handle: Optional[str]
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class GetResponse:
107
+ """WebAuthn get (assertion) response.
108
+
109
+ Args:
110
+ id: <url-safe base64-encoded> credential id (key handle).
111
+ response: The authenticator assertion response.
112
+ authenticator_attachment: <'cross-platform'|'platform'> The attachment status of the authenticator.
113
+ client_extension_results: WebAuthn authentication extensions output results in a dictionary.
114
+ """
115
+
116
+ id: str
117
+ response: AuthenticatorAssertionResponse
118
+ authenticator_attachment: Optional[str]
119
+ client_extension_results: Optional[Dict]
120
+
121
+ @staticmethod
122
+ def from_json(json_str: str):
123
+ """Verify and construct GetResponse from a JSON string."""
124
+ try:
125
+ resp_json = json.loads(json_str)
126
+ except ValueError:
127
+ raise exceptions.MalformedError("Invalid Get JSON response")
128
+ if resp_json.get("type") != "getResponse":
129
+ raise exceptions.MalformedError(
130
+ "Invalid Get response type: {}".format(resp_json.get("type"))
131
+ )
132
+ pk_cred = resp_json.get("responseData")
133
+ if pk_cred is None:
134
+ if resp_json.get("error"):
135
+ raise exceptions.ReauthFailError(
136
+ "WebAuthn.get failure: {}".format(resp_json["error"])
137
+ )
138
+ else:
139
+ raise exceptions.MalformedError("Get response is empty")
140
+ if pk_cred.get("type") != "public-key":
141
+ raise exceptions.MalformedError(
142
+ "Invalid credential type: {}".format(pk_cred.get("type"))
143
+ )
144
+ assertion_json = pk_cred["response"]
145
+ assertion_resp = AuthenticatorAssertionResponse(
146
+ client_data_json=assertion_json["clientDataJSON"],
147
+ authenticator_data=assertion_json["authenticatorData"],
148
+ signature=assertion_json["signature"],
149
+ user_handle=assertion_json.get("userHandle"),
150
+ )
151
+ return GetResponse(
152
+ id=pk_cred["id"],
153
+ response=assertion_resp,
154
+ authenticator_attachment=pk_cred.get("authenticatorAttachment"),
155
+ client_extension_results=pk_cred.get("clientExtensionResults"),
156
+ )
.venv/lib/python3.11/site-packages/google/type/calendar_period_pb2.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Copyright 2024 Google LLC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
18
+ # source: google/type/calendar_period.proto
19
+ """Generated protocol buffer code."""
20
+ from google.protobuf import descriptor as _descriptor
21
+ from google.protobuf import descriptor_pool as _descriptor_pool
22
+ from google.protobuf import symbol_database as _symbol_database
23
+ from google.protobuf.internal import builder as _builder
24
+
25
+ # @@protoc_insertion_point(imports)
26
+
27
+ _sym_db = _symbol_database.Default()
28
+
29
+
30
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
31
+ b"\n!google/type/calendar_period.proto\x12\x0bgoogle.type*\x7f\n\x0e\x43\x61lendarPeriod\x12\x1f\n\x1b\x43\x41LENDAR_PERIOD_UNSPECIFIED\x10\x00\x12\x07\n\x03\x44\x41Y\x10\x01\x12\x08\n\x04WEEK\x10\x02\x12\r\n\tFORTNIGHT\x10\x03\x12\t\n\x05MONTH\x10\x04\x12\x0b\n\x07QUARTER\x10\x05\x12\x08\n\x04HALF\x10\x06\x12\x08\n\x04YEAR\x10\x07\x42x\n\x0f\x63om.google.typeB\x13\x43\x61lendarPeriodProtoP\x01ZHgoogle.golang.org/genproto/googleapis/type/calendarperiod;calendarperiod\xa2\x02\x03GTPb\x06proto3"
32
+ )
33
+
34
+ _globals = globals()
35
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
36
+ _builder.BuildTopDescriptorsAndMessages(
37
+ DESCRIPTOR, "google.type.calendar_period_pb2", _globals
38
+ )
39
+ if _descriptor._USE_C_DESCRIPTORS == False:
40
+ DESCRIPTOR._options = None
41
+ DESCRIPTOR._serialized_options = b"\n\017com.google.typeB\023CalendarPeriodProtoP\001ZHgoogle.golang.org/genproto/googleapis/type/calendarperiod;calendarperiod\242\002\003GTP"
42
+ _globals["_CALENDARPERIOD"]._serialized_start = 50
43
+ _globals["_CALENDARPERIOD"]._serialized_end = 177
44
+ # @@protoc_insertion_point(module_scope)