diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 9fe56aa9de1d..c1932abbe426 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -120,7 +120,7 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo for template_name in client_templates: # Quick check: Skip "private" templates. filename = template_name.split("/")[-1] - if filename.startswith("_") and filename != "__init__.py.j2": + if filename.startswith("_") and filename not in ("__init__.py.j2", "_compat.py.j2"): continue # Append to the output files dictionary. diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 new file mode 100644 index 000000000000..8eda830a5304 --- /dev/null +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -0,0 +1,61 @@ +# {% include '_license.j2' %} + +import os +from typing import Optional, Callable, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError + +try: + from google.api_core.gapic_v1.client_utils import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + client_cert_source = None + if use_cert_flag: + if provided_cert_source: + client_cert_source = provided_cert_source + elif ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` " + "must be `never`, `auto` or `always`" + ) + return use_client_cert, use_mtls_endpoint, universe_domain_env diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index e2e3edb24967..a09977c06355 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -30,6 +30,7 @@ from google.api_core import exceptions as core_exceptions from google.api_core import extended_operation {% endif %} from google.api_core import gapic_v1 +from {{package_path}} import _compat as client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -189,30 +190,9 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -352,44 +332,12 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = {{ service.client_name }}._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env - - @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: @@ -597,7 +545,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): universe_domain_opt = getattr(self._client_options, 'universe_domain', None) - self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = {{ service.client_name }}._read_environment_variables() + self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = client_utils.read_environment_variables() self._client_cert_source = {{ service.client_name }}._get_client_cert_source(self._client_options.client_cert_source, self._use_client_cert) self._universe_domain = {{ service.client_name }}._get_universe_domain(universe_domain_opt, self._universe_domain_env) self._api_endpoint: str = "" # updated below, depending on `transport` diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index c68b390b7c84..8ea8ce177135 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -101,6 +101,15 @@ from google.iam.v1 import policy_pb2 # type: ignore {{ shared_macros.add_google_api_core_version_header_import(service.version) }} +@pytest.fixture(autouse=True) +def mock_should_use_client_cert(): + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): + yield + else: + yield + + CRED_INFO_JSON = { "credential_source": "/path/to/file", "credential_type": "service account credentials", @@ -172,146 +181,6 @@ def test__get_default_mtls_endpoint(): assert {{ service.client_name }}._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert {{ service.client_name }}._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert {{ service.client_name }}._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - {{ service.client_name }}._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert {{ service.client_name }}._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert {{ service.client_name }}._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert {{ service.client_name }}._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - {{ service.client_name }}._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert {{ service.client_name }}._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert {{ service.client_name }}._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert {{ service.client_name }}._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert {{ service.client_name }}._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert {{ service.client_name }}._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - {{ service.client_name }}._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert {{ service.client_name }}._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert {{ service.client_name }}._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert {{ service.client_name }}._get_client_cert_source(None, False) is None - assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, False) is None - assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert {{ service.client_name }}._get_client_cert_source(None, True) is mock_default_cert_source - assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source @mock.patch.object({{ service.client_name }}, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template({{ service.client_name }})) {% if 'grpc' in opts.transport %} @@ -823,6 +692,105 @@ def test_{{ service.client_name|snake_case }}_mtls_env_auto(client_class, transp ) +def test_use_client_cert_effective(): + # Test case 1: Test when `should_use_client_cert` returns True. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): + assert {{ service.client_name }}._use_client_cert_effective() is True + + # Test case 2: Test when `should_use_client_cert` returns False. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should NOT be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 3: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert {{ service.client_name }}._use_client_cert_effective() is True + + # Test case 4: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 5: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): + assert {{ service.client_name }}._use_client_cert_effective() is True + + # Test case 6: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 7: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): + assert {{ service.client_name }}._use_client_cert_effective() is True + + # Test case 8: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 9: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. + # In this case, the method should return False, which is the default value. + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, clear=True): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 10: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should raise a ValueError as the environment variable must be either + # "true" or "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): + with pytest.raises(ValueError): + {{ service.client_name }}._use_client_cert_effective() + + # Test case 11: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should return False as the environment variable is set to an invalid value. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): + assert {{ service.client_name }}._use_client_cert_effective() is False + + # Test case 12: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, + # the GOOGLE_API_CONFIG environment variable is unset. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): + with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): + assert {{ service.client_name }}._use_client_cert_effective() is False + + +def test__get_client_cert_source(): + mock_provided_cert_source = mock.Mock() + mock_default_cert_source = mock.Mock() + + assert {{ service.client_name }}._get_client_cert_source(None, False) is None + assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, False) is None + assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source + + with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): + with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): + assert {{ service.client_name }}._get_client_cert_source(None, True) is mock_default_cert_source + assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source + + @pytest.mark.parametrize("client_class", [ {% if 'grpc' in opts.transport %} {{ service.client_name }}, {{ service.async_client_name }} diff --git a/packages/gapic-generator/noxfile.py b/packages/gapic-generator/noxfile.py index 4c9788e80bf1..52209f41ff38 100644 --- a/packages/gapic-generator/noxfile.py +++ b/packages/gapic-generator/noxfile.py @@ -474,6 +474,24 @@ def showcase_mtls( ) +# TODO(https://github.com/googleapis/google-cloud-python/issues/17752): +# Remove showcase_pqc once grpcio >= 1.83.0 is enforced by default. +@nox.session(python=NEWEST_PYTHON) +def showcase_pqc( + session, + templates="DEFAULT", + other_opts: typing.Iterable[str] = (), + env: typing.Optional[typing.Dict[str, str]] = {}, +): + """Run the Showcase PQC verification test suite against grpcio 1.83+ over standard TLS.""" + with showcase_library(session, templates=templates, other_opts=other_opts): + session.install("pytest", "pytest-asyncio") + # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): + # Update the version below to `1.83.0` once released, and remove `--pre`. + session.install("--pre", "--upgrade", "grpcio>=1.83.0rc0", "grpcio-status>=1.83.0rc0") + session.run("py.test", "--quiet", "--tls", *(session.posargs or ["tests/system/test_pqc.py"]), env=env) + + def run_showcase_unit_tests(session, fail_under=100, rest_async_io_enabled=False): session.install( "coverage", diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py index 590b6fa1c615..948f2c2a02af 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -144,30 +145,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -410,44 +390,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = AssetServiceClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py index ea110a38acc3..fcb618aabd56 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py @@ -137,147 +137,6 @@ def test__get_default_mtls_endpoint(): assert AssetServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert AssetServiceClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert AssetServiceClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert AssetServiceClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert AssetServiceClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - AssetServiceClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert AssetServiceClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert AssetServiceClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert AssetServiceClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert AssetServiceClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - AssetServiceClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert AssetServiceClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert AssetServiceClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert AssetServiceClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert AssetServiceClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert AssetServiceClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - AssetServiceClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert AssetServiceClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert AssetServiceClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert AssetServiceClient._get_client_cert_source(None, False) is None - assert AssetServiceClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert AssetServiceClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert AssetServiceClient._get_client_cert_source(None, True) is mock_default_cert_source - assert AssetServiceClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(AssetServiceClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(AssetServiceClient)) @mock.patch.object(AssetServiceAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(AssetServiceAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py index 4ad970da32e9..b425f00ee56b 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -147,30 +148,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -347,44 +327,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = IAMCredentialsClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py index 14a6074a40f9..c98bde04745f 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py @@ -127,147 +127,6 @@ def test__get_default_mtls_endpoint(): assert IAMCredentialsClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert IAMCredentialsClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert IAMCredentialsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert IAMCredentialsClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert IAMCredentialsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - IAMCredentialsClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert IAMCredentialsClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert IAMCredentialsClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert IAMCredentialsClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert IAMCredentialsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - IAMCredentialsClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert IAMCredentialsClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert IAMCredentialsClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert IAMCredentialsClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert IAMCredentialsClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert IAMCredentialsClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - IAMCredentialsClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert IAMCredentialsClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert IAMCredentialsClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert IAMCredentialsClient._get_client_cert_source(None, False) is None - assert IAMCredentialsClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert IAMCredentialsClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert IAMCredentialsClient._get_client_cert_source(None, True) is mock_default_cert_source - assert IAMCredentialsClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(IAMCredentialsClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(IAMCredentialsClient)) @mock.patch.object(IAMCredentialsAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(IAMCredentialsAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py index 7255d3c91709..ca5663a06a9a 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -165,30 +166,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -530,44 +510,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = EventarcClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py index 533e401eb1e7..2e847d0f0f93 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py @@ -158,147 +158,6 @@ def test__get_default_mtls_endpoint(): assert EventarcClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert EventarcClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert EventarcClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert EventarcClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert EventarcClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - EventarcClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert EventarcClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert EventarcClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert EventarcClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert EventarcClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - EventarcClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert EventarcClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert EventarcClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert EventarcClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert EventarcClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert EventarcClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - EventarcClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert EventarcClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert EventarcClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert EventarcClient._get_client_cert_source(None, False) is None - assert EventarcClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert EventarcClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert EventarcClient._get_client_cert_source(None, True) is mock_default_cert_source - assert EventarcClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(EventarcClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(EventarcClient)) @mock.patch.object(EventarcAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(EventarcAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py index 15922e6d865a..30ead4531923 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -140,30 +141,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -406,44 +386,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = ConfigServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py index e89762755eda..d037b2686110 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -137,30 +138,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -337,44 +317,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = LoggingServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py index 90e9355f8c26..d729acbe829d 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -138,30 +139,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -338,44 +318,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = MetricsServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py index eada5b433c55..cc488e9b40e7 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -128,147 +128,6 @@ def test__get_default_mtls_endpoint(): assert ConfigServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert ConfigServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert ConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert ConfigServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert ConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - ConfigServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert ConfigServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert ConfigServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert ConfigServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert ConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - ConfigServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert ConfigServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert ConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert ConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert ConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert ConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - ConfigServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert ConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert ConfigServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert ConfigServiceV2Client._get_client_cert_source(None, False) is None - assert ConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert ConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert ConfigServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert ConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(ConfigServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(ConfigServiceV2Client)) @mock.patch.object(ConfigServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(ConfigServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 65559a5d1073..5143cb970f72 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -129,147 +129,6 @@ def test__get_default_mtls_endpoint(): assert LoggingServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert LoggingServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert LoggingServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - LoggingServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert LoggingServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - LoggingServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - LoggingServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert LoggingServiceV2Client._get_client_cert_source(None, False) is None - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert LoggingServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(LoggingServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(LoggingServiceV2Client)) @mock.patch.object(LoggingServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(LoggingServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 90cdab2be2b2..d241eb97ef0b 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -127,147 +127,6 @@ def test__get_default_mtls_endpoint(): assert MetricsServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert MetricsServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert MetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert MetricsServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert MetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - MetricsServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert MetricsServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert MetricsServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert MetricsServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert MetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - MetricsServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert MetricsServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert MetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert MetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert MetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert MetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - MetricsServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert MetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert MetricsServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert MetricsServiceV2Client._get_client_cert_source(None, False) is None - assert MetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert MetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert MetricsServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert MetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(MetricsServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(MetricsServiceV2Client)) @mock.patch.object(MetricsServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(MetricsServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py index 61204cb87a52..52eb2c6dc38f 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -140,30 +141,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -406,44 +386,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = BaseConfigServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py index e89762755eda..d037b2686110 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -137,30 +138,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -337,44 +317,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = LoggingServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py index fa55137223d1..6b29e2f924db 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -138,30 +139,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -338,44 +318,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = BaseMetricsServiceV2Client._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py index 9eec837e6f58..31c836c0f2bf 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -128,147 +128,6 @@ def test__get_default_mtls_endpoint(): assert BaseConfigServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert BaseConfigServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - BaseConfigServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert BaseConfigServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - BaseConfigServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert BaseConfigServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert BaseConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - BaseConfigServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert BaseConfigServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert BaseConfigServiceV2Client._get_client_cert_source(None, False) is None - assert BaseConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert BaseConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert BaseConfigServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert BaseConfigServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(BaseConfigServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(BaseConfigServiceV2Client)) @mock.patch.object(BaseConfigServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(BaseConfigServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 65559a5d1073..5143cb970f72 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -129,147 +129,6 @@ def test__get_default_mtls_endpoint(): assert LoggingServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert LoggingServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert LoggingServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - LoggingServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert LoggingServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - LoggingServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert LoggingServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert LoggingServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - LoggingServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert LoggingServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert LoggingServiceV2Client._get_client_cert_source(None, False) is None - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert LoggingServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert LoggingServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(LoggingServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(LoggingServiceV2Client)) @mock.patch.object(LoggingServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(LoggingServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 310677b64bc6..f7d07760952e 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -127,147 +127,6 @@ def test__get_default_mtls_endpoint(): assert BaseMetricsServiceV2Client._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert BaseMetricsServiceV2Client._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - BaseMetricsServiceV2Client._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert BaseMetricsServiceV2Client._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - BaseMetricsServiceV2Client._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert BaseMetricsServiceV2Client._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - BaseMetricsServiceV2Client._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert BaseMetricsServiceV2Client._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert BaseMetricsServiceV2Client._get_client_cert_source(None, False) is None - assert BaseMetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, False) is None - assert BaseMetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert BaseMetricsServiceV2Client._get_client_cert_source(None, True) is mock_default_cert_source - assert BaseMetricsServiceV2Client._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(BaseMetricsServiceV2Client, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(BaseMetricsServiceV2Client)) @mock.patch.object(BaseMetricsServiceV2AsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(BaseMetricsServiceV2AsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py index 33ccce478c8e..2a61fc7932fd 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -175,30 +176,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -375,44 +355,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = CloudRedisClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py index 8ca1fb5194a6..3df1ccf1ed24 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -145,147 +145,6 @@ def test__get_default_mtls_endpoint(): assert CloudRedisClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert CloudRedisClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert CloudRedisClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - CloudRedisClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert CloudRedisClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert CloudRedisClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert CloudRedisClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - CloudRedisClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - CloudRedisClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert CloudRedisClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert CloudRedisClient._get_client_cert_source(None, False) is None - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert CloudRedisClient._get_client_cert_source(None, True) is mock_default_cert_source - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(CloudRedisClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(CloudRedisClient)) @mock.patch.object(CloudRedisAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(CloudRedisAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py index 031573ef83d7..33e2d2a75c97 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,6 +27,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -175,30 +176,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -375,44 +355,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = CloudRedisClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py index 3f6b7aa521f3..d162dd85d01f 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -145,147 +145,6 @@ def test__get_default_mtls_endpoint(): assert CloudRedisClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert CloudRedisClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert CloudRedisClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - CloudRedisClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert CloudRedisClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert CloudRedisClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert CloudRedisClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - CloudRedisClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert CloudRedisClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert CloudRedisClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - CloudRedisClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert CloudRedisClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert CloudRedisClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert CloudRedisClient._get_client_cert_source(None, False) is None - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert CloudRedisClient._get_client_cert_source(None, True) is mock_default_cert_source - assert CloudRedisClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(CloudRedisClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(CloudRedisClient)) @mock.patch.object(CloudRedisAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(CloudRedisAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 5f79cf8e016a..c5e96c8bde6a 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -28,6 +28,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core.gapic_v1 import client_utils from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport import mtls # type: ignore @@ -148,30 +149,9 @@ def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: _DEFAULT_UNIVERSE = "googleapis.com" @staticmethod - def _use_client_cert_effective(): - """Returns whether client certificate should be used for mTLS if the - google-auth version supports should_use_client_cert automatic mTLS enablement. - - Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. - - Returns: - bool: whether client certificate should be used for mTLS - Raises: - ValueError: (If using a version of google-auth without should_use_client_cert and - GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) - """ - # check if google-auth version supports should_use_client_cert for automatic mTLS enablement - if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER - return mtls.should_use_client_cert() - else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() - if use_client_cert_str not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" - " either `true` or `false`" - ) - return use_client_cert_str == "true" + def _use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + return client_utils.use_client_cert_effective() @classmethod def from_service_account_info(cls, info: dict, *args, **kwargs): @@ -370,44 +350,17 @@ def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[client_optio return api_endpoint, client_cert_source @staticmethod - def _read_environment_variables(): - """Returns the environment variables used by the client. - - Returns: - Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, - GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. - - Raises: - ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not - any of ["true", "false"]. - google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT - is not any of ["auto", "never", "always"]. - """ - use_client_cert = StorageBatchOperationsClient._use_client_cert_effective() - use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() - universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") - if use_mtls_endpoint not in ("auto", "never", "always"): - raise MutualTLSChannelError("Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`") - return use_client_cert, use_mtls_endpoint, universe_domain_env + def _read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + return client_utils.read_environment_variables() @staticmethod - def _get_client_cert_source(provided_cert_source, use_cert_flag): - """Return the client cert source to be used by the client. - - Args: - provided_cert_source (bytes): The client certificate source provided. - use_cert_flag (bool): A flag indicating whether to use the client certificate. - - Returns: - bytes or None: The client cert source to be used by the client. - """ - client_cert_source = None - if use_cert_flag: - if provided_cert_source: - client_cert_source = provided_cert_source - elif mtls.has_default_client_cert_source(): - client_cert_source = mtls.default_client_cert_source() - return client_cert_source + def _get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag) @staticmethod def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str: diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py index 5c53e97f8d12..1e1f8c4a00f0 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py @@ -137,147 +137,6 @@ def test__get_default_mtls_endpoint(): assert StorageBatchOperationsClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi assert StorageBatchOperationsClient._get_default_mtls_endpoint(custom_endpoint) == custom_endpoint -def test__read_environment_variables(): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert StorageBatchOperationsClient._read_environment_variables() == (True, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} - ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with pytest.raises(ValueError) as excinfo: - StorageBatchOperationsClient._read_environment_variables() - assert ( - str(excinfo.value) - == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - else: - assert StorageBatchOperationsClient._read_environment_variables() == ( - False, - "auto", - None, - ) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "never", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "always", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "auto", None) - - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): - with pytest.raises(MutualTLSChannelError) as excinfo: - StorageBatchOperationsClient._read_environment_variables() - assert str(excinfo.value) == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" - - with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): - assert StorageBatchOperationsClient._read_environment_variables() == (False, "auto", "foo.com") - - -def test_use_client_cert_effective(): - # Test case 1: Test when `should_use_client_cert` returns True. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True): - assert StorageBatchOperationsClient._use_client_cert_effective() is True - - # Test case 2: Test when `should_use_client_cert` returns False. - # We mock the `should_use_client_cert` function to simulate a scenario where - # the google-auth library supports automatic mTLS and determines that a - # client certificate should NOT be used. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 3: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is True - - # Test case 4: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 5: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is True - - # Test case 6: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 7: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is True - - # Test case 8: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 9: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. - # In this case, the method should return False, which is the default value. - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, clear=True): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 10: Test when `should_use_client_cert` is unavailable and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should raise a ValueError as the environment variable must be either - # "true" or "false". - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - with pytest.raises(ValueError): - StorageBatchOperationsClient._use_client_cert_effective() - - # Test case 11: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. - # The method should return False as the environment variable is set to an invalid value. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - - # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, - # the GOOGLE_API_CONFIG environment variable is unset. - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): - with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): - assert StorageBatchOperationsClient._use_client_cert_effective() is False - -def test__get_client_cert_source(): - mock_provided_cert_source = mock.Mock() - mock_default_cert_source = mock.Mock() - - assert StorageBatchOperationsClient._get_client_cert_source(None, False) is None - assert StorageBatchOperationsClient._get_client_cert_source(mock_provided_cert_source, False) is None - assert StorageBatchOperationsClient._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source - - with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True): - with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source): - assert StorageBatchOperationsClient._get_client_cert_source(None, True) is mock_default_cert_source - assert StorageBatchOperationsClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source - @mock.patch.object(StorageBatchOperationsClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(StorageBatchOperationsClient)) @mock.patch.object(StorageBatchOperationsAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(StorageBatchOperationsAsyncClient)) def test__get_api_endpoint(): diff --git a/packages/gapic-generator/tests/system/conftest.py b/packages/gapic-generator/tests/system/conftest.py index 180e48b8d59a..73169dd8a79f 100644 --- a/packages/gapic-generator/tests/system/conftest.py +++ b/packages/gapic-generator/tests/system/conftest.py @@ -18,6 +18,7 @@ import os import pytest import pytest_asyncio +from requests.adapters import HTTPAdapter from typing import Sequence, Tuple @@ -113,15 +114,18 @@ def async_identity(use_mtls, request, event_loop): ) -dir = os.path.dirname(__file__) -with open(os.path.join(dir, "../cert/mtls.crt"), "rb") as fh: +base_dir = os.path.dirname(__file__) +CERT_PATH = os.path.join(base_dir, "../cert/mtls.crt") +KEY_PATH = os.path.join(base_dir, "../cert/mtls.key") +with open(CERT_PATH, "rb") as fh: cert = fh.read() -with open(os.path.join(dir, "../cert/mtls.key"), "rb") as fh: +with open(KEY_PATH, "rb") as fh: key = fh.read() ssl_credentials = grpc.ssl_channel_credentials( root_certificates=cert, certificate_chain=cert, private_key=key ) +tls_credentials = grpc.ssl_channel_credentials(root_certificates=cert) def callback(): @@ -136,6 +140,9 @@ def pytest_addoption(parser): parser.addoption( "--mtls", action="store_true", help="Run system test with mutual TLS channel" ) + parser.addoption( + "--tls", action="store_true", help="Run system test with standard one-way TLS channel" + ) # TODO: Need to test without passing in a transport class @@ -189,6 +196,11 @@ def use_mtls(request): return request.config.getoption("--mtls") +@pytest.fixture +def use_tls(request): + return request.config.getoption("--tls") or request.config.getoption("--mtls") + + @pytest.fixture def parametrized_echo( use_mtls, @@ -328,7 +340,7 @@ def _read_response_metadata_stream(self): def intercept_unary_unary(self, continuation, client_call_details, request): self._add_request_metadata(client_call_details) response = continuation(client_call_details, request) - metadata = [(k, str(v)) for k, v in response.trailing_metadata()] + metadata = [(k, str(v)) for k, v in response.initial_metadata()] + [(k, str(v)) for k, v in response.trailing_metadata()] self.response_metadata = metadata return response @@ -387,7 +399,7 @@ async def _add_request_metadata(self, client_call_details): async def intercept_unary_unary(self, continuation, client_call_details, request): await self._add_request_metadata(client_call_details) response = await continuation(client_call_details, request) - metadata = [(k, str(v)) for k, v in await response.trailing_metadata()] + metadata = [(k, str(v)) for k, v in await response.initial_metadata()] + [(k, str(v)) for k, v in await response.trailing_metadata()] self.response_metadata = metadata return response @@ -412,7 +424,7 @@ async def intercept_stream_stream( @pytest.fixture -def intercepted_echo_grpc(use_mtls): +def intercepted_echo_grpc(use_mtls, use_tls): # The interceptor adds 'showcase-trailer' client metadata. Showcase server # echoes any metadata with key 'showcase-trailer', so the same metadata # should appear as trailing metadata in the response. @@ -421,11 +433,12 @@ def intercepted_echo_grpc(use_mtls): "intercepted", ) host = "localhost:7469" - channel = ( - grpc.secure_channel(host, ssl_credentials) - if use_mtls - else grpc.insecure_channel(host) - ) + if use_mtls: + channel = grpc.secure_channel(host, ssl_credentials) + elif use_tls: + channel = grpc.secure_channel(host, tls_credentials) + else: + channel = grpc.insecure_channel(host) intercept_channel = grpc.intercept_channel(channel, interceptor) transport = EchoClient.get_transport_class("grpc")( credentials=ga_credentials.AnonymousCredentials(), @@ -435,7 +448,7 @@ def intercepted_echo_grpc(use_mtls): @pytest_asyncio.fixture -async def intercepted_echo_grpc_async(): +async def intercepted_echo_grpc_async(use_mtls, use_tls): # The interceptor adds 'showcase-trailer' client metadata. Showcase server # echoes any metadata with key 'showcase-trailer', so the same metadata # should appear as trailing metadata in the response. @@ -444,8 +457,12 @@ async def intercepted_echo_grpc_async(): "intercepted", ) host = "localhost:7469" - channel = grpc.aio.insecure_channel(host, interceptors=[interceptor]) - # intercept_channel = grpc.aio.intercept_channel(channel, interceptor) + if use_mtls: + channel = grpc.aio.secure_channel(host, ssl_credentials, interceptors=[interceptor]) + elif use_tls: + channel = grpc.aio.secure_channel(host, tls_credentials, interceptors=[interceptor]) + else: + channel = grpc.aio.insecure_channel(host, interceptors=[interceptor]) transport = EchoAsyncClient.get_transport_class("grpc_asyncio")( credentials=ga_credentials.AnonymousCredentials(), channel=channel, @@ -453,19 +470,32 @@ async def intercepted_echo_grpc_async(): return EchoAsyncClient(transport=transport), interceptor +class HostNameIgnoringAdapter(HTTPAdapter): + """Custom HTTPAdapter that disables hostname verification for local self-signed certs.""" + def cert_verify(self, conn, url, verify, cert): + super().cert_verify(conn, url, verify, cert) + conn.assert_hostname = False + + @pytest.fixture -def intercepted_echo_rest(): +def intercepted_echo_rest(use_mtls, use_tls): transport_name = "rest" transport_cls = EchoClient.get_transport_class(transport_name) interceptor = EchoMetadataClientRestInterceptor() - # The custom host explicitly bypasses https. + url_scheme = "https" if (use_mtls or use_tls) else "http" transport = transport_cls( credentials=ga_credentials.AnonymousCredentials(), host="localhost:7469", - url_scheme="http", + url_scheme=url_scheme, interceptor=interceptor, ) + if use_mtls or use_tls: + transport._session.verify = CERT_PATH + transport._session.mount("https://", HostNameIgnoringAdapter()) + if use_mtls: + transport._session.cert = (CERT_PATH, KEY_PATH) + return EchoClient(transport=transport), interceptor @@ -478,11 +508,11 @@ def intercepted_echo_rest_async(): transport_cls = EchoAsyncClient.get_transport_class(transport_name) interceptor = EchoMetadataClientRestAsyncInterceptor() - # The custom host explicitly bypasses https. transport = transport_cls( credentials=async_anonymous_credentials(), host="localhost:7469", url_scheme="http", interceptor=interceptor, ) + return EchoAsyncClient(transport=transport), interceptor diff --git a/packages/gapic-generator/tests/system/test_pqc.py b/packages/gapic-generator/tests/system/test_pqc.py new file mode 100644 index 000000000000..2d694b6528f0 --- /dev/null +++ b/packages/gapic-generator/tests/system/test_pqc.py @@ -0,0 +1,84 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc +from packaging.version import Version +import pytest +from google import showcase + + +@pytest.fixture(autouse=True) +def require_tls(use_tls): + if not use_tls: + pytest.skip("PQC integration test requires standard TLS (--tls flag) to be enabled.") + + +def _verify_pqc_metadata(interceptor, transport_name): + """Extracts and verifies negotiated PQC group and supported groups from interceptor metadata.""" + response_metadata = getattr(interceptor, "response_metadata", []) or [] + headers = {key.lower(): value for key, value in response_metadata} + negotiated_group = headers.get("x-showcase-tls-group") + supported_groups = headers.get("x-showcase-tls-client-supported-groups") + + assert negotiated_group is not None, "Failed: Showcase server did not return negotiated TLS group header." + assert supported_groups is not None, "Failed: Showcase server did not return client advertised supported groups." + + # Enforce PQC compliance by verifying a post-quantum MLKEM group was negotiated. + # Substring check ("MLKEM" in ...) ensures compatibility across different transport and library group strings. + assert ( + "MLKEM" in negotiated_group + ), f"Failed: {transport_name} Connection did not negotiate a post-quantum MLKEM group! Negotiated: {negotiated_group}" + + +def test_pqc_grpc(intercepted_echo_grpc): + """Verifies that the gRPC client library negotiates post-quantum MLKEM with Showcase server.""" + # TODO(https://github.com/googleapis/google-cloud-python/issues/17752): + # Remove this check once grpcio >= 1.83.0 is enforced across all client libraries. + if Version(grpc.__version__) < Version("1.83.0rc0"): + # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): + # Update the version in the check above to `1.83.0` once released. + pytest.skip(f"gRPC PQC negotiation requires grpcio >= 1.83.0 (current: {grpc.__version__})") + + client, interceptor = intercepted_echo_grpc + response = client.echo(request=showcase.EchoRequest(content="Verify PQC connection.")) + assert response.content == "Verify PQC connection." + _verify_pqc_metadata(interceptor, "grpc") + + +def test_pqc_rest(intercepted_echo_rest): + """Verifies that the REST client library negotiates post-quantum MLKEM with Showcase server.""" + client, interceptor = intercepted_echo_rest + response = client.echo(request=showcase.EchoRequest(content="Verify PQC connection.")) + assert response.content == "Verify PQC connection." + _verify_pqc_metadata(interceptor, "rest") + + +@pytest.mark.asyncio +async def test_pqc_grpc_async(intercepted_echo_grpc_async): + """Verifies that the async gRPC client library negotiates post-quantum MLKEM with Showcase server.""" + # TODO(https://github.com/googleapis/google-cloud-python/issues/17752): + # Remove this check once grpcio >= 1.83.0 is enforced across all client libraries. + if Version(grpc.__version__) < Version("1.83.0rc0"): + # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): + # Update the version in the check above to `1.83.0` once released. + pytest.skip( + f"gRPC PQC negotiation requires grpcio >= 1.83.0 (current: {grpc.__version__})" + ) + + client, interceptor = intercepted_echo_grpc_async + response = await client.echo( + request=showcase.EchoRequest(content="Verify PQC connection.") + ) + assert response.content == "Verify PQC connection." + _verify_pqc_metadata(interceptor, "grpc_asyncio") diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index 78937c032670..4001f4caffc1 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -25,10 +25,11 @@ # Older Python versions safely ignore this variable. __lazy_modules__: Set[str] = { "google.api_core.gapic_v1.client_info", + "google.api_core.gapic_v1.client_utils", "google.api_core.gapic_v1.requests", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "requests", "routing_header"] +__all__ = ["client_info", "client_utils", "requests", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -42,6 +43,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, + client_utils, requests, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/client_utils.py b/packages/google-api-core/google/api_core/gapic_v1/client_utils.py new file mode 100644 index 000000000000..c87cdbc06dcc --- /dev/null +++ b/packages/google-api-core/google/api_core/gapic_v1/client_utils.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Helpers for client setup and configuration.""" + +import os +from typing import Any, Callable, Optional, Tuple + +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.auth.transport import mtls # type: ignore + + +def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS endpoint. + + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + + Returns: + Optional[str]: converted mTLS api endpoint. + """ + if not api_endpoint or ".mtls." in api_endpoint: + return api_endpoint + + if api_endpoint.endswith(".sandbox.googleapis.com"): + # len(".sandbox.googleapis.com") == 23 + return api_endpoint[:-23] + ".mtls.sandbox.googleapis.com" + + if api_endpoint.endswith(".googleapis.com"): + # len(".googleapis.com") == 15 + return api_endpoint[:-15] + ".mtls.googleapis.com" + + return api_endpoint + + +def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Any], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, +) -> str: + """Return the API endpoint used by the client. + + Args: + api_override (Optional[str]): The API endpoint override. If specified, + this is always returned. + client_cert_source (Optional[Any]): The client certificate source used by the client. + universe_domain (str): The universe domain used by the client. + use_mtls_endpoint (str): How to use the mTLS endpoint. Possible values + are "always", "auto", or "never". + default_universe (str): The default universe domain. + default_mtls_endpoint (Optional[str]): The default mTLS endpoint. + default_endpoint_template (str): The default endpoint template containing + a placeholder `{UNIVERSE_DOMAIN}`. + + Returns: + str: The API endpoint to be used by the client. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but + not supported in the configured universe domain. + ValueError: If mTLS is requested but no mTLS endpoint is available. + """ + if api_override is not None: + return api_override + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + if universe_domain.lower() != default_universe.lower(): + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + if not default_mtls_endpoint: + raise ValueError("mTLS endpoint is not available.") + return default_mtls_endpoint + else: + return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + + +def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, +) -> str: + """Return the universe domain used by the client. + + Args: + client_universe_domain (Optional[str]): The universe domain configured + via client options. + universe_domain_env (Optional[str]): The universe domain configured + via environment variable. + default_universe (str): The default universe domain. + + Returns: + str: The universe domain to be used by the client. + + Raises: + ValueError: If the resolved universe domain is an empty string. + """ + if client_universe_domain is not None: + universe_domain = client_universe_domain.strip() + elif universe_domain_env is not None: + universe_domain = universe_domain_env.strip() + else: + universe_domain = default_universe + + if not universe_domain: + raise ValueError("Universe Domain cannot be an empty string.") + return universe_domain + + +def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS if the + google-auth version supports should_use_client_cert automatic mTLS + enablement. + + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. + + Returns: + bool: whether client certificate should be used for mTLS + Raises: + ValueError: (If using a version of google-auth without + should_use_client_cert and GOOGLE_API_USE_CLIENT_CERTIFICATE is + set to an unexpected value.) + """ + # check if google-auth version supports should_use_client_cert for + # automatic mTLS enablement + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER + return mtls.should_use_client_cert() + else: # pragma: NO COVER + # if unsupported, fallback to reading from env var + use_client_cert_str = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` " + "must be either `true` or `false`" + ) + return use_client_cert_str == "true" + + +def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, +) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client. + + Args: + provided_cert_source (Callable[[], Tuple[bytes, bytes]]): The client certificate source provided. + use_cert_flag (bool): A flag indicating whether to use the + client certificate. + + Returns: + Callable[[], Tuple[bytes, bytes]] or None: The client cert source to be used by the client. + """ + if use_cert_flag: + if provided_cert_source: + return provided_cert_source + elif ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + return mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return None + + +def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client. + + Returns: + Tuple[bool, str, Optional[str]]: returns the + GOOGLE_API_USE_CLIENT_CERTIFICATE, GOOGLE_API_USE_MTLS_ENDPOINT, + and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. + + Raises: + ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not + any of ["true", "false"]. + google.auth.exceptions.MutualTLSChannelError: If + GOOGLE_API_USE_MTLS_ENDPOINT is not any of + ["auto", "never", "always"]. + """ + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` " + "must be `never`, `auto` or `always`" + ) + return use_client_cert, use_mtls_endpoint, universe_domain_env diff --git a/packages/google-api-core/tests/conftest.py b/packages/google-api-core/tests/conftest.py new file mode 100644 index 000000000000..1872207be107 --- /dev/null +++ b/packages/google-api-core/tests/conftest.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from unittest import mock +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def mock_mtls_env(): + """Autouse session-scoped fixture to isolate unit tests from workstation mTLS environments.""" + with mock.patch.dict( + os.environ, + { + "GOOGLE_API_USE_CLIENT_CERTIFICATE": "false", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "false", + }, + ): + yield diff --git a/packages/google-api-core/tests/unit/gapic/test_client_utils.py b/packages/google-api-core/tests/unit/gapic/test_client_utils.py new file mode 100644 index 000000000000..9bacd506ca9e --- /dev/null +++ b/packages/google-api-core/tests/unit/gapic/test_client_utils.py @@ -0,0 +1,351 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from unittest import mock + +import pytest + +from google.auth.exceptions import MutualTLSChannelError +from google.auth.transport import mtls +from google.api_core.gapic_v1.client_utils import ( + get_api_endpoint, + get_default_mtls_endpoint, + get_universe_domain, + get_client_cert_source, + use_client_cert_effective, + read_environment_variables, +) + + +class MockClient: + _DEFAULT_UNIVERSE = "googleapis.com" + DEFAULT_MTLS_ENDPOINT = "foo.mtls.googleapis.com" + _DEFAULT_ENDPOINT_TEMPLATE = "foo.{UNIVERSE_DOMAIN}" + + +def test_get_default_mtls_endpoint(): + # Test valid API endpoints + assert get_default_mtls_endpoint("foo.googleapis.com") == "foo.mtls.googleapis.com" + assert ( + get_default_mtls_endpoint("foo.sandbox.googleapis.com") + == "foo.mtls.sandbox.googleapis.com" + ) + + # Test endpoints that shouldn't be converted + assert ( + get_default_mtls_endpoint("foo.mtls.googleapis.com") + == "foo.mtls.googleapis.com" + ) + assert get_default_mtls_endpoint("foo.com") == "foo.com" + + # Test empty/None endpoints + assert get_default_mtls_endpoint("") == "" + assert get_default_mtls_endpoint(None) is None + + +def test__get_api_endpoint(): + api_override = "foo.com" + mock_client_cert_source = mock.Mock() + default_universe = MockClient._DEFAULT_UNIVERSE + default_endpoint = MockClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = MockClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + assert ( + get_api_endpoint( + api_override, + mock_client_cert_source, + default_universe, + "always", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == api_override + ) + assert ( + get_api_endpoint( + None, + mock_client_cert_source, + default_universe, + "auto", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == MockClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + get_api_endpoint( + None, + None, + default_universe, + "auto", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == default_endpoint + ) + assert ( + get_api_endpoint( + None, + None, + default_universe, + "always", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == MockClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + get_api_endpoint( + None, + mock_client_cert_source, + default_universe, + "always", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == MockClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + get_api_endpoint( + None, + None, + mock_universe, + "never", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == mock_endpoint + ) + assert ( + get_api_endpoint( + None, + None, + default_universe, + "never", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + == default_endpoint + ) + + with pytest.raises(MutualTLSChannelError) as excinfo: + get_api_endpoint( + None, + mock_client_cert_source, + mock_universe, + "auto", + MockClient._DEFAULT_UNIVERSE, + MockClient.DEFAULT_MTLS_ENDPOINT, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + assert ( + str(excinfo.value) + == "mTLS is not supported in any universe other than googleapis.com." + ) + + with pytest.raises(ValueError) as excinfo: + get_api_endpoint( + None, + mock_client_cert_source, + default_universe, + "always", + MockClient._DEFAULT_UNIVERSE, + None, + MockClient._DEFAULT_ENDPOINT_TEMPLATE, + ) + assert str(excinfo.value) == "mTLS endpoint is not available." + + +def test__get_universe_domain(): + client_universe_domain = "foo.com" + universe_domain_env = "bar.com" + + assert ( + get_universe_domain( + client_universe_domain, universe_domain_env, MockClient._DEFAULT_UNIVERSE + ) + == client_universe_domain + ) + assert ( + get_universe_domain(None, universe_domain_env, MockClient._DEFAULT_UNIVERSE) + == universe_domain_env + ) + assert ( + get_universe_domain(None, None, MockClient._DEFAULT_UNIVERSE) + == MockClient._DEFAULT_UNIVERSE + ) + + with pytest.raises(ValueError) as excinfo: + get_universe_domain("", None, MockClient._DEFAULT_UNIVERSE) + assert str(excinfo.value) == "Universe Domain cannot be an empty string." + + +@pytest.mark.parametrize( + "has_method, method_val, env_val, expected", + [ + # should_use_client_cert is available cases + (True, True, None, "true"), + (True, False, None, "false"), + (True, False, "unsupported", "false"), + # should_use_client_cert is unavailable cases + (False, None, "true", "true"), + (False, None, "false", "false"), + (False, None, "True", "true"), + (False, None, "False", "false"), + (False, None, "TRUE", "true"), + (False, None, "FALSE", "false"), + (False, None, None, "false"), + (False, None, "unsupported", "value_error"), + ], + ids=[ + "google_auth_true", + "google_auth_false", + "google_auth_false_env_unsupported", + "fallback_env_true_lowercase", + "fallback_env_false_lowercase", + "fallback_env_true_titlecase", + "fallback_env_false_titlecase", + "fallback_env_true_uppercase", + "fallback_env_false_uppercase", + "fallback_env_unset", + "fallback_env_unsupported", + ], +) +def test_use_client_cert_effective(has_method, method_val, env_val, expected): + # Mock hasattr to control whether should_use_client_cert exists + original_hasattr = hasattr + + def custom_hasattr(obj, name): + if obj is mtls and name == "should_use_client_cert": + return has_method + return original_hasattr(obj, name) + + with mock.patch("google.api_core.gapic_v1.client_utils.hasattr", custom_hasattr): + with mock.patch( + "google.auth.transport.mtls.should_use_client_cert", + create=True, + return_value=method_val, + ): + env = {} + if env_val is not None: + env["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = env_val + with mock.patch.dict(os.environ, env, clear=True): + if expected == "value_error": + with pytest.raises( + ValueError, match="must be either `true` or `false`" + ): + use_client_cert_effective() + else: + assert use_client_cert_effective() is (expected == "true") + + +@pytest.mark.parametrize( + "provided, use_cert, has_default_avail, default_val, expected", + [ + (None, False, True, b"default", None), + (b"provided", False, True, b"default", None), + (b"provided", True, True, b"default", b"provided"), + (None, True, True, b"default", b"default"), + (None, True, False, b"default", "value_error"), + ], + ids=[ + "use_cert_false_no_provided", + "use_cert_false_with_provided", + "use_cert_true_with_provided", + "use_cert_true_no_provided_default_avail", + "use_cert_true_no_provided_default_unavail", + ], +) +def test_get_client_cert_source( + provided, use_cert, has_default_avail, default_val, expected +): + original_hasattr = hasattr + + def custom_hasattr(obj, name): + if obj is mtls and name == "has_default_client_cert_source": + return has_default_avail + return original_hasattr(obj, name) + + with mock.patch("google.api_core.gapic_v1.client_utils.hasattr", custom_hasattr): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + create=True, + return_value=has_default_avail, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + create=True, + return_value=default_val, + ): + if expected == "value_error": + with pytest.raises( + ValueError, match="Client certificate is required for mTLS" + ): + get_client_cert_source(provided, use_cert) + else: + assert get_client_cert_source(provided, use_cert) == expected + + +@pytest.mark.parametrize( + "env, mock_cert_val, expected", + [ + ({}, False, (False, "auto", None)), + ({}, True, (True, "auto", None)), + ({"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}, False, (False, "never", None)), + ({"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}, False, (False, "always", None)), + ({"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}, False, (False, "auto", None)), + ({"GOOGLE_API_USE_MTLS_ENDPOINT": "invalid"}, False, "mutual_tls_error"), + ( + {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}, + False, + (False, "auto", "foo.com"), + ), + ], + ids=[ + "default_env", + "client_cert_true", + "mtls_never", + "mtls_always", + "mtls_auto", + "mtls_invalid", + "universe_domain", + ], +) +def test_read_environment_variables(env, mock_cert_val, expected): + with mock.patch( + "google.api_core.gapic_v1.client_utils.use_client_cert_effective", + return_value=mock_cert_val, + ): + with mock.patch.dict(os.environ, env, clear=True): + if expected == "mutual_tls_error": + with pytest.raises( + MutualTLSChannelError, match="must be `never`, `auto` or `always`" + ): + read_environment_variables() + else: + assert read_environment_variables() == expected diff --git a/packages/google-auth/google/auth/_agent_identity_utils.py b/packages/google-auth/google/auth/_agent_identity_utils.py index f2545f28238e..4de5d709b4b5 100644 --- a/packages/google-auth/google/auth/_agent_identity_utils.py +++ b/packages/google-auth/google/auth/_agent_identity_utils.py @@ -67,93 +67,88 @@ def _is_certificate_file_ready(path): st = os.stat(path) return stat.S_ISREG(st.st_mode) and st.st_size > 0 except PermissionError: - # Propagate PermissionError to let caller handle it (fail-fast or fallback) + # Propagate PermissionError to let caller handle it (e.g., return early or fallback) raise except OSError: return False def get_agent_identity_certificate_path(): - """Gets the certificate path from the certificate config file. + """Gets the agent certificate path from the certificate config file. The path to the certificate config file is read from the GOOGLE_API_CERTIFICATE_CONFIG environment variable. This function - implements a retry mechanism to handle cases where the environment + can optionally trigger polling to handle cases where the environment variable is set before the files are available on the filesystem. Returns: - str: The path to the leaf certificate file. + Optional[str]: The path to the agent's certificate file, or None if unavailable. Raises: google.auth.exceptions.RefreshError: If the certificate config file or the certificate file cannot be found after retries. """ - import json - cert_config_path = os.environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) - # Check if the well-known workload directory is mounted. + if not cert_config_path: + return None + + # We trigger polling only if the config path points to the well-known directory. + # Cloud Run dynamically generates these files in this directory, and both the + # config file and the certificate file may experience a brief startup latency. + # For all other paths, we return early to avoid introducing unnecessary startup + # delays. well_known_dir = os.path.dirname(_WELL_KNOWN_CERT_PATH) - has_well_known_dir = os.path.exists(well_known_dir) + try: + abs_cert_path = os.path.abspath(cert_config_path) + abs_well_known_dir = os.path.abspath(well_known_dir) + should_poll = ( + os.path.commonpath([abs_well_known_dir, abs_cert_path]) + == abs_well_known_dir + ) + except ValueError: + should_poll = False - # If we have neither a config path nor a well-known mount directory, exit immediately. - if not cert_config_path and not has_well_known_dir: - return None + return _get_cert_path_with_optional_polling(cert_config_path, should_poll) - # If ECP config path is specified but does not exist, and we are on a workstation, fail-fast immediately. - if ( - cert_config_path - and not has_well_known_dir - and not os.path.exists(cert_config_path) - ): - return None +def _get_cert_path_with_optional_polling(cert_config_path, should_poll): + """Gets the certificate path, optionally polling until it is ready. + + Args: + cert_config_path (str): The path to the certificate configuration file. + should_poll (bool): If True, the function will poll for the file and + certificate to be ready. If False, it will check only once and + return early if they are not immediately available. + + Returns: + str: The path to the certificate file, or None if unavailable. + + Raises: + google.auth.exceptions.RefreshError: If the certificate config file + or the certificate file cannot be found after retries. + """ has_logged_config_warning = False has_logged_cert_warning = False for interval in _POLLING_INTERVALS: try: - # Path A: Config file is explicitly set - if cert_config_path: - with open(cert_config_path, "r") as f: - cert_config = json.load(f) - - cert_configs = ( - cert_config.get("cert_configs") - if isinstance(cert_config, dict) - else None - ) - workload_config = ( - cert_configs.get("workload") - if isinstance(cert_configs, dict) - else None - ) - - if ( - not isinstance(workload_config, dict) - or "cert_path" not in workload_config - ): - return None - - cert_path = workload_config["cert_path"] - if _is_certificate_file_ready(cert_path): - return cert_path + cert_path = _parse_cert_path_from_config(cert_config_path) - # The config was parsed, but the cert file is not ready yet - target_path = cert_path + if cert_path is None: + return None - # Path B: Config is NOT set, fallback to the well-known path - else: - if _is_certificate_file_ready(_WELL_KNOWN_CERT_PATH): - return _WELL_KNOWN_CERT_PATH + if _is_certificate_file_ready(cert_path): + return cert_path - # The well-known cert file is not ready yet - target_path = _WELL_KNOWN_CERT_PATH + # The config was parsed, but the cert file is not ready yet + if not should_poll: + # If polling is disabled, return early. + return None - # Log a warning on the first failed attempt to load the certificate file if not has_logged_cert_warning: warnings.warn( - f"Certificate file not ready at {target_path}. Retrying until startup timeout (up to {_TOTAL_TIMEOUT} seconds total)..." + f"Certificate file not ready at {cert_path}. Retrying until startup timeout (up to {_TOTAL_TIMEOUT} seconds total)..." ) has_logged_cert_warning = True @@ -164,25 +159,24 @@ def get_agent_identity_certificate_path(): ) return None except (IOError, ValueError, KeyError) as e: - if cert_config_path and os.path.exists(cert_config_path): + if os.path.exists(cert_config_path): # If the file exists but has invalid JSON or is unreadable, - # we assume it is in its final format and fail-fast by returning None. + # we assume it is in its final format and return early (returning None). + return None + + if not should_poll: + # If polling is disabled, return early if the file doesn't exist. return None - if not has_logged_config_warning and cert_config_path: + if not has_logged_config_warning: warnings.warn( f"Certificate config file not found or incomplete: {e} (from " f"{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG} environment variable). " f"Retrying until startup timeout (up to {_TOTAL_TIMEOUT} seconds total)..." ) has_logged_config_warning = True - pass - # A sleep is required in two cases: - # 1. The config file is not found (the except block). - # 2. The config file/well-known path is found, but the certificate is not yet available. - # In both cases, we need to poll, so we sleep on every iteration - # that doesn't return a certificate. + # Sleep before the next polling attempt. time.sleep(interval) raise exceptions.RefreshError( @@ -193,6 +187,40 @@ def get_agent_identity_certificate_path(): ) +def _parse_cert_path_from_config(cert_config_path): + """Reads the cert config file and returns the cert_path. + + Args: + cert_config_path (str): The path to the certificate configuration file. + + Returns: + Optional[str]: The path to the certificate file, or None if not found + in the config. + + Raises: + IOError: If the certificate config file cannot be read. + ValueError: If the certificate config file contains invalid JSON. + KeyError: If the certificate config file does not contain the + expected structure. + """ + import json + + with open(cert_config_path, "r", encoding="utf-8") as f: + cert_config = json.load(f) + + cert_configs = ( + cert_config.get("cert_configs") if isinstance(cert_config, dict) else None + ) + workload_config = ( + cert_configs.get("workload") if isinstance(cert_configs, dict) else None + ) + + if not isinstance(workload_config, dict) or "cert_path" not in workload_config: + return None + + return workload_config["cert_path"] + + def get_and_parse_agent_identity_certificate(): """Gets and parses the agent identity certificate if not opted out. diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 9497368070dd..eb0600740c0d 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -26,6 +26,7 @@ from typing import cast, Generator, List, Optional, Tuple, Union from google.auth import _agent_identity_utils +from google.auth import _cloud_sdk from google.auth import environment_vars from google.auth import exceptions @@ -51,30 +52,10 @@ _LOGGER = logging.getLogger(__name__) -# A flag to track if we have already logged a warning about mTLS auto-enablement failures. -# This prevents log spam when client libraries create transports or session instances -# frequently within a single process. -_has_logged_mtls_warning = False - - _PASSPHRASE_REGEX = re.compile( b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL ) -# Temporary patch to accomodate incorrect cert config in Cloud Run prod environment. -_WELL_KNOWN_CLOUD_RUN_CERT_PATH = ( - "/var/run/secrets/workload-spiffe-credentials/certificates.pem" -) -_WELL_KNOWN_CLOUD_RUN_KEY_PATH = ( - "/var/run/secrets/workload-spiffe-credentials/private_key.pem" -) -_INCORRECT_CLOUD_RUN_CERT_PATH = ( - "/var/lib/volumes/certificate/workload-certificates/certificates.pem" -) -_INCORRECT_CLOUD_RUN_KEY_PATH = ( - "/var/lib/volumes/certificate/workload-certificates/private_key.pem" -) - class _MemfdCreationError(OSError): """Raised when Linux in-memory virtual file creation (memfd) fails.""" @@ -436,10 +417,15 @@ def _get_cert_config_path(certificate_config_path=None, include_context_aware=Tr The absolute path of the certificate config file, and None if the file does not exist. """ + source = "function argument" + is_explicit = True if certificate_config_path is None: env_path = environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, None) if env_path is not None and env_path != "": certificate_config_path = env_path + source = ( + f"environment variable {environment_vars.GOOGLE_API_CERTIFICATE_CONFIG}" + ) else: env_path = environ.get( environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, @@ -447,11 +433,21 @@ def _get_cert_config_path(certificate_config_path=None, include_context_aware=Tr ) if include_context_aware and env_path is not None and env_path != "": certificate_config_path = env_path + source = f"environment variable {environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH}" else: - certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH + certificate_config_path = os.path.join( + _cloud_sdk.get_config_path(), "certificate_config.json" + ) + is_explicit = False certificate_config_path = path.expanduser(certificate_config_path) if not path.exists(certificate_config_path): + if is_explicit: + _LOGGER.debug( + "Certificate configuration file explicitly specified via %s at %s does not exist", + source, + certificate_config_path, + ) return None return certificate_config_path @@ -489,25 +485,6 @@ def _get_workload_cert_and_key_paths(config_path, include_context_aware=True): cert_path = workload["cert_path"] key_path = workload["key_path"] - # == BEGIN Temporary Cloud Run PATCH == - # See https://github.com/googleapis/google-auth-library-python/issues/1881 - if (cert_path == _INCORRECT_CLOUD_RUN_CERT_PATH) and ( - key_path == _INCORRECT_CLOUD_RUN_KEY_PATH - ): - if not path.exists(cert_path) and not path.exists(key_path): - _LOGGER.debug( - "Applying Cloud Run certificate path patch. " - "Configured paths not found: %s, %s. " - "Using well-known paths: %s, %s", - cert_path, - key_path, - _WELL_KNOWN_CLOUD_RUN_CERT_PATH, - _WELL_KNOWN_CLOUD_RUN_KEY_PATH, - ) - cert_path = _WELL_KNOWN_CLOUD_RUN_CERT_PATH - key_path = _WELL_KNOWN_CLOUD_RUN_KEY_PATH - # == END Temporary Cloud Run PATCH == - return cert_path, key_path @@ -755,36 +732,33 @@ def check_use_client_cert(): will default to False. If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred as True (auto-enabled) if a workload config file exists (pointed at by - GOOGLE_API_CERTIFICATE_CONFIG) containing a "workload" section. + GOOGLE_API_CERTIFICATE_CONFIG or CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, + or the default path like ~/.config/gcloud/certificate_config.json) + containing a "workload" section. Otherwise, it returns False. Returns: bool: Whether the client certificate should be used for mTLS connection. """ - global _has_logged_mtls_warning env_override = _check_use_client_cert_env() if env_override is not None: return env_override # Auto-enablement checks (when GOOGLE_API_USE_CLIENT_CERTIFICATE is not set) - # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. - cert_path = getenv(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) or getenv( - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH - ) + # Check if a workload config file exists. + cert_path = _get_cert_config_path(include_context_aware=True) if cert_path: try: with open(cert_path, "r") as f: content = json.load(f) except (FileNotFoundError, OSError, json.JSONDecodeError) as e: - if not _has_logged_mtls_warning: - _LOGGER.warning( - "mTLS auto-enablement failed: Could not read/parse certificate file at %s. Error: %s", - cert_path, - e, - ) - _has_logged_mtls_warning = True + _LOGGER.debug( + "mTLS auto-enablement failed: Could not read/parse certificate file at %s. Error: %s", + cert_path, + e, + ) return False # Structural validation @@ -794,12 +768,10 @@ def check_use_client_cert(): return True # If we got here, the file exists but the expected structure is missing - if not _has_logged_mtls_warning: - _LOGGER.warning( - "mTLS auto-enablement failed: Certificate configuration file at %s is missing the required ['cert_configs']['workload'] section.", - cert_path, - ) - _has_logged_mtls_warning = True + _LOGGER.debug( + "mTLS auto-enablement failed: Certificate configuration file at %s is missing the required ['cert_configs']['workload'] section.", + cert_path, + ) return False diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index 96b84e8e587f..bc92b4295eaf 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -24,7 +24,6 @@ from google.auth import exceptions from google.auth.transport import _mtls_helper - _LOGGER = logging.getLogger(__name__) @@ -44,25 +43,18 @@ def has_default_client_cert_source(include_context_aware=True): Returns: bool: indicating if the default client cert source exists. """ + cert_path = _mtls_helper._get_cert_config_path( + include_context_aware=include_context_aware + ) + if cert_path is not None: + return True if ( include_context_aware and _mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH) is not None ): return True - if ( - _mtls_helper._check_config_path( - _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH - ) - is not None - ): - return True - cert_config_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") - if ( - cert_config_path - and _mtls_helper._check_config_path(cert_config_path) is not None - ): - return True + return False @@ -146,7 +138,9 @@ def should_use_client_cert(): If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding bool value will be returned 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 + reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG or + CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, or the default path + like ~/.config/gcloud/certificate_config.json, and verifying it contains a "workload" section. If so, the function will return True, otherwise False. diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index eef0384652a6..12f4dda8e084 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -208,7 +208,7 @@ class _MutualTlsAdapter(requests.adapters.HTTPAdapter): google.auth.exceptions.MutualTLSChannelError: If the cert or key is invalid. """ - def __init__(self, cert, key): + def __init__(self, cert, key, **kwargs): import certifi import ssl @@ -250,7 +250,7 @@ def __init__(self, cert, key): self._ctx_poolmanager = ctx_poolmanager self._ctx_proxymanager = ctx_proxymanager - super(_MutualTlsAdapter, self).__init__() + super(_MutualTlsAdapter, self).__init__(**kwargs) def init_poolmanager(self, *args, **kwargs): kwargs["ssl_context"] = self._ctx_poolmanager @@ -457,6 +457,11 @@ def configure_mtls_channel(self, client_cert_callback=None): If the callback is None, application default SSL credentials will be used. + .. warning:: + Calling this method mutates the underlying `requests.Session` adapter + dictionary. It is not thread-safe to call this explicitly while other + threads are making requests. + Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. The existing session state (such @@ -475,10 +480,58 @@ def configure_mtls_channel(self, client_cert_callback=None): client_cert_callback ) + old_adapter = self.adapters.get("https://") + + kwargs = {} + if old_adapter is not None: + kwargs["max_retries"] = getattr(old_adapter, "max_retries", 0) + kwargs["pool_connections"] = getattr( + old_adapter, "_pool_connections", requests.adapters.DEFAULT_POOLSIZE + ) + kwargs["pool_maxsize"] = getattr( + old_adapter, "_pool_maxsize", requests.adapters.DEFAULT_POOLSIZE + ) + kwargs["pool_block"] = getattr( + old_adapter, "_pool_block", requests.adapters.DEFAULT_POOLBLOCK + ) + + old_auth_adapter = None + auth_kwargs = {} + if self._auth_request_session is not None: + old_auth_adapter = self._auth_request_session.adapters.get("https://") + + if old_auth_adapter is not None: + auth_kwargs["max_retries"] = getattr( + old_auth_adapter, "max_retries", 0 + ) + auth_kwargs["pool_connections"] = getattr( + old_auth_adapter, + "_pool_connections", + requests.adapters.DEFAULT_POOLSIZE, + ) + auth_kwargs["pool_maxsize"] = getattr( + old_auth_adapter, + "_pool_maxsize", + requests.adapters.DEFAULT_POOLSIZE, + ) + auth_kwargs["pool_block"] = getattr( + old_auth_adapter, + "_pool_block", + requests.adapters.DEFAULT_POOLBLOCK, + ) + if is_mtls: - new_adapter = _MutualTlsAdapter(cert, key) + new_adapter = _MutualTlsAdapter(cert, key, **kwargs) + if self._auth_request_session is not None: + new_auth_adapter = _MutualTlsAdapter(cert, key, **auth_kwargs) + else: + new_auth_adapter = None else: - new_adapter = requests.adapters.HTTPAdapter() + new_adapter = requests.adapters.HTTPAdapter(**kwargs) + if self._auth_request_session is not None: + new_auth_adapter = requests.adapters.HTTPAdapter(**auth_kwargs) + else: + new_auth_adapter = None except ( exceptions.ClientCertError, ImportError, @@ -489,6 +542,19 @@ def configure_mtls_channel(self, client_cert_callback=None): raise new_exc from caught_exc self.mount("https://", new_adapter) + + if old_adapter is not None and old_adapter is not new_adapter: + old_adapter.close() + + if self._auth_request_session is not None and new_auth_adapter is not None: + self._auth_request_session.mount("https://", new_auth_adapter) + + if ( + old_auth_adapter is not None + and old_auth_adapter is not new_auth_adapter + ): + old_auth_adapter.close() + self._is_mtls = is_mtls if is_mtls: self._cached_cert = cert diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 1b0fac7c342f..18e6128e03bd 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -335,6 +335,11 @@ def configure_mtls_channel(self, client_cert_callback=None): If the callback is None, application default SSL credentials will be used. + .. warning:: + Calling this method mutates the underlying `urllib3.PoolManager`. + It is not thread-safe to call this explicitly while other + threads are making requests. + Returns: True if the channel is mutual TLS and False otherwise. @@ -367,9 +372,15 @@ def configure_mtls_channel(self, client_cert_callback=None): new_exc = exceptions.MutualTLSChannelError(caught_exc) raise new_exc from caught_exc + old_http = self.http + self.http = new_http self._is_mtls = new_is_mtls self._request.http = new_http + + if old_http is not None and old_http is not new_http: + getattr(old_http, "clear", getattr(old_http, "close", lambda: None))() + if new_is_mtls: self._cached_cert = cert else: @@ -491,7 +502,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def __del__(self): if hasattr(self, "http") and self.http is not None: - self.http.clear() + getattr(self.http, "clear", getattr(self.http, "close", lambda: None))() @property def headers(self): diff --git a/packages/google-auth/tests/test_agent_identity_utils.py b/packages/google-auth/tests/test_agent_identity_utils.py index 7394d6914e38..6b830e048412 100644 --- a/packages/google-auth/tests/test_agent_identity_utils.py +++ b/packages/google-auth/tests/test_agent_identity_utils.py @@ -15,6 +15,7 @@ import base64 import hashlib import json +import os from unittest import mock import urllib.parse @@ -65,6 +66,62 @@ def test_parse_certificate(self, mock_load_cert): mock_load_cert.assert_called_once_with(b"cert_bytes") assert result == mock_load_cert.return_value + def test_is_certificate_file_ready_empty_path(self): + result = _agent_identity_utils._is_certificate_file_ready("") + assert result is False + + def test_get_agent_identity_certificate_path_empty_env(self, monkeypatch): + monkeypatch.delenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + ) + result = _agent_identity_utils.get_agent_identity_certificate_path() + assert result is None + + @mock.patch("google.auth._agent_identity_utils.os.path.commonpath") + @mock.patch( + "google.auth._agent_identity_utils._get_cert_path_with_optional_polling" + ) + def test_get_agent_identity_certificate_path_value_error( + self, mock_get_cert, mock_commonpath, monkeypatch + ): + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, "/path/to/config.json" + ) + mock_commonpath.side_effect = ValueError("Different drives") + mock_get_cert.return_value = "cert_path" + + result = _agent_identity_utils.get_agent_identity_certificate_path() + assert result == "cert_path" + mock_get_cert.assert_called_once_with("/path/to/config.json", False) + + @mock.patch( + "google.auth._agent_identity_utils._get_cert_path_with_optional_polling" + ) + def test_get_agent_identity_certificate_path_prefix_false_positive( + self, mock_get_cert, monkeypatch, tmpdir + ): + base_dir = str(tmpdir) + well_known_dir = os.path.join(base_dir, "workload-spiffe-credentials") + well_known_path = os.path.join(well_known_dir, "certificates.pem") + + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + well_known_path, + ) + + fake_dir = os.path.join(base_dir, "workload-spiffe-credentials-fake") + fake_config_path = os.path.join(fake_dir, "config.json") + + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, fake_config_path + ) + mock_get_cert.return_value = "cert_path" + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + assert result == "cert_path" + mock_get_cert.assert_called_once_with(fake_config_path, False) + @mock.patch("google.auth._agent_identity_utils.os.stat") def test_is_certificate_file_ready_permission_error(self, mock_stat): mock_stat.side_effect = PermissionError("Permission denied") @@ -228,42 +285,97 @@ def test_get_agent_identity_certificate_path_success(self, tmpdir, monkeypatch): def test_get_agent_identity_certificate_path_retry( self, mock_exists, mock_sleep, tmpdir, monkeypatch ): + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(tmpdir.join("certificates.pem")), + ) config_path = tmpdir.join("config.json") monkeypatch.setenv( environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) ) - # Simulate workload env (well_known_dir exists) to avoid fail-fast - def exists_side_effect(path): - if path == "/var/run/secrets/workload-spiffe-credentials": - return True - return False - - mock_exists.side_effect = exists_side_effect - # File doesn't exist initially + mock_exists.return_value = False + with pytest.raises(exceptions.RefreshError): _agent_identity_utils.get_agent_identity_certificate_path() assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) + @mock.patch("time.sleep") + @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") + def test_get_agent_identity_certificate_path_retry_success( + self, mock_is_ready, mock_sleep, tmpdir, monkeypatch + ): + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(tmpdir.join("certificates.pem")), + ) + cert_path_str = str(tmpdir.join("cert.pem")) + config_path = tmpdir.join("config.json") + config_path.write( + json.dumps({"cert_configs": {"workload": {"cert_path": cert_path_str}}}) + ) + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + # First attempt: file missing/not ready. Second attempt: succeeds. + mock_is_ready.side_effect = [False, True] + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + assert result == cert_path_str + assert mock_sleep.call_count == 1 + assert mock_is_ready.call_count == 2 + + @mock.patch("time.sleep") + @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") + def test_get_agent_identity_certificate_path_config_retry_success( + self, mock_is_ready, mock_sleep, tmpdir, monkeypatch + ): + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(tmpdir.join("certificates.pem")), + ) + cert_path_str = str(tmpdir.join("cert.pem")) + config_path = tmpdir.join("config.json") + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + mock_is_ready.return_value = True + + def write_config(*args, **kwargs): + config_path.write( + json.dumps({"cert_configs": {"workload": {"cert_path": cert_path_str}}}) + ) + + # First attempt: config file missing. Sleep side effect creates it. + mock_sleep.side_effect = write_config + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + assert result == cert_path_str + assert mock_sleep.call_count == 1 + assert mock_is_ready.call_count == 1 + @mock.patch("time.sleep") @mock.patch("google.auth._agent_identity_utils.os.path.exists") def test_get_agent_identity_certificate_path_failure( self, mock_exists, mock_sleep, tmpdir, monkeypatch ): + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(tmpdir.join("certificates.pem")), + ) config_path = tmpdir.join("non_existent_config.json") monkeypatch.setenv( environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) ) # Simulate workload env (well_known_dir exists) to avoid fail-fast - def exists_side_effect(path): - if path == "/var/run/secrets/workload-spiffe-credentials": - return True - return False - - mock_exists.side_effect = exists_side_effect + mock_exists.return_value = False with pytest.raises(exceptions.RefreshError) as excinfo: _agent_identity_utils.get_agent_identity_certificate_path() @@ -275,24 +387,72 @@ def exists_side_effect(path): ) assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) - def test_get_agent_identity_certificate_path_workstation_fail_fast( - self, tmpdir, monkeypatch + @mock.patch("time.sleep") + @mock.patch("google.auth._agent_identity_utils.os.path.exists") + def test_get_agent_identity_certificate_path_fail_fast_config_missing( + self, mock_exists, mock_sleep, tmpdir, monkeypatch ): - config_path = tmpdir.join("non_existent_config.json") + # Simulate config path outside well-known dir where config file does not exist. + well_known_path = tmpdir.mkdir("well_known").join("certificates.pem") + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(well_known_path), + ) + + config_path = tmpdir.mkdir("custom").join("custom_config.json") + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) + ) + + mock_exists.return_value = False + + result = _agent_identity_utils.get_agent_identity_certificate_path() + + assert result is None + mock_sleep.assert_not_called() + + @mock.patch("time.sleep") + @mock.patch("google.auth._agent_identity_utils.os.path.exists") + def test_get_agent_identity_certificate_path_fail_fast_cert_missing( + self, mock_exists, mock_sleep, tmpdir, monkeypatch + ): + # Simulate config path outside well-known dir where config is valid but cert is missing. + well_known_path = tmpdir.mkdir("well_known_cert").join("certificates.pem") + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(well_known_path), + ) + + custom_dir = tmpdir.mkdir("custom_cert") + config_path = custom_dir.join("custom_config.json") + cert_path_str = str(custom_dir.join("cert.pem")) + + config_path.write( + json.dumps({"cert_configs": {"workload": {"cert_path": cert_path_str}}}) + ) monkeypatch.setenv( environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) ) - # On a workstation, well_known_dir does not exist, and config file is missing. - # It should fail-fast and return None immediately. + def exists_side_effect(path): + return path == str(config_path) + + mock_exists.side_effect = exists_side_effect + result = _agent_identity_utils.get_agent_identity_certificate_path() + assert result is None + mock_sleep.assert_not_called() @mock.patch("time.sleep") @mock.patch("google.auth._agent_identity_utils.os.path.exists") def test_get_agent_identity_certificate_path_cert_not_found( self, mock_exists, mock_sleep, tmpdir, monkeypatch ): + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(tmpdir.join("certificates.pem")), + ) cert_path_str = str(tmpdir.join("cert.pem")) config_path = tmpdir.join("config.json") config_path.write( @@ -380,115 +540,50 @@ def test_get_agent_identity_certificate_path_workload_config_missing_cert_path( @mock.patch("time.sleep") @mock.patch("google.auth._agent_identity_utils.os.path.exists") - @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") - def test_get_agent_identity_certificate_path_no_config_but_has_well_known_dir( - self, mock_is_ready, mock_exists, mock_sleep, monkeypatch + def test_get_agent_identity_certificate_path_permission_error_config( + self, mock_exists, mock_sleep, tmpdir, monkeypatch ): - monkeypatch.delenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + config_path = tmpdir.join("config.json") + monkeypatch.setenv( + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) ) - - # Simulate that the well-known workload mount directory exists, and the cert is ready + # Mock os.path.exists so ECP workstation fail-fast is not triggered mock_exists.return_value = True - mock_is_ready.return_value = True - - result = _agent_identity_utils.get_agent_identity_certificate_path() - - # Should return the well-known path immediately - assert result == _agent_identity_utils._WELL_KNOWN_CERT_PATH - mock_sleep.assert_not_called() - @mock.patch("time.sleep") - @mock.patch("google.auth._agent_identity_utils.os.path.exists") - def test_get_agent_identity_certificate_path_no_config_no_well_known_dir( - self, mock_exists, mock_sleep, monkeypatch - ): - monkeypatch.delenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False - ) - - # Simulate that the well-known mount directory does NOT exist - mock_exists.return_value = False + # Mocking open to raise PermissionError + mock_open = mock.mock_open() + mock_open.side_effect = PermissionError("Permission denied") - result = _agent_identity_utils.get_agent_identity_certificate_path() + with mock.patch("builtins.open", mock_open): + result = _agent_identity_utils.get_agent_identity_certificate_path() - # Should return None immediately without polling assert result is None mock_sleep.assert_not_called() @mock.patch("time.sleep") - @mock.patch("google.auth._agent_identity_utils.os.path.exists") @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") - def test_get_agent_identity_certificate_path_no_config_well_known_polling_success( - self, mock_is_ready, mock_exists, mock_sleep, monkeypatch + def test_get_agent_identity_certificate_path_permission_error_cert_file( + self, mock_is_ready, mock_sleep, tmpdir, monkeypatch ): - monkeypatch.delenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + well_known_path = tmpdir.mkdir("well_known").join("certificates.pem") + monkeypatch.setattr( + "google.auth._agent_identity_utils._WELL_KNOWN_CERT_PATH", + str(well_known_path), ) - # Simulate that the directory exists, file appears on 2nd try - mock_exists.return_value = True - mock_is_ready.side_effect = [False, True] - - result = _agent_identity_utils.get_agent_identity_certificate_path() - - assert result == _agent_identity_utils._WELL_KNOWN_CERT_PATH - assert mock_sleep.call_count == 1 - - @mock.patch("time.sleep") - @mock.patch("google.auth._agent_identity_utils.os.path.exists") - @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") - def test_get_agent_identity_certificate_path_no_config_well_known_polling_timeout( - self, mock_is_ready, mock_exists, mock_sleep, monkeypatch - ): - monkeypatch.delenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False - ) - - # Simulate that the directory exists, but file never appears - mock_exists.return_value = True - mock_is_ready.return_value = False - - with pytest.raises(exceptions.RefreshError): - _agent_identity_utils.get_agent_identity_certificate_path() - - assert mock_sleep.call_count == len(_agent_identity_utils._POLLING_INTERVALS) - - @mock.patch("time.sleep") - @mock.patch("google.auth._agent_identity_utils._is_certificate_file_ready") - @mock.patch("google.auth._agent_identity_utils.os.path.exists") - def test_get_agent_identity_certificate_path_permission_error_well_known( - self, mock_exists, mock_is_ready, mock_sleep, monkeypatch - ): - monkeypatch.delenv( - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, raising=False + config_path = tmpdir.mkdir("custom").join("custom_config.json") + cert_path_str = str(tmpdir.join("cert.pem")) + config_path.write( + json.dumps({"cert_configs": {"workload": {"cert_path": cert_path_str}}}) ) - mock_exists.return_value = True - mock_is_ready.side_effect = PermissionError("Permission denied") - - # It should fail-fast and return None immediately - result = _agent_identity_utils.get_agent_identity_certificate_path() - assert result is None - mock_sleep.assert_not_called() - - @mock.patch("time.sleep") - @mock.patch("google.auth._agent_identity_utils.os.path.exists") - def test_get_agent_identity_certificate_path_permission_error_config( - self, mock_exists, mock_sleep, tmpdir, monkeypatch - ): - config_path = tmpdir.join("config.json") monkeypatch.setenv( environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, str(config_path) ) - # Mock os.path.exists so ECP workstation fail-fast is not triggered - mock_exists.return_value = True - # Mocking open to raise PermissionError - mock_open = mock.mock_open() - mock_open.side_effect = PermissionError("Permission denied") + # Mock _is_certificate_file_ready to raise PermissionError + mock_is_ready.side_effect = PermissionError("Permission denied") - with mock.patch("builtins.open", mock_open): - result = _agent_identity_utils.get_agent_identity_certificate_path() + result = _agent_identity_utils.get_agent_identity_certificate_path() assert result is None mock_sleep.assert_not_called() diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index e7b80e92d0ab..537ef47e7295 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -315,13 +315,16 @@ def test_success_with_context_aware_metadata( ) @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) + @mock.patch("os.path.exists", autospec=True) def test_success_with_certificate_config( self, + mock_path_exists, mock_check_config_path, mock_load_json_file, mock_get_cert_config_path, mock_read_cert_and_key_files, ): + mock_path_exists.return_value = True cert_config_path = "/path/to/config" mock_check_config_path.return_value = cert_config_path mock_load_json_file.return_value = { @@ -341,93 +344,6 @@ def test_success_with_certificate_config( assert key == pytest.private_key_bytes assert passphrase is None - @mock.patch( - "google.auth.transport._mtls_helper._read_cert_and_key_files", autospec=True - ) - @mock.patch( - "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True - ) - @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) - @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) - def test_success_with_certificate_config_cloud_run_patch( - self, - mock_check_config_path, - mock_load_json_file, - mock_get_cert_config_path, - mock_read_cert_and_key_files, - ): - cert_config_path = "/path/to/config" - mock_check_config_path.return_value = cert_config_path - mock_load_json_file.return_value = { - "cert_configs": { - "workload": { - "cert_path": _mtls_helper._INCORRECT_CLOUD_RUN_CERT_PATH, - "key_path": _mtls_helper._INCORRECT_CLOUD_RUN_KEY_PATH, - } - } - } - mock_get_cert_config_path.return_value = cert_config_path - mock_read_cert_and_key_files.return_value = ( - pytest.public_cert_bytes, - pytest.private_key_bytes, - ) - - has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() - assert has_cert - assert cert == pytest.public_cert_bytes - assert key == pytest.private_key_bytes - assert passphrase is None - - mock_read_cert_and_key_files.assert_called_once_with( - _mtls_helper._WELL_KNOWN_CLOUD_RUN_CERT_PATH, - _mtls_helper._WELL_KNOWN_CLOUD_RUN_KEY_PATH, - ) - - @mock.patch("os.path.exists", autospec=True) - @mock.patch( - "google.auth.transport._mtls_helper._read_cert_and_key_files", autospec=True - ) - @mock.patch( - "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True - ) - @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) - @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) - def test_success_with_certificate_config_cloud_run_patch_skipped_if_cert_exists( - self, - mock_check_config_path, - mock_load_json_file, - mock_get_cert_config_path, - mock_read_cert_and_key_files, - mock_os_path_exists, - ): - cert_config_path = "/path/to/config" - mock_check_config_path.return_value = cert_config_path - mock_os_path_exists.return_value = True - mock_load_json_file.return_value = { - "cert_configs": { - "workload": { - "cert_path": _mtls_helper._INCORRECT_CLOUD_RUN_CERT_PATH, - "key_path": _mtls_helper._INCORRECT_CLOUD_RUN_KEY_PATH, - } - } - } - mock_get_cert_config_path.return_value = cert_config_path - mock_read_cert_and_key_files.return_value = ( - pytest.public_cert_bytes, - pytest.private_key_bytes, - ) - - has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() - assert has_cert - assert cert == pytest.public_cert_bytes - assert key == pytest.private_key_bytes - assert passphrase is None - - mock_read_cert_and_key_files.assert_called_once_with( - _mtls_helper._INCORRECT_CLOUD_RUN_CERT_PATH, - _mtls_helper._INCORRECT_CLOUD_RUN_KEY_PATH, - ) - @mock.patch( "google.auth.transport._mtls_helper._get_workload_cert_and_key", autospec=True ) @@ -531,12 +447,15 @@ class TestGetWorkloadCertAndKey(object): @mock.patch( "google.auth.transport._mtls_helper._read_cert_and_key_files", autospec=True ) + @mock.patch("os.path.exists", autospec=True) def test_success( self, + mock_path_exists, mock_read_cert_and_key_files, mock_get_cert_config_path, mock_load_json_file, ): + mock_path_exists.return_value = True cert_config_path = "/path/to/cert" mock_get_cert_config_path.return_value = "/path/to/cert" mock_load_json_file.return_value = { @@ -569,7 +488,11 @@ def test_file_not_found_returns_none(self, mock_get_cert_config_path): @mock.patch( "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True ) - def test_no_cert_configs(self, mock_get_cert_config_path, mock_load_json_file): + @mock.patch("os.path.exists", autospec=True) + def test_no_cert_configs( + self, mock_path_exists, mock_get_cert_config_path, mock_load_json_file + ): + mock_path_exists.return_value = True mock_get_cert_config_path.return_value = "/path/to/cert" mock_load_json_file.return_value = {} @@ -592,7 +515,11 @@ def test_no_workload(self, mock_get_cert_config_path, mock_load_json_file): @mock.patch( "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True ) - def test_no_cert_file(self, mock_get_cert_config_path, mock_load_json_file): + @mock.patch("os.path.exists", autospec=True) + def test_no_cert_file( + self, mock_path_exists, mock_get_cert_config_path, mock_load_json_file + ): + mock_path_exists.return_value = True mock_get_cert_config_path.return_value = "/path/to/cert" mock_load_json_file.return_value = { "cert_configs": {"workload": {"key_path": "path/to/key"}} @@ -605,7 +532,11 @@ def test_no_cert_file(self, mock_get_cert_config_path, mock_load_json_file): @mock.patch( "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True ) - def test_no_key_file(self, mock_get_cert_config_path, mock_load_json_file): + @mock.patch("os.path.exists", autospec=True) + def test_no_key_file( + self, mock_path_exists, mock_get_cert_config_path, mock_load_json_file + ): + mock_path_exists.return_value = True mock_get_cert_config_path.return_value = "/path/to/cert" mock_load_json_file.return_value = { "cert_configs": {"workload": {"cert_path": "path/to/key"}} @@ -657,10 +588,16 @@ def test_success_with_override(self): returned_path = _mtls_helper._get_cert_config_path(config_path) assert returned_path == config_path - def test_override_does_not_exist(self): + @mock.patch("google.auth.transport._mtls_helper._LOGGER.debug") + def test_override_does_not_exist(self, mock_debug): config_path = "fake/file/path" returned_path = _mtls_helper._get_cert_config_path(config_path) assert returned_path is None + mock_debug.assert_called_once_with( + "Certificate configuration file explicitly specified via %s at %s does not exist", + "function argument", + "fake/file/path", + ) @mock.patch.dict( os.environ, @@ -674,7 +611,9 @@ def test_default(self, mock_path_exists): mock_path_exists.return_value = True returned_path = _mtls_helper._get_cert_config_path() expected_path = os.path.expanduser( - _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH + os.path.join( + _mtls_helper._cloud_sdk.get_config_path(), "certificate_config.json" + ) ) assert returned_path == expected_path @@ -688,9 +627,15 @@ def test_env_variable(self, mock_path_exists): expected_path = "path/to/config/file" assert returned_path == expected_path - @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_env_variable_file_does_not_exist(self, mock_path_exists): + def test_default_file_does_not_exist(self, mock_path_exists): mock_path_exists.return_value = False returned_path = _mtls_helper._get_cert_config_path() assert returned_path is None @@ -730,11 +675,29 @@ def test_cert_config_path_fallback(self): os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": "path/to/config/file"} ) @mock.patch("os.path.exists", autospec=True) - def test_default_file_does_not_exist(self, mock_path_exists): + def test_env_variable_file_does_not_exist(self, mock_path_exists): mock_path_exists.return_value = False returned_path = _mtls_helper._get_cert_config_path() assert returned_path is None + @mock.patch.dict( + os.environ, + { + "GOOGLE_API_CERTIFICATE_CONFIG": "", + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "path/to/context/aware/config", + }, + ) + @mock.patch("os.path.exists", autospec=True) + def test_cert_config_path_ignore_context_aware(self, mock_path_exists): + mock_path_exists.return_value = True + returned_path = _mtls_helper._get_cert_config_path(include_context_aware=False) + expected_path = os.path.expanduser( + os.path.join( + _mtls_helper._cloud_sdk.get_config_path(), "certificate_config.json" + ) + ) + assert returned_path == expected_path + class TestGetClientCertAndKey(object): def test_callback_success(self): @@ -848,7 +811,9 @@ def test_env_var_explicit_garbage(self): "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", }, ) - def test_config_file_success(self, mock_file): + @mock.patch("os.path.exists", autospec=True) + def test_config_file_success(self, mock_exists, mock_file): + mock_exists.return_value = True # We manually apply mock_open here so we can keep autospec=True on the decorator mock_file.side_effect = mock.mock_open( read_data='{"cert_configs": {"workload": "exists"}}' @@ -865,7 +830,9 @@ def test_config_file_success(self, mock_file): "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", }, ) - def test_config_file_missing_keys(self, mock_file): + @mock.patch("os.path.exists", autospec=True) + def test_config_file_missing_keys(self, mock_exists, mock_file): + mock_exists.return_value = True mock_file.side_effect = mock.mock_open(read_data='{"cert_configs": {}}') assert _mtls_helper.check_use_client_cert() is False @@ -879,7 +846,9 @@ def test_config_file_missing_keys(self, mock_file): "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", }, ) - def test_config_file_bad_json(self, mock_file): + @mock.patch("os.path.exists", autospec=True) + def test_config_file_bad_json(self, mock_exists, mock_file): + mock_exists.return_value = True mock_file.side_effect = mock.mock_open(read_data="{bad_json") assert _mtls_helper.check_use_client_cert() is False @@ -893,12 +862,34 @@ def test_config_file_bad_json(self, mock_file): "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", }, ) - def test_config_file_not_found(self, mock_file): + @mock.patch("os.path.exists", autospec=True) + def test_config_file_not_found(self, mock_exists, mock_file): + mock_exists.return_value = True mock_file.side_effect = FileNotFoundError assert _mtls_helper.check_use_client_cert() is False + @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", + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH": "", + }, + ) + @mock.patch("os.path.exists", autospec=True) + def test_config_file_invalid_json_type(self, mock_exists, mock_file): + mock_exists.return_value = True + mock_file.side_effect = mock.mock_open(read_data="[]") + assert _mtls_helper.check_use_client_cert() is False + + @mock.patch("builtins.open", autospec=True) @mock.patch.dict(os.environ, {}, clear=True) - def test_no_env_vars_set(self): + @mock.patch("os.path.exists", autospec=True) + def test_no_env_vars_set(self, mock_exists, mock_open): + mock_exists.return_value = False + mock_open.side_effect = FileNotFoundError() assert _mtls_helper.check_use_client_cert() is False def test_use_client_cert_precedence(self): @@ -941,7 +932,9 @@ def test_use_client_cert_fallback(self): assert _mtls_helper.check_use_client_cert() is False @mock.patch("builtins.open", autospec=True) - def test_check_use_client_cert_config_fallback(self, mock_file): + @mock.patch("os.path.exists", autospec=True) + def test_check_use_client_cert_config_fallback(self, mock_exists, mock_file): + mock_exists.return_value = True # Test fallback for config file when determining if client cert should be used cloudsdk_path = "/path/to/cloudsdk/config" diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 9f3c117ed933..de3e882ba25a 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -404,13 +404,16 @@ def test_secure_authorized_channel_cert_callback_without_client_cert_env( @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) class TestSslCredentials(object): + @mock.patch("os.path.exists", autospec=True) def test_no_context_aware_metadata( self, + mock_path_exists, mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): + mock_path_exists.return_value = False # Mock that the metadata file doesn't exist. mock_check_config_path.return_value = None @@ -608,19 +611,14 @@ def test_get_client_ssl_credentials_transient_error_retry( certificate_chain=b"cert", private_key=b"key" ) - @mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", autospec=True - ) def test_get_client_ssl_credentials_auto_enablement( self, - mock_has_default_client_cert_source, mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): fake_config_content = '{"version": 1, "cert_configs": {"workload": {"cert_path": "/tmp/mock_cert.pem", "key_path": "/tmp/mock_key.pem"}}}' - mock_has_default_client_cert_source.return_value = True mock_get_client_ssl_credentials.return_value = ( True, PUBLIC_CERT_BYTES, @@ -633,9 +631,16 @@ def test_get_client_ssl_credentials_auto_enablement( { environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: "fake_config_path.json", }, - ), mock.patch("builtins.open", mock.mock_open(read_data=fake_config_content)): - # Ensure GOOGLE_API_USE_CLIENT_CERTIFICATE is not present in the environment + ), mock.patch( + "builtins.open", mock.mock_open(read_data=fake_config_content) + ), mock.patch( + "os.path.exists", return_value=True + ): + # Ensure mTLS explicit flags are not present in the environment os.environ.pop(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, None) + os.environ.pop( + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE, None + ) ssl_credentials = google.auth.transport.grpc.SslCredentials() assert ssl_credentials.ssl_credentials is not None diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py index 70f258c3ff26..ee108448fa72 100644 --- a/packages/google-auth/tests/transport/test_mtls.py +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -23,13 +23,18 @@ from google.auth.transport import mtls +@mock.patch("google.auth.transport._mtls_helper._get_cert_config_path") @mock.patch("google.auth.transport._mtls_helper._check_config_path") -def test_has_default_client_cert_source_with_context_aware_metadata(mock_check): +def test_has_default_client_cert_source_with_context_aware_metadata( + mock_check, mock_get_cert +): """ Directly tests the logic: if CONTEXT_AWARE_METADATA_PATH is found, return True. """ - # Setup: Return a path only for the Context Aware Metadata Path + # Setup: _get_cert_config_path returns None, so it falls back to context aware metadata + mock_get_cert.return_value = None + def side_effect(path): if path == _mtls_helper.CONTEXT_AWARE_METADATA_PATH: return "/path/to/context_aware_metadata.json" @@ -42,23 +47,36 @@ def side_effect(path): # Assert assert result is True + mock_get_cert.assert_called_once_with(include_context_aware=True) mock_check.assert_any_call(_mtls_helper.CONTEXT_AWARE_METADATA_PATH) - assert side_effect("non-matching-path") is None +@mock.patch("google.auth.transport._mtls_helper._get_cert_config_path") @mock.patch("google.auth.transport._mtls_helper._check_config_path") -def test_has_default_client_cert_source_falls_back(mock_check): +def test_has_default_client_cert_source_without_context_aware( + mock_check, mock_get_cert +): """ - Tests that it skips CONTEXT_AWARE_METADATA_PATH if None, and checks the next path. + Tests that if include_context_aware is False, it skips checking CONTEXT_AWARE_METADATA_PATH. """ + mock_get_cert.return_value = None - # Setup: First path is None, second path is valid - def side_effect(path): - if path == _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH: - return "/path/to/default_cert.json" - return None + result = mtls.has_default_client_cert_source(include_context_aware=False) - mock_check.side_effect = side_effect + assert result is False + mock_get_cert.assert_called_once_with(include_context_aware=False) + mock_check.assert_not_called() + + +@mock.patch("google.auth.transport._mtls_helper._check_config_path") +@mock.patch("google.auth.transport._mtls_helper._get_cert_config_path") +def test_has_default_client_cert_source_falls_back(mock_get_cert, mock_check): + """ + Tests that it checks X.509 WIF first, and if found, returns True without checking context aware metadata. + """ + + # Setup: First path is valid + mock_get_cert.return_value = "/path/to/default_cert.json" # Execute result = mtls.has_default_client_cert_source(True) @@ -66,47 +84,30 @@ def side_effect(path): # Assert assert result is True # Verify the sequence of calls - expected_calls = [ - mock.call(_mtls_helper.CONTEXT_AWARE_METADATA_PATH), - mock.call(_mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH), - ] - mock_check.assert_has_calls(expected_calls) + mock_get_cert.assert_called_once_with(include_context_aware=True) + mock_check.assert_not_called() -@mock.patch("google.auth.transport.mtls.getenv", autospec=True) +@mock.patch("google.auth.transport._mtls_helper._get_cert_config_path", autospec=True) @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) -def test_has_default_client_cert_source_env_var_success(check_config_path, mock_getenv): - # 1. Mock getenv to return our test path - mock_getenv.side_effect = lambda var: ( - "path/to/cert.json" if var == "GOOGLE_API_CERTIFICATE_CONFIG" else None - ) - - # 2. Mock _check_config_path side effect - def side_effect(path): - # Return None for legacy paths to ensure we reach the env var logic - if path == "path/to/cert.json": - return "/absolute/path/to/cert.json" - return None - - check_config_path.side_effect = side_effect +def test_has_default_client_cert_source_env_var_success( + check_config_path, get_cert_config_path +): + check_config_path.return_value = None + get_cert_config_path.return_value = "/absolute/path/to/cert.json" - # 3. This should now return True assert mtls.has_default_client_cert_source(True) - # 4. Verify the env var path was checked - check_config_path.assert_called_with("path/to/cert.json") + get_cert_config_path.assert_called_with(include_context_aware=True) -@mock.patch("google.auth.transport.mtls.getenv", autospec=True) +@mock.patch("google.auth.transport._mtls_helper._get_cert_config_path", autospec=True) @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_has_default_client_cert_source_env_var_invalid_config_path( - check_config_path, mock_getenv + check_config_path, get_cert_config_path ): - # Set the env var but make the check fail - mock_getenv.side_effect = lambda var: ( - "invalid/path" if var == "GOOGLE_API_CERTIFICATE_CONFIG" else None - ) check_config_path.return_value = None + get_cert_config_path.return_value = None assert not mtls.has_default_client_cert_source(True) diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index f14ccea58465..5aba3772132e 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -453,6 +453,141 @@ def test_configure_mtls_channel_with_metadata(self, mock_get_client_cert_and_key google.auth.transport.requests._MutualTlsAdapter, ) + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_closes_old_adapters( + self, mock_get_client_cert_and_key + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + old_main_adapter = mock.Mock(spec=requests.adapters.HTTPAdapter) + old_auth_adapter = mock.Mock(spec=requests.adapters.HTTPAdapter) + + auth_session.mount("https://", old_main_adapter) + auth_session._auth_request_session.mount("https://", old_auth_adapter) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() + + old_main_adapter.close.assert_called_once() + old_auth_adapter.close.assert_called_once() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_mounts_adapter_to_auth_request_session( + self, mock_get_client_cert_and_key + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() + + assert auth_session.is_mtls + # Main session gets the mTLS adapter + assert isinstance( + auth_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + # _auth_request_session gets a separate adapter instance + assert isinstance( + auth_session._auth_request_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + assert ( + auth_session.adapters["https://"] + is not auth_session._auth_request_session.adapters["https://"] + ) + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_without_https_adapter( + self, mock_get_client_cert_and_key + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + + # Remove the 'https://' adapter to trigger InvalidSchema + auth_session.adapters.pop("https://", None) + auth_session._auth_request_session.adapters.pop("https://", None) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() + + assert auth_session.is_mtls + # Main session gets the mTLS adapter + assert isinstance( + auth_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + # _auth_request_session gets the exact same adapter + assert isinstance( + auth_session._auth_request_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_without_auth_request_session( + self, mock_get_client_cert_and_key + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock(), auth_request=mock.Mock() + ) + assert auth_session._auth_request_session is None + + old_main_adapter = mock.Mock(spec=requests.adapters.HTTPAdapter) + auth_session.mount("https://", old_main_adapter) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() + + old_main_adapter.close.assert_called_once() + assert auth_session.is_mtls + assert isinstance( + auth_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + @mock.patch.object(google.auth.transport.requests._MutualTlsAdapter, "__init__") @mock.patch( "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 33674030aa8d..e1c92dbebc2c 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -243,6 +243,63 @@ def test_configure_mtls_channel_with_metadata( cert=pytest.public_cert_bytes, key=pytest.private_key_bytes ) + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_closes_old_poolmanager( + self, mock_get_client_cert_and_key, mock_make_mutual_tls_http + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + old_http = mock.create_autospec(urllib3.PoolManager) + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock(), http=old_http + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + is_mtls = authed_http.configure_mtls_channel() + + assert is_mtls + old_http.clear.assert_called_once() + mock_make_mutual_tls_http.assert_called_once_with( + cert=pytest.public_cert_bytes, key=pytest.private_key_bytes + ) + + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_with_none_http( + self, mock_get_client_cert_and_key, mock_make_mutual_tls_http + ): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock() + ) + authed_http.http = None # Force old_http to be None + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + is_mtls = authed_http.configure_mtls_channel() + + assert is_mtls + mock_make_mutual_tls_http.assert_called_once_with( + cert=pytest.public_cert_bytes, key=pytest.private_key_bytes + ) + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) @mock.patch( "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True