@@ -48,6 +48,7 @@ using ::testing::Contains;
4848using ::testing::ElementsAre;
4949using ::testing::HasSubstr;
5050using ::testing::MatcherCast;
51+ using ::testing::Optional;
5152using ::testing::Pair;
5253using ::testing::Property;
5354using ::testing::ResultOf;
@@ -304,6 +305,27 @@ TEST(ExternalAccount, ParseWithImpersonationDefaultLifetimeSuccess) {
304305 std::chrono::seconds (3600 ));
305306}
306307
308+ TEST (ExternalAccount, ParseUserProjectSuccess) {
309+ auto const configuration = nlohmann::json{
310+ {" type" , " external_account" },
311+ {" audience" , " test-audience" },
312+ {" subject_token_type" , " test-subject-token-type" },
313+ {" token_url" , " test-token-url" },
314+ {" credential_source" , nlohmann::json{{" file" , " /dev/null-test-only" }}},
315+ {" workforce_pool_user_project" , " project-id-or-name" },
316+ };
317+ auto ec = internal::ErrorContext (
318+ {{" program" , " test" }, {" full-configuration" , configuration.dump ()}});
319+ auto const actual =
320+ ParseExternalAccountConfiguration (configuration.dump (), ec);
321+ ASSERT_STATUS_OK (actual);
322+ EXPECT_EQ (actual->audience , " test-audience" );
323+ EXPECT_EQ (actual->subject_token_type , " test-subject-token-type" );
324+ EXPECT_EQ (actual->token_url , " test-token-url" );
325+ EXPECT_THAT (actual->workforce_pool_user_project ,
326+ Optional (std::string (" project-id-or-name" )));
327+ }
328+
307329TEST (ExternalAccount, ParseNotJson) {
308330 auto const configuration = std::string{" not-json" };
309331 auto ec = internal::ErrorContext (
@@ -657,7 +679,8 @@ TEST(ExternalAccount, Working) {
657679 auto const info =
658680 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
659681 test_url, mock_source,
660- absl::nullopt , {}};
682+ absl::nullopt , {},
683+ absl::nullopt };
661684
662685 MockClientFactory client_factory;
663686 EXPECT_CALL (client_factory, Call (make_expected_options ())).WillOnce ([&]() {
@@ -689,6 +712,58 @@ TEST(ExternalAccount, Working) {
689712 EXPECT_EQ (access_token->token , expected_access_token);
690713}
691714
715+ TEST (ExternalAccount, WorkingWorkforceIdentity) {
716+ auto const test_url = std::string{" https://sts.example.com/" };
717+ auto const expected_access_token = std::string{" test-access-token" };
718+ auto const expected_expires_in = std::chrono::seconds (3456 );
719+ auto const json_response = nlohmann::json{
720+ {" access_token" , expected_access_token},
721+ {" expires_in" , expected_expires_in.count ()},
722+ {" issued_token_type" , " urn:ietf:params:oauth:token-type:access_token" },
723+ {" token_type" , " Bearer" },
724+ };
725+ auto mock_source = [](HttpClientFactory const &, Options const &) {
726+ return make_status_or (internal::SubjectToken{" test-subject-token" });
727+ };
728+ auto const info = ExternalAccountInfo{" test-audience" ,
729+ " test-subject-token-type" ,
730+ test_url,
731+ mock_source,
732+ absl::nullopt ,
733+ {},
734+ " project-id-or-name" };
735+
736+ MockClientFactory client_factory;
737+ EXPECT_CALL (client_factory, Call (make_expected_options ())).WillOnce ([&]() {
738+ auto mock = std::make_unique<MockRestClient>();
739+ auto expected_request = make_expected_token_exchange_request (test_url);
740+ auto expected_payload =
741+ MatcherCast<FormDataType const &>(UnorderedElementsAre (
742+ Pair (" grant_type" ,
743+ " urn:ietf:params:oauth:grant-type:token-exchange" ),
744+ Pair (" requested_token_type" ,
745+ " urn:ietf:params:oauth:token-type:access_token" ),
746+ Pair (" scope" , " https://www.googleapis.com/auth/cloud-platform" ),
747+ Pair (" audience" , " test-audience" ),
748+ Pair (" subject_token_type" , " test-subject-token-type" ),
749+ Pair (" subject_token" , " test-subject-token" ),
750+ Pair (" options" , R"( {"userProject": "project-id-or-name"})" )));
751+ EXPECT_CALL (*mock, Post (_, expected_request, expected_payload))
752+ .WillOnce (
753+ Return (ByMove (MakeMockResponseSuccess (json_response.dump ()))));
754+ return mock;
755+ });
756+
757+ auto credentials =
758+ ExternalAccountCredentials (info, client_factory.AsStdFunction (),
759+ Options{}.set <TestOnlyOption>(" test-option" ));
760+ auto const now = std::chrono::system_clock::now ();
761+ auto access_token = credentials.GetToken (now);
762+ ASSERT_STATUS_OK (access_token);
763+ EXPECT_EQ (access_token->expiration , now + expected_expires_in);
764+ EXPECT_EQ (access_token->token , expected_access_token);
765+ }
766+
692767TEST (ExternalAccount, WorkingWithImpersonation) {
693768 auto const sts_test_url = std::string{" https://sts.example.com/" };
694769 auto const sts_access_token = std::string{" test-sts-access-token" };
@@ -727,7 +802,8 @@ TEST(ExternalAccount, WorkingWithImpersonation) {
727802 mock_source,
728803 ExternalAccountImpersonationConfig{
729804 impersonate_test_url, impersonate_test_lifetime},
730- {}};
805+ {},
806+ absl::nullopt };
731807
732808 auto sts_client = [&] {
733809 auto expected_sts_request = Property (&RestRequest::path, sts_test_url);
@@ -798,7 +874,8 @@ TEST(ExternalAccount, HandleHttpError) {
798874 auto const info =
799875 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
800876 test_url, mock_source,
801- absl::nullopt , {}};
877+ absl::nullopt , {},
878+ absl::nullopt };
802879 MockClientFactory client_factory;
803880 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
804881 auto mock = std::make_unique<MockRestClient>();
@@ -836,7 +913,8 @@ TEST(ExternalAccount, HandleHttpPartialError) {
836913 auto const info =
837914 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
838915 test_url, mock_source,
839- absl::nullopt , {}};
916+ absl::nullopt , {},
917+ absl::nullopt };
840918 MockClientFactory client_factory;
841919 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
842920 auto mock = std::make_unique<MockRestClient>();
@@ -875,7 +953,8 @@ TEST(ExternalAccount, HandleNotJson) {
875953 auto const info =
876954 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
877955 test_url, mock_source,
878- absl::nullopt , {}};
956+ absl::nullopt , {},
957+ absl::nullopt };
879958 MockClientFactory client_factory;
880959 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
881960 auto mock = std::make_unique<MockRestClient>();
@@ -914,7 +993,8 @@ TEST(ExternalAccount, HandleNotJsonObject) {
914993 auto const info =
915994 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
916995 test_url, mock_source,
917- absl::nullopt , {}};
996+ absl::nullopt , {},
997+ absl::nullopt };
918998 MockClientFactory client_factory;
919999 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
9201000 auto mock = std::make_unique<MockRestClient>();
@@ -959,7 +1039,8 @@ TEST(ExternalAccount, MissingToken) {
9591039 auto const info =
9601040 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
9611041 test_url, mock_source,
962- absl::nullopt , {}};
1042+ absl::nullopt , {},
1043+ absl::nullopt };
9631044 MockClientFactory client_factory;
9641045 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
9651046 auto mock = std::make_unique<MockRestClient>();
@@ -993,7 +1074,8 @@ TEST(ExternalAccount, MissingIssuedTokenType) {
9931074 auto const info =
9941075 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
9951076 test_url, mock_source,
996- absl::nullopt , {}};
1077+ absl::nullopt , {},
1078+ absl::nullopt };
9971079 MockClientFactory client_factory;
9981080 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
9991081 auto mock = std::make_unique<MockRestClient>();
@@ -1027,7 +1109,8 @@ TEST(ExternalAccount, MissingTokenType) {
10271109 auto const info =
10281110 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
10291111 test_url, mock_source,
1030- absl::nullopt , {}};
1112+ absl::nullopt , {},
1113+ absl::nullopt };
10311114 MockClientFactory client_factory;
10321115 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
10331116 auto mock = std::make_unique<MockRestClient>();
@@ -1061,7 +1144,8 @@ TEST(ExternalAccount, InvalidIssuedTokenType) {
10611144 auto const info =
10621145 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
10631146 test_url, mock_source,
1064- absl::nullopt , {}};
1147+ absl::nullopt , {},
1148+ absl::nullopt };
10651149 MockClientFactory client_factory;
10661150 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
10671151 auto mock = std::make_unique<MockRestClient>();
@@ -1097,7 +1181,8 @@ TEST(ExternalAccount, InvalidTokenType) {
10971181 auto const info =
10981182 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
10991183 test_url, mock_source,
1100- absl::nullopt , {}};
1184+ absl::nullopt , {},
1185+ absl::nullopt };
11011186 MockClientFactory client_factory;
11021187 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
11031188 auto mock = std::make_unique<MockRestClient>();
@@ -1134,7 +1219,8 @@ TEST(ExternalAccount, MissingExpiresIn) {
11341219 auto const info =
11351220 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
11361221 test_url, mock_source,
1137- absl::nullopt , {}};
1222+ absl::nullopt , {},
1223+ absl::nullopt };
11381224 MockClientFactory client_factory;
11391225 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
11401226 auto mock = std::make_unique<MockRestClient>();
@@ -1169,7 +1255,8 @@ TEST(ExternalAccount, InvalidExpiresIn) {
11691255 auto const info =
11701256 ExternalAccountInfo{" test-audience" , " test-subject-token-type" ,
11711257 test_url, mock_source,
1172- absl::nullopt , {}};
1258+ absl::nullopt , {},
1259+ absl::nullopt };
11731260 MockClientFactory client_factory;
11741261 EXPECT_CALL (client_factory, Call).WillOnce ([&]() {
11751262 auto mock = std::make_unique<MockRestClient>();
0 commit comments