diff --git a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc index 16dfbb286a0d6..0af7f2852324e 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials.cc @@ -67,24 +67,7 @@ ParseImpersonatedServiceAccountCredentials(std::string const& content, } // We strip the service account from the path URL. auto url = it->get(); - auto colon = url.rfind(':'); - if (colon == std::string::npos) { - return internal::InvalidArgumentError( - "Malformed `service_account_impersonation_url` field contents on data " - "from " + - source, - GCP_ERROR_INFO()); - } - if (url.substr(colon) != ":generateAccessToken") { - // While `generateIdToken` is a valid RPC, we do not currently support ID - // token flow. So we might as well error when parsing the credentials. - return internal::InvalidArgumentError( - "Only access token authentication is supported for impersonated " - "service accounts from " + - source, - GCP_ERROR_INFO()); - } - auto slash = url.rfind('/', colon); + auto slash = url.rfind('/'); if (slash == std::string::npos) { return internal::InvalidArgumentError( "Malformed `service_account_impersonation_url` field contents on data " @@ -92,7 +75,13 @@ ParseImpersonatedServiceAccountCredentials(std::string const& content, source, GCP_ERROR_INFO()); } - info.service_account = std::string{url.substr(slash + 1, colon - slash - 1)}; + + // In the url, after the last slash, it is in the format of + // `service_account[:action]` + auto service_account_action = url.substr(slash + 1); + auto colon = service_account_action.rfind(':'); + auto end = colon == std::string::npos ? service_account_action.size() : colon; + info.service_account = service_account_action.substr(0, end); it = credentials.find("delegates"); if (it != credentials.end()) { 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 96563806cf687..f626d85cf7309 100644 --- a/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_impersonate_service_account_credentials_test.cc @@ -50,6 +50,19 @@ auto constexpr kFullValidConfig = R"""({ "type": "impersonated_service_account" })"""; +auto constexpr kFullValidConfigNoAction = R"""({ + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/sa3@developer.gserviceaccount.com", + "delegates": [ + "sa1@developer.gserviceaccount.com", + "sa2@developer.gserviceaccount.com" + ], + "quota_project_id": "my-project", + "source_credentials": { + "type": "authorized_user" + }, + "type": "impersonated_service_account" +})"""; + TEST(ParseImpersonatedServiceAccountCredentials, Success) { auto actual = ParseImpersonatedServiceAccountCredentials(kFullValidConfig, "test-data"); @@ -146,6 +159,19 @@ TEST(ImpersonateServiceAccountCredentialsTest, Basic) { ASSERT_THAT(token, StatusIs(StatusCode::kPermissionDenied)); } +TEST(ParseImpersonatedServiceAccountCredentialsWithoutAction, Success) { + auto actual = ParseImpersonatedServiceAccountCredentials( + kFullValidConfigNoAction, "test-data"); + ASSERT_STATUS_OK(actual); + EXPECT_EQ(actual->service_account, "sa3@developer.gserviceaccount.com"); + EXPECT_THAT(actual->delegates, + ElementsAre("sa1@developer.gserviceaccount.com", + "sa2@developer.gserviceaccount.com")); + EXPECT_THAT(actual->quota_project_id, Optional("my-project")); + EXPECT_THAT(actual->source_credentials, + AllOf(HasSubstr("type"), HasSubstr("authorized_user"))); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace oauth2_internal