Skip to content

Commit 94105f6

Browse files
committed
impl(oauth2): add AllowedLocations methods to minimal iam rest stub
1 parent fc9f8f0 commit 94105f6

File tree

6 files changed

+471
-14
lines changed

6 files changed

+471
-14
lines changed

google/cloud/internal/json_parsing.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ StatusOr<std::int32_t> ValidateIntField(nlohmann::json const& json,
6363
return it->get<std::int32_t>();
6464
}
6565

66+
StatusOr<std::vector<std::string>> ValidateStringArrayField(
67+
nlohmann::json const& json, absl::string_view name,
68+
absl::string_view object_name, internal::ErrorContext const& ec) {
69+
auto it = json.find(std::string{name});
70+
if (it == json.end()) return MissingFieldError(name, object_name, ec);
71+
if (!it->is_array()) return InvalidTypeError(name, object_name, ec);
72+
if (!std::all_of(it->begin(), it->end(),
73+
[](nlohmann::json const& e) { return e.is_string(); })) {
74+
return InvalidTypeError(name, object_name, ec);
75+
}
76+
return it->get<std::vector<std::string>>();
77+
}
78+
6679
Status MissingFieldError(absl::string_view name, absl::string_view object_name,
6780
internal::ErrorContext const& ec) {
6881
return InvalidArgumentError(

google/cloud/internal/json_parsing.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ StatusOr<std::int32_t> ValidateIntField(nlohmann::json const& json,
5757
std::int32_t default_value,
5858
internal::ErrorContext const& ec);
5959

60+
/// Returns the string values for `json[name]` (which must exist) or a
61+
/// descriptive error.
62+
StatusOr<std::vector<std::string>> ValidateStringArrayField(
63+
nlohmann::json const& json, absl::string_view name,
64+
absl::string_view object_name, internal::ErrorContext const& ec);
65+
6066
/// Use when a JSON field cannot be found but is required.
6167
Status MissingFieldError(absl::string_view name, absl::string_view object_name,
6268
internal::ErrorContext const& ec);

google/cloud/internal/json_parsing_test.cc

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ namespace {
2525
using ::google::cloud::testing_util::StatusIs;
2626
using ::testing::AllOf;
2727
using ::testing::Contains;
28+
using ::testing::ElementsAre;
2829
using ::testing::HasSubstr;
2930
using ::testing::Pair;
3031

31-
TEST(ExternalAccountParsing, ValidateStringFieldSuccess) {
32+
TEST(JsonParsingTest, ValidateStringFieldSuccess) {
3233
auto const json = nlohmann::json{{"someField", "value"}};
3334
auto actual = ValidateStringField(
3435
json, "someField", "test-object",
@@ -37,7 +38,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldSuccess) {
3738
EXPECT_EQ(*actual, "value");
3839
}
3940

40-
TEST(ExternalAccountParsing, ValidateStringFieldMissing) {
41+
TEST(JsonParsingTest, ValidateStringFieldMissing) {
4142
auto const json = nlohmann::json{{"some-field", "value"}};
4243
auto actual = ValidateStringField(
4344
json, "missingField", "test-object",
@@ -51,7 +52,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldMissing) {
5152
Contains(Pair("origin", "test")));
5253
}
5354

54-
TEST(ExternalAccountParsing, ValidateStringFieldNotString) {
55+
TEST(JsonParsingTest, ValidateStringFieldNotString) {
5556
auto const json =
5657
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
5758
auto actual = ValidateStringField(
@@ -66,7 +67,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldNotString) {
6667
Contains(Pair("origin", "test")));
6768
}
6869

69-
TEST(ExternalAccountParsing, ValidateStringFieldDefaultSuccess) {
70+
TEST(JsonParsingTest, ValidateStringFieldDefaultSuccess) {
7071
auto const json = nlohmann::json{{"someField", "value"}};
7172
auto actual = ValidateStringField(
7273
json, "someField", "test-object", "default-value",
@@ -75,7 +76,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldDefaultSuccess) {
7576
EXPECT_EQ(*actual, "value");
7677
}
7778

78-
TEST(ExternalAccountParsing, ValidateStringFieldDefaultMissing) {
79+
TEST(JsonParsingTest, ValidateStringFieldDefaultMissing) {
7980
auto const json = nlohmann::json{{"anotherField", "value"}};
8081
auto actual = ValidateStringField(
8182
json, "someField", "test-object", "default-value",
@@ -84,7 +85,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldDefaultMissing) {
8485
EXPECT_EQ(*actual, "default-value");
8586
}
8687

87-
TEST(ExternalAccountParsing, ValidateStringFieldDefaultNotInt) {
88+
TEST(JsonParsingTest, ValidateStringFieldDefaultNotInt) {
8889
auto const json =
8990
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
9091
auto actual = ValidateStringField(
@@ -99,7 +100,7 @@ TEST(ExternalAccountParsing, ValidateStringFieldDefaultNotInt) {
99100
Contains(Pair("origin", "test")));
100101
}
101102

102-
TEST(ExternalAccountParsing, ValidateIntFieldSuccess) {
103+
TEST(JsonParsingTest, ValidateIntFieldSuccess) {
103104
auto const json = nlohmann::json{{"someField", 42}};
104105
auto actual = ValidateIntField(
105106
json, "someField", "test-object",
@@ -108,7 +109,7 @@ TEST(ExternalAccountParsing, ValidateIntFieldSuccess) {
108109
EXPECT_EQ(*actual, 42);
109110
}
110111

111-
TEST(ExternalAccountParsing, ValidateIntFieldMissing) {
112+
TEST(JsonParsingTest, ValidateIntFieldMissing) {
112113
auto const json = nlohmann::json{{"some-field", 42}};
113114
auto actual = ValidateIntField(
114115
json, "missingField", "test-object",
@@ -122,7 +123,7 @@ TEST(ExternalAccountParsing, ValidateIntFieldMissing) {
122123
Contains(Pair("origin", "test")));
123124
}
124125

125-
TEST(ExternalAccountParsing, ValidateIntFieldNotString) {
126+
TEST(JsonParsingTest, ValidateIntFieldNotString) {
126127
auto const json =
127128
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
128129
auto actual = ValidateIntField(
@@ -137,7 +138,7 @@ TEST(ExternalAccountParsing, ValidateIntFieldNotString) {
137138
Contains(Pair("origin", "test")));
138139
}
139140

140-
TEST(ExternalAccountParsing, ValidateIntFieldDefaultSuccess) {
141+
TEST(JsonParsingTest, ValidateIntFieldDefaultSuccess) {
141142
auto const json = nlohmann::json{{"someField", 42}};
142143
auto actual = ValidateIntField(
143144
json, "someField", "test-object", 42,
@@ -146,7 +147,7 @@ TEST(ExternalAccountParsing, ValidateIntFieldDefaultSuccess) {
146147
EXPECT_EQ(*actual, 42);
147148
}
148149

149-
TEST(ExternalAccountParsing, ValidateIntFieldDefaultMissing) {
150+
TEST(JsonParsingTest, ValidateIntFieldDefaultMissing) {
150151
auto const json = nlohmann::json{{"anotherField", "value"}};
151152
auto actual = ValidateIntField(
152153
json, "someField", "test-object", 42,
@@ -155,7 +156,7 @@ TEST(ExternalAccountParsing, ValidateIntFieldDefaultMissing) {
155156
EXPECT_EQ(*actual, 42);
156157
}
157158

158-
TEST(ExternalAccountParsing, ValidateIntFieldDefaultNotString) {
159+
TEST(JsonParsingTest, ValidateIntFieldDefaultNotString) {
159160
auto const json =
160161
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
161162
auto actual = ValidateIntField(
@@ -170,6 +171,43 @@ TEST(ExternalAccountParsing, ValidateIntFieldDefaultNotString) {
170171
Contains(Pair("origin", "test")));
171172
}
172173

174+
TEST(JsonParsingTest, ValidateStringArrayFieldSuccess) {
175+
auto const json = nlohmann::json{{"someField", {"value1", "value2"}}};
176+
auto actual = ValidateStringArrayField(
177+
json, "someField", "test-object",
178+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
179+
ASSERT_STATUS_OK(actual);
180+
EXPECT_THAT(*actual, ElementsAre("value1", "value2"));
181+
}
182+
183+
TEST(JsonParsingTest, ValidateStringArrayFieldMissing) {
184+
auto const json = nlohmann::json{{"some-field", {"value1", "value2"}}};
185+
auto actual = ValidateStringArrayField(
186+
json, "missingField", "test-object",
187+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
188+
EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument,
189+
AllOf(HasSubstr("missingField"),
190+
HasSubstr("test-object"))));
191+
EXPECT_THAT(actual.status().error_info().metadata(),
192+
Contains(Pair("filename", "/dev/null")));
193+
EXPECT_THAT(actual.status().error_info().metadata(),
194+
Contains(Pair("origin", "test")));
195+
}
196+
197+
TEST(JsonParsingTest, ValidateStringArrayFieldNotString) {
198+
auto const json = nlohmann::json({"wrongType", {"value1", true}});
199+
auto actual = ValidateStringField(
200+
json, "wrongType", "test-object",
201+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
202+
EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument,
203+
AllOf(HasSubstr("wrongType"),
204+
HasSubstr("test-object"))));
205+
EXPECT_THAT(actual.status().error_info().metadata(),
206+
Contains(Pair("filename", "/dev/null")));
207+
EXPECT_THAT(actual.status().error_info().metadata(),
208+
Contains(Pair("origin", "test")));
209+
}
210+
173211
} // namespace
174212
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
175213
} // namespace oauth2_internal

google/cloud/internal/oauth2_minimal_iam_credentials_rest.cc

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ namespace google {
3333
namespace cloud {
3434
namespace oauth2_internal {
3535
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
36+
namespace {
37+
38+
std::string IamCredentialsEndpoint(
39+
StatusOr<std::string> const& universe_domain) {
40+
return absl::StrCat("https://iamcredentials.",
41+
universe_domain ? *universe_domain : "googleapis.com");
42+
}
43+
44+
} // namespace
3645

3746
using ::google::cloud::internal::InvalidArgumentError;
3847

@@ -73,12 +82,62 @@ MinimalIamCredentialsRestStub::GenerateAccessToken(
7382

7483
std::string MinimalIamCredentialsRestStub::MakeRequestPath(
7584
GenerateAccessTokenRequest const& request) const {
76-
auto ud = universe_domain(Options{});
77-
return absl::StrCat("https://iamcredentials.", ud ? *ud : "googleapis.com",
85+
return absl::StrCat(IamCredentialsEndpoint(universe_domain(Options{})),
7886
"/v1/projects/-/serviceAccounts/",
7987
request.service_account, ":generateAccessToken");
8088
}
8189

90+
StatusOr<AllowedLocationsResponse>
91+
MinimalIamCredentialsRestStub::AllowedLocationsHelper(std::string path) {
92+
auto authorization_header =
93+
credentials_->Authorization(std::chrono::system_clock::now());
94+
if (!authorization_header) return std::move(authorization_header).status();
95+
rest_internal::RestRequest rest_request;
96+
rest_request.AddHeader(*std::move(authorization_header));
97+
rest_request.AddHeader("Content-Type", "application/json");
98+
rest_request.SetPath(std::move(path));
99+
nlohmann::json payload{};
100+
101+
auto client = client_factory_(options_);
102+
rest_internal::RestContext context;
103+
auto response = client->Post(context, rest_request, {payload.dump()});
104+
if (!response) return std::move(response).status();
105+
return ParseAllowedLocationsResponse(
106+
**response,
107+
internal::ErrorContext(
108+
{{"gcloud-cpp.root.class", "MinimalIamCredentialsRestStub"},
109+
{"gcloud-cpp.root.function", __func__},
110+
{"path", rest_request.path()}}));
111+
}
112+
113+
StatusOr<AllowedLocationsResponse>
114+
MinimalIamCredentialsRestStub::AllowedLocations(
115+
ServiceAccountAllowedLocationsRequest const& request) {
116+
auto path = absl::StrCat(IamCredentialsEndpoint(universe_domain(Options{})),
117+
"/v1/projects/-/serviceAccounts/",
118+
request.service_account_email, "/allowedLocations");
119+
return AllowedLocationsHelper(std::move(path));
120+
}
121+
122+
StatusOr<AllowedLocationsResponse>
123+
MinimalIamCredentialsRestStub::AllowedLocations(
124+
WorkloadIdentityAllowedLocationsRequest const& request) {
125+
auto path = absl::StrCat(IamCredentialsEndpoint(universe_domain(Options{})),
126+
"/v1/projects/", request.project_id,
127+
"/locations/global/workloadIdentityPools/",
128+
request.pool_id, "/allowedLocations");
129+
return AllowedLocationsHelper(std::move(path));
130+
}
131+
132+
StatusOr<AllowedLocationsResponse>
133+
MinimalIamCredentialsRestStub::AllowedLocations(
134+
WorkforceIdentityAllowedLocationsRequest const& request) {
135+
auto path = absl::StrCat(IamCredentialsEndpoint(universe_domain(Options{})),
136+
"/v1/locations/global/workforcePools/",
137+
request.pool_id, "/allowedLocations");
138+
return AllowedLocationsHelper(std::move(path));
139+
}
140+
82141
MinimalIamCredentialsRestLogging::MinimalIamCredentialsRestLogging(
83142
std::shared_ptr<MinimalIamCredentialsRest> child)
84143
: child_(std::move(child)) {}
@@ -104,6 +163,53 @@ MinimalIamCredentialsRestLogging::GenerateAccessToken(
104163
return response;
105164
}
106165

166+
StatusOr<AllowedLocationsResponse>
167+
MinimalIamCredentialsRestLogging::AllowedLocations(
168+
ServiceAccountAllowedLocationsRequest const& request) {
169+
GCP_LOG(INFO) << __func__ << "() << {service_account_email="
170+
<< request.service_account_email << "}";
171+
auto response = child_->AllowedLocations(request);
172+
if (!response) {
173+
GCP_LOG(INFO) << __func__ << "() >> status={" << response.status() << "}";
174+
return response;
175+
}
176+
GCP_LOG(INFO) << __func__ << "() >> response={locations="
177+
<< absl::StrJoin(response->locations, ",")
178+
<< ", encoded_locations=" << response->encoded_locations << "}";
179+
return response;
180+
}
181+
182+
StatusOr<AllowedLocationsResponse>
183+
MinimalIamCredentialsRestLogging::AllowedLocations(
184+
WorkloadIdentityAllowedLocationsRequest const& request) {
185+
GCP_LOG(INFO) << __func__ << "() << {project_id=" << request.project_id
186+
<< ", pool_id=" << request.pool_id << "}";
187+
auto response = child_->AllowedLocations(request);
188+
if (!response) {
189+
GCP_LOG(INFO) << __func__ << "() >> status={" << response.status() << "}";
190+
return response;
191+
}
192+
GCP_LOG(INFO) << __func__ << "() >> response={locations="
193+
<< absl::StrJoin(response->locations, ",")
194+
<< ", encoded_locations=" << response->encoded_locations << "}";
195+
return response;
196+
}
197+
198+
StatusOr<AllowedLocationsResponse>
199+
MinimalIamCredentialsRestLogging::AllowedLocations(
200+
WorkforceIdentityAllowedLocationsRequest const& request) {
201+
GCP_LOG(INFO) << __func__ << "() << {pool_id=" << request.pool_id << "}";
202+
auto response = child_->AllowedLocations(request);
203+
if (!response) {
204+
GCP_LOG(INFO) << __func__ << "() >> status={" << response.status() << "}";
205+
return response;
206+
}
207+
GCP_LOG(INFO) << __func__ << "() >> response={locations="
208+
<< absl::StrJoin(response->locations, ",")
209+
<< ", encoded_locations=" << response->encoded_locations << "}";
210+
return response;
211+
}
212+
107213
StatusOr<AccessToken> ParseGenerateAccessTokenResponse(
108214
rest_internal::RestResponse& response,
109215
google::cloud::internal::ErrorContext const& ec) {
@@ -132,6 +238,29 @@ StatusOr<AccessToken> ParseGenerateAccessTokenResponse(
132238
return google::cloud::AccessToken{*std::move(token), *expire_time};
133239
}
134240

241+
StatusOr<AllowedLocationsResponse> ParseAllowedLocationsResponse(
242+
rest_internal::RestResponse& response,
243+
google::cloud::internal::ErrorContext const& ec) {
244+
if (IsHttpError(response)) return AsStatus(std::move(response));
245+
auto response_payload =
246+
rest_internal::ReadAll(std::move(response).ExtractPayload());
247+
if (!response_payload) return std::move(response_payload).status();
248+
auto parsed = nlohmann::json::parse(*response_payload, nullptr, false);
249+
if (!parsed.is_object()) {
250+
return InvalidArgumentError("cannot parse response as a JSON object",
251+
GCP_ERROR_INFO().WithContext(ec));
252+
}
253+
auto locations = ValidateStringArrayField(parsed, "locations",
254+
"AllowedLocations() response", ec);
255+
if (!locations) return std::move(locations).status();
256+
257+
auto encoded_locations = ValidateStringField(
258+
parsed, "encodedLocations", "AllowedLocations() response", ec);
259+
if (!encoded_locations) return std::move(encoded_locations).status();
260+
return AllowedLocationsResponse{*std::move(locations),
261+
*std::move(encoded_locations)};
262+
}
263+
135264
std::shared_ptr<MinimalIamCredentialsRest> MakeMinimalIamCredentialsRestStub(
136265
std::shared_ptr<oauth2_internal::Credentials> credentials, Options options,
137266
HttpClientFactory client_factory) {

0 commit comments

Comments
 (0)