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