Skip to content

Commit 01705ee

Browse files
committed
refactor(generator): keep deprecated client cert fallbacks in generator templates and add TODO
1 parent d662472 commit 01705ee

2 files changed

Lines changed: 165 additions & 11 deletions

File tree

  • packages/gapic-generator/gapic/templates

packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ from google.api_core import exceptions as core_exceptions
3030
from google.api_core import extended_operation
3131
{% endif %}
3232
from google.api_core import gapic_v1
33-
from google.api_core.gapic_v1 import client_utils
3433
from google.api_core import retry as retries
3534
from google.auth import credentials as ga_credentials # type: ignore
3635
from google.auth.transport import mtls # type: ignore
@@ -191,8 +190,30 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
191190

192191
@staticmethod
193192
def _use_client_cert_effective() -> bool:
194-
"""Returns whether client certificate should be used for mTLS."""
195-
return client_utils.use_client_cert_effective()
193+
"""Returns whether client certificate should be used for mTLS if the
194+
google-auth version supports should_use_client_cert automatic mTLS enablement.
195+
196+
Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.
197+
198+
Returns:
199+
bool: whether client certificate should be used for mTLS
200+
Raises:
201+
ValueError: (If using a version of google-auth without should_use_client_cert and
202+
GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
203+
"""
204+
# TODO: Remove this fallback when google-auth >= 2.43.0 is the minimum required version
205+
# check if google-auth version supports should_use_client_cert for automatic mTLS enablement
206+
if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER
207+
return mtls.should_use_client_cert()
208+
else: # pragma: NO COVER
209+
# if unsupported, fallback to reading from env var
210+
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
211+
if use_client_cert_str not in ("true", "false"):
212+
raise ValueError(
213+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
214+
" either `true` or `false`"
215+
)
216+
return use_client_cert_str == "true"
196217

197218
@classmethod
198219
def from_service_account_info(cls, info: dict, *args, **kwargs):
@@ -331,18 +352,35 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
331352

332353
return api_endpoint, client_cert_source
333354

334-
@staticmethod
335-
def _read_environment_variables() -> Tuple[bool, str, Optional[str]]:
336-
"""Returns the environment variables used by the client."""
337-
return client_utils.read_environment_variables()
338-
339355
@staticmethod
340356
def _get_client_cert_source(
341357
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
342358
use_cert_flag: bool,
343359
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
344-
"""Return the client cert source to be used by the client."""
345-
return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag)
360+
"""Return the client cert source to be used by the client.
361+
362+
Args:
363+
provided_cert_source (Callable[[], Tuple[bytes, bytes]]): The client certificate source provided.
364+
use_cert_flag (bool): A flag indicating whether to use the client certificate.
365+
366+
Returns:
367+
Callable[[], Tuple[bytes, bytes]] or None: The client cert source to be used by the client.
368+
"""
369+
# TODO: Remove this fallback when google-auth >= 2.43.0 is the minimum required version
370+
client_cert_source = None
371+
if use_cert_flag:
372+
if provided_cert_source:
373+
client_cert_source = provided_cert_source
374+
elif (
375+
hasattr(mtls, "has_default_client_cert_source")
376+
and mtls.has_default_client_cert_source()
377+
):
378+
client_cert_source = mtls.default_client_cert_source()
379+
else:
380+
raise ValueError(
381+
"Client certificate is required for mTLS, but no client certificate source was provided or found."
382+
)
383+
return client_cert_source
346384

347385
@staticmethod
348386
def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str:
@@ -550,7 +588,15 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
550588

551589
universe_domain_opt = getattr(self._client_options, 'universe_domain', None)
552590

553-
self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = {{ service.client_name }}._read_environment_variables()
591+
# TODO: Remove the use_client_cert fallback when google-auth >= 2.43.0 is the minimum required version
592+
self._use_client_cert = {{ service.client_name }}._use_client_cert_effective()
593+
self._use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
594+
if self._use_mtls_endpoint not in ("auto", "never", "always"):
595+
raise MutualTLSChannelError(
596+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
597+
"must be `never`, `auto` or `always`"
598+
)
599+
self._universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
554600
self._client_cert_source = {{ service.client_name }}._get_client_cert_source(self._client_options.client_cert_source, self._use_client_cert)
555601
self._universe_domain = {{ service.client_name }}._get_universe_domain(universe_domain_opt, self._universe_domain_env)
556602
self._api_endpoint: str = "" # updated below, depending on `transport`

packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ from google.iam.v1 import policy_pb2 # type: ignore
101101
{{ shared_macros.add_google_api_core_version_header_import(service.version) }}
102102

103103

