google-auth 2.48.0rc0__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.
- google/auth/__init__.py +57 -0
- google/auth/_agent_identity_utils.py +272 -0
- google/auth/_cache.py +64 -0
- google/auth/_cloud_sdk.py +153 -0
- google/auth/_constants.py +5 -0
- google/auth/_credentials_async.py +171 -0
- google/auth/_credentials_base.py +75 -0
- google/auth/_default.py +752 -0
- google/auth/_default_async.py +288 -0
- google/auth/_exponential_backoff.py +164 -0
- google/auth/_helpers.py +575 -0
- google/auth/_jwt_async.py +164 -0
- google/auth/_oauth2client.py +167 -0
- google/auth/_refresh_worker.py +109 -0
- google/auth/_service_account_info.py +80 -0
- google/auth/aio/__init__.py +25 -0
- google/auth/aio/_helpers.py +62 -0
- google/auth/aio/credentials.py +143 -0
- google/auth/aio/transport/__init__.py +144 -0
- google/auth/aio/transport/aiohttp.py +190 -0
- google/auth/aio/transport/sessions.py +268 -0
- google/auth/api_key.py +76 -0
- google/auth/app_engine.py +179 -0
- google/auth/aws.py +863 -0
- google/auth/compute_engine/__init__.py +22 -0
- google/auth/compute_engine/_metadata.py +505 -0
- google/auth/compute_engine/_mtls.py +164 -0
- google/auth/compute_engine/credentials.py +556 -0
- google/auth/credentials.py +667 -0
- google/auth/crypt/__init__.py +96 -0
- google/auth/crypt/_cryptography_rsa.py +151 -0
- google/auth/crypt/_helpers.py +0 -0
- google/auth/crypt/_python_rsa.py +199 -0
- google/auth/crypt/base.py +127 -0
- google/auth/crypt/es.py +221 -0
- google/auth/crypt/es256.py +45 -0
- google/auth/crypt/rsa.py +127 -0
- google/auth/downscoped.py +512 -0
- google/auth/environment_vars.py +119 -0
- google/auth/exceptions.py +108 -0
- google/auth/external_account.py +716 -0
- google/auth/external_account_authorized_user.py +458 -0
- google/auth/iam.py +146 -0
- google/auth/identity_pool.py +575 -0
- google/auth/impersonated_credentials.py +712 -0
- google/auth/jwt.py +877 -0
- google/auth/metrics.py +156 -0
- google/auth/pluggable.py +445 -0
- google/auth/py.typed +2 -0
- google/auth/transport/__init__.py +104 -0
- google/auth/transport/_aiohttp_requests.py +396 -0
- google/auth/transport/_custom_tls_signer.py +283 -0
- google/auth/transport/_http_client.py +114 -0
- google/auth/transport/_mtls_helper.py +511 -0
- google/auth/transport/_requests_base.py +53 -0
- google/auth/transport/grpc.py +337 -0
- google/auth/transport/mtls.py +137 -0
- google/auth/transport/requests.py +634 -0
- google/auth/transport/urllib3.py +493 -0
- google/auth/version.py +15 -0
- google/oauth2/__init__.py +40 -0
- google/oauth2/_client.py +631 -0
- google/oauth2/_client_async.py +290 -0
- google/oauth2/_credentials_async.py +118 -0
- google/oauth2/_id_token_async.py +287 -0
- google/oauth2/_reauth_async.py +330 -0
- google/oauth2/_service_account_async.py +132 -0
- google/oauth2/challenges.py +281 -0
- google/oauth2/credentials.py +617 -0
- google/oauth2/gdch_credentials.py +251 -0
- google/oauth2/id_token.py +373 -0
- google/oauth2/py.typed +2 -0
- google/oauth2/reauth.py +373 -0
- google/oauth2/service_account.py +880 -0
- google/oauth2/sts.py +201 -0
- google/oauth2/utils.py +168 -0
- google/oauth2/webauthn_handler.py +82 -0
- google/oauth2/webauthn_handler_factory.py +16 -0
- google/oauth2/webauthn_types.py +156 -0
- google_auth-2.48.0rc0.dist-info/LICENSE +201 -0
- google_auth-2.48.0rc0.dist-info/METADATA +166 -0
- google_auth-2.48.0rc0.dist-info/RECORD +84 -0
- google_auth-2.48.0rc0.dist-info/WHEEL +5 -0
- google_auth-2.48.0rc0.dist-info/top_level.txt +3 -0
google/auth/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
# Raise warnings for deprecated versions
|
|
45
|
+
eol_message = (
|
|
46
|
+
"You are using a Python version {} past its end of life. Google will update "
|
|
47
|
+
"google-auth with critical bug fixes on a best-effort basis, but not "
|
|
48
|
+
"with any other fixes or features. Please upgrade your Python version, "
|
|
49
|
+
"and then update google-auth."
|
|
50
|
+
)
|
|
51
|
+
if sys.version_info.major == 3 and sys.version_info.minor == 8: # pragma: NO COVER
|
|
52
|
+
warnings.warn(eol_message.format("3.8"), FutureWarning)
|
|
53
|
+
elif sys.version_info.major == 3 and sys.version_info.minor == 9: # pragma: NO COVER
|
|
54
|
+
warnings.warn(eol_message.format("3.9"), FutureWarning)
|
|
55
|
+
|
|
56
|
+
# Set default logging handler to avoid "No handler found" warnings.
|
|
57
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Helpers for Agent Identity credentials."""
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import hashlib
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import time
|
|
23
|
+
from urllib.parse import quote, urlparse
|
|
24
|
+
|
|
25
|
+
from google.auth import environment_vars
|
|
26
|
+
from google.auth import exceptions
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_LOGGER = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
CRYPTOGRAPHY_NOT_FOUND_ERROR = (
|
|
32
|
+
"The cryptography library is required for certificate-based authentication."
|
|
33
|
+
"Please install it with `pip install google-auth[cryptography]`."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# SPIFFE trust domain patterns for Agent Identities.
|
|
37
|
+
_AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS = [
|
|
38
|
+
r"^agents\.global\.org-\d+\.system\.id\.goog$",
|
|
39
|
+
r"^agents\.global\.proj-\d+\.system\.id\.goog$",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
_WELL_KNOWN_CERT_PATH = "/var/run/secrets/workload-spiffe-credentials/certificates.pem"
|
|
43
|
+
|
|
44
|
+
# Constants for polling the certificate file.
|
|
45
|
+
_FAST_POLL_CYCLES = 50
|
|
46
|
+
_FAST_POLL_INTERVAL = 0.1 # 100ms
|
|
47
|
+
_SLOW_POLL_INTERVAL = 0.5 # 500ms
|
|
48
|
+
_TOTAL_TIMEOUT = 30 # seconds
|
|
49
|
+
|
|
50
|
+
# Calculate the number of slow poll cycles based on the total timeout.
|
|
51
|
+
_SLOW_POLL_CYCLES = int(
|
|
52
|
+
(_TOTAL_TIMEOUT - (_FAST_POLL_CYCLES * _FAST_POLL_INTERVAL)) / _SLOW_POLL_INTERVAL
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_POLLING_INTERVALS = ([_FAST_POLL_INTERVAL] * _FAST_POLL_CYCLES) + (
|
|
56
|
+
[_SLOW_POLL_INTERVAL] * _SLOW_POLL_CYCLES
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_certificate_file_ready(path):
|
|
61
|
+
"""Checks if a file exists and is not empty."""
|
|
62
|
+
return path and os.path.exists(path) and os.path.getsize(path) > 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_agent_identity_certificate_path():
|
|
66
|
+
"""Gets the certificate path from the certificate config file.
|
|
67
|
+
|
|
68
|
+
The path to the certificate config file is read from the
|
|
69
|
+
GOOGLE_API_CERTIFICATE_CONFIG environment variable. This function
|
|
70
|
+
implements a retry mechanism to handle cases where the environment
|
|
71
|
+
variable is set before the files are available on the filesystem.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
str: The path to the leaf certificate file.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
google.auth.exceptions.RefreshError: If the certificate config file
|
|
78
|
+
or the certificate file cannot be found after retries.
|
|
79
|
+
"""
|
|
80
|
+
import json
|
|
81
|
+
|
|
82
|
+
cert_config_path = os.environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG)
|
|
83
|
+
if not cert_config_path:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
has_logged_warning = False
|
|
87
|
+
|
|
88
|
+
for interval in _POLLING_INTERVALS:
|
|
89
|
+
try:
|
|
90
|
+
with open(cert_config_path, "r") as f:
|
|
91
|
+
cert_config = json.load(f)
|
|
92
|
+
cert_path = (
|
|
93
|
+
cert_config.get("cert_configs", {})
|
|
94
|
+
.get("workload", {})
|
|
95
|
+
.get("cert_path")
|
|
96
|
+
)
|
|
97
|
+
if _is_certificate_file_ready(cert_path):
|
|
98
|
+
return cert_path
|
|
99
|
+
except (IOError, ValueError, KeyError):
|
|
100
|
+
if not has_logged_warning:
|
|
101
|
+
_LOGGER.warning(
|
|
102
|
+
"Certificate config file not found at %s (from %s environment "
|
|
103
|
+
"variable). Retrying for up to %s seconds.",
|
|
104
|
+
cert_config_path,
|
|
105
|
+
environment_vars.GOOGLE_API_CERTIFICATE_CONFIG,
|
|
106
|
+
_TOTAL_TIMEOUT,
|
|
107
|
+
)
|
|
108
|
+
has_logged_warning = True
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
# As a fallback, check the well-known certificate path.
|
|
112
|
+
if _is_certificate_file_ready(_WELL_KNOWN_CERT_PATH):
|
|
113
|
+
return _WELL_KNOWN_CERT_PATH
|
|
114
|
+
|
|
115
|
+
# A sleep is required in two cases:
|
|
116
|
+
# 1. The config file is not found (the except block).
|
|
117
|
+
# 2. The config file is found, but the certificate is not yet available.
|
|
118
|
+
# In both cases, we need to poll, so we sleep on every iteration
|
|
119
|
+
# that doesn't return a certificate.
|
|
120
|
+
time.sleep(interval)
|
|
121
|
+
|
|
122
|
+
raise exceptions.RefreshError(
|
|
123
|
+
"Certificate config or certificate file not found after multiple retries. "
|
|
124
|
+
f"Token binding protection is failing. You can turn off this protection by setting "
|
|
125
|
+
f"{environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES} to false "
|
|
126
|
+
"to fall back to unbound tokens."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_and_parse_agent_identity_certificate():
|
|
131
|
+
"""Gets and parses the agent identity certificate if not opted out.
|
|
132
|
+
|
|
133
|
+
Checks if the user has opted out of certificate-bound tokens. If not,
|
|
134
|
+
it gets the certificate path, reads the file, and parses it.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The parsed certificate object if found and not opted out, otherwise None.
|
|
138
|
+
"""
|
|
139
|
+
# If the user has opted out of cert bound tokens, there is no need to
|
|
140
|
+
# look up the certificate.
|
|
141
|
+
is_opted_out = (
|
|
142
|
+
os.environ.get(
|
|
143
|
+
environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES,
|
|
144
|
+
"true",
|
|
145
|
+
).lower()
|
|
146
|
+
== "false"
|
|
147
|
+
)
|
|
148
|
+
if is_opted_out:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
cert_path = get_agent_identity_certificate_path()
|
|
152
|
+
if not cert_path:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
with open(cert_path, "rb") as cert_file:
|
|
156
|
+
cert_bytes = cert_file.read()
|
|
157
|
+
|
|
158
|
+
return parse_certificate(cert_bytes)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def parse_certificate(cert_bytes):
|
|
162
|
+
"""Parses a PEM-encoded certificate.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
cert_bytes (bytes): The PEM-encoded certificate bytes.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
cryptography.x509.Certificate: The parsed certificate object.
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
from cryptography import x509
|
|
172
|
+
|
|
173
|
+
return x509.load_pem_x509_certificate(cert_bytes)
|
|
174
|
+
except ImportError as e:
|
|
175
|
+
raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_agent_identity_certificate(cert):
|
|
179
|
+
"""Checks if a certificate is an Agent Identity certificate.
|
|
180
|
+
|
|
181
|
+
This is determined by checking the Subject Alternative Name (SAN) for a
|
|
182
|
+
SPIFFE ID with a trust domain matching Agent Identity patterns.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
cert (cryptography.x509.Certificate): The parsed certificate object.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
bool: True if the certificate is an Agent Identity certificate,
|
|
189
|
+
False otherwise.
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
from cryptography import x509
|
|
193
|
+
from cryptography.x509.oid import ExtensionOID
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
ext = cert.extensions.get_extension_for_oid(
|
|
197
|
+
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
|
198
|
+
)
|
|
199
|
+
except x509.ExtensionNotFound:
|
|
200
|
+
return False
|
|
201
|
+
uris = ext.value.get_values_for_type(x509.UniformResourceIdentifier)
|
|
202
|
+
|
|
203
|
+
for uri in uris:
|
|
204
|
+
parsed_uri = urlparse(uri)
|
|
205
|
+
if parsed_uri.scheme == "spiffe":
|
|
206
|
+
trust_domain = parsed_uri.netloc
|
|
207
|
+
for pattern in _AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS:
|
|
208
|
+
if re.match(pattern, trust_domain):
|
|
209
|
+
return True
|
|
210
|
+
return False
|
|
211
|
+
except ImportError as e:
|
|
212
|
+
raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def calculate_certificate_fingerprint(cert):
|
|
216
|
+
"""Calculates the URL-encoded, unpadded, base64-encoded SHA256 hash of a
|
|
217
|
+
DER-encoded certificate.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
cert (cryptography.x509.Certificate): The parsed certificate object.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
str: The URL-encoded, unpadded, base64-encoded SHA256 fingerprint.
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
from cryptography.hazmat.primitives import serialization
|
|
227
|
+
|
|
228
|
+
der_cert = cert.public_bytes(serialization.Encoding.DER)
|
|
229
|
+
fingerprint = hashlib.sha256(der_cert).digest()
|
|
230
|
+
# The certificate fingerprint is generated in two steps to align with GFE's
|
|
231
|
+
# expectations and ensure proper URL transmission:
|
|
232
|
+
# 1. Standard base64 encoding is applied, and padding ('=') is removed.
|
|
233
|
+
# 2. The resulting string is then URL-encoded to handle special characters
|
|
234
|
+
# ('+', '/') that would otherwise be misinterpreted in URL parameters.
|
|
235
|
+
base64_fingerprint = base64.b64encode(fingerprint).decode("utf-8")
|
|
236
|
+
unpadded_base64_fingerprint = base64_fingerprint.rstrip("=")
|
|
237
|
+
return quote(unpadded_base64_fingerprint)
|
|
238
|
+
except ImportError as e:
|
|
239
|
+
raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def should_request_bound_token(cert):
|
|
243
|
+
"""Determines if a bound token should be requested.
|
|
244
|
+
|
|
245
|
+
This is based on the GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES
|
|
246
|
+
environment variable and whether the certificate is an agent identity cert.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
cert (cryptography.x509.Certificate): The parsed certificate object.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
bool: True if a bound token should be requested, False otherwise.
|
|
253
|
+
"""
|
|
254
|
+
is_agent_cert = _is_agent_identity_certificate(cert)
|
|
255
|
+
is_opted_in = (
|
|
256
|
+
os.environ.get(
|
|
257
|
+
environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES,
|
|
258
|
+
"true",
|
|
259
|
+
).lower()
|
|
260
|
+
== "true"
|
|
261
|
+
)
|
|
262
|
+
return is_agent_cert and is_opted_in
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_cached_cert_fingerprint(cached_cert):
|
|
266
|
+
"""Returns the fingerprint of the cached certificate."""
|
|
267
|
+
if cached_cert:
|
|
268
|
+
cert_obj = parse_certificate(cached_cert)
|
|
269
|
+
cached_cert_fingerprint = calculate_certificate_fingerprint(cert_obj)
|
|
270
|
+
else:
|
|
271
|
+
raise ValueError("mTLS connection is not configured.")
|
|
272
|
+
return cached_cert_fingerprint
|
google/auth/_cache.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
# https://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
|
+
from collections import OrderedDict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LRUCache(dict):
|
|
19
|
+
def __init__(self, maxsize):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self._order = OrderedDict()
|
|
22
|
+
self.maxsize = maxsize
|
|
23
|
+
|
|
24
|
+
def clear(self):
|
|
25
|
+
super().clear()
|
|
26
|
+
self._order.clear()
|
|
27
|
+
|
|
28
|
+
def get(self, key, default=None):
|
|
29
|
+
try:
|
|
30
|
+
value = super().__getitem__(key)
|
|
31
|
+
self._update(key)
|
|
32
|
+
return value
|
|
33
|
+
except KeyError:
|
|
34
|
+
return default
|
|
35
|
+
|
|
36
|
+
def __getitem__(self, key):
|
|
37
|
+
value = super().__getitem__(key)
|
|
38
|
+
self._update(key)
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
def __setitem__(self, key, value):
|
|
42
|
+
maxsize = self.maxsize
|
|
43
|
+
if maxsize <= 0:
|
|
44
|
+
return
|
|
45
|
+
if key not in self:
|
|
46
|
+
while len(self) >= maxsize:
|
|
47
|
+
self.popitem()
|
|
48
|
+
super().__setitem__(key, value)
|
|
49
|
+
self._update(key)
|
|
50
|
+
|
|
51
|
+
def __delitem__(self, key):
|
|
52
|
+
super().__delitem__(key)
|
|
53
|
+
del self._order[key]
|
|
54
|
+
|
|
55
|
+
def popitem(self):
|
|
56
|
+
"""Remove and return the least recently used key-value pair."""
|
|
57
|
+
key, _ = self._order.popitem(last=False)
|
|
58
|
+
return key, super().pop(key)
|
|
59
|
+
|
|
60
|
+
def _update(self, key):
|
|
61
|
+
try:
|
|
62
|
+
self._order.move_to_end(key)
|
|
63
|
+
except KeyError:
|
|
64
|
+
self._order[key] = None
|
|
@@ -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
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Shared constants."""
|
|
2
|
+
|
|
3
|
+
_SERVICE_ACCOUNT_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/projects/-/serviceAccounts/{service_account_email}/allowedLocations"
|
|
4
|
+
_WORKFORCE_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/locations/global/workforcePools/{pool_id}/allowedLocations"
|
|
5
|
+
_WORKLOAD_IDENTITY_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/projects/{project_number}/locations/global/workloadIdentityPools/{pool_id}/allowedLocations"
|