diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 2838e138ff92..7239a37b4ec6 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -362,8 +362,12 @@ def _build_regional_access_boundary_lookup_url( Returns: Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None - if the service account email is missing. + if the service account email is missing. Returns None if the subject is populated. """ + if self._subject: + # RAB does not apply to Workspace User Accounts via Domain-wide Delegation. + return None + if not self.service_account_email: _LOGGER.error( "Service account email is required to build the Regional Access Boundary lookup URL for impersonated credentials." diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 1241aee70121..754079eb796f 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -585,7 +585,17 @@ def _perform_refresh_token(self, request): self.token, self.expiry = self._make_jwt() def _build_regional_access_boundary_lookup_url(self, request=None): - """Builds the lookup URL using the service account's email address.""" + """Builds the lookup URL using the service account's email address. + + Returns None if the subject is populated. + """ + # In jwt.Credentials, subject defaults to client_email (which is the issuer). + # We must check self._subject != self._issuer to correctly determine if + # Domain-Wide Delegation is active. + if self._subject and self._subject != self._issuer: + # RAB does not apply to Workspace User Accounts via Domain-wide Delegation. + return None + if not self.signer_email: return None diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 7f719ade2cdb..0abc78b5000d 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -514,8 +514,12 @@ def _build_regional_access_boundary_lookup_url( Returns: Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None - if the service account email is missing. + if the service account email is missing. Returns None if the subject is populated. """ + if self._subject: + # RAB does not apply to Workspace User Accounts via Domain-wide Delegation. + return None + if not self.service_account_email: _LOGGER.error( "Service account email is required to build the Regional Access Boundary lookup URL for service account credentials." diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 1d70543057d7..9573aad7e970 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -290,6 +290,10 @@ def test_build_regional_access_boundary_lookup_url_mtls(self, monkeypatch): ) assert url == expected_url + def test_build_regional_access_boundary_lookup_url_with_subject(self): + credentials = self.make_credentials().with_subject("user@example.com") + assert credentials._build_regional_access_boundary_lookup_url() is None + def test_with_token_uri(self): credentials = self.make_credentials() new_token_uri = "https://example2.com/oauth2/token" diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 1207ed874b31..215c68620aa0 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -759,6 +759,10 @@ def test_build_regional_access_boundary_lookup_url_success_mtls(self, monkeypatc ) assert url == expected_url + def test_build_regional_access_boundary_lookup_url_with_subject(self): + credentials = self.make_credentials(subject="user@example.com") + assert credentials._build_regional_access_boundary_lookup_url() is None + def test_with_scopes_provide_default_scopes(self): credentials = self.make_credentials() credentials._target_scopes = [] diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 27b951b8b7bc..8b90b0ecd093 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -559,7 +559,14 @@ def test_build_regional_access_boundary_lookup_url_standard(self, monkeypatch): # Mock check_use_client_cert to return False to simulate standard TLS monkeypatch.setattr(_mtls_helper, "check_use_client_cert", lambda: False) - url = self.credentials._build_regional_access_boundary_lookup_url() + credentials = jwt.Credentials( + self.credentials._signer, + self.credentials.signer_email, + self.credentials.signer_email, + self.credentials._audience, + ) + + url = credentials._build_regional_access_boundary_lookup_url() expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}/allowedLocations".format( self.SERVICE_ACCOUNT_EMAIL ) @@ -571,12 +578,28 @@ def test_build_regional_access_boundary_lookup_url_mtls(self, monkeypatch): # Mock check_use_client_cert to return True to simulate mTLS monkeypatch.setattr(_mtls_helper, "check_use_client_cert", lambda: True) - url = self.credentials._build_regional_access_boundary_lookup_url() + credentials = jwt.Credentials( + self.credentials._signer, + self.credentials.signer_email, + self.credentials.signer_email, + self.credentials._audience, + ) + + url = credentials._build_regional_access_boundary_lookup_url() expected_url = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/{}/allowedLocations".format( self.SERVICE_ACCOUNT_EMAIL ) assert url == expected_url + def test_build_regional_access_boundary_lookup_url_with_subject(self): + credentials = jwt.Credentials( + self.credentials._signer, + self.credentials._issuer, + "user@example.com", + self.credentials._audience, + ) + assert credentials._build_regional_access_boundary_lookup_url() is None + def test_cloning_retains_rab_manager_data(self): self.credentials._rab_manager._data = mock.sentinel.rab_data