diff options
author | AlexSm <alex@ydb.tech> | 2023-12-27 23:31:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-27 23:31:58 +0100 |
commit | d67bfb4b4b7549081543e87a31bc6cb5c46ac973 (patch) | |
tree | 8674f2f1570877cb653e7ddcff37ba00288de15a /contrib/python/google-auth/py3/google | |
parent | 1f6bef05ed441c3aa2d565ac792b26cded704ac7 (diff) | |
download | ydb-d67bfb4b4b7549081543e87a31bc6cb5c46ac973.tar.gz |
Import libs 4 (#758)
Diffstat (limited to 'contrib/python/google-auth/py3/google')
17 files changed, 353 insertions, 60 deletions
diff --git a/contrib/python/google-auth/py3/google/auth/__init__.py b/contrib/python/google-auth/py3/google/auth/__init__.py index 2875772b37..765bbd7058 100644 --- a/contrib/python/google-auth/py3/google/auth/__init__.py +++ b/contrib/python/google-auth/py3/google/auth/__init__.py @@ -15,6 +15,8 @@ """Google Auth Library for Python.""" import logging +import sys +import warnings from google.auth import version as google_auth_version from google.auth._default import ( @@ -29,5 +31,23 @@ __version__ = google_auth_version.__version__ __all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"] + +class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER + """ + Deprecation warning raised when Python 3.7 runtime is detected. + Python 3.7 support will be dropped after January 1, 2024. + """ + + pass + + +# Checks if the current runtime is Python 3.7. +if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER + message = ( + "After January 1, 2024, new releases of this library will drop support " + "for Python 3.7." + ) + warnings.warn(message, Python37DeprecationWarning) + # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/contrib/python/google-auth/py3/google/auth/_helpers.py b/contrib/python/google-auth/py3/google/auth/_helpers.py index ad2c095f28..a6c07f7d82 100644 --- a/contrib/python/google-auth/py3/google/auth/_helpers.py +++ b/contrib/python/google-auth/py3/google/auth/_helpers.py @@ -17,16 +17,15 @@ import base64 import calendar import datetime +from email.message import Message import sys import urllib from google.auth import exceptions -# Token server doesn't provide a new a token when doing refresh unless the -# token is expiring within 30 seconds, so refresh threshold should not be -# more than 30 seconds. Otherwise auth lib will send tons of refresh requests -# until 30 seconds before the expiration, and cause a spike of CPU usage. -REFRESH_THRESHOLD = datetime.timedelta(seconds=20) +# The smallest MDS cache used by this library stores tokens until 4 minutes from +# expiry. +REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45) def copy_docstring(source_class): @@ -63,13 +62,42 @@ def copy_docstring(source_class): return decorator +def parse_content_type(header_value): + """Parse a 'content-type' header value to get just the plain media-type (without parameters). + + This is done using the class Message from email.message as suggested in PEP 594 + (because the cgi is now deprecated and will be removed in python 3.13, + see https://peps.python.org/pep-0594/#cgi). + + Args: + header_value (str): The value of a 'content-type' header as a string. + + Returns: + str: A string with just the lowercase media-type from the parsed 'content-type' header. + If the provided content-type is not parsable, returns 'text/plain', + the default value for textual files. + """ + m = Message() + m["content-type"] = header_value + return ( + m.get_content_type() + ) # Despite the name, actually returns just the media-type + + def utcnow(): """Returns the current UTC datetime. Returns: datetime: The current time in UTC. """ - return datetime.datetime.utcnow() + # We used datetime.utcnow() before, since it's deprecated from python 3.12, + # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native + # (no timezone info), but "now()" is offset-aware (with timezone info). + # This will cause datetime comparison problem. For backward compatibility, + # we need to remove the timezone info. + now = datetime.datetime.now(datetime.timezone.utc) + now = now.replace(tzinfo=None) + return now def datetime_to_secs(value): diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/__init__.py b/contrib/python/google-auth/py3/google/auth/compute_engine/__init__.py index 5c84234e93..7e1206fc1b 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/__init__.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/__init__.py @@ -14,8 +14,9 @@ """Google Compute Engine authentication.""" +from google.auth.compute_engine._metadata import detect_gce_residency_linux from google.auth.compute_engine.credentials import Credentials from google.auth.compute_engine.credentials import IDTokenCredentials -__all__ = ["Credentials", "IDTokenCredentials"] +__all__ = ["Credentials", "IDTokenCredentials", "detect_gce_residency_linux"] diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py index 04abe178f5..1c884c3c43 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py @@ -156,6 +156,7 @@ def get( recursive=False, retry_count=5, headers=None, + return_none_for_not_found_error=False, ): """Fetch a resource from the metadata server. @@ -173,6 +174,8 @@ def get( retry_count (int): How many times to attempt connecting to metadata server using above timeout. headers (Optional[Mapping[str, str]]): Headers for the request. + return_none_for_not_found_error (Optional[bool]): If True, returns None + for 404 error instead of throwing an exception. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -216,9 +219,21 @@ def get( "metadata service. Compute Engine Metadata server unavailable".format(url) ) + content = _helpers.from_bytes(response.data) + + if response.status == http_client.NOT_FOUND and return_none_for_not_found_error: + _LOGGER.info( + "Compute Engine Metadata server call to %s returned 404, reason: %s", + path, + content, + ) + return None + if response.status == http_client.OK: - content = _helpers.from_bytes(response.data) - if response.headers["content-type"] == "application/json": + if ( + _helpers.parse_content_type(response.headers["content-type"]) + == "application/json" + ): try: return json.loads(content) except ValueError as caught_exc: @@ -229,14 +244,14 @@ def get( raise new_exc from caught_exc else: return content - else: - raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine " - "metadata service. Status: {} Response:\n{}".format( - url, response.status, response.data - ), - response, - ) + + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine " + "metadata service. Status: {} Response:\n{}".format( + url, response.status, response.data + ), + response, + ) def get_project_id(request): @@ -256,6 +271,29 @@ def get_project_id(request): return get(request, "project/project-id") +def get_universe_domain(request): + """Get the universe domain value from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The universe domain value. If the universe domain endpoint is not + not found, return the default value, which is googleapis.com + + Raises: + google.auth.exceptions.TransportError: if an error other than + 404 occurs while retrieving metadata. + """ + universe_domain = get( + request, "universe/universe_domain", return_none_for_not_found_error=True + ) + if not universe_domain: + return "googleapis.com" + return universe_domain + + def get_service_account_info(request, service_account="default"): """Get information about a service account from the metadata server. diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/credentials.py b/contrib/python/google-auth/py3/google/auth/compute_engine/credentials.py index 7ae673880f..a035c7697a 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/credentials.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/credentials.py @@ -28,6 +28,7 @@ from google.auth import iam from google.auth import jwt from google.auth import metrics from google.auth.compute_engine import _metadata +from google.auth.transport import requests as google_auth_requests from google.oauth2 import _client @@ -73,6 +74,8 @@ class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): self._quota_project_id = quota_project_id self._scopes = scopes self._default_scopes = default_scopes + self._universe_domain_cached = False + self._universe_domain_request = google_auth_requests.Request() def _retrieve_info(self, request): """Retrieve information about the service account. @@ -131,6 +134,16 @@ class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): def requires_scopes(self): return not self._scopes + @property + def universe_domain(self): + if self._universe_domain_cached: + return self._universe_domain + self._universe_domain = _metadata.get_universe_domain( + self._universe_domain_request + ) + self._universe_domain_cached = True + return self._universe_domain + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( diff --git a/contrib/python/google-auth/py3/google/auth/credentials.py b/contrib/python/google-auth/py3/google/auth/credentials.py index 80a2a5c0b4..800781c408 100644 --- a/contrib/python/google-auth/py3/google/auth/credentials.py +++ b/contrib/python/google-auth/py3/google/auth/credentials.py @@ -52,8 +52,9 @@ class Credentials(metaclass=abc.ABCMeta): self._quota_project_id = None """Optional[str]: Project to use for quota and billing purposes.""" self._trust_boundary = None - """Optional[str]: Encoded string representation of credentials trust - boundary.""" + """Optional[dict]: Cache of a trust boundary response which has a list + of allowed regions and an encoded string representation of credentials + trust boundary.""" self._universe_domain = "googleapis.com" """Optional[str]: The universe domain value, default is googleapis.com """ @@ -135,8 +136,21 @@ class Credentials(metaclass=abc.ABCMeta): headers["authorization"] = "Bearer {}".format( _helpers.from_bytes(token or self.token) ) + """Trust boundary value will be a cached value from global lookup. + + The response of trust boundary will be a list of regions and a hex + encoded representation. + + An example of global lookup response: + { + "locations": [ + "us-central1", "us-east1", "europe-west1", "asia-east1" + ] + "encoded_locations": "0xA30" + } + """ if self._trust_boundary is not None: - headers["x-identity-trust-boundary"] = self._trust_boundary + headers["x-allowed-locations"] = self._trust_boundary["encoded_locations"] if self.quota_project_id: headers["x-goog-user-project"] = self.quota_project_id diff --git a/contrib/python/google-auth/py3/google/auth/crypt/_cryptography_rsa.py b/contrib/python/google-auth/py3/google/auth/crypt/_cryptography_rsa.py index 4f2d611666..1a3e9ff52c 100644 --- a/contrib/python/google-auth/py3/google/auth/crypt/_cryptography_rsa.py +++ b/contrib/python/google-auth/py3/google/auth/crypt/_cryptography_rsa.py @@ -134,3 +134,18 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): key, password=None, backend=_BACKEND ) return cls(private_key, key_id=key_id) + + def __getstate__(self): + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/contrib/python/google-auth/py3/google/auth/crypt/es256.py b/contrib/python/google-auth/py3/google/auth/crypt/es256.py index 7920cc7ffb..820e4becce 100644 --- a/contrib/python/google-auth/py3/google/auth/crypt/es256.py +++ b/contrib/python/google-auth/py3/google/auth/crypt/es256.py @@ -158,3 +158,18 @@ class ES256Signer(base.Signer, base.FromServiceAccountMixin): key, password=None, backend=_BACKEND ) return cls(private_key, key_id=key_id) + + def __getstate__(self): + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/contrib/python/google-auth/py3/google/auth/external_account.py b/contrib/python/google-auth/py3/google/auth/external_account.py index c45e6f2133..e7fed8695a 100644 --- a/contrib/python/google-auth/py3/google/auth/external_account.py +++ b/contrib/python/google-auth/py3/google/auth/external_account.py @@ -132,7 +132,10 @@ class Credentials( self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN - self._trust_boundary = "0" # expose a placeholder trust boundary value. + self._trust_boundary = { + "locations": [], + "encoded_locations": "0x0", + } # expose a placeholder trust boundary value. if self._client_id: self._client_auth = utils.ClientAuthentication( @@ -412,6 +415,22 @@ class Credentials( new_cred._metrics_options = self._metrics_options return new_cred + def with_universe_domain(self, universe_domain): + """Create a copy of these credentials with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.auth.external_account.Credentials: A new credentials + instance. + """ + kwargs = self._constructor_args() + kwargs.update(universe_domain=universe_domain) + new_cred = self.__class__(**kwargs) + new_cred._metrics_options = self._metrics_options + return new_cred + def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. diff --git a/contrib/python/google-auth/py3/google/auth/transport/_custom_tls_signer.py b/contrib/python/google-auth/py3/google/auth/transport/_custom_tls_signer.py index 07f14df02d..57a563d03b 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/_custom_tls_signer.py +++ b/contrib/python/google-auth/py3/google/auth/transport/_custom_tls_signer.py @@ -107,6 +107,22 @@ def load_signer_lib(signer_lib_path): return lib +def load_provider_lib(provider_lib_path): + _LOGGER.debug("loading provider library from %s", provider_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(provider_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(provider_lib_path) + ) + + lib.ECP_attach_to_ctx.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + lib.ECP_attach_to_ctx.restype = ctypes.c_int + + return lib + + # Computes SHA256 hash. def _compute_sha256_digest(to_be_signed, to_be_signed_len): from cryptography.hazmat.primitives import hashes @@ -199,21 +215,31 @@ class CustomTlsSigner(object): self._enterprise_cert_file_path = enterprise_cert_file_path self._cert = None self._sign_callback = None + self._provider_lib = None def load_libraries(self): - try: - with open(self._enterprise_cert_file_path, "r") as f: - enterprise_cert_json = json.load(f) - libs = enterprise_cert_json["libs"] - signer_library = libs["ecp_client"] - offload_library = libs["tls_offload"] - except (KeyError, ValueError) as caught_exc: - new_exc = exceptions.MutualTLSChannelError( - "enterprise cert file is invalid", caught_exc - ) - raise new_exc from caught_exc - self._offload_lib = load_offload_lib(offload_library) - self._signer_lib = load_signer_lib(signer_library) + with open(self._enterprise_cert_file_path, "r") as f: + enterprise_cert_json = json.load(f) + libs = enterprise_cert_json.get("libs", {}) + + signer_library = libs.get("ecp_client", None) + offload_library = libs.get("tls_offload", None) + provider_library = libs.get("ecp_provider", None) + + # Using newer provider implementation. This is mutually exclusive to the + # offload implementation. + if provider_library: + self._provider_lib = load_provider_lib(provider_library) + return + + # Using old offload implementation + if offload_library and signer_library: + self._offload_lib = load_offload_lib(offload_library) + self._signer_lib = load_signer_lib(signer_library) + self.set_up_custom_key() + return + + raise exceptions.MutualTLSChannelError("enterprise cert file is invalid") def set_up_custom_key(self): # We need to keep a reference of the cert and sign callback so it won't @@ -224,11 +250,22 @@ class CustomTlsSigner(object): ) def attach_to_ssl_context(self, ctx): - # In the TLS handshake, the signing operation will be done by the - # sign_callback. - if not self._offload_lib.ConfigureSslContext( - self._sign_callback, - ctypes.c_char_p(self._cert), - _cast_ssl_ctx_to_void_p(ctx._ctx._context), - ): - raise exceptions.MutualTLSChannelError("failed to configure SSL context") + if self._provider_lib: + if not self._provider_lib.ECP_attach_to_ctx( + _cast_ssl_ctx_to_void_p(ctx._ctx._context), + self._enterprise_cert_file_path.encode("ascii"), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Provider SSL context" + ) + elif self._offload_lib and self._signer_lib: + if not self._offload_lib.ConfigureSslContext( + self._sign_callback, + ctypes.c_char_p(self._cert), + _cast_ssl_ctx_to_void_p(ctx._ctx._context), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Offload SSL context" + ) + else: + raise exceptions.MutualTLSChannelError("Invalid ECP configuration.") diff --git a/contrib/python/google-auth/py3/google/auth/transport/requests.py b/contrib/python/google-auth/py3/google/auth/transport/requests.py index b9bcad359f..aa16113226 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/requests.py +++ b/contrib/python/google-auth/py3/google/auth/transport/requests.py @@ -274,7 +274,6 @@ class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) self.signer.load_libraries() - self.signer.set_up_custom_key() poolmanager = create_urllib3_context() poolmanager.load_verify_locations(cafile=certifi.where()) diff --git a/contrib/python/google-auth/py3/google/auth/transport/urllib3.py b/contrib/python/google-auth/py3/google/auth/transport/urllib3.py index 053d6f7b72..63144f5fff 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/urllib3.py +++ b/contrib/python/google-auth/py3/google/auth/transport/urllib3.py @@ -40,11 +40,18 @@ except ImportError as caught_exc: # pragma: NO COVER "urllib3 package to use the urllib3 transport." ) from caught_exc +from packaging import version # type: ignore + from google.auth import environment_vars from google.auth import exceptions from google.auth import transport from google.oauth2 import service_account +if version.parse(urllib3.__version__) >= version.parse("2.0.0"): # pragma: NO COVER + RequestMethods = urllib3._request_methods.RequestMethods # type: ignore +else: # pragma: NO COVER + RequestMethods = urllib3.request.RequestMethods # type: ignore + _LOGGER = logging.getLogger(__name__) @@ -179,7 +186,7 @@ def _make_mutual_tls_http(cert, key): return http -class AuthorizedHttp(urllib3.request.RequestMethods): +class AuthorizedHttp(RequestMethods): # type: ignore """A urllib3 HTTP class with credentials. This class is used to perform requests to API endpoints that require diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py index 491187e6d7..31cc30242a 100644 --- a/contrib/python/google-auth/py3/google/auth/version.py +++ b/contrib/python/google-auth/py3/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.0" +__version__ = "2.25.2" diff --git a/contrib/python/google-auth/py3/google/oauth2/__init__.py b/contrib/python/google-auth/py3/google/oauth2/__init__.py index 4fb71fd1ad..accae96579 100644 --- a/contrib/python/google-auth/py3/google/oauth2/__init__.py +++ b/contrib/python/google-auth/py3/google/oauth2/__init__.py @@ -13,3 +13,24 @@ # limitations under the License. """Google OAuth 2.0 Library for Python.""" + +import sys +import warnings + + +class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER + """ + Deprecation warning raised when Python 3.7 runtime is detected. + Python 3.7 support will be dropped after January 1, 2024. + """ + + pass + + +# Checks if the current runtime is Python 3.7. +if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER + message = ( + "After January 1, 2024, new releases of this library will drop support " + "for Python 3.7." + ) + warnings.warn(message, Python37DeprecationWarning) diff --git a/contrib/python/google-auth/py3/google/oauth2/_credentials_async.py b/contrib/python/google-auth/py3/google/oauth2/_credentials_async.py index e7b9637c82..b5561aae02 100644 --- a/contrib/python/google-auth/py3/google/oauth2/_credentials_async.py +++ b/contrib/python/google-auth/py3/google/oauth2/_credentials_async.py @@ -96,6 +96,12 @@ class Credentials(oauth2_credentials.Credentials): ) ) + @_helpers.copy_docstring(credentials.Credentials) + async def before_request(self, request, method, url, headers): + if not self.valid: + await self.refresh(request) + self.apply(headers) + class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials): """Access token credentials for user account. diff --git a/contrib/python/google-auth/py3/google/oauth2/credentials.py b/contrib/python/google-auth/py3/google/oauth2/credentials.py index 4643fdbea6..a5c93ecc2f 100644 --- a/contrib/python/google-auth/py3/google/oauth2/credentials.py +++ b/contrib/python/google-auth/py3/google/oauth2/credentials.py @@ -49,13 +49,15 @@ _LOGGER = logging.getLogger(__name__) # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): """Credentials using OAuth 2.0 access and refresh tokens. - The credentials are considered immutable. If you want to modify the - quota project, use :meth:`with_quota_project` or :: + The credentials are considered immutable except the tokens and the token + expiry, which are updated after refresh. If you want to modify the quota + project, use :meth:`with_quota_project` or :: credentials = credentials.with_quota_project('myproject-123') @@ -84,6 +86,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr enable_reauth_refresh=False, granted_scopes=None, trust_boundary=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """ Args: @@ -125,6 +128,9 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user. This could be different from the requested scopes and it could be empty if granted and requested scopes were same. + trust_boundary (str): String representation of trust boundary meta. + universe_domain (Optional[str]): The universe domain. The default + universe domain is googleapis.com. """ super(Credentials, self).__init__() self.token = token @@ -142,6 +148,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr self.refresh_handler = refresh_handler self._enable_reauth_refresh = enable_reauth_refresh self._trust_boundary = trust_boundary + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -173,7 +180,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr self._rapt_token = d.get("_rapt_token") self._enable_reauth_refresh = d.get("_enable_reauth_refresh") self._trust_boundary = d.get("_trust_boundary") - self._universe_domain = d.get("_universe_domain") + self._universe_domain = d.get("_universe_domain") or _DEFAULT_UNIVERSE_DOMAIN # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None @@ -272,6 +279,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, ) @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) @@ -291,6 +299,34 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, + ) + + def with_universe_domain(self, universe_domain): + """Create a copy of the credential with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + + return self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=self._token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, + quota_project_id=self.quota_project_id, + rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, + universe_domain=universe_domain, ) def _metric_header_for_usage(self): @@ -298,6 +334,17 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.RefreshError( + "User credential refresh is only supported in the default " + "googleapis.com universe domain, but the current universe " + "domain is {}. If you created the credential with an access " + "token, it's likely that the provided token is expired now, " + "please update your code with a valid token.".format( + self._universe_domain + ) + ) + scopes = self._scopes if self._scopes is not None else self._default_scopes # Use refresh handler if available and no refresh token is # available. This is useful in general when tokens are obtained by calling @@ -427,6 +474,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr expiry=expiry, rapt_token=info.get("rapt_token"), # may not exist trust_boundary=info.get("trust_boundary"), # may not exist + universe_domain=info.get("universe_domain"), # may not exist ) @classmethod @@ -470,6 +518,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr "client_secret": self.client_secret, "scopes": self.scopes, "rapt_token": self.rapt_token, + "universe_domain": self._universe_domain, } if self.expiry: # flatten expiry timestamp prep["expiry"] = self.expiry.isoformat() + "Z" diff --git a/contrib/python/google-auth/py3/google/oauth2/service_account.py b/contrib/python/google-auth/py3/google/oauth2/service_account.py index e08899f8e5..68db41af40 100644 --- a/contrib/python/google-auth/py3/google/oauth2/service_account.py +++ b/contrib/python/google-auth/py3/google/oauth2/service_account.py @@ -182,10 +182,7 @@ class Credentials( self._quota_project_id = quota_project_id self._token_uri = token_uri self._always_use_jwt_access = always_use_jwt_access - if not universe_domain: - self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN - else: - self._universe_domain = universe_domain + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: self._always_use_jwt_access = True @@ -196,7 +193,7 @@ class Credentials( self._additional_claims = additional_claims else: self._additional_claims = {} - self._trust_boundary = "0" + self._trust_boundary = {"locations": [], "encoded_locations": "0x0"} @classmethod def _from_signer_and_info(cls, signer, info, **kwargs): @@ -328,6 +325,22 @@ class Credentials( cred._always_use_jwt_access = always_use_jwt_access return cred + def with_universe_domain(self, universe_domain): + """Create a copy of these credentials with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + cred = self._make_copy() + cred._universe_domain = universe_domain + if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + cred._always_use_jwt_access = True + return cred + def with_subject(self, subject): """Create a copy of these credentials with the specified subject. @@ -417,13 +430,11 @@ class Credentials( @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if ( - self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN - and not self._jwt_credentials - ): - raise exceptions.RefreshError( - "self._jwt_credentials is missing for non-default universe domain" - ) + if self._always_use_jwt_access and not self._jwt_credentials: + # If self signed jwt should be used but jwt credential is not + # created, try to create one with scopes + self._create_self_signed_jwt(None) + if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and self._subject: raise exceptions.RefreshError( "domain wide delegation is not supported for non-default universe domain" |