Skip to content

Commit 1abba92

Browse files
committed
refactor(api-core): parse ports using urlparse and utilize should_use_mtls_endpoint
1 parent 2aeabc7 commit 1abba92

2 files changed

Lines changed: 84 additions & 39 deletions

File tree

packages/google-api-core/google/api_core/gapic_v1/client_utils.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
"""Helpers for client setup and configuration."""
1818

19+
import os
1920
from typing import Callable, Optional, Tuple
21+
from urllib.parse import urlparse, urlunparse
2022

23+
import google.auth.transport.mtls # type: ignore
2124
from google.auth.exceptions import MutualTLSChannelError # type: ignore
2225

2326

@@ -38,28 +41,62 @@ def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
3841
if not api_endpoint or ".mtls." in api_endpoint.lower():
3942
return api_endpoint
4043

41-
# Handle optional port suffix (e.g. ":443")
42-
parts = api_endpoint.split(":")
43-
host = parts[0]
44-
port = ":" + parts[1] if len(parts) > 1 else ""
44+
has_scheme = "://" in api_endpoint
45+
if not has_scheme:
46+
parsed = urlparse("//" + api_endpoint)
47+
else:
48+
parsed = urlparse(api_endpoint)
49+
50+
host = parsed.hostname
51+
if not host:
52+
return api_endpoint
53+
54+
port = f":{parsed.port}" if parsed.port else ""
4555

4656
lowered_host = host.lower()
4757
if lowered_host.endswith(".sandbox.googleapis.com"):
48-
# len(".sandbox.googleapis.com") == 23
49-
return host[:-23] + ".mtls.sandbox.googleapis.com" + port
58+
new_host = host[:-23] + ".mtls.sandbox.googleapis.com"
59+
elif lowered_host.endswith(".googleapis.com"):
60+
new_host = host[:-15] + ".mtls.googleapis.com"
61+
else:
62+
return api_endpoint
5063

51-
if lowered_host.endswith(".googleapis.com"):
52-
# len(".googleapis.com") == 15
53-
return host[:-15] + ".mtls.googleapis.com" + port
64+
netloc = new_host + port
65+
new_parsed = parsed._replace(netloc=netloc)
5466

55-
return api_endpoint
67+
if not has_scheme:
68+
return urlunparse(new_parsed)[2:]
69+
else:
70+
return urlunparse(new_parsed)
71+
72+
73+
def should_use_mtls_endpoint(client_cert_available: bool) -> bool:
74+
"""Helper to determine whether to use mTLS endpoint.
75+
76+
Uses google.auth.transport.mtls.should_use_mtls_endpoint if available,
77+
otherwise falls back to evaluation of GOOGLE_API_USE_MTLS_ENDPOINT.
78+
"""
79+
if hasattr(google.auth.transport.mtls, "should_use_mtls_endpoint"):
80+
return google.auth.transport.mtls.should_use_mtls_endpoint(client_cert_available)
81+
82+
# Fallback logic for older google-auth versions
83+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").strip().lower()
84+
if use_mtls_endpoint == "always":
85+
return True
86+
elif use_mtls_endpoint == "never":
87+
return False
88+
elif use_mtls_endpoint == "auto":
89+
return client_cert_available
90+
else:
91+
raise MutualTLSChannelError(
92+
f"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
93+
)
5694

5795

5896
def get_api_endpoint(
5997
api_override: Optional[str],
6098
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
6199
universe_domain: str,
62-
use_mtls_endpoint: str,
63100
default_universe: str,
64101
default_mtls_endpoint: Optional[str],
65102
default_endpoint_template: str,
@@ -72,8 +109,6 @@ def get_api_endpoint(
72109
client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): The client
73110
certificate source used by the client.
74111
universe_domain (str): The universe domain used by the client.
75-
use_mtls_endpoint (str): How to use the mTLS endpoint. Possible values
76-
are "always", "auto", or "never".
77112
default_universe (str): The default universe domain.
78113
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
79114
default_endpoint_template (str): The default endpoint template containing
@@ -89,9 +124,9 @@ def get_api_endpoint(
89124
"""
90125
if api_override is not None:
91126
return api_override
92-
elif use_mtls_endpoint == "always" or (
93-
use_mtls_endpoint == "auto" and client_cert_source
94-
):
127+
128+
client_cert_available = client_cert_source is not None
129+
if should_use_mtls_endpoint(client_cert_available):
95130
if universe_domain.lower() != default_universe.lower():
96131
raise MutualTLSChannelError(
97132
f"mTLS is not supported in any universe other than {default_universe}."

packages/google-api-core/tests/unit/gapic/test_client_utils.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
import os
1617
from unittest import mock
1718

1819
import pytest
@@ -45,6 +46,16 @@ def test_get_default_mtls_endpoint():
4546
== "foo.mtls.sandbox.googleapis.com"
4647
)
4748

49+
# Test valid API endpoints with schemes
50+
assert (
51+
get_default_mtls_endpoint("https://foo.googleapis.com")
52+
== "https://foo.mtls.googleapis.com"
53+
)
54+
assert (
55+
get_default_mtls_endpoint("http://foo.googleapis.com:8080/v1")
56+
== "http://foo.mtls.googleapis.com:8080/v1"
57+
)
58+
4859
# Test valid API endpoints with ports
4960
assert (
5061
get_default_mtls_endpoint("foo.googleapis.com:443")
@@ -182,30 +193,29 @@ def test_get_api_endpoint(
182193
default_endpoint_template,
183194
expected,
184195
):
185-
if isinstance(expected, type) and issubclass(expected, Exception):
186-
with pytest.raises(expected):
187-
get_api_endpoint(
188-
api_override,
189-
client_cert_source,
190-
universe_domain,
191-
use_mtls_endpoint,
192-
default_universe,
193-
default_mtls_endpoint,
194-
default_endpoint_template,
195-
)
196-
else:
197-
assert (
198-
get_api_endpoint(
199-
api_override,
200-
client_cert_source,
201-
universe_domain,
202-
use_mtls_endpoint,
203-
default_universe,
204-
default_mtls_endpoint,
205-
default_endpoint_template,
196+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_endpoint}):
197+
if isinstance(expected, type) and issubclass(expected, Exception):
198+
with pytest.raises(expected):
199+
get_api_endpoint(
200+
api_override,
201+
client_cert_source,
202+
universe_domain,
203+
default_universe,
204+
default_mtls_endpoint,
205+
default_endpoint_template,
206+
)
207+
else:
208+
assert (
209+
get_api_endpoint(
210+
api_override,
211+
client_cert_source,
212+
universe_domain,
213+
default_universe,
214+
default_mtls_endpoint,
215+
default_endpoint_template,
216+
)
217+
== expected
206218
)
207-
== expected
208-
)
209219

210220

211221
def test__get_universe_domain():

0 commit comments

Comments
 (0)