Skip to content

Commit a2c9082

Browse files
authored
impl(common): helper functions to parse JSON configuration (#10274)
To support workload/workforce identity federation (aka BYOID, aka external accounts) we will need to valid the JSON configuration. This involves a lot of "is this field there and is it a string", or "if the field is not there use this default, otherwise it must be a string". These two functions save a lot of repetition in the validation code.
1 parent b7894e5 commit a2c9082

File tree

6 files changed

+222
-0
lines changed

6 files changed

+222
-0
lines changed

google/cloud/google_cloud_cpp_rest_internal.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ google_cloud_cpp_rest_internal_hdrs = [
2626
"internal/curl_rest_client.h",
2727
"internal/curl_rest_response.h",
2828
"internal/curl_wrappers.h",
29+
"internal/external_account_parsing.h",
2930
"internal/http_payload.h",
3031
"internal/make_jwt_assertion.h",
3132
"internal/oauth2_access_token_credentials.h",
@@ -61,6 +62,7 @@ google_cloud_cpp_rest_internal_srcs = [
6162
"internal/curl_rest_client.cc",
6263
"internal/curl_rest_response.cc",
6364
"internal/curl_wrappers.cc",
65+
"internal/external_account_parsing.cc",
6466
"internal/make_jwt_assertion.cc",
6567
"internal/oauth2_access_token_credentials.cc",
6668
"internal/oauth2_anonymous_credentials.cc",

google/cloud/google_cloud_cpp_rest_internal.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ add_library(
3838
internal/curl_rest_response.h
3939
internal/curl_wrappers.cc
4040
internal/curl_wrappers.h
41+
internal/external_account_parsing.cc
42+
internal/external_account_parsing.h
4143
internal/http_payload.h
4244
internal/make_jwt_assertion.cc
4345
internal/make_jwt_assertion.h
@@ -192,6 +194,7 @@ if (BUILD_TESTING)
192194
internal/curl_wrappers_locking_disabled_test.cc
193195
internal/curl_wrappers_locking_enabled_test.cc
194196
internal/curl_wrappers_test.cc
197+
internal/external_account_parsing_test.cc
195198
internal/make_jwt_assertion_test.cc
196199
internal/oauth2_access_token_credentials_test.cc
197200
internal/oauth2_anonymous_credentials_test.cc

google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ google_cloud_cpp_rest_internal_unit_tests = [
2929
"internal/curl_wrappers_locking_disabled_test.cc",
3030
"internal/curl_wrappers_locking_enabled_test.cc",
3131
"internal/curl_wrappers_test.cc",
32+
"internal/external_account_parsing_test.cc",
3233
"internal/make_jwt_assertion_test.cc",
3334
"internal/oauth2_access_token_credentials_test.cc",
3435
"internal/oauth2_anonymous_credentials_test.cc",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/external_account_parsing.h"
16+
#include "google/cloud/internal/absl_str_cat_quiet.h"
17+
#include "google/cloud/internal/make_status.h"
18+
19+
namespace google {
20+
namespace cloud {
21+
namespace oauth2_internal {
22+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
23+
24+
StatusOr<std::string> ValidateStringField(nlohmann::json const& json,
25+
absl::string_view name,
26+
absl::string_view object_name,
27+
internal::ErrorContext const& ec) {
28+
auto it = json.find(std::string{name});
29+
if (it == json.end()) {
30+
return InvalidArgumentError(
31+
absl::StrCat("cannot find `", name, "` field in `", object_name, "`"),
32+
GCP_ERROR_INFO().WithContext(ec));
33+
}
34+
if (!it->is_string()) {
35+
return InvalidArgumentError(absl::StrCat("invalid type for `", name,
36+
"` field in `", object_name, "`"),
37+
GCP_ERROR_INFO().WithContext(ec));
38+
}
39+
return it->get<std::string>();
40+
}
41+
42+
StatusOr<std::string> ValidateStringField(nlohmann::json const& json,
43+
absl::string_view name,
44+
absl::string_view object_name,
45+
absl::string_view default_value,
46+
internal::ErrorContext const& ec) {
47+
auto it = json.find(std::string{name});
48+
if (it == json.end()) return std::string{default_value};
49+
if (!it->is_string()) {
50+
return InvalidArgumentError(absl::StrCat("invalid type for `", name,
51+
"` field in `", object_name, "`"),
52+
GCP_ERROR_INFO().WithContext(ec));
53+
}
54+
return it->get<std::string>();
55+
}
56+
57+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
58+
} // namespace oauth2_internal
59+
} // namespace cloud
60+
} // namespace google
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_EXTERNAL_ACCOUNT_PARSING_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_EXTERNAL_ACCOUNT_PARSING_H
17+
18+
#include "google/cloud/internal/error_metadata.h"
19+
#include "google/cloud/status_or.h"
20+
#include "google/cloud/version.h"
21+
#include "absl/strings/string_view.h"
22+
#include <nlohmann/json.hpp>
23+
#include <string>
24+
25+
namespace google {
26+
namespace cloud {
27+
namespace oauth2_internal {
28+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
29+
30+
/// Returns the string value for `json[name]` (which must exist) or a
31+
/// descriptive error.
32+
StatusOr<std::string> ValidateStringField(nlohmann::json const& json,
33+
absl::string_view name,
34+
absl::string_view object_name,
35+
internal::ErrorContext const& ec);
36+
37+
/// Returns the string value for `json[name]`, a default value if it does not
38+
/// exist, or a descriptive error if it exists but it is not a string.
39+
StatusOr<std::string> ValidateStringField(nlohmann::json const& json,
40+
absl::string_view name,
41+
absl::string_view object_name,
42+
absl::string_view default_value,
43+
internal::ErrorContext const& ec);
44+
45+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
46+
} // namespace oauth2_internal
47+
} // namespace cloud
48+
} // namespace google
49+
50+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_EXTERNAL_ACCOUNT_PARSING_H
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/external_account_parsing.h"
16+
#include "google/cloud/testing_util/status_matchers.h"
17+
#include <gmock/gmock.h>
18+
19+
namespace google {
20+
namespace cloud {
21+
namespace oauth2_internal {
22+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
23+
namespace {
24+
25+
using ::google::cloud::testing_util::StatusIs;
26+
using ::testing::AllOf;
27+
using ::testing::Contains;
28+
using ::testing::HasSubstr;
29+
using ::testing::Pair;
30+
31+
TEST(ExternalAccountParsing, ValidateStringFieldSuccess) {
32+
auto const json = nlohmann::json{{"someField", "value"}};
33+
auto actual = ValidateStringField(
34+
json, "someField", "test-object",
35+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
36+
ASSERT_STATUS_OK(actual);
37+
EXPECT_EQ(*actual, "value");
38+
}
39+
40+
TEST(ExternalAccountParsing, ValidateStringFieldMissing) {
41+
auto const json = nlohmann::json{{"some-field", "value"}};
42+
auto actual = ValidateStringField(
43+
json, "missingField", "test-object",
44+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
45+
EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument,
46+
AllOf(HasSubstr("missingField"),
47+
HasSubstr("test-object"))));
48+
EXPECT_THAT(actual.status().error_info().metadata(),
49+
Contains(Pair("filename", "/dev/null")));
50+
EXPECT_THAT(actual.status().error_info().metadata(),
51+
Contains(Pair("origin", "test")));
52+
}
53+
54+
TEST(ExternalAccountParsing, ValidateStringFieldNotString) {
55+
auto const json =
56+
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
57+
auto actual = ValidateStringField(
58+
json, "wrongType", "test-object",
59+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
60+
EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument,
61+
AllOf(HasSubstr("wrongType"),
62+
HasSubstr("test-object"))));
63+
EXPECT_THAT(actual.status().error_info().metadata(),
64+
Contains(Pair("filename", "/dev/null")));
65+
EXPECT_THAT(actual.status().error_info().metadata(),
66+
Contains(Pair("origin", "test")));
67+
}
68+
69+
TEST(ExternalAccountParsing, ValidateStringFieldDefaultSuccess) {
70+
auto const json = nlohmann::json{{"someField", "value"}};
71+
auto actual = ValidateStringField(
72+
json, "someField", "test-object", "default-value",
73+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
74+
ASSERT_STATUS_OK(actual);
75+
EXPECT_EQ(*actual, "value");
76+
}
77+
78+
TEST(ExternalAccountParsing, ValidateStringFieldDefaultMissing) {
79+
auto const json = nlohmann::json{{"anotherField", "value"}};
80+
auto actual = ValidateStringField(
81+
json, "someField", "test-object", "default-value",
82+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
83+
ASSERT_STATUS_OK(actual);
84+
EXPECT_EQ(*actual, "default-value");
85+
}
86+
87+
TEST(ExternalAccountParsing, ValidateStringFieldDefaultNotString) {
88+
auto const json =
89+
nlohmann::json{{"some-field", "value"}, {"wrongType", true}};
90+
auto actual = ValidateStringField(
91+
json, "wrongType", "test-object", "default-value",
92+
internal::ErrorContext({{"origin", "test"}, {"filename", "/dev/null"}}));
93+
EXPECT_THAT(actual, StatusIs(StatusCode::kInvalidArgument,
94+
AllOf(HasSubstr("wrongType"),
95+
HasSubstr("test-object"))));
96+
EXPECT_THAT(actual.status().error_info().metadata(),
97+
Contains(Pair("filename", "/dev/null")));
98+
EXPECT_THAT(actual.status().error_info().metadata(),
99+
Contains(Pair("origin", "test")));
100+
}
101+
102+
} // namespace
103+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
104+
} // namespace oauth2_internal
105+
} // namespace cloud
106+
} // namespace google

0 commit comments

Comments
 (0)