From ee070535ce06661eeb12e407e782c155b142cecf Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 22 May 2023 11:57:57 -0700 Subject: [PATCH] feat: expose `universe_domain` for external account creds (#1296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: expose for external account creds * add universe_domain as info property * fix info error * adding coverage of explicit universe_domain assigning * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- google/auth/external_account.py | 8 ++++++++ tests/test_aws.py | 7 +++++++ tests/test_external_account.py | 9 ++++++++- tests/test_identity_pool.py | 10 ++++++++++ tests/test_pluggable.py | 6 ++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/google/auth/external_account.py b/google/auth/external_account.py index 646e31340..436cb34cc 100644 --- a/google/auth/external_account.py +++ b/google/auth/external_account.py @@ -52,6 +52,8 @@ # Cloud resource manager URL used to retrieve project information. _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" + @six.add_metaclass(abc.ABCMeta) class Credentials( @@ -82,6 +84,7 @@ def __init__( scopes=None, default_scopes=None, workforce_pool_user_project=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """Instantiates an external account credentials object. @@ -105,6 +108,8 @@ def __init__( a workload identity pool. The underlying principal must still have serviceusage.services.use IAM permission to use the project for billing/quota. + universe_domain (str): The universe domain. The default universe + domain is googleapis.com. Raises: google.auth.exceptions.RefreshError: If the generateAccessToken endpoint returned an error. @@ -125,6 +130,7 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN if self._client_id: self._client_auth = utils.ClientAuthentication( @@ -186,6 +192,7 @@ def _constructor_args(self): "workforce_pool_user_project": self._workforce_pool_user_project, "scopes": self._scopes, "default_scopes": self._default_scopes, + "universe_domain": self._universe_domain, } if not self.is_workforce_pool: args.pop("workforce_pool_user_project") @@ -458,6 +465,7 @@ def from_info(cls, info, **kwargs): credential_source=info.get("credential_source"), quota_project_id=info.get("quota_project_id"), workforce_pool_user_project=info.get("workforce_pool_user_project"), + universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), **kwargs ) diff --git a/tests/test_aws.py b/tests/test_aws.py index 805aa3ce2..d50a8f4e1 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -69,6 +69,7 @@ # Each tuple contains the following entries: # region, time, credentials, original_request, signed_request +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", "https://us-east-1.sts.googleapis.com", @@ -925,6 +926,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -952,6 +954,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -967,6 +970,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } config_file = tmpdir.join("config.json") config_file.write(json.dumps(info)) @@ -986,6 +990,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1014,6 +1019,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_invalid_credential_source(self): @@ -1067,6 +1073,7 @@ def test_info(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_token_info_url(self): diff --git a/tests/test_external_account.py b/tests/test_external_account.py index c8900a493..598c3760c 100644 --- a/tests/test_external_account.py +++ b/tests/test_external_account.py @@ -144,6 +144,7 @@ def make_credentials( default_scopes=None, service_account_impersonation_url=None, service_account_impersonation_options={}, + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ): return CredentialsImpl( audience=cls.AUDIENCE, @@ -158,6 +159,7 @@ def make_credentials( quota_project_id=quota_project_id, scopes=scopes, default_scopes=default_scopes, + universe_domain=universe_domain, ) @classmethod @@ -378,6 +380,7 @@ def test_with_scopes_full_options_propagated(self): quota_project_id=self.QUOTA_PROJECT_ID, scopes=["email"], default_scopes=["default2"], + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ) def test_with_token_uri(self): @@ -465,6 +468,7 @@ def test_with_quota_project_full_options_propagated(self): quota_project_id="project-foo", scopes=self.SCOPES, default_scopes=["default1"], + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ) def test_with_invalid_impersonation_target_principal(self): @@ -478,7 +482,7 @@ def test_with_invalid_impersonation_target_principal(self): ) def test_info(self): - credentials = self.make_credentials() + credentials = self.make_credentials(universe_domain="dummy_universe.com") assert credentials.info == { "type": "external_account", @@ -486,6 +490,7 @@ def test_info(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, "token_url": self.TOKEN_URL, "credential_source": self.CREDENTIAL_SOURCE.copy(), + "universe_domain": "dummy_universe.com", } def test_info_workforce_pool(self): @@ -500,6 +505,7 @@ def test_info_workforce_pool(self): "token_url": self.TOKEN_URL, "credential_source": self.CREDENTIAL_SOURCE.copy(), "workforce_pool_user_project": self.WORKFORCE_POOL_USER_PROJECT, + "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_full_options(self): @@ -524,6 +530,7 @@ def test_info_with_full_options(self): "quota_project_id": self.QUOTA_PROJECT_ID, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, + "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, } def test_service_account_email_without_impersonation(self): diff --git a/tests/test_identity_pool.py b/tests/test_identity_pool.py index 6651f0b5c..6c1c6623c 100644 --- a/tests/test_identity_pool.py +++ b/tests/test_identity_pool.py @@ -66,6 +66,7 @@ WORKFORCE_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token" WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", @@ -410,6 +411,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -437,6 +439,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -465,6 +468,7 @@ def test_from_info_workforce_pool(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -499,6 +503,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -527,6 +532,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -556,6 +562,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_nonworkforce_with_workforce_pool_user_project(self): @@ -639,6 +646,7 @@ def test_info_with_workforce_pool_user_project(self): "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_file_credential_source(self): @@ -653,6 +661,7 @@ def test_info_with_file_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_url_credential_source(self): @@ -667,6 +676,7 @@ def test_info_with_url_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_JSON_URL, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_retrieve_subject_token_missing_subject_token(self, tmpdir): diff --git a/tests/test_pluggable.py b/tests/test_pluggable.py index e9b3d9a86..7d601dfd4 100644 --- a/tests/test_pluggable.py +++ b/tests/test_pluggable.py @@ -53,6 +53,7 @@ TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", @@ -278,6 +279,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -305,6 +307,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -339,6 +342,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -367,6 +371,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_invalid_options(self): @@ -395,6 +400,7 @@ def test_info_with_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_token_info_url(self):