Skip to content

Commit a694ceb

Browse files
feat: add cred info to auth related errors (#2115)
1 parent 558e2e2 commit a694ceb

File tree

17 files changed

+850
-151
lines changed

17 files changed

+850
-151
lines changed

gapic/templates/%namespace/%name_%version/%sub/services/%service/_mixins.py.j2

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@
4343
# Validate the universe domain.
4444
self._validate_universe_domain()
4545

46-
# Send the request.
47-
response = rpc(
48-
request, retry=retry, timeout=timeout, metadata=metadata,)
46+
try:
47+
# Send the request.
48+
response = rpc(
49+
request, retry=retry, timeout=timeout, metadata=metadata,)
4950

50-
# Done; return the response.
51-
return response
51+
# Done; return the response.
52+
return response
53+
except core_exceptions.GoogleAPICallError as e:
54+
self._add_cred_info_for_auth_errors(e)
55+
raise e
5256

5357
{% endif %}
5458

@@ -95,12 +99,16 @@
9599
# Validate the universe domain.
96100
self._validate_universe_domain()
97101

98-
# Send the request.
99-
response = rpc(
100-
request, retry=retry, timeout=timeout, metadata=metadata,)
102+
try:
103+
# Send the request.
104+
response = rpc(
105+
request, retry=retry, timeout=timeout, metadata=metadata,)
101106

102-
# Done; return the response.
103-
return response
107+
# Done; return the response.
108+
return response
109+
except core_exceptions.GoogleAPICallError as e:
110+
self._add_cred_info_for_auth_errors(e)
111+
raise e
104112
{% endif %}
105113

106114
{% if "DeleteOperation" in api.mixin_api_methods %}
@@ -253,12 +261,16 @@
253261
# Validate the universe domain.
254262
self._validate_universe_domain()
255263

256-
# Send the request.
257-
response = rpc(
258-
request, retry=retry, timeout=timeout, metadata=metadata,)
264+
try:
265+
# Send the request.
266+
response = rpc(
267+
request, retry=retry, timeout=timeout, metadata=metadata,)
259268

260-
# Done; return the response.
261-
return response
269+
# Done; return the response.
270+
return response
271+
except core_exceptions.GoogleAPICallError as e:
272+
self._add_cred_info_for_auth_errors(e)
273+
raise e
262274
{% endif %}
263275
{% endif %} {# LRO #}
264276

@@ -375,12 +387,16 @@
375387
# Validate the universe domain.
376388
self._validate_universe_domain()
377389

378-
# Send the request.
379-
response = rpc(
380-
request, retry=retry, timeout=timeout, metadata=metadata,)
390+
try:
391+
# Send the request.
392+
response = rpc(
393+
request, retry=retry, timeout=timeout, metadata=metadata,)
381394

382-
# Done; return the response.
383-
return response
395+
# Done; return the response.
396+
return response
397+
except core_exceptions.GoogleAPICallError as e:
398+
self._add_cred_info_for_auth_errors(e)
399+
raise e
384400
{% endif %}
385401

386402
{% if "GetIamPolicy" in api.mixin_api_methods %}
@@ -493,12 +509,16 @@
493509
# Validate the universe domain.
494510
self._validate_universe_domain()
495511

496-
# Send the request.
497-
response = rpc(
498-
request, retry=retry, timeout=timeout, metadata=metadata,)
512+
try:
513+
# Send the request.
514+
response = rpc(
515+
request, retry=retry, timeout=timeout, metadata=metadata,)
499516

500-
# Done; return the response.
501-
return response
517+
# Done; return the response.
518+
return response
519+
except core_exceptions.GoogleAPICallError as e:
520+
self._add_cred_info_for_auth_errors(e)
521+
raise e
502522
{% endif %}
503523

504524
{% if "TestIamPermissions" in api.mixin_api_methods %}
@@ -549,12 +569,16 @@
549569
# Validate the universe domain.
550570
self._validate_universe_domain()
551571

552-
# Send the request.
553-
response = rpc(
554-
request, retry=retry, timeout=timeout, metadata=metadata,)
572+
try:
573+
# Send the request.
574+
response = rpc(
575+
request, retry=retry, timeout=timeout, metadata=metadata,)
555576

556-
# Done; return the response.
557-
return response
577+
# Done; return the response.
578+
return response
579+
except core_exceptions.GoogleAPICallError as e:
580+
self._add_cred_info_for_auth_errors(e)
581+
raise e
558582
{% endif %}
559583
{% endif %}
560584

@@ -604,12 +628,16 @@
604628
# Validate the universe domain.
605629
self._validate_universe_domain()
606630

607-
# Send the request.
608-
response = rpc(
609-
request, retry=retry, timeout=timeout, metadata=metadata,)
631+
try:
632+
# Send the request.
633+
response = rpc(
634+
request, retry=retry, timeout=timeout, metadata=metadata,)
610635

611-
# Done; return the response.
612-
return response
636+
# Done; return the response.
637+
return response
638+
except core_exceptions.GoogleAPICallError as e:
639+
self._add_cred_info_for_auth_errors(e)
640+
raise e
613641
{% endif %}
614642

615643
{% if "ListLocations" in api.mixin_api_methods %}
@@ -655,11 +683,15 @@
655683
# Validate the universe domain.
656684
self._validate_universe_domain()
657685

658-
# Send the request.
659-
response = rpc(
660-
request, retry=retry, timeout=timeout, metadata=metadata,)
686+
try:
687+
# Send the request.
688+
response = rpc(
689+
request, retry=retry, timeout=timeout, metadata=metadata,)
661690

662-
# Done; return the response.
663-
return response
691+
# Done; return the response.
692+
return response
693+
except core_exceptions.GoogleAPICallError as e:
694+
self._add_cred_info_for_auth_errors(e)
695+
raise e
664696
{% endif %}
665697
{% endif %}

gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ from collections import OrderedDict
1010
{% if service.any_extended_operations_methods %}
1111
import functools
1212
{% endif %}
13+
from http import HTTPStatus
14+
import json
1315
import logging as std_logging
1416
import os
1517
import re
@@ -423,6 +425,30 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
423425
# NOTE (b/349488459): universe validation is disabled until further notice.
424426
return True
425427

428+
def _add_cred_info_for_auth_errors(
429+
self,
430+
error: core_exceptions.GoogleAPICallError
431+
) -> None:
432+
"""Adds credential info string to error details for 401/403/404 errors.
433+
434+
Args:
435+
error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info.
436+
"""
437+
if error.code not in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN, HTTPStatus.NOT_FOUND]:
438+
return
439+
440+
cred = self._transport._credentials
441+
442+
# get_cred_info is only available in google-auth>=2.35.0
443+
if not hasattr(cred, "get_cred_info"):
444+
return
445+
446+
# ignore the type check since pypy test fails when get_cred_info
447+
# is not available
448+
cred_info = cred.get_cred_info() # type: ignore
449+
if cred_info and hasattr(error._details, "append"):
450+
error._details.append(json.dumps(cred_info))
451+
426452
@property
427453
def api_endpoint(self):
428454
"""Return the API endpoint used by the client instance.
@@ -765,12 +791,16 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
765791
# Validate the universe domain.
766792
self._validate_universe_domain()
767793

768-
# Send the request.
769-
response = rpc(
770-
request, retry=retry, timeout=timeout, metadata=metadata,)
794+
try:
795+
# Send the request.
796+
response = rpc(
797+
request, retry=retry, timeout=timeout, metadata=metadata,)
771798

772-
# Done; return the response.
773-
return response
799+
# Done; return the response.
800+
return response
801+
except core_exceptions.GoogleAPICallError as e:
802+
self._add_cred_info_for_auth_errors(e)
803+
raise e
774804

775805
def get_iam_policy(
776806
self,
@@ -885,12 +915,16 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
885915
# Validate the universe domain.
886916
self._validate_universe_domain()
887917

888-
# Send the request.
889-
response = rpc(
890-
request, retry=retry, timeout=timeout, metadata=metadata,)
918+
try:
919+
# Send the request.
920+
response = rpc(
921+
request, retry=retry, timeout=timeout, metadata=metadata,)
891922

892-
# Done; return the response.
893-
return response
923+
# Done; return the response.
924+
return response
925+
except core_exceptions.GoogleAPICallError as e:
926+
self._add_cred_info_for_auth_errors(e)
927+
raise e
894928

895929
def test_iam_permissions(
896930
self,
@@ -943,12 +977,16 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
943977
# Validate the universe domain.
944978
self._validate_universe_domain()
945979

946-
# Send the request.
947-
response = rpc(
948-
request, retry=retry, timeout=timeout, metadata=metadata,)
980+
try:
981+
# Send the request.
982+
response = rpc(
983+
request, retry=retry, timeout=timeout, metadata=metadata,)
949984

950-
# Done; return the response.
951-
return response
985+
# Done; return the response.
986+
return response
987+
except core_exceptions.GoogleAPICallError as e:
988+
self._add_cred_info_for_auth_errors(e)
989+
raise e
952990
{% endif %}
953991

954992
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(gapic_version=package_version.__version__)

gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ from grpc.experimental import aio
2222
{% if "rest" in opts.transport %}
2323
from collections.abc import Iterable, AsyncIterable
2424
from google.protobuf import json_format
25-
import json
2625
{% endif %}
26+
import json
2727
import math
2828
import pytest
2929
from google.api_core import api_core_version
@@ -102,6 +102,15 @@ from google.iam.v1 import policy_pb2 # type: ignore
102102
{% endfilter %}
103103
{{ shared_macros.add_google_api_core_version_header_import(service.version) }}
104104

105+
106+
CRED_INFO_JSON = {
107+
"credential_source": "/path/to/file",
108+
"credential_type": "service account credentials",
109+
"principal": "service-account@example.com",
110+
}
111+
CRED_INFO_STRING = json.dumps(CRED_INFO_JSON)
112+
113+
105114
async def mock_async_gen(data, chunk_size=1):
106115
for i in range(0, len(data)): # pragma: NO COVER
107116
chunk = data[i : i + chunk_size]
@@ -268,6 +277,43 @@ def test__get_universe_domain():
268277
{{ service.client_name }}._get_universe_domain("", None)
269278
assert str(excinfo.value) == "Universe Domain cannot be an empty string."
270279

280+
@pytest.mark.parametrize("error_code,cred_info_json,show_cred_info", [
281+
(401, CRED_INFO_JSON, True),
282+
(403, CRED_INFO_JSON, True),
283+
(404, CRED_INFO_JSON, True),
284+
(500, CRED_INFO_JSON, False),
285+
(401, None, False),
286+
(403, None, False),
287+
(404, None, False),
288+
(500, None, False)
289+
])
290+
def test__add_cred_info_for_auth_errors(error_code, cred_info_json, show_cred_info):
291+
cred = mock.Mock(["get_cred_info"])
292+
cred.get_cred_info = mock.Mock(return_value=cred_info_json)
293+
client = {{ service.client_name }}(credentials=cred)
294+
client._transport._credentials = cred
295+
296+
error = core_exceptions.GoogleAPICallError("message", details=["foo"])
297+
error.code = error_code
298+
299+
client._add_cred_info_for_auth_errors(error)
300+
if show_cred_info:
301+
assert error.details == ["foo", CRED_INFO_STRING]
302+
else:
303+
assert error.details == ["foo"]
304+
305+
@pytest.mark.parametrize("error_code", [401,403,404,500])
306+
def test__add_cred_info_for_auth_errors_no_get_cred_info(error_code):
307+
cred = mock.Mock([])
308+
assert not hasattr(cred, "get_cred_info")
309+
client = {{ service.client_name }}(credentials=cred)
310+
client._transport._credentials = cred
311+
312+
error = core_exceptions.GoogleAPICallError("message", details=[])
313+
error.code = error_code
314+
315+
client._add_cred_info_for_auth_errors(error)
316+
assert error.details == []
271317

272318
@pytest.mark.parametrize("client_class,transport_name", [
273319
{% if 'grpc' in opts.transport %}

0 commit comments

Comments
 (0)