Skip to content

Commit 75068f9

Browse files
fix: external account user cred universe domain support (#1437)
* fix: external account user cred universe domain support * refactor --------- Co-authored-by: Jin <[email protected]>
1 parent 0afc61a commit 75068f9

File tree

6 files changed

+65
-28
lines changed

6 files changed

+65
-28
lines changed

google/auth/credentials.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def with_quota_project(self, quota_project_id):
188188
billing purposes
189189
190190
Returns:
191-
google.oauth2.credentials.Credentials: A new credentials instance.
191+
google.auth.credentials.Credentials: A new credentials instance.
192192
"""
193193
raise NotImplementedError("This credential does not support quota project.")
194194

@@ -209,11 +209,28 @@ def with_token_uri(self, token_uri):
209209
token_uri (str): The uri to use for fetching/exchanging tokens
210210
211211
Returns:
212-
google.oauth2.credentials.Credentials: A new credentials instance.
212+
google.auth.credentials.Credentials: A new credentials instance.
213213
"""
214214
raise NotImplementedError("This credential does not use token uri.")
215215

216216

217+
class CredentialsWithUniverseDomain(Credentials):
218+
"""Abstract base for credentials supporting ``with_universe_domain`` factory"""
219+
220+
def with_universe_domain(self, universe_domain):
221+
"""Returns a copy of these credentials with a modified universe domain.
222+
223+
Args:
224+
universe_domain (str): The universe domain to use
225+
226+
Returns:
227+
google.auth.credentials.Credentials: A new credentials instance.
228+
"""
229+
raise NotImplementedError(
230+
"This credential does not support with_universe_domain."
231+
)
232+
233+
217234
class AnonymousCredentials(Credentials):
218235
"""Credentials that do not provide any authentication information.
219236

google/auth/external_account.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -415,16 +415,8 @@ def with_token_uri(self, token_uri):
415415
new_cred._metrics_options = self._metrics_options
416416
return new_cred
417417

418+
@_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
418419
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-
"""
428420
kwargs = self._constructor_args()
429421
kwargs.update(universe_domain=universe_domain)
430422
new_cred = self.__class__(**kwargs)

google/auth/external_account_authorized_user.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from google.oauth2 import sts
4444
from google.oauth2 import utils
4545

46+
_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"
4647
_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user"
4748

4849

@@ -75,6 +76,7 @@ def __init__(
7576
revoke_url=None,
7677
scopes=None,
7778
quota_project_id=None,
79+
universe_domain=_DEFAULT_UNIVERSE_DOMAIN,
7880
):
7981
"""Instantiates a external account authorized user credentials object.
8082
@@ -98,6 +100,8 @@ def __init__(
98100
quota_project_id (str): The optional project ID used for quota and billing.
99101
This project may be different from the project used to
100102
create the credentials.
103+
universe_domain (Optional[str]): The universe domain. The default value
104+
is googleapis.com.
101105
102106
Returns:
103107
google.auth.external_account_authorized_user.Credentials: The
@@ -116,6 +120,7 @@ def __init__(
116120
self._revoke_url = revoke_url
117121
self._quota_project_id = quota_project_id
118122
self._scopes = scopes
123+
self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN
119124

120125
if not self.valid and not self.can_refresh:
121126
raise exceptions.InvalidOperation(
@@ -162,6 +167,7 @@ def constructor_args(self):
162167
"revoke_url": self._revoke_url,
163168
"scopes": self._scopes,
164169
"quota_project_id": self._quota_project_id,
170+
"universe_domain": self._universe_domain,
165171
}
166172

167173
@property
@@ -297,6 +303,12 @@ def with_token_uri(self, token_uri):
297303
kwargs.update(token_url=token_uri)
298304
return self.__class__(**kwargs)
299305

306+
@_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
307+
def with_universe_domain(self, universe_domain):
308+
kwargs = self.constructor_args()
309+
kwargs.update(universe_domain=universe_domain)
310+
return self.__class__(**kwargs)
311+
300312
@classmethod
301313
def from_info(cls, info, **kwargs):
302314
"""Creates a Credentials instance from parsed external account info.

google/oauth2/credentials.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,15 +302,8 @@ def with_token_uri(self, token_uri):
302302
universe_domain=self._universe_domain,
303303
)
304304

305+
@_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
305306
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-
"""
314307

315308
return self.__class__(
316309
self.token,

google/oauth2/service_account.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -325,16 +325,8 @@ def with_always_use_jwt_access(self, always_use_jwt_access):
325325
cred._always_use_jwt_access = always_use_jwt_access
326326
return cred
327327

328+
@_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)
328329
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-
"""
338330
cred = self._make_copy()
339331
cred._universe_domain = universe_domain
340332
if universe_domain != _DEFAULT_UNIVERSE_DOMAIN:

tests/test_external_account_authorized_user.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ="
4545
SCOPES = ["email", "profile"]
4646
NOW = datetime.datetime(1990, 8, 27, 6, 54, 30)
47+
FAKE_UNIVERSE_DOMAIN = "fake-universe-domain"
48+
DEFAULT_UNIVERSE_DOMAIN = external_account_authorized_user._DEFAULT_UNIVERSE_DOMAIN
4749

