diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index f9faab5e7336..9e8897fd5594 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -446,6 +446,7 @@ def _make_copy(self): target_principal=self._target_principal, target_scopes=self._target_scopes, delegates=self._delegates, + subject=self._subject, lifetime=self._lifetime, quota_project_id=self._quota_project_id, iam_endpoint_override=self._iam_endpoint_override, diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 2cfcbf150f86..9b801409ec64 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -685,6 +685,15 @@ def test_with_quota_project(self): quota_project_creds = credentials.with_quota_project("project-foo") assert quota_project_creds._quota_project_id == "project-foo" + def test_with_quota_project_preserves_subject(self): + credentials = self.make_credentials(subject="user@example.com") + + quota_project_creds = credentials.with_quota_project("project-foo") + assert quota_project_creds._subject == "user@example.com" + # Domain-wide delegation still disables the Regional Access Boundary + # lookup on the copy. + assert quota_project_creds._build_regional_access_boundary_lookup_url() is None + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_with_quota_project_iam_endpoint_override( self, use_data_bytes, mock_donor_credentials @@ -723,6 +732,36 @@ def test_with_scopes(self): assert credentials.requires_scopes is False assert credentials._target_scopes == ["fake_scope1", "fake_scope2"] + def test_with_scopes_preserves_subject(self): + credentials = self.make_credentials(subject="user@example.com") + + scoped_credentials = credentials.with_scopes(["fake_scope1"]) + assert scoped_credentials._subject == "user@example.com" + + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_with_scopes_refresh_with_subject_success( + self, use_data_bytes, mock_dwd_credentials + ): + credentials = self.make_credentials( + subject="test@email.com", lifetime=None + ).with_scopes(["fake_scope1"]) + + response_body = {"signedJwt": "example_signed_jwt"} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == "1/fFAGRNJasdfz70BzhT3Zg" + # The copy still runs the domain-wide delegation flow instead of + # falling back to plain service account impersonation. + assert request.call_args.kwargs["url"].endswith(":signJwt") + def test_build_regional_access_boundary_lookup_url_no_email(self): credentials = self.make_credentials(target_principal=None)