Skip to content

Commit 505910c

Browse files
fix: add with_universe_domain (#1408)
* fix: add with_universe_domain to service account and external cred * update * update * chore: refresh sys test cred
1 parent 39eb287 commit 505910c

File tree

7 files changed

+115
-4
lines changed

7 files changed

+115
-4
lines changed

google/auth/external_account.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,22 @@ def with_token_uri(self, token_uri):
415415
new_cred._metrics_options = self._metrics_options
416416
return new_cred
417417

418+
def with_universe_domain(self, universe_domain):
419+
"""Create a copy of these credentials with the given universe domain.
420+
421+
Args:
422+
universe_domain (str): The universe domain value.
423+
424+
Returns:
425+
google.auth.external_account.Credentials: A new credentials
426+
instance.
427+
"""
428+
kwargs = self._constructor_args()
429+
kwargs.update(universe_domain=universe_domain)
430+
new_cred = self.__class__(**kwargs)
431+
new_cred._metrics_options = self._metrics_options
432+
return new_cred
433+
418434
def _initialize_impersonated_credentials(self):
419435
"""Generates an impersonated credentials.
420436

google/oauth2/credentials.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
5151
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
52+
_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
5253

5354

5455
class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
@@ -85,6 +86,7 @@ def __init__(
8586
enable_reauth_refresh=False,
8687
granted_scopes=None,
8788
trust_boundary=None,
89+
universe_domain=_DEFAULT_UNIVERSE_DOMAIN,
8890
):
8991
"""
9092
Args:
@@ -126,6 +128,9 @@ def __init__(
126128
granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user.
127129
This could be different from the requested scopes and it could be empty if granted
128130
and requested scopes were same.
131+
trust_boundary (str): String representation of trust boundary meta.
132+
universe_domain (Optional[str]): The universe domain. The default
133+
universe domain is googleapis.com.
129134
"""
130135
super(Credentials, self).__init__()
131136
self.token = token
@@ -143,6 +148,7 @@ def __init__(
143148
self.refresh_handler = refresh_handler
144149
self._enable_reauth_refresh = enable_reauth_refresh
145150
self._trust_boundary = trust_boundary
151+
self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN
146152

147153
def __getstate__(self):
148154
"""A __getstate__ method must exist for the __setstate__ to be called
@@ -273,6 +279,7 @@ def with_quota_project(self, quota_project_id):
273279
rapt_token=self.rapt_token,
274280
enable_reauth_refresh=self._enable_reauth_refresh,
275281
trust_boundary=self._trust_boundary,
282+
universe_domain=self._universe_domain,
276283
)
277284

278285
@_helpers.copy_docstring(credentials.CredentialsWithTokenUri)
@@ -292,13 +299,52 @@ def with_token_uri(self, token_uri):
292299
rapt_token=self.rapt_token,
293300
enable_reauth_refresh=self._enable_reauth_refresh,
294301
trust_boundary=self._trust_boundary,
302+
universe_domain=self._universe_domain,
303+
)
304+
305+
def with_universe_domain(self, universe_domain):
306+
"""Create a copy of the credential with the given universe domain.
307+
308+
Args:
309+
universe_domain (str): The universe domain value.
310+
311+
Returns:
312+
google.oauth2.credentials.Credentials: A new credentials instance.
313+
"""
314+
315+
return self.__class__(
316+
self.token,
317+
refresh_token=self.refresh_token,
318+
id_token=self.id_token,
319+
token_uri=self._token_uri,
320+
client_id=self.client_id,
321+
client_secret=self.client_secret,
322+
scopes=self.scopes,
323+
default_scopes=self.default_scopes,
324+
granted_scopes=self.granted_scopes,
325+
quota_project_id=self.quota_project_id,
326+
rapt_token=self.rapt_token,
327+
enable_reauth_refresh=self._enable_reauth_refresh,
328+
trust_boundary=self._trust_boundary,
329+
universe_domain=universe_domain,
295330
)
296331

297332
def _metric_header_for_usage(self):
298333
return metrics.CRED_TYPE_USER
299334

300335
@_helpers.copy_docstring(credentials.Credentials)
301336
def refresh(self, request):
337+
if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN:
338+
raise exceptions.RefreshError(
339+
"User credential refresh is only supported in the default "
340+
"googleapis.com universe domain, but the current universe "
341+
"domain is {}. If you created the credential with an access "
342+
"token, it's likely that the provided token is expired now, "
343+
"please update your code with a valid token.".format(
344+
self._universe_domain
345+
)
346+
)
347+
302348
scopes = self._scopes if self._scopes is not None else self._default_scopes
303349
# Use refresh handler if available and no refresh token is
304350
# available. This is useful in general when tokens are obtained by calling
@@ -428,6 +474,7 @@ def from_authorized_user_info(cls, info, scopes=None):
428474
expiry=expiry,
429475
rapt_token=info.get("rapt_token"), # may not exist
430476
trust_boundary=info.get("trust_boundary"), # may not exist
477+
universe_domain=info.get("universe_domain"), # may not exist
431478
)
432479

