From f5e1f8be4840750aabea7b44d5dcb41e584af4a6 Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Fri, 31 Oct 2025 18:02:45 +0000 Subject: [PATCH 1/2] feat: Support scopes field in impersonated json --- .../internal/oauth2_google_credentials.cc | 7 +++++ ...impersonate_service_account_credentials.cc | 12 ++++++++ ..._impersonate_service_account_credentials.h | 1 + ...sonate_service_account_credentials_test.cc | 30 +++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/google/cloud/internal/oauth2_google_credentials.cc b/google/cloud/internal/oauth2_google_credentials.cc index 796670ea4a311..2f51da36f4539 100644 --- a/google/cloud/internal/oauth2_google_credentials.cc +++ b/google/cloud/internal/oauth2_google_credentials.cc @@ -80,6 +80,13 @@ StatusOr> LoadCredsFromString( delegates.push_back(std::move(delegate)); } + auto& scopes = opts.lookup(); + if (scopes.empty()) { + for (auto& scope : info->scopes) { + scopes.push_back(std::move(scope)); + } + } + internal::ImpersonateServiceAccountConfig config( // The base credentials (GUAC) are used to create the IAM REST Stub. We // are going to override them by supplying our own IAM REST Stub, diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc index 0af7f2852324e..a2da884ed20bc 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc @@ -95,6 +95,18 @@ ParseImpersonatedServiceAccountCredentials(std::string const& content, } } + it = credentials.find("scopes"); + if (it != credentials.end()) { + if (!it->is_array()) { + return internal::InvalidArgumentError( + "Malformed `scopes` field is not an array on data from " + source, + GCP_ERROR_INFO()); + } + for (auto const& scope : it->items()) { + info.scopes.push_back(scope.value().get()); + } + } + it = credentials.find("quota_project_id"); if (it != credentials.end()) { if (!it->is_string()) { diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials.h b/google/cloud/internal/oauth2_impersonate_service_account_credentials.h index 2cec516420a59..cdd9d82607fb6 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials.h +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials.h @@ -29,6 +29,7 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN struct ImpersonatedServiceAccountCredentialsInfo { std::string service_account; std::vector delegates; + std::vector scopes; absl::optional quota_project_id; std::string source_credentials; }; diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc index f626d85cf7309..a8d216677d090 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc @@ -63,6 +63,36 @@ auto constexpr kFullValidConfigNoAction = R"""({ "type": "impersonated_service_account" })"""; +auto constexpr kWithScopes = R"""({ + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/sa3@developer.gserviceaccount.com:generateAccessToken", + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append" + ], + "source_credentials": { + "type": "authorized_user" + }, + "type": "impersonated_service_account" +})"""; + +TEST(ParseImpersonatedServiceAccountCredentials, WithScopes) { + auto actual = + ParseImpersonatedServiceAccountCredentials(kWithScopes, "test-data"); + ASSERT_STATUS_OK(actual); + EXPECT_THAT(actual->scopes, + ElementsAre("https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append")); +} + +TEST(ParseImpersonatedServiceAccountCredentials, MalformedScopes) { + auto json = nlohmann::json::parse(kFullValidConfig); + json["scopes"] = "not-an-array"; + auto actual = ParseImpersonatedServiceAccountCredentials(json.dump(), ""); + EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument, + AllOf(HasSubstr("Malformed"), + HasSubstr("scopes")))); +} + TEST(ParseImpersonatedServiceAccountCredentials, Success) { auto actual = ParseImpersonatedServiceAccountCredentials(kFullValidConfig, "test-data"); From c3b3f3cf709136ad708f68949d76eb9bbf7bc51a Mon Sep 17 00:00:00 2001 From: Sai Sunder Srinivasan Date: Fri, 31 Oct 2025 20:49:39 +0000 Subject: [PATCH 2/2] lint --- .../oauth2_impersonate_service_account_credentials_test.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc index a8d216677d090..5747814b5147e 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc @@ -88,9 +88,9 @@ TEST(ParseImpersonatedServiceAccountCredentials, MalformedScopes) { auto json = nlohmann::json::parse(kFullValidConfig); json["scopes"] = "not-an-array"; auto actual = ParseImpersonatedServiceAccountCredentials(json.dump(), ""); - EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument, - AllOf(HasSubstr("Malformed"), - HasSubstr("scopes")))); + EXPECT_THAT(actual, + StatusIs(StatusCode::kInvalidArgument, + AllOf(HasSubstr("Malformed"), HasSubstr("scopes")))); } TEST(ParseImpersonatedServiceAccountCredentials, Success) {