Skip to content

Commit eebb7b6

Browse files
authored
fix: Do not create new JWT credentials if they make the same claims as the existing. (#1267)
* fix: Do not create new JWT credentials if they make the same claims as the existing.
1 parent 7cdeef5 commit eebb7b6

File tree

6 files changed

+90
-11
lines changed

6 files changed

+90
-11
lines changed

google/auth/jwt.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,11 @@ def signer_email(self):
590590
def signer(self):
591591
return self._signer
592592

593+
@property # type: ignore
594+
def additional_claims(self):
595+
""" Additional claims the JWT object was created with."""
596+
return self._additional_claims
597+
593598

594599
class OnDemandCredentials(
595600
google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject

google/oauth2/service_account.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -441,19 +441,32 @@ def _create_self_signed_jwt(self, audience):
441441
# https://google.aip.dev/auth/4111
442442
if self._always_use_jwt_access:
443443
if self._scopes:
444-
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
445-
self, None, additional_claims={"scope": " ".join(self._scopes)}
446-
)
444+
additional_claims = {"scope": " ".join(self._scopes)}
445+
if (
446+
self._jwt_credentials is None
447+
or self._jwt_credentials.additional_claims != additional_claims
448+
):
449+
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
450+
self, None, additional_claims=additional_claims
451+
)
447452
elif audience:
448-
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
449-
self, audience
450-
)
453+
if (
454+
self._jwt_credentials is None
455+
or self._jwt_credentials._audience != audience
456+
):
457+
458+
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
459+
self, audience
460+
)
451461
elif self._default_scopes:
452-
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
453-
self,
454-
None,
455-
additional_claims={"scope": " ".join(self._default_scopes)},
456-
)
462+
additional_claims = {"scope": " ".join(self._default_scopes)}
463+
if (
464+
self._jwt_credentials is None
465+
or additional_claims != self._jwt_credentials.additional_claims
466+
):
467+
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
468+
self, None, additional_claims=additional_claims
469+
)
457470
elif not self._scopes and audience:
458471
self._jwt_credentials = jwt.Credentials.from_signing_credentials(
459472
self, audience

system_tests/secrets.tar.enc

0 Bytes
Binary file not shown.

tests/oauth2/test_service_account.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,24 @@ def test__create_self_signed_jwt_always_use_jwt_access_with_audience(self, jwt):
253253
credentials._create_self_signed_jwt(audience)
254254
jwt.from_signing_credentials.assert_called_once_with(credentials, audience)
255255

256+
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
257+
def test__create_self_signed_jwt_always_use_jwt_access_with_audience_similar_jwt_is_reused(
258+
self, jwt
259+
):
260+
credentials = service_account.Credentials(
261+
SIGNER,
262+
self.SERVICE_ACCOUNT_EMAIL,
263+
self.TOKEN_URI,
264+
default_scopes=["bar", "foo"],
265+
always_use_jwt_access=True,
266+
)
267+
268+
audience = "https://pubsub.googleapis.com"
269+
credentials._create_self_signed_jwt(audience)
270+
credentials._jwt_credentials._audience = audience
271+
credentials._create_self_signed_jwt(audience)
272+
jwt.from_signing_credentials.assert_called_once_with(credentials, audience)
273+
256274
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
257275
def test__create_self_signed_jwt_always_use_jwt_access_with_scopes(self, jwt):
258276
credentials = service_account.Credentials(
@@ -269,6 +287,26 @@ def test__create_self_signed_jwt_always_use_jwt_access_with_scopes(self, jwt):
269287
credentials, None, additional_claims={"scope": "bar foo"}
270288
)
271289

290+
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
291+
def test__create_self_signed_jwt_always_use_jwt_access_with_scopes_similar_jwt_is_reused(
292+
self, jwt
293+
):
294+
credentials = service_account.Credentials(
295+
SIGNER,
296+
self.SERVICE_ACCOUNT_EMAIL,
297+
self.TOKEN_URI,
298+
scopes=["bar", "foo"],
299+
always_use_jwt_access=True,
300+
)
301+
302+
audience = "https://pubsub.googleapis.com"
303+
credentials._create_self_signed_jwt(audience)
304+
credentials._jwt_credentials.additional_claims = {"scope": "bar foo"}
305+
credentials._create_self_signed_jwt(audience)
306+
jwt.from_signing_credentials.assert_called_once_with(
307+
credentials, None, additional_claims={"scope": "bar foo"}
308+
)
309+
272310
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
273311
def test__create_self_signed_jwt_always_use_jwt_access_with_default_scopes(
274312
self, jwt
@@ -286,6 +324,25 @@ def test__create_self_signed_jwt_always_use_jwt_access_with_default_scopes(
286324
credentials, None, additional_claims={"scope": "bar foo"}
287325
)
288326

327+
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
328+
def test__create_self_signed_jwt_always_use_jwt_access_with_default_scopes_similar_jwt_is_reused(
329+
self, jwt
330+
):
331+
credentials = service_account.Credentials(
332+
SIGNER,
333+
self.SERVICE_ACCOUNT_EMAIL,
334+
self.TOKEN_URI,
335+
default_scopes=["bar", "foo"],
336+
always_use_jwt_access=True,
337+
)
338+
339+
credentials._create_self_signed_jwt(None)
340+
credentials._jwt_credentials.additional_claims = {"scope": "bar foo"}
341+
credentials._create_self_signed_jwt(None)
342+
jwt.from_signing_credentials.assert_called_once_with(
343+
credentials, None, additional_claims={"scope": "bar foo"}
344+
)
345+
289346
@mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True)
290347
def test__create_self_signed_jwt_always_use_jwt_access(self, jwt):
291348
credentials = service_account.Credentials(

tests/test_jwt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ def test_with_quota_project(self):
464464
assert new_credentials._subject == self.credentials._subject
465465
assert new_credentials._audience == self.credentials._audience
466466
assert new_credentials._additional_claims == self.credentials._additional_claims
467+
assert new_credentials.additional_claims == self.credentials._additional_claims
467468
assert new_credentials._quota_project_id == quota_project_id
468469

469470
def test_sign_bytes(self):

tests/transport/test_urllib3.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs):
8282
self.requests.append((method, url, body, headers, kwargs))
8383
return self.responses.pop(0)
8484

85+
def clear(self):
86+
pass
87+
8588

8689
class ResponseStub(object):
8790
def __init__(self, status=http_client.OK, data=None):

0 commit comments

Comments
 (0)