Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 01d3770

Browse files
authored
fix: Make external_account resistant to string type 'expires_in' responses from non-compliant services (#1379)
Make external_account resistant to string type 'expires_in' responses from non-compliant services. This is to complete [#1208](https://togithub.com/googleapis/google-auth-library-python/pull/1208/). A client run into this error.
1 parent 7d453dc commit 01d3770

3 files changed

Lines changed: 12 additions & 4 deletions

File tree

google/auth/external_account.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,14 @@ def refresh(self, request):
385385
additional_headers=additional_headers,
386386
)
387387
self.token = response_data.get("access_token")
388-
lifetime = datetime.timedelta(seconds=response_data.get("expires_in"))
388+
expires_in = response_data.get("expires_in")
389+
# Some services do not respect the OAUTH2.0 RFC and send expires_in as a
390+
# JSON String.
391+
if isinstance(expires_in, str):
392+
expires_in = int(expires_in)
393+
394+
lifetime = datetime.timedelta(seconds=expires_in)
395+
389396
self.expiry = now + lifetime
390397

391398
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)

system_tests/secrets.tar.enc

0 Bytes
Binary file not shown.

tests/test_external_account.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,19 +625,20 @@ def test_is_workforce_pool_with_users_and_impersonation(self, audience):
625625
# Even though impersonation is used, is_workforce_pool should still return True.
626626
assert credentials.is_workforce_pool is True
627627

628+
@pytest.mark.parametrize("mock_expires_in", [2800, "2800"])
628629
@mock.patch(
629630
"google.auth.metrics.python_and_auth_lib_version",
630631
return_value=LANG_LIBRARY_METRICS_HEADER_VALUE,
631632
)
632633
@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
633634
def test_refresh_without_client_auth_success(
634-
self, unused_utcnow, mock_auth_lib_value
635+
self, unused_utcnow, mock_auth_lib_value, mock_expires_in
635636
):
636637
response = self.SUCCESS_RESPONSE.copy()
637638
# Test custom expiration to confirm expiry is set correctly.
638-
response["expires_in"] = 2800
639+
response["expires_in"] = mock_expires_in
639640
expected_expiry = datetime.datetime.min + datetime.timedelta(
640-
seconds=response["expires_in"]
641+
seconds=int(mock_expires_in)
641642
)
642643
headers = {
643644
"Content-Type": "application/x-www-form-urlencoded",

0 commit comments

Comments
 (0)