1 // Copyright 2019 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 // http://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/storage/iam_policy.h"
16 #include "google/cloud/testing_util/assert_ok.h"
17 #include <gmock/gmock.h>
18 #include <type_traits>
19
20 namespace google {
21 namespace cloud {
22 namespace storage {
23 inline namespace STORAGE_CLIENT_NS {
24 namespace {
25
26 static_assert(std::is_copy_constructible<NativeExpression>::value,
27 "NativeExpression shoud be copy constructible");
28 static_assert(std::is_move_constructible<NativeExpression>::value,
29 "NativeExpression shoud be move constructible");
30 static_assert(std::is_copy_assignable<NativeExpression>::value,
31 "NativeExpression shoud be copy assignable");
32 static_assert(std::is_move_assignable<NativeExpression>::value,
33 "NativeExpression shoud be move assignable");
34
35 static_assert(std::is_copy_constructible<NativeIamBinding>::value,
36 "NativeIamBinding shoud be copy constructible");
37 static_assert(std::is_move_constructible<NativeIamBinding>::value,
38 "NativeIamBinding shoud be move constructible");
39 static_assert(std::is_copy_assignable<NativeIamBinding>::value,
40 "NativeIamBinding shoud be copy assignable");
41 static_assert(std::is_move_assignable<NativeIamBinding>::value,
42 "NativeIamBinding shoud be move assignable");
43
44 static_assert(std::is_copy_constructible<NativeIamPolicy>::value,
45 "NativeIamPolicy shoud be copy constructible");
46 static_assert(std::is_move_constructible<NativeIamPolicy>::value,
47 "NativeIamPolicy shoud be move constructible");
48 static_assert(std::is_copy_assignable<NativeIamPolicy>::value,
49 "NativeIamPolicy shoud be copy assignable");
50 static_assert(std::is_move_assignable<NativeIamPolicy>::value,
51 "NativeIamPolicy shoud be move assignable");
52
TEST(NativeIamExpression,CtorAndAccessors)53 TEST(NativeIamExpression, CtorAndAccessors) {
54 NativeExpression expr("expr", "title", "descr", "loc");
55 NativeExpression const& const_expr = expr;
56 EXPECT_EQ("expr", const_expr.expression());
57 EXPECT_EQ("title", const_expr.title());
58 EXPECT_EQ("descr", const_expr.description());
59 EXPECT_EQ("loc", const_expr.location());
60
61 expr = NativeExpression("expr2");
62 EXPECT_EQ("expr2", const_expr.expression());
63 EXPECT_EQ("", const_expr.title());
64 EXPECT_EQ("", const_expr.description());
65 EXPECT_EQ("", const_expr.location());
66
67 expr.set_expression("expr3");
68 expr.set_title("title3");
69 expr.set_description("descr3");
70 expr.set_location("loc3");
71 EXPECT_EQ("expr3", const_expr.expression());
72 EXPECT_EQ("title3", const_expr.title());
73 EXPECT_EQ("descr3", const_expr.description());
74 EXPECT_EQ("loc3", const_expr.location());
75 }
76
TEST(NativeIamExpression,Printing)77 TEST(NativeIamExpression, Printing) {
78 NativeExpression expr("expr");
79 NativeExpression const& const_expr = expr;
80
81 {
82 std::stringstream sstream;
83 sstream << const_expr;
84 EXPECT_EQ("(expr)", sstream.str());
85 }
86 expr.set_title("title");
87 {
88 std::stringstream sstream;
89 sstream << const_expr;
90 EXPECT_EQ("(expr, title=\"title\")", sstream.str());
91 }
92 expr.set_description("descr");
93 {
94 std::stringstream sstream;
95 sstream << const_expr;
96 EXPECT_EQ("(expr, title=\"title\", description=\"descr\")", sstream.str());
97 }
98 expr.set_location("loc");
99 {
100 std::stringstream sstream;
101 sstream << const_expr;
102 EXPECT_EQ(
103 "(expr, title=\"title\", description=\"descr\", location=\"loc\")",
104 sstream.str());
105 }
106 }
107
TEST(NativeIamBinding,CtorAndAccessors)108 TEST(NativeIamBinding, CtorAndAccessors) {
109 NativeIamBinding binding("role", {"member1", "member2"});
110 NativeIamBinding const& const_binding = binding;
111 ASSERT_EQ("role", const_binding.role());
112 binding.set_role("role2");
113 ASSERT_EQ("role2", const_binding.role());
114 ASSERT_EQ(std::vector<std::string>({"member1", "member2"}),
115 const_binding.members());
116 binding.members().emplace_back("member3");
117 ASSERT_EQ(std::vector<std::string>({"member1", "member2", "member3"}),
118 const_binding.members());
119 ASSERT_FALSE(binding.has_condition());
120
121 binding = NativeIamBinding("role", {"member1"}, NativeExpression("expr"));
122 ASSERT_EQ("role", const_binding.role());
123 ASSERT_EQ(std::vector<std::string>({"member1"}), const_binding.members());
124 ASSERT_TRUE(binding.has_condition());
125 ASSERT_EQ("expr", binding.condition().expression());
126
127 binding.set_condition(NativeExpression("expr2"));
128 ASSERT_TRUE(binding.has_condition());
129 ASSERT_EQ("expr2", binding.condition().expression());
130
131 binding.clear_condition();
132 ASSERT_FALSE(binding.has_condition());
133 }
134
TEST(NativeIamBinding,Printing)135 TEST(NativeIamBinding, Printing) {
136 NativeIamBinding binding("role", {"member1", "member2"});
137 NativeIamBinding const& const_binding = binding;
138 {
139 std::stringstream sstream;
140 sstream << const_binding;
141 EXPECT_EQ("role: [member1, member2]", sstream.str());
142 }
143 binding.set_condition(NativeExpression("expr"));
144 {
145 std::stringstream sstream;
146 sstream << const_binding;
147 EXPECT_EQ("role: [member1, member2] when (expr)", sstream.str());
148 }
149 }
150
TEST(NativeIamPolicy,CtorAndAccessors)151 TEST(NativeIamPolicy, CtorAndAccessors) {
152 NativeIamPolicy policy({NativeIamBinding("role1", {"member1", "member2"})},
153 "etag", 14);
154 NativeIamPolicy const& const_policy = policy;
155 ASSERT_EQ(14, const_policy.version());
156 ASSERT_EQ("etag", const_policy.etag());
157 policy.set_version(13);
158 ASSERT_EQ(13, const_policy.version());
159 policy.set_etag("etag_1");
160 ASSERT_EQ("etag_1", const_policy.etag());
161
162 ASSERT_EQ(1U, const_policy.bindings().size());
163 ASSERT_EQ("role1", const_policy.bindings()[0].role());
164 policy.bindings().emplace_back(
165 NativeIamBinding("role2", {"member1", "member3"}));
166 ASSERT_EQ(2U, const_policy.bindings().size());
167 ASSERT_EQ("role1", const_policy.bindings()[0].role());
168 ASSERT_EQ("role2", const_policy.bindings()[1].role());
169 }
170
TEST(NativeIamPolicy,Json)171 TEST(NativeIamPolicy, Json) {
172 NativeIamPolicy policy({NativeIamBinding("role1", {"member1", "member2"})},
173 "etag1", 17);
174 auto json = policy.ToJson();
175 auto maybe_policy = NativeIamPolicy::CreateFromJson(json);
176 ASSERT_STATUS_OK(maybe_policy);
177 policy = *std::move(maybe_policy);
178
179 ASSERT_EQ(17, policy.version());
180 ASSERT_EQ("etag1", policy.etag());
181 ASSERT_EQ(1U, policy.bindings().size());
182 ASSERT_EQ("role1", policy.bindings().front().role());
183 ASSERT_EQ(std::vector<std::string>({"member1", "member2"}),
184 policy.bindings().front().members());
185 }
186
187 // Check that expressions are parsed correctly.
TEST(NativeIamPolicy,ParseExpressionSuccess)188 TEST(NativeIamPolicy, ParseExpressionSuccess) {
189 auto policy = NativeIamPolicy::CreateFromJson(R"""(
190 {
191 "bindings": [
192 {
193 "condition":
194 {
195 "description": "descr",
196 "expression": "expr",
197 "location": "loc",
198 "title": "title"
199 },
200 "members": ["member1"],
201 "role": "role1"
202 }
203 ],
204 "version": 0
205 }
206 )""");
207 ASSERT_STATUS_OK(policy);
208 ASSERT_EQ(1U, policy->bindings().size());
209 ASSERT_TRUE(policy->bindings()[0].has_condition());
210 EXPECT_EQ("descr", policy->bindings()[0].condition().description());
211 EXPECT_EQ("expr", policy->bindings()[0].condition().expression());
212 EXPECT_EQ("loc", policy->bindings()[0].condition().location());
213 EXPECT_EQ("title", policy->bindings()[0].condition().title());
214 }
215
216 // Check that expressions are parsed correctly when values are not specified.
TEST(NativeIamPolicy,ParseExpressionSuccessDefaults)217 TEST(NativeIamPolicy, ParseExpressionSuccessDefaults) {
218 auto policy = NativeIamPolicy::CreateFromJson(R"""(
219 {
220 "bindings": [
221 {
222 "condition": {},
223 "members": ["member1"],
224 "role": "role1"
225 }
226 ],
227 "version": 0
228 }
229 )""");
230 ASSERT_STATUS_OK(policy);
231 ASSERT_EQ(1U, policy->bindings().size());
232 ASSERT_TRUE(policy->bindings()[0].has_condition());
233 EXPECT_EQ("", policy->bindings()[0].condition().description());
234 EXPECT_EQ("", policy->bindings()[0].condition().expression());
235 EXPECT_EQ("", policy->bindings()[0].condition().location());
236 EXPECT_EQ("", policy->bindings()[0].condition().title());
237 }
238
239 // Check that various errors parsing expressions are caught.
TEST(NativeIamPolicy,ParseConditionFailures)240 TEST(NativeIamPolicy, ParseConditionFailures) {
241 std::string const json_header = R"""(
242 {
243 "bindings": [
244 {
245 "condition":
246 )""";
247 std::string const json_footer = R"""(
248 ,
249 "members": ["member1"],
250 "role": "role1"
251 }
252 ],
253 "version": 0
254 }
255 )""";
256 using testing::HasSubstr;
257
258 auto policy =
259 NativeIamPolicy::CreateFromJson(json_header + "0" + json_footer);
260 ASSERT_FALSE(policy);
261 EXPECT_THAT(policy.status().message(),
262 HasSubstr("expected object for 'condition' field."));
263
264 policy = NativeIamPolicy::CreateFromJson(
265 json_header + "{\"expression\": {}}" + json_footer);
266 ASSERT_FALSE(policy);
267 EXPECT_THAT(policy.status().message(),
268 HasSubstr("expected string for 'expression' field"));
269
270 policy = NativeIamPolicy::CreateFromJson(
271 json_header + "{\"description\": {}}" + json_footer);
272 ASSERT_FALSE(policy);
273 EXPECT_THAT(policy.status().message(),
274 HasSubstr("expected string for 'description' field"));
275
276 policy = NativeIamPolicy::CreateFromJson(json_header + "{\"title\": {}}" +
277 json_footer);
278 ASSERT_FALSE(policy);
279 EXPECT_THAT(policy.status().message(),
280 HasSubstr("expected string for 'title' field"));
281
282 policy = NativeIamPolicy::CreateFromJson(json_header + "{\"location\": {}}" +
283 json_footer);
284 ASSERT_FALSE(policy);
285 EXPECT_THAT(policy.status().message(),
286 HasSubstr("expected string for 'location' field"));
287 }
288
289 // Check that bindings are parsed correctly.
TEST(NativeIamPolicy,ParseBindingSuccess)290 TEST(NativeIamPolicy, ParseBindingSuccess) {
291 auto policy = NativeIamPolicy::CreateFromJson(R"""(
292 {
293 "bindings": [
294 {
295 "members": ["member1", "member2"],
296 "role": "role1"
297 }
298 ]
299 }
300 )""");
301 ASSERT_STATUS_OK(policy);
302 ASSERT_EQ(1U, policy->bindings().size());
303 auto& binding = policy->bindings()[0];
304 auto const& const_binding = binding;
305 EXPECT_EQ(binding.members(),
306 std::vector<std::string>({"member1", "member2"}));
307 EXPECT_EQ(const_binding.members(),
308 std::vector<std::string>({"member1", "member2"}));
309 EXPECT_EQ("role1", const_binding.role());
310 }
311
312 // Check that bindings are parsed correctly with defaults.
TEST(NativeIamPolicy,ParseBindingSuccessDefaults)313 TEST(NativeIamPolicy, ParseBindingSuccessDefaults) {
314 auto policy = NativeIamPolicy::CreateFromJson(R"""(
315 {
316 "bindings": [
317 {
318 }
319 ]
320 }
321 )""");
322 ASSERT_STATUS_OK(policy);
323 ASSERT_EQ(1U, policy->bindings().size());
324 auto& binding = policy->bindings()[0];
325 auto const& const_binding = binding;
326 EXPECT_TRUE(binding.members().empty());
327 EXPECT_TRUE(const_binding.members().empty());
328 EXPECT_EQ("", const_binding.role());
329 }
330
331 // Check that various errors parsing expressions are caught.
TEST(NativeIamPolicy,ParseBindingsFailures)332 TEST(NativeIamPolicy, ParseBindingsFailures) {
333 std::string const json_header = R"""(
334 {
335 "bindings":
336 )""";
337 std::string const json_footer = R"""(
338 }
339 )""";
340 using testing::HasSubstr;
341
342 auto policy =
343 NativeIamPolicy::CreateFromJson(json_header + "0" + json_footer);
344 ASSERT_FALSE(policy);
345 EXPECT_THAT(policy.status().message(),
346 HasSubstr("expected array for 'bindings' field."));
347
348 policy = NativeIamPolicy::CreateFromJson(json_header + "[0]" + json_footer);
349 ASSERT_FALSE(policy);
350 EXPECT_THAT(policy.status().message(),
351 HasSubstr("expected object for 'bindings' entry"));
352
353 policy = NativeIamPolicy::CreateFromJson(json_header + "[{\"role\": 0}]" +
354 json_footer);
355 ASSERT_FALSE(policy);
356 EXPECT_THAT(policy.status().message(),
357 HasSubstr("expected string for 'role' field"));
358
359 policy = NativeIamPolicy::CreateFromJson(json_header + "[{\"members\": 0}]" +
360 json_footer);
361 ASSERT_FALSE(policy);
362 EXPECT_THAT(policy.status().message(),
363 HasSubstr("expected array for 'members' field"));
364
365 policy = NativeIamPolicy::CreateFromJson(
366 json_header + "[{\"members\": [0]}]" + json_footer);
367 ASSERT_FALSE(policy);
368 EXPECT_THAT(policy.status().message(),
369 HasSubstr("expected string for 'members' entry"));
370 }
371
372 // Check that policies are parsed correctly.
TEST(NativeIamPolicy,ParsePolicySuccess)373 TEST(NativeIamPolicy, ParsePolicySuccess) {
374 auto policy = NativeIamPolicy::CreateFromJson(R"""(
375 {
376 "version": 18,
377 "etag": "etag1"
378 }
379 )""");
380 ASSERT_STATUS_OK(policy);
381 EXPECT_TRUE(policy->bindings().empty());
382 EXPECT_EQ(18, policy->version());
383 EXPECT_EQ("etag1", policy->etag());
384 }
385
386 // Check that policies are parsed correctly when defaults are used.
TEST(NativeIamPolicy,ParsePolicySuccessDefaults)387 TEST(NativeIamPolicy, ParsePolicySuccessDefaults) {
388 auto policy = NativeIamPolicy::CreateFromJson(R"""(
389 {
390 }
391 )""");
392 ASSERT_STATUS_OK(policy);
393 EXPECT_EQ(0, policy->version());
394 EXPECT_EQ("", policy->etag());
395 }
396
397 // Check that various errors parsing policies are caught.
TEST(NativeIamPolicy,ParsePoliciesFailures)398 TEST(NativeIamPolicy, ParsePoliciesFailures) {
399 using testing::HasSubstr;
400
401 auto policy = NativeIamPolicy::CreateFromJson("{");
402 ASSERT_FALSE(policy);
403 EXPECT_THAT(policy.status().message(),
404 HasSubstr("it failed to parse as valid JSON"));
405
406 policy = NativeIamPolicy::CreateFromJson("0");
407 ASSERT_FALSE(policy);
408 EXPECT_THAT(policy.status().message(),
409 HasSubstr("expected object for top level node"));
410
411 policy = NativeIamPolicy::CreateFromJson("{\"etag\": 0}");
412 ASSERT_FALSE(policy);
413 EXPECT_THAT(policy.status().message(),
414 HasSubstr("expected string for 'etag' field"));
415
416 policy = NativeIamPolicy::CreateFromJson(R"({"version": "13"})");
417 ASSERT_FALSE(policy);
418 EXPECT_THAT(policy.status().message(),
419 HasSubstr("expected integer for 'version' field"));
420 }
421
TEST(NativeIamPolicy,UnknownFields)422 TEST(NativeIamPolicy, UnknownFields) {
423 auto policy = NativeIamPolicy::CreateFromJson(R"""(
424 {
425 "bindings": [
426 {
427 "condition":
428 {
429 "description": "descr",
430 "expression": "expr",
431 "location": "loc",
432 "title": "title",
433 "unknown_expr_field": "opaque1"
434 },
435 "members": ["member1"],
436 "role": "role1",
437 "unknown_binding_field": "opaque2"
438 }
439 ],
440 "version": 0,
441 "unknown_policy_field": "opaque3"
442 }
443 )""");
444 ASSERT_STATUS_OK(policy);
445 auto json = nlohmann::json::parse(policy->ToJson());
446 EXPECT_EQ("opaque3", json["unknown_policy_field"]);
447 EXPECT_EQ("opaque2", json["bindings"][0]["unknown_binding_field"]);
448 EXPECT_EQ("opaque1", json["bindings"][0]["condition"]["unknown_expr_field"]);
449 }
450
TEST(NativeIamPolicy,Printing)451 TEST(NativeIamPolicy, Printing) {
452 NativeIamPolicy policy({NativeIamBinding("role", {"member1", "member2"})},
453 "etag1", 18);
454 NativeIamPolicy const& const_policy = policy;
455 {
456 std::stringstream sstream;
457 sstream << const_policy;
458 EXPECT_EQ(
459 "NativeIamPolicy={version=18, bindings=NativeIamBindings={role: "
460 "[member1, member2]}, etag=etag1}",
461 sstream.str());
462 }
463 }
464
465 } // namespace
466 } // namespace STORAGE_CLIENT_NS
467 } // namespace storage
468 } // namespace cloud
469 } // namespace google
470