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