Skip to content
This repository was archived by the owner on Oct 16, 2023. It is now read-only.

Commit 8145ace

Browse files
feat: add api key support (#147)
* chore: upgrade gapic-generator-java, gax-java and gapic-generator-python PiperOrigin-RevId: 423842556 Source-Link: googleapis/googleapis@a616ca0 Source-Link: https://github.com/googleapis/googleapis-gen/commit/29b938c58c1e51d019f2ee539d55dc0a3c86a905 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMjliOTM4YzU4YzFlNTFkMDE5ZjJlZTUzOWQ1NWRjMGEzYzg2YTkwNSJ9 * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 97e49ae commit 8145ace

File tree

3 files changed

+249
-44
lines changed

3 files changed

+249
-44
lines changed

google/cloud/iam_credentials_v1/services/iam_credentials/async_client.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections import OrderedDict
1717
import functools
1818
import re
19-
from typing import Dict, Sequence, Tuple, Type, Union
19+
from typing import Dict, Optional, Sequence, Tuple, Type, Union
2020
import pkg_resources
2121

2222
from google.api_core.client_options import ClientOptions
@@ -119,6 +119,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
119119

120120
from_service_account_json = from_service_account_file
121121

122+
@classmethod
123+
def get_mtls_endpoint_and_cert_source(
124+
cls, client_options: Optional[ClientOptions] = None
125+
):
126+
"""Return the API endpoint and client cert source for mutual TLS.
127+
128+
The client cert source is determined in the following order:
129+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
130+
client cert source is None.
131+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
132+
default client cert source exists, use the default one; otherwise the client cert
133+
source is None.
134+
135+
The API endpoint is determined in the following order:
136+
(1) if `client_options.api_endpoint` if provided, use the provided one.
137+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
138+
default mTLS endpoint; if the environment variabel is "never", use the default API
139+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
140+
use the default API endpoint.
141+
142+
More details can be found at https://google.aip.dev/auth/4114.
143+
144+
Args:
145+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
146+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
147+
in this method.
148+
149+
Returns:
150+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
151+
client cert source to use.
152+
153+
Raises:
154+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
155+
"""
156+
return IAMCredentialsClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore
157+
122158
@property
123159
def transport(self) -> IAMCredentialsTransport:
124160
"""Returns the transport used by the client instance.

google/cloud/iam_credentials_v1/services/iam_credentials/client.py

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]:
245245
m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
246246
return m.groupdict() if m else {}
247247

248+
@classmethod
249+
def get_mtls_endpoint_and_cert_source(
250+
cls, client_options: Optional[client_options_lib.ClientOptions] = None
251+
):
252+
"""Return the API endpoint and client cert source for mutual TLS.
253+
254+
The client cert source is determined in the following order:
255+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
256+
client cert source is None.
257+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
258+
default client cert source exists, use the default one; otherwise the client cert
259+
source is None.
260+
261+
The API endpoint is determined in the following order:
262+
(1) if `client_options.api_endpoint` if provided, use the provided one.
263+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
264+
default mTLS endpoint; if the environment variabel is "never", use the default API
265+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
266+
use the default API endpoint.
267+
268+
More details can be found at https://google.aip.dev/auth/4114.
269+
270+
Args:
271+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
272+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
273+
in this method.
274+
275+
Returns:
276+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
277+
client cert source to use.
278+
279+
Raises:
280+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
281+
"""
282+
if client_options is None:
283+
client_options = client_options_lib.ClientOptions()
284+
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
285+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
286+
if use_client_cert not in ("true", "false"):
287+
raise ValueError(
288+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
289+
)
290+
if use_mtls_endpoint not in ("auto", "never", "always"):
291+
raise MutualTLSChannelError(
292+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
293+
)
294+
295+
# Figure out the client cert source to use.
296+
client_cert_source = None
297+
if use_client_cert == "true":
298+
if client_options.client_cert_source:
299+
client_cert_source = client_options.client_cert_source
300+
elif mtls.has_default_client_cert_source():
301+
client_cert_source = mtls.default_client_cert_source()
302+
303+
# Figure out which api endpoint to use.
304+
if client_options.api_endpoint is not None:
305+
api_endpoint = client_options.api_endpoint
306+
elif use_mtls_endpoint == "always" or (
307+
use_mtls_endpoint == "auto" and client_cert_source
308+
):
309+
api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
310+
else:
311+
api_endpoint = cls.DEFAULT_ENDPOINT
312+
313+
return api_endpoint, client_cert_source
314+
248315
def __init__(
249316
self,
250317
*,
@@ -295,57 +362,22 @@ def __init__(
295362
if client_options is None:
296363
client_options = client_options_lib.ClientOptions()
297364

298-
# Create SSL credentials for mutual TLS if needed.
299-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
300-
"true",
301-
"false",
302-
):
303-
raise ValueError(
304-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
305-
)
306-
use_client_cert = (
307-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
365+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
366+
client_options
308367
)
309368

310-
client_cert_source_func = None
311-
is_mtls = False
312-
if use_client_cert:
313-
if client_options.client_cert_source:
314-
is_mtls = True
315-
client_cert_source_func = client_options.client_cert_source
316-
else:
317-
is_mtls = mtls.has_default_client_cert_source()
318-
if is_mtls:
319-
client_cert_source_func = mtls.default_client_cert_source()
320-
else:
321-
client_cert_source_func = None
322-
323-
# Figure out which api endpoint to use.
324-
if client_options.api_endpoint is not None:
325-
api_endpoint = client_options.api_endpoint
326-
else:
327-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
328-
if use_mtls_env == "never":
329-
api_endpoint = self.DEFAULT_ENDPOINT
330-
elif use_mtls_env == "always":
331-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
332-
elif use_mtls_env == "auto":
333-
if is_mtls:
334-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
335-
else:
336-
api_endpoint = self.DEFAULT_ENDPOINT
337-
else:
338-
raise MutualTLSChannelError(
339-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
340-
"values: never, auto, always"
341-
)
369+
api_key_value = getattr(client_options, "api_key", None)
370+
if api_key_value and credentials:
371+
raise ValueError(
372+
"client_options.api_key and credentials are mutually exclusive"
373+
)
342374

343375
# Save or instantiate the transport.
344376
# Ordinarily, we provide the transport, but allowing a custom transport
345377
# instance provides an extensibility point for unusual situations.
346378
if isinstance(transport, IAMCredentialsTransport):
347379
# transport is a IAMCredentialsTransport instance.
348-
if credentials or client_options.credentials_file:
380+
if credentials or client_options.credentials_file or api_key_value:
349381
raise ValueError(
350382
"When providing a transport instance, "
351383
"provide its credentials directly."
@@ -357,6 +389,15 @@ def __init__(
357389
)
358390
self._transport = transport
359391
else:
392+
import google.auth._default # type: ignore
393+
394+
if api_key_value and hasattr(
395+
google.auth._default, "get_api_key_credentials"
396+
):
397+
credentials = google.auth._default.get_api_key_credentials(
398+
api_key_value
399+
)
400+
360401
Transport = type(self).get_transport_class(transport)
361402
self._transport = Transport(
362403
credentials=credentials,

tests/unit/gapic/iam_credentials_v1/test_iam_credentials.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,87 @@ def test_iam_credentials_client_mtls_env_auto(
400400
)
401401

402402

403+
@pytest.mark.parametrize(
404+
"client_class", [IAMCredentialsClient, IAMCredentialsAsyncClient]
405+
)
406+
@mock.patch.object(
407+
IAMCredentialsClient,
408+
"DEFAULT_ENDPOINT",
409+
modify_default_endpoint(IAMCredentialsClient),
410+
)
411+
@mock.patch.object(
412+
IAMCredentialsAsyncClient,
413+
"DEFAULT_ENDPOINT",
414+
modify_default_endpoint(IAMCredentialsAsyncClient),
415+
)
416+
def test_iam_credentials_client_get_mtls_endpoint_and_cert_source(client_class):
417+
mock_client_cert_source = mock.Mock()
418+
419+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
420+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
421+
mock_api_endpoint = "foo"
422+
options = client_options.ClientOptions(
423+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
424+
)
425+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
426+
options
427+
)
428+
assert api_endpoint == mock_api_endpoint
429+
assert cert_source == mock_client_cert_source
430+
431+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
432+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
433+
mock_client_cert_source = mock.Mock()
434+
mock_api_endpoint = "foo"
435+
options = client_options.ClientOptions(
436+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
437+
)
438+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
439+
options
440+
)
441+
assert api_endpoint == mock_api_endpoint
442+
assert cert_source is None
443+
444+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
445+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
446+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
447+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
448+
assert cert_source is None
449+
450+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
451+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
452+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
453+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
454+
assert cert_source is None
455+
456+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
457+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
458+
with mock.patch(
459+
"google.auth.transport.mtls.has_default_client_cert_source",
460+
return_value=False,
461+
):
462+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
463+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
464+
assert cert_source is None
465+
466+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
467+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
468+
with mock.patch(
469+
"google.auth.transport.mtls.has_default_client_cert_source",
470+
return_value=True,
471+
):
472+
with mock.patch(
473+
"google.auth.transport.mtls.default_client_cert_source",
474+
return_value=mock_client_cert_source,
475+
):
476+
(
477+
api_endpoint,
478+
cert_source,
479+
) = client_class.get_mtls_endpoint_and_cert_source()
480+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
481+
assert cert_source == mock_client_cert_source
482+
483+
403484
@pytest.mark.parametrize(
404485
"client_class,transport_class,transport_name",
405486
[
@@ -1464,6 +1545,23 @@ def test_credentials_transport_error():
14641545
transport=transport,
14651546
)
14661547

1548+
# It is an error to provide an api_key and a transport instance.
1549+
transport = transports.IAMCredentialsGrpcTransport(
1550+
credentials=ga_credentials.AnonymousCredentials(),
1551+
)
1552+
options = client_options.ClientOptions()
1553+
options.api_key = "api_key"
1554+
with pytest.raises(ValueError):
1555+
client = IAMCredentialsClient(client_options=options, transport=transport,)
1556+
1557+
# It is an error to provide an api_key and a credential.
1558+
options = mock.Mock()
1559+
options.api_key = "api_key"
1560+
with pytest.raises(ValueError):
1561+
client = IAMCredentialsClient(
1562+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1563+
)
1564+
14671565
# It is an error to provide scopes and a transport instance.
14681566
transport = transports.IAMCredentialsGrpcTransport(
14691567
credentials=ga_credentials.AnonymousCredentials(),
@@ -2024,3 +2122,33 @@ def test_client_ctx():
20242122
with client:
20252123
pass
20262124
close.assert_called()
2125+
2126+
2127+
@pytest.mark.parametrize(
2128+
"client_class,transport_class",
2129+
[
2130+
(IAMCredentialsClient, transports.IAMCredentialsGrpcTransport),
2131+
(IAMCredentialsAsyncClient, transports.IAMCredentialsGrpcAsyncIOTransport),
2132+
],
2133+
)
2134+
def test_api_key_credentials(client_class, transport_class):
2135+
with mock.patch.object(
2136+
google.auth._default, "get_api_key_credentials", create=True
2137+
) as get_api_key_credentials:
2138+
mock_cred = mock.Mock()
2139+
get_api_key_credentials.return_value = mock_cred
2140+
options = client_options.ClientOptions()
2141+
options.api_key = "api_key"
2142+
with mock.patch.object(transport_class, "__init__") as patched:
2143+
patched.return_value = None
2144+
client = client_class(client_options=options)
2145+
patched.assert_called_once_with(
2146+
credentials=mock_cred,
2147+
credentials_file=None,
2148+
host=client.DEFAULT_ENDPOINT,
2149+
scopes=None,
2150+
client_cert_source_for_mtls=None,
2151+
quota_project_id=None,
2152+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2153+
always_use_jwt_access=True,
2154+
)

0 commit comments

Comments
 (0)