Skip to content

Commit bc3f542

Browse files
feat: add api key support (#15)
* chore: upgrade gapic-generator-java, gax-java and gapic-generator-python PiperOrigin-RevId: 423842556 Source-Link: googleapis/googleapis@a616ca0 Source-Link: googleapis/googleapis-gen@29b938c 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 c06c06f commit bc3f542

File tree

3 files changed

+241
-44
lines changed

3 files changed

+241
-44
lines changed

packages/google-cloud-ids/google/cloud/ids_v1/services/ids/async_client.py

+37-1
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
@@ -100,6 +100,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
100100

101101
from_service_account_json = from_service_account_file
102102

103+
@classmethod
104+
def get_mtls_endpoint_and_cert_source(
105+
cls, client_options: Optional[ClientOptions] = None
106+
):
107+
"""Return the API endpoint and client cert source for mutual TLS.
108+
109+
The client cert source is determined in the following order:
110+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
111+
client cert source is None.
112+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
113+
default client cert source exists, use the default one; otherwise the client cert
114+
source is None.
115+
116+
The API endpoint is determined in the following order:
117+
(1) if `client_options.api_endpoint` if provided, use the provided one.
118+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
119+
default mTLS endpoint; if the environment variabel is "never", use the default API
120+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
121+
use the default API endpoint.
122+
123+
More details can be found at https://google.aip.dev/auth/4114.
124+
125+
Args:
126+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
127+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
128+
in this method.
129+
130+
Returns:
131+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
132+
client cert source to use.
133+
134+
Raises:
135+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
136+
"""
137+
return IDSClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore
138+
103139
@property
104140
def transport(self) -> IDSTransport:
105141
"""Returns the transport used by the client instance.

packages/google-cloud-ids/google/cloud/ids_v1/services/ids/client.py

+84-43
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]:
236236
m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
237237
return m.groupdict() if m else {}
238238

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

289-
# Create SSL credentials for mutual TLS if needed.
290-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
291-
"true",
292-
"false",
293-
):
294-
raise ValueError(
295-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
296-
)
297-
use_client_cert = (
298-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
356+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
357+
client_options
299358
)
300359

301-
client_cert_source_func = None
302-
is_mtls = False
303-
if use_client_cert:
304-
if client_options.client_cert_source:
305-
is_mtls = True
306-
client_cert_source_func = client_options.client_cert_source
307-
else:
308-
is_mtls = mtls.has_default_client_cert_source()
309-
if is_mtls:
310-
client_cert_source_func = mtls.default_client_cert_source()
311-
else:
312-
client_cert_source_func = None
313-
314-
# Figure out which api endpoint to use.
315-
if client_options.api_endpoint is not None:
316-
api_endpoint = client_options.api_endpoint
317-
else:
318-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
319-
if use_mtls_env == "never":
320-
api_endpoint = self.DEFAULT_ENDPOINT
321-
elif use_mtls_env == "always":
322-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
323-
elif use_mtls_env == "auto":
324-
if is_mtls:
325-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
326-
else:
327-
api_endpoint = self.DEFAULT_ENDPOINT
328-
else:
329-
raise MutualTLSChannelError(
330-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
331-
"values: never, auto, always"
332-
)
360+
api_key_value = getattr(client_options, "api_key", None)
361+
if api_key_value and credentials:
362+
raise ValueError(
363+
"client_options.api_key and credentials are mutually exclusive"
364+
)
333365

334366
# Save or instantiate the transport.
335367
# Ordinarily, we provide the transport, but allowing a custom transport
336368
# instance provides an extensibility point for unusual situations.
337369
if isinstance(transport, IDSTransport):
338370
# transport is a IDSTransport instance.
339-
if credentials or client_options.credentials_file:
371+
if credentials or client_options.credentials_file or api_key_value:
340372
raise ValueError(
341373
"When providing a transport instance, "
342374
"provide its credentials directly."
@@ -348,6 +380,15 @@ def __init__(
348380
)
349381
self._transport = transport
350382
else:
383+
import google.auth._default # type: ignore
384+
385+
if api_key_value and hasattr(
386+
google.auth._default, "get_api_key_credentials"
387+
):
388+
credentials = google.auth._default.get_api_key_credentials(
389+
api_key_value
390+
)
391+
351392
Transport = type(self).get_transport_class(transport)
352393
self._transport = Transport(
353394
credentials=credentials,

packages/google-cloud-ids/tests/unit/gapic/ids_v1/test_ids.py

+120
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,79 @@ def test_ids_client_mtls_env_auto(
357357
)
358358

359359

360+
@pytest.mark.parametrize("client_class", [IDSClient, IDSAsyncClient])
361+
@mock.patch.object(IDSClient, "DEFAULT_ENDPOINT", modify_default_endpoint(IDSClient))
362+
@mock.patch.object(
363+
IDSAsyncClient, "DEFAULT_ENDPOINT", modify_default_endpoint(IDSAsyncClient)
364+
)
365+
def test_ids_client_get_mtls_endpoint_and_cert_source(client_class):
366+
mock_client_cert_source = mock.Mock()
367+
368+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
369+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
370+
mock_api_endpoint = "foo"
371+
options = client_options.ClientOptions(
372+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
373+
)
374+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
375+
options
376+
)
377+
assert api_endpoint == mock_api_endpoint
378+
assert cert_source == mock_client_cert_source
379+
380+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
381+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
382+
mock_client_cert_source = mock.Mock()
383+
mock_api_endpoint = "foo"
384+
options = client_options.ClientOptions(
385+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
386+
)
387+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
388+
options
389+
)
390+
assert api_endpoint == mock_api_endpoint
391+
assert cert_source is None
392+
393+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
394+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
395+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
396+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
397+
assert cert_source is None
398+
399+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
400+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
401+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
402+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
403+
assert cert_source is None
404+
405+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
406+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
407+
with mock.patch(
408+
"google.auth.transport.mtls.has_default_client_cert_source",
409+
return_value=False,
410+
):
411+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
412+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
413+
assert cert_source is None
414+
415+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
416+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
417+
with mock.patch(
418+
"google.auth.transport.mtls.has_default_client_cert_source",
419+
return_value=True,
420+
):
421+
with mock.patch(
422+
"google.auth.transport.mtls.default_client_cert_source",
423+
return_value=mock_client_cert_source,
424+
):
425+
(
426+
api_endpoint,
427+
cert_source,
428+
) = client_class.get_mtls_endpoint_and_cert_source()
429+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
430+
assert cert_source == mock_client_cert_source
431+
432+
360433
@pytest.mark.parametrize(
361434
"client_class,transport_class,transport_name",
362435
[
@@ -1404,6 +1477,23 @@ def test_credentials_transport_error():
14041477
transport=transport,
14051478
)
14061479

1480+
# It is an error to provide an api_key and a transport instance.
1481+
transport = transports.IDSGrpcTransport(
1482+
credentials=ga_credentials.AnonymousCredentials(),
1483+
)
1484+
options = client_options.ClientOptions()
1485+
options.api_key = "api_key"
1486+
with pytest.raises(ValueError):
1487+
client = IDSClient(client_options=options, transport=transport,)
1488+
1489+
# It is an error to provide an api_key and a credential.
1490+
options = mock.Mock()
1491+
options.api_key = "api_key"
1492+
with pytest.raises(ValueError):
1493+
client = IDSClient(
1494+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1495+
)
1496+
14071497
# It is an error to provide scopes and a transport instance.
14081498
transport = transports.IDSGrpcTransport(
14091499
credentials=ga_credentials.AnonymousCredentials(),
@@ -1967,3 +2057,33 @@ def test_client_ctx():
19672057
with client:
19682058
pass
19692059
close.assert_called()
2060+
2061+
2062+
@pytest.mark.parametrize(
2063+
"client_class,transport_class",
2064+
[
2065+
(IDSClient, transports.IDSGrpcTransport),
2066+
(IDSAsyncClient, transports.IDSGrpcAsyncIOTransport),
2067+
],
2068+
)
2069+
def test_api_key_credentials(client_class, transport_class):
2070+
with mock.patch.object(
2071+
google.auth._default, "get_api_key_credentials", create=True
2072+
) as get_api_key_credentials:
2073+
mock_cred = mock.Mock()
2074+
get_api_key_credentials.return_value = mock_cred
2075+
options = client_options.ClientOptions()
2076+
options.api_key = "api_key"
2077+
with mock.patch.object(transport_class, "__init__") as patched:
2078+
patched.return_value = None
2079+
client = client_class(client_options=options)
2080+
patched.assert_called_once_with(
2081+
credentials=mock_cred,
2082+
credentials_file=None,
2083+
host=client.DEFAULT_ENDPOINT,
2084+
scopes=None,
2085+
client_cert_source_for_mtls=None,
2086+
quota_project_id=None,
2087+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2088+
always_use_jwt_access=True,
2089+
)

0 commit comments

Comments
 (0)