104+
@pytest.fixture(autouse=True)
105+
def mock_should_use_client_cert():
106+
if hasattr(google.auth.transport.mtls, "should_use_client_cert"):
107+
with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False):
108+
yield
109+
else:
110+
yield
111+
112+
104113
CRED_INFO_JSON = {
105114
"credential_source": "/path/to/file",
106115
"credential_type": "service account credentials",
@@ -683,6 +692,105 @@ def test_{{ service.client_name|snake_case }}_mtls_env_auto(client_class, transp
683692
)
684693

685694

695+
def test_use_client_cert_effective():
696+
# Test case 1: Test when `should_use_client_cert` returns True.
697+
# We mock the `should_use_client_cert` function to simulate a scenario where
698+
# the google-auth library supports automatic mTLS and determines that a
699+
# client certificate should be used.
700+
if hasattr(google.auth.transport.mtls, "should_use_client_cert"):
701+
with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=True):
702+
assert {{ service.client_name }}._use_client_cert_effective() is True
703+
704+
# Test case 2: Test when `should_use_client_cert` returns False.
705+
# We mock the `should_use_client_cert` function to simulate a scenario where
706+
# the google-auth library supports automatic mTLS and determines that a
707+
# client certificate should NOT be used.
708+
if hasattr(google.auth.transport.mtls, "should_use_client_cert"):
709+
with mock.patch("google.auth.transport.mtls.should_use_client_cert", return_value=False):
710+
assert {{ service.client_name }}._use_client_cert_effective() is False
711+
712+
# Test case 3: Test when `should_use_client_cert` is unavailable and the
713+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true".
714+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
715+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
716+
assert {{ service.client_name }}._use_client_cert_effective() is True
717+
718+
# Test case 4: Test when `should_use_client_cert` is unavailable and the
719+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false".
720+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
721+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
722+
assert {{ service.client_name }}._use_client_cert_effective() is False
723+
724+
# Test case 5: Test when `should_use_client_cert` is unavailable and the
725+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True".
726+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
727+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}):
728+
assert {{ service.client_name }}._use_client_cert_effective() is True
729+
730+
# Test case 6: Test when `should_use_client_cert` is unavailable and the
731+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False".
732+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
733+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"}):
734+
assert {{ service.client_name }}._use_client_cert_effective() is False
735+
736+
# Test case 7: Test when `should_use_client_cert` is unavailable and the
737+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE".
738+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
739+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}):
740+
assert {{ service.client_name }}._use_client_cert_effective() is True
741+
742+
# Test case 8: Test when `should_use_client_cert` is unavailable and the
743+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE".
744+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
745+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"}):
746+
assert {{ service.client_name }}._use_client_cert_effective() is False
747+
748+
# Test case 9: Test when `should_use_client_cert` is unavailable and the
749+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set.
750+
# In this case, the method should return False, which is the default value.
751+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
752+
with mock.patch.dict(os.environ, clear=True):
753+
assert {{ service.client_name }}._use_client_cert_effective() is False
754+
755+
# Test case 10: Test when `should_use_client_cert` is unavailable and the
756+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value.
757+
# The method should raise a ValueError as the environment variable must be either
758+
# "true" or "false".
759+
if not hasattr(google.auth.transport.mtls, "should_use_client_cert"):
760+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}):
761+
with pytest.raises(ValueError):
762+
{{ service.client_name }}._use_client_cert_effective()
763+
764+
# Test case 11: Test when `should_use_client_cert` is available and the
765+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value.
766+
# The method should return False as the environment variable is set to an invalid value.
767+
if hasattr(google.auth.transport.mtls, "should_use_client_cert"):
768+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"}):
769+
assert {{ service.client_name }}._use_client_cert_effective() is False
770+
771+
# Test case 12: Test when `should_use_client_cert` is available and the
772+
# `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also,
773+
# the GOOGLE_API_CONFIG environment variable is unset.
774+
if hasattr(google.auth.transport.mtls, "should_use_client_cert"):
775+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}):
776+
with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}):
777+
assert {{ service.client_name }}._use_client_cert_effective() is False
778+
779+
780+
def test__get_client_cert_source():
781+
mock_provided_cert_source = mock.Mock()
782+
mock_default_cert_source = mock.Mock()
783+
784+
assert {{ service.client_name }}._get_client_cert_source(None, False) is None
785+
assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, False) is None
786+
assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source
787+
788+
with mock.patch('google.auth.transport.mtls.has_default_client_cert_source', return_value=True):
789+
with mock.patch('google.auth.transport.mtls.default_client_cert_source', return_value=mock_default_cert_source):
790+
assert {{ service.client_name }}._get_client_cert_source(None, True) is mock_default_cert_source
791+
assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source
792+
793+
686794
@pytest.mark.parametrize("client_class", [
687795
{% if 'grpc' in opts.transport %}
688796
{{ service.client_name }}, {{ service.async_client_name }}

0 commit comments

Comments
 (0)