1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/policy/core/common/schema_map.h"
6 #include <memory>
7
8 #include "base/memory/weak_ptr.h"
9 #include "base/values.h"
10 #include "components/policy/core/common/external_data_fetcher.h"
11 #include "components/policy/core/common/external_data_manager.h"
12 #include "components/policy/core/common/policy_bundle.h"
13 #include "components/policy/core/common/policy_map.h"
14 #include "components/policy/core/common/policy_types.h"
15 #include "components/policy/core/common/schema.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace policy {
19
20 namespace {
21
22 const char kTestSchema[] =
23 "{"
24 " \"type\": \"object\","
25 " \"properties\": {"
26 " \"string\": { \"type\": \"string\" },"
27 " \"integer\": { \"type\": \"integer\" },"
28 " \"boolean\": { \"type\": \"boolean\" },"
29 " \"null\": { \"type\": \"null\" },"
30 " \"double\": { \"type\": \"number\" },"
31 " \"list\": {"
32 " \"type\": \"array\","
33 " \"items\": { \"type\": \"string\" }"
34 " },"
35 " \"object\": {"
36 " \"type\": \"object\","
37 " \"properties\": {"
38 " \"a\": { \"type\": \"string\" },"
39 " \"b\": { \"type\": \"integer\" }"
40 " }"
41 " }"
42 " }"
43 "}";
44
45 } // namespace
46
47 class SchemaMapTest : public testing::Test {
48 protected:
CreateTestSchema()49 Schema CreateTestSchema() {
50 std::string error;
51 Schema schema = Schema::Parse(kTestSchema, &error);
52 if (!schema.valid())
53 ADD_FAILURE() << error;
54 return schema;
55 }
56
CreateTestMap()57 scoped_refptr<SchemaMap> CreateTestMap() {
58 Schema schema = CreateTestSchema();
59 ComponentMap component_map;
60 component_map["extension-1"] = schema;
61 component_map["extension-2"] = schema;
62 component_map["legacy-extension"] = Schema();
63
64 DomainMap domain_map;
65 domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
66
67 return new SchemaMap(domain_map);
68 }
69 };
70
TEST_F(SchemaMapTest,Empty)71 TEST_F(SchemaMapTest, Empty) {
72 scoped_refptr<SchemaMap> map = new SchemaMap();
73 EXPECT_TRUE(map->GetDomains().empty());
74 EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_CHROME));
75 EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_EXTENSIONS));
76 EXPECT_FALSE(map->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
77 EXPECT_FALSE(map->HasComponents());
78 }
79
TEST_F(SchemaMapTest,HasComponents)80 TEST_F(SchemaMapTest, HasComponents) {
81 scoped_refptr<SchemaMap> map = new SchemaMap();
82 EXPECT_FALSE(map->HasComponents());
83
84 // The Chrome schema does not count as a component.
85 Schema schema = CreateTestSchema();
86 ComponentMap component_map;
87 component_map[""] = schema;
88 DomainMap domain_map;
89 domain_map[POLICY_DOMAIN_CHROME] = component_map;
90 map = new SchemaMap(domain_map);
91 EXPECT_FALSE(map->HasComponents());
92
93 // An extension schema does.
94 domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
95 map = new SchemaMap(domain_map);
96 EXPECT_TRUE(map->HasComponents());
97 }
98
TEST_F(SchemaMapTest,Lookups)99 TEST_F(SchemaMapTest, Lookups) {
100 scoped_refptr<SchemaMap> map = CreateTestMap();
101 ASSERT_TRUE(map.get());
102 EXPECT_TRUE(map->HasComponents());
103
104 EXPECT_FALSE(map->GetSchema(
105 PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
106 EXPECT_FALSE(map->GetSchema(
107 PolicyNamespace(POLICY_DOMAIN_CHROME, "extension-1")));
108 EXPECT_FALSE(map->GetSchema(
109 PolicyNamespace(POLICY_DOMAIN_CHROME, "legacy-extension")));
110 EXPECT_FALSE(map->GetSchema(
111 PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "")));
112 EXPECT_FALSE(map->GetSchema(
113 PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-3")));
114
115 const Schema* schema =
116 map->GetSchema(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-1"));
117 ASSERT_TRUE(schema);
118 EXPECT_TRUE(schema->valid());
119
120 schema = map->GetSchema(
121 PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "legacy-extension"));
122 ASSERT_TRUE(schema);
123 EXPECT_FALSE(schema->valid());
124 }
125
TEST_F(SchemaMapTest,FilterBundle)126 TEST_F(SchemaMapTest, FilterBundle) {
127 std::string error;
128 Schema schema = Schema::Parse(kTestSchema, &error);
129 ASSERT_TRUE(schema.valid()) << error;
130
131 DomainMap domain_map;
132 domain_map[POLICY_DOMAIN_EXTENSIONS]["abc"] = schema;
133 scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
134
135 PolicyBundle bundle;
136 schema_map->FilterBundle(&bundle);
137 const PolicyBundle empty_bundle;
138 EXPECT_TRUE(bundle.Equals(empty_bundle));
139
140 // The Chrome namespace isn't filtered.
141 PolicyBundle expected_bundle;
142 PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
143 expected_bundle.Get(chrome_ns).Set(
144 "ChromePolicy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
145 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"), nullptr);
146 bundle.CopyFrom(expected_bundle);
147
148 // Unknown components are filtered out.
149 PolicyNamespace another_extension_ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
150 bundle.Get(another_extension_ns)
151 .Set("AnotherExtensionPolicy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
152 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"),
153 nullptr);
154 schema_map->FilterBundle(&bundle);
155 EXPECT_TRUE(bundle.Equals(expected_bundle));
156
157 PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "abc");
158 PolicyMap& map = expected_bundle.Get(extension_ns);
159 base::ListValue list;
160 list.AppendString("a");
161 list.AppendString("b");
162 map.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
163 POLICY_SOURCE_CLOUD, list.CreateDeepCopy(), nullptr);
164 map.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
165 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(true), nullptr);
166 map.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
167 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1), nullptr);
168 map.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
169 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(), nullptr);
170 map.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
171 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(1.2), nullptr);
172 base::DictionaryValue dict;
173 dict.SetString("a", "b");
174 dict.SetInteger("b", 2);
175 map.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
176 POLICY_SOURCE_CLOUD, dict.CreateDeepCopy(), nullptr);
177 map.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
178 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value"), nullptr);
179
180 bundle.MergeFrom(expected_bundle);
181 bundle.Get(extension_ns)
182 .Set("Unexpected", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
183 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("to-be-removed"),
184 nullptr);
185
186 schema_map->FilterBundle(&bundle);
187 EXPECT_TRUE(bundle.Equals(expected_bundle));
188
189 // Mismatched types are also removed.
190 bundle.Clear();
191 PolicyMap& badmap = bundle.Get(extension_ns);
192 badmap.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
193 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
194 nullptr);
195 badmap.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
196 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(0), nullptr);
197 badmap.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
198 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
199 nullptr);
200 badmap.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
201 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
202 nullptr);
203 badmap.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
204 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
205 nullptr);
206 badmap.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
207 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(false),
208 nullptr);
209 badmap.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
210 POLICY_SOURCE_CLOUD, nullptr,
211 std::make_unique<ExternalDataFetcher>(nullptr, std::string()));
212
213 schema_map->FilterBundle(&bundle);
214 EXPECT_TRUE(bundle.Equals(empty_bundle));
215 }
216
TEST_F(SchemaMapTest,LegacyComponents)217 TEST_F(SchemaMapTest, LegacyComponents) {
218 std::string error;
219 Schema schema = Schema::Parse(
220 "{"
221 " \"type\":\"object\","
222 " \"properties\": {"
223 " \"String\": { \"type\": \"string\" }"
224 " }"
225 "}", &error);
226 ASSERT_TRUE(schema.valid()) << error;
227
228 DomainMap domain_map;
229 domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
230 domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
231 scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
232
233 // |bundle| contains policies loaded by a policy provider.
234 PolicyBundle bundle;
235
236 // Known components with schemas are filtered.
237 PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
238 bundle.Get(extension_ns)
239 .Set("String", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
240 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 1"),
241 nullptr);
242
243 // The Chrome namespace isn't filtered.
244 PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
245 bundle.Get(chrome_ns).Set("ChromePolicy", POLICY_LEVEL_MANDATORY,
246 POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
247 std::make_unique<base::Value>("value 3"), nullptr);
248
249 PolicyBundle expected_bundle;
250 expected_bundle.MergeFrom(bundle);
251
252 // Known components without a schema are filtered out completely.
253 PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
254 bundle.Get(without_schema_ns)
255 .Set("Schemaless", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
256 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 2"),
257 nullptr);
258
259 // Unknown policies of known components with a schema are removed.
260 bundle.Get(extension_ns)
261 .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
262 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 4"),
263 nullptr);
264
265 // Unknown components are removed.
266 PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
267 bundle.Get(unknown_ns)
268 .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
269 POLICY_SOURCE_CLOUD, std::make_unique<base::Value>("value 5"),
270 nullptr);
271
272 schema_map->FilterBundle(&bundle);
273 EXPECT_TRUE(bundle.Equals(expected_bundle));
274 }
275
TEST_F(SchemaMapTest,GetChanges)276 TEST_F(SchemaMapTest, GetChanges) {
277 DomainMap map;
278 map[POLICY_DOMAIN_CHROME][""] = Schema();
279 scoped_refptr<SchemaMap> older = new SchemaMap(map);
280 map[POLICY_DOMAIN_CHROME][""] = Schema();
281 scoped_refptr<SchemaMap> newer = new SchemaMap(map);
282
283 PolicyNamespaceList removed;
284 PolicyNamespaceList added;
285 newer->GetChanges(older, &removed, &added);
286 EXPECT_TRUE(removed.empty());
287 EXPECT_TRUE(added.empty());
288
289 map[POLICY_DOMAIN_CHROME][""] = Schema();
290 map[POLICY_DOMAIN_EXTENSIONS]["xyz"] = Schema();
291 newer = new SchemaMap(map);
292 newer->GetChanges(older, &removed, &added);
293 EXPECT_TRUE(removed.empty());
294 ASSERT_EQ(1u, added.size());
295 EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), added[0]);
296
297 older = newer;
298 map[POLICY_DOMAIN_EXTENSIONS]["abc"] = Schema();
299 newer = new SchemaMap(map);
300 newer->GetChanges(older, &removed, &added);
301 ASSERT_EQ(2u, removed.size());
302 EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), removed[0]);
303 EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), removed[1]);
304 ASSERT_EQ(1u, added.size());
305 EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), added[0]);
306 }
307
308 } // namespace policy
309