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);
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                                     base::Value::CreateStringValue("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(
151      "AnotherExtensionPolicy",
152      POLICY_LEVEL_MANDATORY,
153      POLICY_SCOPE_USER,
154      base::Value::CreateStringValue("value"),
155      NULL);
156  schema_map->FilterBundle(&bundle);
157  EXPECT_TRUE(bundle.Equals(expected_bundle));
158
159  PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "abc");
160  PolicyMap& map = expected_bundle.Get(extension_ns);
161  base::ListValue list;
162  list.AppendString("a");
163  list.AppendString("b");
164  map.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
165          list.DeepCopy(), NULL);
166  map.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
167          base::Value::CreateBooleanValue(true), NULL);
168  map.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
169          base::Value::CreateIntegerValue(1), NULL);
170  map.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
171          base::Value::CreateNullValue(), NULL);
172  map.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
173          base::Value::CreateDoubleValue(1.2), NULL);
174  base::DictionaryValue dict;
175  dict.SetString("a", "b");
176  dict.SetInteger("b", 2);
177  map.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
178          dict.DeepCopy(), NULL);
179  map.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
180          base::Value::CreateStringValue("value"), NULL);
181
182  bundle.MergeFrom(expected_bundle);
183  bundle.Get(extension_ns).Set("Unexpected",
184                               POLICY_LEVEL_MANDATORY,
185                               POLICY_SCOPE_USER,
186                               base::Value::CreateStringValue("to-be-removed"),
187                               NULL);
188
189  schema_map->FilterBundle(&bundle);
190  EXPECT_TRUE(bundle.Equals(expected_bundle));
191
192  // Mismatched types are also removed.
193  bundle.Clear();
194  PolicyMap& badmap = bundle.Get(extension_ns);
195  badmap.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
196             base::Value::CreateBooleanValue(false), NULL);
197  badmap.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
198             base::Value::CreateIntegerValue(0), NULL);
199  badmap.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
200             base::Value::CreateBooleanValue(false), NULL);
201  badmap.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
202             base::Value::CreateBooleanValue(false), NULL);
203  badmap.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
204             base::Value::CreateBooleanValue(false), NULL);
205  badmap.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
206             base::Value::CreateBooleanValue(false), NULL);
207  badmap.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
208             NULL,
209             new ExternalDataFetcher(base::WeakPtr<ExternalDataManager>(),
210                                     std::string()));
211
212  schema_map->FilterBundle(&bundle);
213  EXPECT_TRUE(bundle.Equals(empty_bundle));
214}
215
216TEST_F(SchemaMapTest, LegacyComponents) {
217  std::string error;
218  Schema schema = Schema::Parse(
219      "{"
220      "  \"type\":\"object\","
221      "  \"properties\": {"
222      "    \"String\": { \"type\": \"string\" }"
223      "  }"
224      "}", &error);
225  ASSERT_TRUE(schema.valid()) << error;
226
227  DomainMap domain_map;
228  domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
229  domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
230  scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
231
232  // |bundle| contains policies loaded by a policy provider.
233  PolicyBundle bundle;
234
235  // Known components with schemas are filtered.
236  PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
237  bundle.Get(extension_ns).Set("String",
238                               POLICY_LEVEL_MANDATORY,
239                               POLICY_SCOPE_USER,
240                               base::Value::CreateStringValue("value 1"),
241                               NULL);
242
243  // Known components without a schema are not filtered.
244  PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
245  bundle.Get(without_schema_ns).Set("Schemaless",
246                                    POLICY_LEVEL_MANDATORY,
247                                    POLICY_SCOPE_USER,
248                                    base::Value::CreateStringValue("value 2"),
249                                    NULL);
250
251  // The Chrome namespace isn't filtered.
252  PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
253  bundle.Get(chrome_ns).Set("ChromePolicy",
254                            POLICY_LEVEL_MANDATORY,
255                            POLICY_SCOPE_USER,
256                            base::Value::CreateStringValue("value 3"),
257                            NULL);
258
259  PolicyBundle expected_bundle;
260  expected_bundle.MergeFrom(bundle);
261
262  // Unknown policies of known components with a schema are removed.
263  bundle.Get(extension_ns).Set("Surprise",
264                               POLICY_LEVEL_MANDATORY,
265                               POLICY_SCOPE_USER,
266                               base::Value::CreateStringValue("value 4"),
267                               NULL);
268
269  // Unknown components are removed.
270  PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
271  bundle.Get(unknown_ns).Set("Surprise",
272                             POLICY_LEVEL_MANDATORY,
273                             POLICY_SCOPE_USER,
274                             base::Value::CreateStringValue("value 5"),
275                             NULL);
276
277  schema_map->FilterBundle(&bundle);
278  EXPECT_TRUE(bundle.Equals(expected_bundle));
279}
280
281TEST_F(SchemaMapTest, GetChanges) {
282  DomainMap map;
283  map[POLICY_DOMAIN_CHROME][""] = Schema();
284  scoped_refptr<SchemaMap> older = new SchemaMap(map);
285  map[POLICY_DOMAIN_CHROME][""] = Schema();
286  scoped_refptr<SchemaMap> newer = new SchemaMap(map);
287
288  PolicyNamespaceList removed;
289  PolicyNamespaceList added;
290  newer->GetChanges(older, &removed, &added);
291  EXPECT_TRUE(removed.empty());
292  EXPECT_TRUE(added.empty());
293
294  map[POLICY_DOMAIN_CHROME][""] = Schema();
295  map[POLICY_DOMAIN_EXTENSIONS]["xyz"] = Schema();
296  newer = new SchemaMap(map);
297  newer->GetChanges(older, &removed, &added);
298  EXPECT_TRUE(removed.empty());
299  ASSERT_EQ(1u, added.size());
300  EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), added[0]);
301
302  older = newer;
303  map[POLICY_DOMAIN_EXTENSIONS]["abc"] = Schema();
304  newer = new SchemaMap(map);
305  newer->GetChanges(older, &removed, &added);
306  ASSERT_EQ(2u, removed.size());
307  EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), removed[0]);
308  EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), removed[1]);
309  ASSERT_EQ(1u, added.size());
310  EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), added[0]);
311}
312
313}  // namespace policy
314