py2docfx 0.1.22.dev2259826__py3-none-any.whl → 0.1.22.dev2270449__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py2docfx/venv/basevenv/Lib/site-packages/certifi/__init__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/default.py +8 -9
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/imds.py +7 -3
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/managed_identity.py +7 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/shared_cache.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/interactive.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/msal_managed_identity_client.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/default.py +8 -9
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/imds.py +7 -3
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/managed_identity.py +7 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/shared_cache.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/cachetools/__init__.py +96 -122
- py2docfx/venv/venv1/Lib/site-packages/cachetools/{_decorators.py → _cached.py} +106 -13
- py2docfx/venv/venv1/Lib/site-packages/cachetools/_cachedmethod.py +128 -0
- py2docfx/venv/venv1/Lib/site-packages/cachetools/func.py +5 -25
- py2docfx/venv/venv1/Lib/site-packages/certifi/__init__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/cryptography/__about__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/client_options.py +9 -2
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/general_helpers.py +36 -0
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/grpc_helpers.py +10 -7
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/grpc_helpers_async.py +8 -3
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/base.py +13 -7
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/rest.py +19 -12
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/operations_v1/transports/rest_asyncio.py +21 -0
- py2docfx/venv/venv1/Lib/site-packages/google/api_core/version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/google/auth/_default.py +66 -12
- py2docfx/venv/venv1/Lib/site-packages/google/auth/_default_async.py +16 -10
- py2docfx/venv/venv1/Lib/site-packages/google/auth/_helpers.py +41 -0
- py2docfx/venv/venv1/Lib/site-packages/google/auth/compute_engine/credentials.py +67 -6
- py2docfx/venv/venv1/Lib/site-packages/google/auth/credentials.py +161 -18
- py2docfx/venv/venv1/Lib/site-packages/google/auth/environment_vars.py +4 -0
- py2docfx/venv/venv1/Lib/site-packages/google/auth/external_account.py +33 -10
- py2docfx/venv/venv1/Lib/site-packages/google/auth/external_account_authorized_user.py +24 -1
- py2docfx/venv/venv1/Lib/site-packages/google/auth/identity_pool.py +25 -1
- py2docfx/venv/venv1/Lib/site-packages/google/auth/impersonated_credentials.py +57 -9
- py2docfx/venv/venv1/Lib/site-packages/google/auth/pluggable.py +25 -1
- py2docfx/venv/venv1/Lib/site-packages/google/auth/version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/google/oauth2/_client.py +117 -0
- py2docfx/venv/venv1/Lib/site-packages/google/oauth2/service_account.py +39 -4
- {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22.dev2270449.dist-info}/METADATA +1 -1
- {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22.dev2270449.dist-info}/RECORD +44 -43
- {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22.dev2270449.dist-info}/WHEEL +0 -0
- {py2docfx-0.1.22.dev2259826.dist-info → py2docfx-0.1.22.dev2270449.dist-info}/top_level.txt +0 -0
@@ -59,6 +59,38 @@ or "API not enabled" error. See the following page for troubleshooting: \
|
|
59
59
|
https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \
|
60
60
|
"""
|
61
61
|
|
62
|
+
_GENERIC_LOAD_METHOD_WARNING = """\
|
63
|
+
The {} method is deprecated because of a potential security risk.
|
64
|
+
|
65
|
+
This method does not validate the credential configuration. The security
|
66
|
+
risk occurs when a credential configuration is accepted from a source that
|
67
|
+
is not under your control and used without validation on your side.
|
68
|
+
|
69
|
+
If you know that you will be loading credential configurations of a
|
70
|
+
specific type, it is recommended to use a credential-type-specific
|
71
|
+
load method.
|
72
|
+
This will ensure that an unexpected credential type with potential for
|
73
|
+
malicious intent is not loaded unintentionally. You might still have to do
|
74
|
+
validation for certain credential types. Please follow the recommendations
|
75
|
+
for that method. For example, if you want to load only service accounts,
|
76
|
+
you can create the service account credentials explicitly:
|
77
|
+
|
78
|
+
```
|
79
|
+
from google.oauth2 import service_account
|
80
|
+
creds = service_account.Credentials.from_service_account_file(filename)
|
81
|
+
```
|
82
|
+
|
83
|
+
If you are loading your credential configuration from an untrusted source and have
|
84
|
+
not mitigated the risks (e.g. by validating the configuration yourself), make
|
85
|
+
these changes as soon as possible to prevent security risks to your environment.
|
86
|
+
|
87
|
+
Regardless of the method used, it is always your responsibility to validate
|
88
|
+
configurations received from external sources.
|
89
|
+
|
90
|
+
Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
91
|
+
for more details.
|
92
|
+
"""
|
93
|
+
|
62
94
|
# The subject token type used for AWS external_account credentials.
|
63
95
|
_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
|
64
96
|
|
@@ -76,6 +108,20 @@ def _warn_about_problematic_credentials(credentials):
|
|
76
108
|
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
|
77
109
|
|
78
110
|
|
111
|
+
def _warn_about_generic_load_method(method_name): # pragma: NO COVER
|
112
|
+
"""Warns that a generic load method is being used.
|
113
|
+
|
114
|
+
This is to discourage use of the generic load methods in favor of
|
115
|
+
more specific methods. The generic methods are more likely to lead to
|
116
|
+
security issues if the input is not validated.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
method_name (str): The name of the method being used.
|
120
|
+
"""
|
121
|
+
|
122
|
+
warnings.warn(_GENERIC_LOAD_METHOD_WARNING.format(method_name), DeprecationWarning)
|
123
|
+
|
124
|
+
|
79
125
|
def load_credentials_from_file(
|
80
126
|
filename, scopes=None, default_scopes=None, quota_project_id=None, request=None
|
81
127
|
):
|
@@ -121,6 +167,8 @@ def load_credentials_from_file(
|
|
121
167
|
google.auth.exceptions.DefaultCredentialsError: if the file is in the
|
122
168
|
wrong format or is missing.
|
123
169
|
"""
|
170
|
+
_warn_about_generic_load_method("load_credentials_from_file")
|
171
|
+
|
124
172
|
if not os.path.exists(filename):
|
125
173
|
raise exceptions.DefaultCredentialsError(
|
126
174
|
"File {} was not found.".format(filename)
|
@@ -184,6 +232,7 @@ def load_credentials_from_dict(
|
|
184
232
|
google.auth.exceptions.DefaultCredentialsError: if the file is in the
|
185
233
|
wrong format or is missing.
|
186
234
|
"""
|
235
|
+
_warn_about_generic_load_method("load_credentials_from_dict")
|
187
236
|
if not isinstance(info, dict):
|
188
237
|
raise exceptions.DefaultCredentialsError(
|
189
238
|
"info object was of type {} but dict type was expected.".format(type(info))
|
@@ -256,15 +305,17 @@ def _get_gcloud_sdk_credentials(quota_project_id=None):
|
|
256
305
|
_LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
|
257
306
|
return None, None
|
258
307
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
308
|
+
with warnings.catch_warnings():
|
309
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
310
|
+
credentials, project_id = load_credentials_from_file(
|
311
|
+
credentials_filename, quota_project_id=quota_project_id
|
312
|
+
)
|
313
|
+
credentials._cred_file_path = credentials_filename
|
263
314
|
|
264
|
-
|
265
|
-
|
315
|
+
if not project_id:
|
316
|
+
project_id = _cloud_sdk.get_project_id()
|
266
317
|
|
267
|
-
|
318
|
+
return credentials, project_id
|
268
319
|
|
269
320
|
|
270
321
|
def _get_explicit_environ_credentials(quota_project_id=None):
|
@@ -290,12 +341,15 @@ def _get_explicit_environ_credentials(quota_project_id=None):
|
|
290
341
|
return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)
|
291
342
|
|
292
343
|
if explicit_file is not None:
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
344
|
+
with warnings.catch_warnings():
|
345
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
346
|
+
credentials, project_id = load_credentials_from_file(
|
347
|
+
os.environ[environment_vars.CREDENTIALS],
|
348
|
+
quota_project_id=quota_project_id,
|
349
|
+
)
|
350
|
+
credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable"
|
297
351
|
|
298
|
-
|
352
|
+
return credentials, project_id
|
299
353
|
|
300
354
|
else:
|
301
355
|
return None, None
|
@@ -20,6 +20,7 @@ Implements application default credentials and project ID detection.
|
|
20
20
|
import io
|
21
21
|
import json
|
22
22
|
import os
|
23
|
+
import warnings
|
23
24
|
|
24
25
|
from google.auth import _default
|
25
26
|
from google.auth import environment_vars
|
@@ -116,14 +117,16 @@ def _get_gcloud_sdk_credentials(quota_project_id=None):
|
|
116
117
|
if not os.path.isfile(credentials_filename):
|
117
118
|
return None, None
|
118
119
|
|
119
|
-
|
120
|
-
|
121
|
-
|
120
|
+
with warnings.catch_warnings():
|
121
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
122
|
+
credentials, project_id = load_credentials_from_file(
|
123
|
+
credentials_filename, quota_project_id=quota_project_id
|
124
|
+
)
|
122
125
|
|
123
|
-
|
124
|
-
|
126
|
+
if not project_id:
|
127
|
+
project_id = _cloud_sdk.get_project_id()
|
125
128
|
|
126
|
-
|
129
|
+
return credentials, project_id
|
127
130
|
|
128
131
|
|
129
132
|
def _get_explicit_environ_credentials(quota_project_id=None):
|
@@ -141,11 +144,14 @@ def _get_explicit_environ_credentials(quota_project_id=None):
|
|
141
144
|
return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)
|
142
145
|
|
143
146
|
if explicit_file is not None:
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
+
with warnings.catch_warnings():
|
148
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
149
|
+
credentials, project_id = load_credentials_from_file(
|
150
|
+
os.environ[environment_vars.CREDENTIALS],
|
151
|
+
quota_project_id=quota_project_id,
|
152
|
+
)
|
147
153
|
|
148
|
-
|
154
|
+
return credentials, project_id
|
149
155
|
|
150
156
|
else:
|
151
157
|
return None, None
|
@@ -21,6 +21,7 @@ from email.message import Message
|
|
21
21
|
import hashlib
|
22
22
|
import json
|
23
23
|
import logging
|
24
|
+
import os
|
24
25
|
import sys
|
25
26
|
from typing import Any, Dict, Mapping, Optional, Union
|
26
27
|
import urllib
|
@@ -287,6 +288,46 @@ def unpadded_urlsafe_b64encode(value):
|
|
287
288
|
return base64.urlsafe_b64encode(value).rstrip(b"=")
|
288
289
|
|
289
290
|
|
291
|
+
def get_bool_from_env(variable_name, default=False):
|
292
|
+
"""Gets a boolean value from an environment variable.
|
293
|
+
|
294
|
+
The environment variable is interpreted as a boolean with the following
|
295
|
+
(case-insensitive) rules:
|
296
|
+
- "true", "1" are considered true.
|
297
|
+
- "false", "0" are considered false.
|
298
|
+
Any other values will raise an exception.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
variable_name (str): The name of the environment variable.
|
302
|
+
default (bool): The default value if the environment variable is not
|
303
|
+
set.
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
bool: The boolean value of the environment variable.
|
307
|
+
|
308
|
+
Raises:
|
309
|
+
google.auth.exceptions.InvalidValue: If the environment variable is
|
310
|
+
set to a value that can not be interpreted as a boolean.
|
311
|
+
"""
|
312
|
+
value = os.environ.get(variable_name)
|
313
|
+
|
314
|
+
if value is None:
|
315
|
+
return default
|
316
|
+
|
317
|
+
value = value.lower()
|
318
|
+
|
319
|
+
if value in ("true", "1"):
|
320
|
+
return True
|
321
|
+
elif value in ("false", "0"):
|
322
|
+
return False
|
323
|
+
else:
|
324
|
+
raise exceptions.InvalidValue(
|
325
|
+
'Environment variable "{}" must be one of "true", "false", "1", or "0".'.format(
|
326
|
+
variable_name
|
327
|
+
)
|
328
|
+
)
|
329
|
+
|
330
|
+
|
290
331
|
def is_python_3():
|
291
332
|
"""Check if the Python interpreter is Python 2 or 3.
|
292
333
|
|
@@ -30,11 +30,16 @@ from google.auth import metrics
|
|
30
30
|
from google.auth.compute_engine import _metadata
|
31
31
|
from google.oauth2 import _client
|
32
32
|
|
33
|
+
_TRUST_BOUNDARY_LOOKUP_ENDPOINT = (
|
34
|
+
"https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations"
|
35
|
+
)
|
36
|
+
|
33
37
|
|
34
38
|
class Credentials(
|
35
39
|
credentials.Scoped,
|
36
40
|
credentials.CredentialsWithQuotaProject,
|
37
41
|
credentials.CredentialsWithUniverseDomain,
|
42
|
+
credentials.CredentialsWithTrustBoundary,
|
38
43
|
):
|
39
44
|
"""Compute Engine Credentials.
|
40
45
|
|
@@ -61,6 +66,7 @@ class Credentials(
|
|
61
66
|
scopes=None,
|
62
67
|
default_scopes=None,
|
63
68
|
universe_domain=None,
|
69
|
+
trust_boundary=None,
|
64
70
|
):
|
65
71
|
"""
|
66
72
|
Args:
|
@@ -76,6 +82,7 @@ class Credentials(
|
|
76
82
|
provided or None, credential will attempt to fetch the value
|
77
83
|
from metadata server. If metadata server doesn't have universe
|
78
84
|
domain endpoint, then the default googleapis.com will be used.
|
85
|
+
trust_boundary (Mapping[str,str]): A credential trust boundary.
|
79
86
|
"""
|
80
87
|
super(Credentials, self).__init__()
|
81
88
|
self._service_account_email = service_account_email
|
@@ -86,6 +93,7 @@ class Credentials(
|
|
86
93
|
if universe_domain:
|
87
94
|
self._universe_domain = universe_domain
|
88
95
|
self._universe_domain_cached = True
|
96
|
+
self._trust_boundary = trust_boundary
|
89
97
|
|
90
98
|
def _retrieve_info(self, request):
|
91
99
|
"""Retrieve information about the service account.
|
@@ -100,16 +108,22 @@ class Credentials(
|
|
100
108
|
request, service_account=self._service_account_email
|
101
109
|
)
|
102
110
|
|
111
|
+
if not info or "email" not in info:
|
112
|
+
raise exceptions.RefreshError(
|
113
|
+
"Unexpected response from metadata server: "
|
114
|
+
"service account info is missing 'email' field."
|
115
|
+
)
|
116
|
+
|
103
117
|
self._service_account_email = info["email"]
|
104
118
|
|
105
119
|
# Don't override scopes requested by the user.
|
106
120
|
if self._scopes is None:
|
107
|
-
self._scopes = info
|
121
|
+
self._scopes = info.get("scopes")
|
108
122
|
|
109
123
|
def _metric_header_for_usage(self):
|
110
124
|
return metrics.CRED_TYPE_SA_MDS
|
111
125
|
|
112
|
-
def
|
126
|
+
def _refresh_token(self, request):
|
113
127
|
"""Refresh the access token and scopes.
|
114
128
|
|
115
129
|
Args:
|
@@ -132,6 +146,37 @@ class Credentials(
|
|
132
146
|
new_exc = exceptions.RefreshError(caught_exc)
|
133
147
|
raise new_exc from caught_exc
|
134
148
|
|
149
|
+
def _build_trust_boundary_lookup_url(self):
|
150
|
+
"""Builds and returns the URL for the trust boundary lookup API for GCE."""
|
151
|
+
# If the service account email is 'default', we need to get the
|
152
|
+
# actual email address from the metadata server.
|
153
|
+
if self._service_account_email == "default":
|
154
|
+
from google.auth.transport import requests as google_auth_requests
|
155
|
+
|
156
|
+
request = google_auth_requests.Request()
|
157
|
+
try:
|
158
|
+
info = _metadata.get_service_account_info(request, "default")
|
159
|
+
if not info or "email" not in info:
|
160
|
+
raise exceptions.RefreshError(
|
161
|
+
"Unexpected response from metadata server: "
|
162
|
+
"service account info is missing 'email' field."
|
163
|
+
)
|
164
|
+
self._service_account_email = info["email"]
|
165
|
+
|
166
|
+
except exceptions.TransportError as e:
|
167
|
+
# If fetching the service account email fails due to a transport error,
|
168
|
+
# it means we cannot build the trust boundary lookup URL.
|
169
|
+
# Wrap this in a RefreshError so it's caught by _refresh_trust_boundary.
|
170
|
+
raise exceptions.RefreshError(
|
171
|
+
"Failed to get service account email for trust boundary lookup: {}".format(
|
172
|
+
e
|
173
|
+
)
|
174
|
+
) from e
|
175
|
+
|
176
|
+
return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format(
|
177
|
+
self.universe_domain, self.service_account_email
|
178
|
+
)
|
179
|
+
|
135
180
|
@property
|
136
181
|
def service_account_email(self):
|
137
182
|
"""The service account email.
|
@@ -173,8 +218,9 @@ class Credentials(
|
|
173
218
|
quota_project_id=quota_project_id,
|
174
219
|
scopes=self._scopes,
|
175
220
|
default_scopes=self._default_scopes,
|
221
|
+
universe_domain=self._universe_domain,
|
222
|
+
trust_boundary=self._trust_boundary,
|
176
223
|
)
|
177
|
-
creds._universe_domain = self._universe_domain
|
178
224
|
creds._universe_domain_cached = self._universe_domain_cached
|
179
225
|
return creds
|
180
226
|
|
@@ -188,8 +234,9 @@ class Credentials(
|
|
188
234
|
default_scopes=default_scopes,
|
189
235
|
service_account_email=self._service_account_email,
|
190
236
|
quota_project_id=self._quota_project_id,
|
237
|
+
universe_domain=self._universe_domain,
|
238
|
+
trust_boundary=self._trust_boundary,
|
191
239
|
)
|
192
|
-
creds._universe_domain = self._universe_domain
|
193
240
|
creds._universe_domain_cached = self._universe_domain_cached
|
194
241
|
return creds
|
195
242
|
|
@@ -200,9 +247,23 @@ class Credentials(
|
|
200
247
|
default_scopes=self._default_scopes,
|
201
248
|
service_account_email=self._service_account_email,
|
202
249
|
quota_project_id=self._quota_project_id,
|
250
|
+
trust_boundary=self._trust_boundary,
|
203
251
|
universe_domain=universe_domain,
|
204
252
|
)
|
205
253
|
|
254
|
+
@_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary)
|
255
|
+
def with_trust_boundary(self, trust_boundary):
|
256
|
+
creds = self.__class__(
|
257
|
+
service_account_email=self._service_account_email,
|
258
|
+
quota_project_id=self._quota_project_id,
|
259
|
+
scopes=self._scopes,
|
260
|
+
default_scopes=self._default_scopes,
|
261
|
+
universe_domain=self._universe_domain,
|
262
|
+
trust_boundary=trust_boundary,
|
263
|
+
)
|
264
|
+
creds._universe_domain_cached = self._universe_domain_cached
|
265
|
+
return creds
|
266
|
+
|
206
267
|
|
207
268
|
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
|
208
269
|
_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"
|
@@ -275,7 +336,7 @@ class IDTokenCredentials(
|
|
275
336
|
|
276
337
|
if use_metadata_identity_endpoint:
|
277
338
|
if token_uri or additional_claims or service_account_email or signer:
|
278
|
-
raise
|
339
|
+
raise ValueError(
|
279
340
|
"If use_metadata_identity_endpoint is set, token_uri, "
|
280
341
|
"additional_claims, service_account_email, signer arguments"
|
281
342
|
" must not be set"
|
@@ -366,7 +427,7 @@ class IDTokenCredentials(
|
|
366
427
|
# since the signer is already instantiated,
|
367
428
|
# the request is not needed
|
368
429
|
if self._use_metadata_identity_endpoint:
|
369
|
-
raise
|
430
|
+
raise ValueError(
|
370
431
|
"If use_metadata_identity_endpoint is set, token_uri" " must not be set"
|
371
432
|
)
|
372
433
|
else:
|
@@ -18,14 +18,18 @@
|
|
18
18
|
import abc
|
19
19
|
from enum import Enum
|
20
20
|
import os
|
21
|
+
from typing import List
|
21
22
|
|
22
23
|
from google.auth import _helpers, environment_vars
|
23
24
|
from google.auth import exceptions
|
24
25
|
from google.auth import metrics
|
25
26
|
from google.auth._credentials_base import _BaseCredentials
|
27
|
+
from google.auth._default import _LOGGER
|
26
28
|
from google.auth._refresh_worker import RefreshThreadManager
|
27
29
|
|
28
30
|
DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
|
31
|
+
NO_OP_TRUST_BOUNDARY_LOCATIONS: List[str] = []
|
32
|
+
NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS = "0x0"
|
29
33
|
|
30
34
|
|
31
35
|
class Credentials(_BaseCredentials):
|
@@ -178,22 +182,7 @@ class Credentials(_BaseCredentials):
|
|
178
182
|
token (Optional[str]): If specified, overrides the current access
|
179
183
|
token.
|
180
184
|
"""
|
181
|
-
self._apply(headers, token
|
182
|
-
"""Trust boundary value will be a cached value from global lookup.
|
183
|
-
|
184
|
-
The response of trust boundary will be a list of regions and a hex
|
185
|
-
encoded representation.
|
186
|
-
|
187
|
-
An example of global lookup response:
|
188
|
-
{
|
189
|
-
"locations": [
|
190
|
-
"us-central1", "us-east1", "europe-west1", "asia-east1"
|
191
|
-
]
|
192
|
-
"encoded_locations": "0xA30"
|
193
|
-
}
|
194
|
-
"""
|
195
|
-
if self._trust_boundary is not None:
|
196
|
-
headers["x-allowed-locations"] = self._trust_boundary["encoded_locations"]
|
185
|
+
self._apply(headers, token)
|
197
186
|
if self.quota_project_id:
|
198
187
|
headers["x-goog-user-project"] = self.quota_project_id
|
199
188
|
|
@@ -299,6 +288,161 @@ class CredentialsWithUniverseDomain(Credentials):
|
|
299
288
|
)
|
300
289
|
|
301
290
|
|
291
|
+
class CredentialsWithTrustBoundary(Credentials):
|
292
|
+
"""Abstract base for credentials supporting ``with_trust_boundary`` factory"""
|
293
|
+
|
294
|
+
@abc.abstractmethod
|
295
|
+
def _refresh_token(self, request):
|
296
|
+
"""Refreshes the access token.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
request (google.auth.transport.Request): The object used to make
|
300
|
+
HTTP requests.
|
301
|
+
|
302
|
+
Raises:
|
303
|
+
google.auth.exceptions.RefreshError: If the credentials could
|
304
|
+
not be refreshed.
|
305
|
+
"""
|
306
|
+
raise NotImplementedError("_refresh_token must be implemented")
|
307
|
+
|
308
|
+
def with_trust_boundary(self, trust_boundary):
|
309
|
+
"""Returns a copy of these credentials with a modified trust boundary.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
trust_boundary Mapping[str, str]: The trust boundary to use for the
|
313
|
+
credential. This should be a map with a "locations" key that maps to
|
314
|
+
a list of GCP regions, and a "encodedLocations" key that maps to a
|
315
|
+
hex string.
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
google.auth.credentials.Credentials: A new credentials instance.
|
319
|
+
"""
|
320
|
+
raise NotImplementedError("This credential does not support trust boundaries.")
|
321
|
+
|
322
|
+
def _is_trust_boundary_lookup_required(self):
|
323
|
+
"""Checks if a trust boundary lookup is required.
|
324
|
+
|
325
|
+
A lookup is required if the feature is enabled via an environment
|
326
|
+
variable, the universe domain is supported, and a no-op boundary
|
327
|
+
is not already cached.
|
328
|
+
|
329
|
+
Returns:
|
330
|
+
bool: True if a trust boundary lookup is required, False otherwise.
|
331
|
+
"""
|
332
|
+
# 1. Check if the feature is enabled via environment variable.
|
333
|
+
if not _helpers.get_bool_from_env(
|
334
|
+
environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED, default=False
|
335
|
+
):
|
336
|
+
return False
|
337
|
+
|
338
|
+
# 2. Skip trust boundary flow for non-default universe domains.
|
339
|
+
if self.universe_domain != DEFAULT_UNIVERSE_DOMAIN:
|
340
|
+
return False
|
341
|
+
|
342
|
+
# 3. Do not trigger refresh if credential has a cached no-op trust boundary.
|
343
|
+
return not self._has_no_op_trust_boundary()
|
344
|
+
|
345
|
+
def _get_trust_boundary_header(self):
|
346
|
+
if self._trust_boundary is not None:
|
347
|
+
if self._has_no_op_trust_boundary():
|
348
|
+
# STS expects an empty string if the trust boundary value is no-op.
|
349
|
+
return {"x-allowed-locations": ""}
|
350
|
+
else:
|
351
|
+
return {"x-allowed-locations": self._trust_boundary["encodedLocations"]}
|
352
|
+
return {}
|
353
|
+
|
354
|
+
def apply(self, headers, token=None):
|
355
|
+
"""Apply the token to the authentication header."""
|
356
|
+
super().apply(headers, token)
|
357
|
+
headers.update(self._get_trust_boundary_header())
|
358
|
+
|
359
|
+
def refresh(self, request):
|
360
|
+
"""Refreshes the access token and the trust boundary.
|
361
|
+
|
362
|
+
This method calls the subclass's token refresh logic and then
|
363
|
+
refreshes the trust boundary if applicable.
|
364
|
+
"""
|
365
|
+
self._refresh_token(request)
|
366
|
+
self._refresh_trust_boundary(request)
|
367
|
+
|
368
|
+
def _refresh_trust_boundary(self, request):
|
369
|
+
"""Triggers a refresh of the trust boundary and updates the cache if necessary.
|
370
|
+
|
371
|
+
Args:
|
372
|
+
request (google.auth.transport.Request): The object used to make
|
373
|
+
HTTP requests.
|
374
|
+
|
375
|
+
Raises:
|
376
|
+
google.auth.exceptions.RefreshError: If the trust boundary could
|
377
|
+
not be refreshed and no cached value is available.
|
378
|
+
"""
|
379
|
+
if not self._is_trust_boundary_lookup_required():
|
380
|
+
return
|
381
|
+
try:
|
382
|
+
self._trust_boundary = self._lookup_trust_boundary(request)
|
383
|
+
except exceptions.RefreshError as error:
|
384
|
+
# If the call to the lookup API failed, check if there is a trust boundary
|
385
|
+
# already cached. If there is, do nothing. If not, then throw the error.
|
386
|
+
if self._trust_boundary is None:
|
387
|
+
raise error
|
388
|
+
if _helpers.is_logging_enabled(_LOGGER):
|
389
|
+
_LOGGER.debug(
|
390
|
+
"Using cached trust boundary due to refresh error: %s", error
|
391
|
+
)
|
392
|
+
return
|
393
|
+
|
394
|
+
def _lookup_trust_boundary(self, request):
|
395
|
+
"""Calls the trust boundary lookup API to refresh the trust boundary cache.
|
396
|
+
|
397
|
+
Args:
|
398
|
+
request (google.auth.transport.Request): The object used to make
|
399
|
+
HTTP requests.
|
400
|
+
|
401
|
+
Returns:
|
402
|
+
trust_boundary (dict): The trust boundary object returned by the lookup API.
|
403
|
+
|
404
|
+
Raises:
|
405
|
+
google.auth.exceptions.RefreshError: If the trust boundary could not be
|
406
|
+
retrieved.
|
407
|
+
"""
|
408
|
+
from google.oauth2 import _client
|
409
|
+
|
410
|
+
url = self._build_trust_boundary_lookup_url()
|
411
|
+
if not url:
|
412
|
+
raise exceptions.InvalidValue("Failed to build trust boundary lookup URL.")
|
413
|
+
|
414
|
+
headers = {}
|
415
|
+
self._apply(headers)
|
416
|
+
headers.update(self._get_trust_boundary_header())
|
417
|
+
return _client._lookup_trust_boundary(request, url, headers=headers)
|
418
|
+
|
419
|
+
@abc.abstractmethod
|
420
|
+
def _build_trust_boundary_lookup_url(self):
|
421
|
+
"""
|
422
|
+
Builds and returns the URL for the trust boundary lookup API.
|
423
|
+
|
424
|
+
This method should be implemented by subclasses to provide the
|
425
|
+
specific URL based on the credential type and its properties.
|
426
|
+
|
427
|
+
Returns:
|
428
|
+
str: The URL for the trust boundary lookup endpoint, or None
|
429
|
+
if lookup should be skipped (e.g., for non-applicable universe domains).
|
430
|
+
"""
|
431
|
+
raise NotImplementedError(
|
432
|
+
"_build_trust_boundary_lookup_url must be implemented"
|
433
|
+
)
|
434
|
+
|
435
|
+
def _has_no_op_trust_boundary(self):
|
436
|
+
# A no-op trust boundary is indicated by encodedLocations being "0x0".
|
437
|
+
# The "locations" list may or may not be present as an empty list.
|
438
|
+
if self._trust_boundary is None:
|
439
|
+
return False
|
440
|
+
return (
|
441
|
+
self._trust_boundary.get("encodedLocations")
|
442
|
+
== NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS
|
443
|
+
)
|
444
|
+
|
445
|
+
|
302
446
|
class AnonymousCredentials(Credentials):
|
303
447
|
"""Credentials that do not provide any authentication information.
|
304
448
|
|
@@ -382,8 +526,7 @@ class ReadOnlyScoped(metaclass=abc.ABCMeta):
|
|
382
526
|
|
383
527
|
@abc.abstractproperty
|
384
528
|
def requires_scopes(self):
|
385
|
-
"""True if these credentials require scopes to obtain an access token.
|
386
|
-
"""
|
529
|
+
"""True if these credentials require scopes to obtain an access token."""
|
387
530
|
return False
|
388
531
|
|
389
532
|
def has_scopes(self, scopes):
|
@@ -82,3 +82,7 @@ AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
|
|
82
82
|
AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN"
|
83
83
|
AWS_REGION = "AWS_REGION"
|
84
84
|
AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION"
|
85
|
+
|
86
|
+
GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"
|
87
|
+
"""Environment variable controlling whether to enable trust boundary feature.
|
88
|
+
The default value is false. Users have to explicitly set this value to true."""
|