433480
@classmethod
@@ -471,6 +518,7 @@ def to_json(self, strip=None):
471518
"client_secret": self.client_secret,
472519
"scopes": self.scopes,
473520
"rapt_token": self.rapt_token,
521+
"universe_domain": self._universe_domain,
474522
}
475523
if self.expiry: # flatten expiry timestamp
476524
prep["expiry"] = self.expiry.isoformat() + "Z"

google/oauth2/service_account.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,7 @@ def __init__(
182182
self._quota_project_id = quota_project_id
183183
self._token_uri = token_uri
184184
self._always_use_jwt_access = always_use_jwt_access
185-
if not universe_domain:
186-
self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN
187-
else:
188-
self._universe_domain = universe_domain
185+
self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN
189186

190187
if universe_domain != _DEFAULT_UNIVERSE_DOMAIN:
191188
self._always_use_jwt_access = True
@@ -328,6 +325,22 @@ def with_always_use_jwt_access(self, always_use_jwt_access):
328325
cred._always_use_jwt_access = always_use_jwt_access
329326
return cred
330327

328+
def with_universe_domain(self, universe_domain):
329+
"""Create a copy of these credentials with the given universe domain.
330+
331+
Args:
332+
universe_domain (str): The universe domain value.
333+
334+
Returns:
335+
google.auth.service_account.Credentials: A new credentials
336+
instance.
337+
"""
338+
cred = self._make_copy()
339+
cred._universe_domain = universe_domain
340+
if universe_domain != _DEFAULT_UNIVERSE_DOMAIN:
341+
cred._always_use_jwt_access = True
342+
return cred
343+
331344
def with_subject(self, subject):
332345
"""Create a copy of these credentials with the specified subject.
333346

system_tests/secrets.tar.enc

0 Bytes
Binary file not shown.

tests/oauth2/test_credentials.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ def test_invalid_refresh_handler(self):
122122

123123
assert excinfo.match("The provided refresh_handler is not a callable or None.")
124124

125+
def test_refresh_with_non_default_universe_domain(self):
126+
creds = credentials.Credentials(
127+
token="token", universe_domain="dummy_universe.com"
128+
)
129+
with pytest.raises(exceptions.RefreshError) as excinfo:
130+
creds.refresh(mock.Mock())
131+
132+
assert excinfo.match(
133+
"refresh is only supported in the default googleapis.com universe domain"
134+
)
135+
125136
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
126137
@mock.patch(
127138
"google.auth._helpers.utcnow",
@@ -774,6 +785,12 @@ def test_with_quota_project(self):
774785
creds.apply(headers)
775786
assert "x-goog-user-project" in headers
776787

788+
def test_with_universe_domain(self):
789+
creds = credentials.Credentials(token="token")
790+
assert creds.universe_domain == "googleapis.com"
791+
new_creds = creds.with_universe_domain("dummy_universe.com")
792+
assert new_creds.universe_domain == "dummy_universe.com"
793+
777794
def test_with_token_uri(self):
778795
info = AUTH_USER_INFO.copy()
779796

@@ -868,6 +885,7 @@ def test_to_json(self):
868885
assert json_asdict.get("scopes") == creds.scopes
869886
assert json_asdict.get("client_secret") == creds.client_secret
870887
assert json_asdict.get("expiry") == info["expiry"]
888+
assert json_asdict.get("universe_domain") == creds.universe_domain
871889

872890
# Test with a `strip` arg
873891
json_output = creds.to_json(strip=["client_secret"])

tests/oauth2/test_service_account.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ def test_with_token_uri(self):
205205
creds_with_new_token_uri = credentials.with_token_uri(new_token_uri)
206206
assert creds_with_new_token_uri._token_uri == new_token_uri
207207

208+
def test_with_universe_domain(self):
209+
credentials = self.make_credentials()
210+
211+
new_credentials = credentials.with_universe_domain("dummy_universe.com")
212+
assert new_credentials.universe_domain == "dummy_universe.com"
213+
assert new_credentials._always_use_jwt_access
214+
215+
new_credentials = credentials.with_universe_domain("googleapis.com")
216+
assert new_credentials.universe_domain == "googleapis.com"
217+
assert not new_credentials._always_use_jwt_access
218+
208219
def test__with_always_use_jwt_access(self):
209220
credentials = self.make_credentials()
210221
assert not credentials._always_use_jwt_access

tests/test_external_account.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,11 @@ def test_universe_domain(self):
505505
credentials = self.make_credentials()
506506
assert credentials.universe_domain == external_account._DEFAULT_UNIVERSE_DOMAIN
507507

508+
def test_with_universe_domain(self):
509+
credentials = self.make_credentials()
510+
new_credentials = credentials.with_universe_domain("dummy_universe.com")
511+
assert new_credentials.universe_domain == "dummy_universe.com"
512+
508513
def test_info_workforce_pool(self):
509514
credentials = self.make_workforce_pool_credentials(
510515
workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT

0 commit comments

Comments
 (0)