diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index f0ce4f41e0ac..0ac572dd0132 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -23,11 +23,13 @@ from urllib.parse import urlparse import warnings - -from google.auth import _helpers, environment_vars -from google.auth import _regional_access_boundary_utils -from google.auth import exceptions -from google.auth import metrics +from google.auth import ( + _helpers, + _regional_access_boundary_utils, + environment_vars, + exceptions, + metrics, +) from google.auth._credentials_base import _BaseCredentials from google.auth._refresh_worker import RefreshThreadManager @@ -532,7 +534,9 @@ def _lookup_regional_access_boundary( Returns: Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed. """ - from google.oauth2 import _client + import importlib + + _client = importlib.import_module("google.oauth2._client") url = self._build_regional_access_boundary_lookup_url(request=request) if not url: @@ -547,7 +551,8 @@ def _lookup_regional_access_boundary( @abc.abstractmethod def _build_regional_access_boundary_lookup_url( - self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + self, + request: "Optional[google.auth.transport.Request]" = None, # noqa: F821 ): """ Builds and returns the URL for the Regional Access Boundary lookup API. diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index 639be9069549..0aef0a3371fe 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -19,12 +19,10 @@ for implmentations using different third party libraries """ -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey from google.auth import _helpers -from google.auth.crypt import _cryptography_rsa -from google.auth.crypt import base +from google.auth.crypt import _cryptography_rsa, base RSA_KEY_MODULE_PREFIX = "rsa.key" @@ -92,10 +90,9 @@ class RSASigner(base.Signer, base.FromServiceAccountMixin): """ def __init__(self, private_key, key_id=None): - module_str = private_key.__class__.__module__ - if isinstance(private_key, RSAPrivateKey): + if private_key is None or isinstance(private_key, RSAPrivateKey): impl_lib = _cryptography_rsa - elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + elif private_key.__class__.__module__.startswith(RSA_KEY_MODULE_PREFIX): from google.auth.crypt import _python_rsa impl_lib = _python_rsa @@ -106,6 +103,12 @@ def __init__(self, private_key, key_id=None): @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): + # Support subclasses or mock signer implementations (e.g. in test suites) + # that define `_key_id` directly on the outer instance instead of fully + # initializing an underlying `_impl` backend. + key_id = getattr(self, "_key_id", None) + if key_id is not None: + return key_id return self._impl.key_id @_helpers.copy_docstring(base.Signer) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 2f14d809319e..27bffc8e00bb 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -33,15 +33,16 @@ import logging from typing import Optional, TYPE_CHECKING - -from google.auth import _exponential_backoff -from google.auth import _helpers -from google.auth import _regional_access_boundary_utils -from google.auth import credentials -from google.auth import exceptions -from google.auth import iam -from google.auth import jwt -from google.auth import metrics +from google.auth import ( + _exponential_backoff, + _helpers, + _regional_access_boundary_utils, + credentials, + exceptions, + iam, + jwt, + metrics, +) from google.oauth2 import _client if TYPE_CHECKING: # pragma: NO COVER @@ -262,7 +263,12 @@ def __init__( ): self._source_credentials._create_self_signed_jwt(None) - self._universe_domain = source_credentials.universe_domain + universe_domain = getattr(source_credentials, "universe_domain", None) + self._universe_domain = ( + universe_domain + if isinstance(universe_domain, str) + else credentials.DEFAULT_UNIVERSE_DOMAIN + ) self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates @@ -352,7 +358,8 @@ def _perform_refresh_token(self, request): ) def _build_regional_access_boundary_lookup_url( - self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + self, + request: "Optional[google.auth.transport.Request]" = None, # noqa: F821 ): """Builds and returns the URL for the Regional Access Boundary lookup API. @@ -564,7 +571,7 @@ def __init__( if not isinstance(target_credentials, Credentials): raise exceptions.GoogleAuthError( - "Provided Credential must be " "impersonated_credentials" + "Provided Credential must be impersonated_credentials" ) self._target_credentials = target_credentials self._target_audience = target_audience diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index d6450291c7f2..60c136d19434 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -20,9 +20,7 @@ import re import subprocess -from google.auth import _agent_identity_utils -from google.auth import environment_vars -from google.auth import exceptions +from google.auth import _agent_identity_utils, environment_vars, exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" @@ -453,7 +451,7 @@ def check_use_client_cert(): If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding bool value will be returned. If the value is set to an unexpected string, it - will default to False. + will raise a ValueError. If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying it contains a "workload" section. If so, the function will return True, @@ -470,6 +468,11 @@ def check_use_client_cert(): # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. if use_client_cert: + if use_client_cert.lower() not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) return use_client_cert.lower() == "true" else: # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 078df67470d2..030e92d42ef3 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -657,7 +657,13 @@ def test_override_does_not_exist(self): returned_path = _mtls_helper._get_cert_config_path(config_path) assert returned_path is None - @mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}) + @mock.patch.dict( + os.environ, + { + "GOOGLE_API_CERTIFICATE_CONFIG": "", + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", + }, + ) @mock.patch("os.path.exists", autospec=True) def test_default(self, mock_path_exists): mock_path_exists.return_value = True @@ -788,13 +794,17 @@ def test_env_var_explicit_false(self): @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "garbage"}) def test_env_var_explicit_garbage(self): - assert _mtls_helper.check_use_client_cert() is False + import pytest + + with pytest.raises(ValueError, match="must be either `true` or `false`"): + _mtls_helper.check_use_client_cert() @mock.patch("builtins.open", autospec=True) @mock.patch.dict( os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -810,6 +820,7 @@ def test_config_file_success(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -822,6 +833,7 @@ def test_config_file_missing_keys(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/to/config", }, ) @@ -834,6 +846,7 @@ def test_config_file_bad_json(self, mock_file): os.environ, { "GOOGLE_API_USE_CLIENT_CERTIFICATE": "", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "", "GOOGLE_API_CERTIFICATE_CONFIG": "/path/does/not/exist", }, )