1414# limitations under the License.
1515#
1616
17- """Helpers for client setup and configuration."""
18-
1917import os
20- from typing import Any , Callable , Optional , Tuple
18+ from typing import Callable , Optional , Tuple
19+ from urllib .parse import urlparse , urlunparse
2120
21+ import google .auth .transport .mtls # type: ignore
2222from google .auth .exceptions import MutualTLSChannelError # type: ignore
23- from google .auth .transport import mtls # type: ignore
2423
2524
2625def get_default_mtls_endpoint (api_endpoint : Optional [str ]) -> Optional [str ]:
2726 """Converts api endpoint to mTLS endpoint.
2827
2928 Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
3029 "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
30+ Other URLs (including those that do not match these domain suffixes or
31+ already contain '.mtls.') are passed through as-is.
3132
3233 Args:
3334 api_endpoint (Optional[str]): the api endpoint to convert.
3435
3536 Returns:
3637 Optional[str]: converted mTLS api endpoint.
3738 """
38- if not api_endpoint or ".mtls." in api_endpoint :
39+ if not api_endpoint or ".mtls." in api_endpoint .lower ():
40+ return api_endpoint
41+
42+ has_scheme = "://" in api_endpoint
43+ if not has_scheme :
44+ parsed = urlparse ("//" + api_endpoint )
45+ else :
46+ parsed = urlparse (api_endpoint )
47+
48+ host = parsed .hostname
49+ if not host :
3950 return api_endpoint
4051
41- if api_endpoint .endswith (".sandbox.googleapis.com" ):
42- # len(".sandbox.googleapis.com") == 23
43- return api_endpoint [:- 23 ] + ".mtls.sandbox.googleapis.com"
52+ port = f":{ parsed .port } " if parsed .port else ""
4453
45- if api_endpoint .endswith (".googleapis.com" ):
46- # len(".googleapis.com") == 15
47- return api_endpoint [:- 15 ] + ".mtls.googleapis.com"
54+ lowered_host = host .lower ()
55+ if lowered_host .endswith (".sandbox.googleapis.com" ):
56+ new_host = host [:- 23 ] + ".mtls.sandbox.googleapis.com"
57+ elif lowered_host .endswith (".googleapis.com" ):
58+ new_host = host [:- 15 ] + ".mtls.googleapis.com"
59+ else :
60+ return api_endpoint
4861
49- return api_endpoint
62+ netloc = new_host + port
63+ new_parsed = parsed ._replace (netloc = netloc )
64+
65+ if not has_scheme :
66+ return urlunparse (new_parsed )[2 :]
67+ else :
68+ return urlunparse (new_parsed )
69+
70+
71+ def should_use_mtls_endpoint (client_cert_available : bool ) -> bool :
72+ """Helper to determine whether to use mTLS endpoint.
73+
74+ Uses google.auth.transport.mtls.should_use_mtls_endpoint if available,
75+ otherwise falls back to evaluation of GOOGLE_API_USE_MTLS_ENDPOINT.
76+ """
77+ if hasattr (google .auth .transport .mtls , "should_use_mtls_endpoint" ):
78+ return google .auth .transport .mtls .should_use_mtls_endpoint (client_cert_available )
79+
80+ # Fallback logic for older google-auth versions
81+ use_mtls_endpoint = os .getenv ("GOOGLE_API_USE_MTLS_ENDPOINT" , "auto" ).strip ().lower ()
82+ if use_mtls_endpoint == "always" :
83+ return True
84+ elif use_mtls_endpoint == "never" :
85+ return False
86+ elif use_mtls_endpoint == "auto" :
87+ return client_cert_available
88+ else :
89+ raise MutualTLSChannelError (
90+ f"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
91+ )
5092
5193
5294def get_api_endpoint (
5395 api_override : Optional [str ],
54- client_cert_source : Optional [Any ],
96+ client_cert_source : Optional [Callable [[], Tuple [ bytes , bytes ]] ],
5597 universe_domain : str ,
56- use_mtls_endpoint : str ,
5798 default_universe : str ,
5899 default_mtls_endpoint : Optional [str ],
59100 default_endpoint_template : str ,
@@ -63,10 +104,9 @@ def get_api_endpoint(
63104 Args:
64105 api_override (Optional[str]): The API endpoint override. If specified,
65106 this is always returned.
66- client_cert_source (Optional[Any]): The client certificate source used by the client.
107+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): The client
108+ certificate source used by the client.
67109 universe_domain (str): The universe domain used by the client.
68- use_mtls_endpoint (str): How to use the mTLS endpoint. Possible values
69- are "always", "auto", or "never".
70110 default_universe (str): The default universe domain.
71111 default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
72112 default_endpoint_template (str): The default endpoint template containing
@@ -82,9 +122,9 @@ def get_api_endpoint(
82122 """
83123 if api_override is not None :
84124 return api_override
85- elif use_mtls_endpoint == "always" or (
86- use_mtls_endpoint == "auto" and client_cert_source
87- ):
125+
126+ client_cert_available = client_cert_source is not None
127+ if should_use_mtls_endpoint ( client_cert_available ):
88128 if universe_domain .lower () != default_universe .lower ():
89129 raise MutualTLSChannelError (
90130 f"mTLS is not supported in any universe other than { default_universe } ."
0 commit comments