4850

4951
class TestCredentials(object):
@@ -98,13 +100,15 @@ def test_default_state(self):
98100
assert creds.refresh_token == REFRESH_TOKEN
99101
assert creds.audience == AUDIENCE
100102
assert creds.token_url == TOKEN_URL
103+
assert creds.universe_domain == DEFAULT_UNIVERSE_DOMAIN
101104

102105
def test_basic_create(self):
103106
creds = external_account_authorized_user.Credentials(
104107
token=ACCESS_TOKEN,
105108
expiry=datetime.datetime.max,
106109
scopes=SCOPES,
107110
revoke_url=REVOKE_URL,
111+
universe_domain=FAKE_UNIVERSE_DOMAIN,
108112
)
109113

110114
assert creds.expiry == datetime.datetime.max
@@ -115,6 +119,7 @@ def test_basic_create(self):
115119
assert creds.scopes == SCOPES
116120
assert creds.is_user
117121
assert creds.revoke_url == REVOKE_URL
122+
assert creds.universe_domain == FAKE_UNIVERSE_DOMAIN
118123

119124
def test_stunted_create_no_refresh_token(self):
120125
with pytest.raises(ValueError) as excinfo:
@@ -339,6 +344,7 @@ def test_info(self):
339344
assert info["token_info_url"] == TOKEN_INFO_URL
340345
assert info["client_id"] == CLIENT_ID
341346
assert info["client_secret"] == CLIENT_SECRET
347+
assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN
342348
assert "token" not in info
343349
assert "expiry" not in info
344350
assert "revoke_url" not in info
@@ -350,6 +356,7 @@ def test_info_full(self):
350356
expiry=NOW,
351357
revoke_url=REVOKE_URL,
352358
quota_project_id=QUOTA_PROJECT_ID,
359+
universe_domain=FAKE_UNIVERSE_DOMAIN,
353360
)
354361
info = creds.info
355362

@@ -363,6 +370,7 @@ def test_info_full(self):
363370
assert info["expiry"] == NOW.isoformat() + "Z"
364371
assert info["revoke_url"] == REVOKE_URL
365372
assert info["quota_project_id"] == QUOTA_PROJECT_ID
373+
assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN
366374

367375
def test_to_json(self):
368376
creds = self.make_credentials()
@@ -375,6 +383,7 @@ def test_to_json(self):
375383
assert info["token_info_url"] == TOKEN_INFO_URL
376384
assert info["client_id"] == CLIENT_ID
377385
assert info["client_secret"] == CLIENT_SECRET
386+
assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN
378387
assert "token" not in info
379388
assert "expiry" not in info
380389
assert "revoke_url" not in info
@@ -386,6 +395,7 @@ def test_to_json_full(self):
386395
expiry=NOW,
387396
revoke_url=REVOKE_URL,
388397
quota_project_id=QUOTA_PROJECT_ID,
398+
universe_domain=FAKE_UNIVERSE_DOMAIN,
389399
)
390400
json_info = creds.to_json()
391401
info = json.loads(json_info)
@@ -400,6 +410,7 @@ def test_to_json_full(self):
400410
assert info["expiry"] == NOW.isoformat() + "Z"
401411
assert info["revoke_url"] == REVOKE_URL
402412
assert info["quota_project_id"] == QUOTA_PROJECT_ID
413+
assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN
403414

404415
def test_to_json_full_with_strip(self):
405416
creds = self.make_credentials(
@@ -467,6 +478,26 @@ def test_with_token_uri(self):
467478
assert new_creds._revoke_url == creds._revoke_url
468479
assert new_creds._quota_project_id == creds._quota_project_id
469480

481+
def test_with_universe_domain(self):
482+
creds = self.make_credentials(
483+
token=ACCESS_TOKEN,
484+
expiry=NOW,
485+
revoke_url=REVOKE_URL,
486+
quota_project_id=QUOTA_PROJECT_ID,
487+
)
488+
new_creds = creds.with_universe_domain(FAKE_UNIVERSE_DOMAIN)
489+
assert new_creds._audience == creds._audience
490+
assert new_creds._refresh_token == creds._refresh_token
491+
assert new_creds._token_url == creds._token_url
492+
assert new_creds._token_info_url == creds._token_info_url
493+
assert new_creds._client_id == creds._client_id
494+
assert new_creds._client_secret == creds._client_secret
495+
assert new_creds.token == creds.token
496+
assert new_creds.expiry == creds.expiry
497+
assert new_creds._revoke_url == creds._revoke_url
498+
assert new_creds._quota_project_id == QUOTA_PROJECT_ID
499+
assert new_creds.universe_domain == FAKE_UNIVERSE_DOMAIN
500+
470501
def test_from_file_required_options_only(self, tmpdir):
471502
from_creds = self.make_credentials()
472503
config_file = tmpdir.join("config.json")

0 commit comments

Comments
 (0)