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

Commit fdf62ae

Browse files
feat: add api key support (#230)
* 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 6ab2992 commit fdf62ae

3 files changed

Lines changed: 256 additions & 44 deletions

File tree

google/cloud/kms_v1/services/key_management_service/async_client.py

Lines changed: 38 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
@@ -145,6 +145,43 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
145145

146146
from_service_account_json = from_service_account_file
147147

148+
@classmethod
149+
def get_mtls_endpoint_and_cert_source(
150+
cls, client_options: Optional[ClientOptions] = None
151+
):
152+
"""Return the API endpoint and client cert source for mutual TLS.
153+
154+
The client cert source is determined in the following order:
155+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
156+
client cert source is None.
157+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
158+
default client cert source exists, use the default one; otherwise the client cert
159+
source is None.
160+
161+
The API endpoint is determined in the following order:
162+
(1) if `client_options.api_endpoint` if provided, use the provided one.
163+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
164+
default mTLS endpoint; if the environment variabel is "never", use the default API
165+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
166+
use the default API endpoint.
167+
168+
More details can be found at https://google.aip.dev/auth/4114.
169+
170+
171+
Args:
172+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
173+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
174+
in this method.
175+
176+
Returns:
177+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
178+
client cert source to use.
179+
180+
Raises:
181+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
182+
"""
183+
return KeyManagementServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore
184+
148185
@property
149186
def transport(self) -> KeyManagementServiceTransport:
150187
"""Returns the transport used by the client instance.

google/cloud/kms_v1/services/key_management_service/client.py

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

356+
@classmethod
357+
def get_mtls_endpoint_and_cert_source(
358+
cls, client_options: Optional[client_options_lib.ClientOptions] = None
359+
):
360+
"""Return the API endpoint and client cert source for mutual TLS.
361+
362+
The client cert source is determined in the following order:
363+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
364+
client cert source is None.
365+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
366+
default client cert source exists, use the default one; otherwise the client cert
367+
source is None.
368+
369+
The API endpoint is determined in the following order:
370+
(1) if `client_options.api_endpoint` if provided, use the provided one.
371+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
372+
default mTLS endpoint; if the environment variabel is "never", use the default API
373+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
374+
use the default API endpoint.
375+
376+
More details can be found at https://google.aip.dev/auth/4114.
377+
378+
379+
Args:
380+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
381+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
382+
in this method.
383+
384+
Returns:
385+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
386+
client cert source to use.
387+
388+
Raises:
389+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
390+
"""
391+
if client_options is None:
392+
client_options = client_options_lib.ClientOptions()
393+
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
394+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
395+
if use_client_cert not in ("true", "false"):
396+
raise ValueError(
397+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
398+
)
399+
if use_mtls_endpoint not in ("auto", "never", "always"):
400+
raise MutualTLSChannelError(
401+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
402+
)
403+
404+
# Figure out the client cert source to use.
405+
client_cert_source = None
406+
if use_client_cert == "true":
407+
if client_options.client_cert_source:
408+
client_cert_source = client_options.client_cert_source
409+
elif mtls.has_default_client_cert_source():
410+
client_cert_source = mtls.default_client_cert_source()
411+
412+
# Figure out which api endpoint to use.
413+
if client_options.api_endpoint is not None:
414+
api_endpoint = client_options.api_endpoint
415+
elif use_mtls_endpoint == "always" or (
416+
use_mtls_endpoint == "auto" and client_cert_source
417+
):
418+
api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
419+
else:
420+
api_endpoint = cls.DEFAULT_ENDPOINT
421+
422+
return api_endpoint, client_cert_source
423+
356424
def __init__(
357425
self,
358426
*,
@@ -404,57 +472,22 @@ def __init__(
404472
if client_options is None:
405473
client_options = client_options_lib.ClientOptions()
406474

407-
# Create SSL credentials for mutual TLS if needed.
408-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
409-
"true",
410-
"false",
411-
):
412-
raise ValueError(
413-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
414-
)
415-
use_client_cert = (
416-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
475+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
476+
client_options
417477
)
418478

419-
client_cert_source_func = None
420-
is_mtls = False
421-
if use_client_cert:
422-
if client_options.client_cert_source:
423-
is_mtls = True
424-
client_cert_source_func = client_options.client_cert_source
425-
else:
426-
is_mtls = mtls.has_default_client_cert_source()
427-
if is_mtls:
428-
client_cert_source_func = mtls.default_client_cert_source()
429-
else:
430-
client_cert_source_func = None
431-
432-
# Figure out which api endpoint to use.
433-
if client_options.api_endpoint is not None:
434-
api_endpoint = client_options.api_endpoint
435-
else:
436-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
437-
if use_mtls_env == "never":
438-
api_endpoint = self.DEFAULT_ENDPOINT
439-
elif use_mtls_env == "always":
440-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
441-
elif use_mtls_env == "auto":
442-
if is_mtls:
443-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
444-
else:
445-
api_endpoint = self.DEFAULT_ENDPOINT
446-
else:
447-
raise MutualTLSChannelError(
448-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
449-
"values: never, auto, always"
450-
)
479+
api_key_value = getattr(client_options, "api_key", None)
480+
if api_key_value and credentials:
481+
raise ValueError(
482+
"client_options.api_key and credentials are mutually exclusive"
483+
)
451484

452485
# Save or instantiate the transport.
453486
# Ordinarily, we provide the transport, but allowing a custom transport
454487
# instance provides an extensibility point for unusual situations.
455488
if isinstance(transport, KeyManagementServiceTransport):
456489
# transport is a KeyManagementServiceTransport instance.
457-
if credentials or client_options.credentials_file:
490+
if credentials or client_options.credentials_file or api_key_value:
458491
raise ValueError(
459492
"When providing a transport instance, "
460493
"provide its credentials directly."
@@ -466,6 +499,15 @@ def __init__(
466499
)
467500
self._transport = transport
468501
else:
502+
import google.auth._default # type: ignore
503+
504+
if api_key_value and hasattr(
505+
google.auth._default, "get_api_key_credentials"
506+
):
507+
credentials = google.auth._default.get_api_key_credentials(
508+
api_key_value
509+
)
510+
469511
Transport = type(self).get_transport_class(transport)
470512
self._transport = Transport(
471513
credentials=credentials,

tests/unit/gapic/kms_v1/test_key_management_service.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,87 @@ def test_key_management_service_client_mtls_env_auto(
422422
)
423423

424424

425+
@pytest.mark.parametrize(
426+
"client_class", [KeyManagementServiceClient, KeyManagementServiceAsyncClient]
427+
)
428+
@mock.patch.object(
429+
KeyManagementServiceClient,
430+
"DEFAULT_ENDPOINT",
431+
modify_default_endpoint(KeyManagementServiceClient),
432+
)
433+
@mock.patch.object(
434+
KeyManagementServiceAsyncClient,
435+
"DEFAULT_ENDPOINT",
436+
modify_default_endpoint(KeyManagementServiceAsyncClient),
437+
)
438+
def test_key_management_service_client_get_mtls_endpoint_and_cert_source(client_class):
439+
mock_client_cert_source = mock.Mock()
440+
441+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
442+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
443+
mock_api_endpoint = "foo"
444+
options = client_options.ClientOptions(
445+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
446+
)
447+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
448+
options
449+
)
450+
assert api_endpoint == mock_api_endpoint
451+
assert cert_source == mock_client_cert_source
452+
453+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
454+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
455+
mock_client_cert_source = mock.Mock()
456+
mock_api_endpoint = "foo"
457+
options = client_options.ClientOptions(
458+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
459+
)
460+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
461+
options
462+
)
463+
assert api_endpoint == mock_api_endpoint
464+
assert cert_source is None
465+
466+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
467+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
468+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
469+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
470+
assert cert_source is None
471+
472+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
473+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
474+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
475+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
476+
assert cert_source is None
477+
478+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
479+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
480+
with mock.patch(
481+
"google.auth.transport.mtls.has_default_client_cert_source",
482+
return_value=False,
483+
):
484+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
485+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
486+
assert cert_source is None
487+
488+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
489+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
490+
with mock.patch(
491+
"google.auth.transport.mtls.has_default_client_cert_source",
492+
return_value=True,
493+
):
494+
with mock.patch(
495+
"google.auth.transport.mtls.default_client_cert_source",
496+
return_value=mock_client_cert_source,
497+
):
498+
(
499+
api_endpoint,
500+
cert_source,
501+
) = client_class.get_mtls_endpoint_and_cert_source()
502+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
503+
assert cert_source == mock_client_cert_source
504+
505+
425506
@pytest.mark.parametrize(
426507
"client_class,transport_class,transport_name",
427508
[
@@ -7341,6 +7422,25 @@ def test_credentials_transport_error():
73417422
transport=transport,
73427423
)
73437424

7425+
# It is an error to provide an api_key and a transport instance.
7426+
transport = transports.KeyManagementServiceGrpcTransport(
7427+
credentials=ga_credentials.AnonymousCredentials(),
7428+
)
7429+
options = client_options.ClientOptions()
7430+
options.api_key = "api_key"
7431+
with pytest.raises(ValueError):
7432+
client = KeyManagementServiceClient(
7433+
client_options=options, transport=transport,
7434+
)
7435+
7436+
# It is an error to provide an api_key and a credential.
7437+
options = mock.Mock()
7438+
options.api_key = "api_key"
7439+
with pytest.raises(ValueError):
7440+
client = KeyManagementServiceClient(
7441+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
7442+
)
7443+
73447444
# It is an error to provide scopes and a transport instance.
73457445
transport = transports.KeyManagementServiceGrpcTransport(
73467446
credentials=ga_credentials.AnonymousCredentials(),
@@ -8543,3 +8643,36 @@ def test_client_ctx():
85438643
with client:
85448644
pass
85458645
close.assert_called()
8646+
8647+
8648+
@pytest.mark.parametrize(
8649+
"client_class,transport_class",
8650+
[
8651+
(KeyManagementServiceClient, transports.KeyManagementServiceGrpcTransport),
8652+
(
8653+
KeyManagementServiceAsyncClient,
8654+
transports.KeyManagementServiceGrpcAsyncIOTransport,
8655+
),
8656+
],
8657+
)
8658+
def test_api_key_credentials(client_class, transport_class):
8659+
with mock.patch.object(
8660+
google.auth._default, "get_api_key_credentials", create=True
8661+
) as get_api_key_credentials:
8662+
mock_cred = mock.Mock()
8663+
get_api_key_credentials.return_value = mock_cred
8664+
options = client_options.ClientOptions()
8665+
options.api_key = "api_key"
8666+
with mock.patch.object(transport_class, "__init__") as patched:
8667+
patched.return_value = None
8668+
client = client_class(client_options=options)
8669+
patched.assert_called_once_with(
8670+
credentials=mock_cred,
8671+
credentials_file=None,
8672+
host=client.DEFAULT_ENDPOINT,
8673+
scopes=None,
8674+
client_cert_source_for_mtls=None,
8675+
quota_project_id=None,
8676+
client_info=transports.base.DEFAULT_CLIENT_INFO,
8677+
always_use_jwt_access=True,
8678+
)

0 commit comments

Comments
 (0)