summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authordconnelly@chromium.org <dconnelly@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-22 17:50:47 +0000
committerdconnelly@chromium.org <dconnelly@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-22 17:50:47 +0000
commitf39e98eb6e1f501e11eb596879a59c1420411500 (patch)
tree40c1211b431e3af82a9ae996d3949436e196373b /components
parenta404789baf4191b0c3fd3b0d5b050c6937c68d87 (diff)
downloadchromium_src-f39e98eb6e1f501e11eb596879a59c1420411500.zip
chromium_src-f39e98eb6e1f501e11eb596879a59c1420411500.tar.gz
chromium_src-f39e98eb6e1f501e11eb596879a59c1420411500.tar.bz2
Move PolicySchema and PolicySchemaRegistry to components/policy/.
This facilitates the refactoring of chrome/browser/policy into a layered component. Depends on https://codereview.chromium.org/78823004/ BUG=271392 Review URL: https://codereview.chromium.org/78453005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236785 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp2
-rw-r--r--components/policy.gypi4
-rw-r--r--components/policy/core/common/schema_map.cc105
-rw-r--r--components/policy/core/common/schema_map.h65
-rw-r--r--components/policy/core/common/schema_map_unittest.cc313
-rw-r--r--components/policy/core/common/schema_registry.cc175
-rw-r--r--components/policy/core/common/schema_registry.h115
-rw-r--r--components/policy/core/common/schema_registry_unittest.cc242
8 files changed, 1021 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 96d1cd3..2e00f4a 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -173,6 +173,8 @@
'sources': [
'policy/core/common/policy_bundle_unittest.cc',
'policy/core/common/policy_map_unittest.cc',
+ 'policy/core/common/schema_map_unittest.cc',
+ 'policy/core/common/schema_registry_unittest.cc',
'policy/core/common/schema_unittest.cc',
],
}],
diff --git a/components/policy.gypi b/components/policy.gypi
index 9c2c34c..165c477 100644
--- a/components/policy.gypi
+++ b/components/policy.gypi
@@ -38,6 +38,10 @@
'policy/core/common/schema.cc',
'policy/core/common/schema.h',
'policy/core/common/schema_internal.h',
+ 'policy/core/common/schema_map.cc',
+ 'policy/core/common/schema_map.h',
+ 'policy/core/common/schema_registry.cc',
+ 'policy/core/common/schema_registry.h',
'policy/policy_export.h',
],
}, { # configuration_policy==0
diff --git a/components/policy/core/common/schema_map.cc b/components/policy/core/common/schema_map.cc
new file mode 100644
index 0000000..488af8d
--- /dev/null
+++ b/components/policy/core/common/schema_map.cc
@@ -0,0 +1,105 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+SchemaMap::SchemaMap() {}
+
+SchemaMap::SchemaMap(DomainMap& map) {
+ map_.swap(map);
+}
+
+SchemaMap::~SchemaMap() {}
+
+const DomainMap& SchemaMap::GetDomains() const {
+ return map_;
+}
+
+const ComponentMap* SchemaMap::GetComponents(PolicyDomain domain) const {
+ DomainMap::const_iterator it = map_.find(domain);
+ return it == map_.end() ? NULL : &it->second;
+}
+
+const Schema* SchemaMap::GetSchema(const PolicyNamespace& ns) const {
+ const ComponentMap* map = GetComponents(ns.domain);
+ if (!map)
+ return NULL;
+ ComponentMap::const_iterator it = map->find(ns.component_id);
+ return it == map->end() ? NULL : &it->second;
+}
+
+void SchemaMap::FilterBundle(PolicyBundle* bundle) const {
+ for (PolicyBundle::iterator it = bundle->begin(); it != bundle->end(); ++it) {
+ // Chrome policies are not filtered, so that typos appear in about:policy.
+ // Everything else gets filtered, so that components only see valid policy.
+ if (it->first.domain == POLICY_DOMAIN_CHROME)
+ continue;
+
+ const Schema* schema = GetSchema(it->first);
+
+ if (!schema) {
+ it->second->Clear();
+ continue;
+ }
+
+ // TODO(joaodasilva): if a component is registered but doesn't have a schema
+ // then its policies aren't filtered. This behavior is enabled to allow a
+ // graceful update of the Legacy Browser Support extension; it'll be removed
+ // in a future release. http://crbug.com/240704
+ if (!schema->valid())
+ continue;
+
+ PolicyMap* map = it->second;
+ for (PolicyMap::const_iterator it_map = map->begin();
+ it_map != map->end();) {
+ const std::string& policy_name = it_map->first;
+ const base::Value* policy_value = it_map->second.value;
+ Schema policy_schema = schema->GetProperty(policy_name);
+ ++it_map;
+ if (!policy_value || !policy_schema.Validate(*policy_value))
+ map->Erase(policy_name);
+ }
+ }
+}
+
+bool SchemaMap::HasComponents() const {
+ for (DomainMap::const_iterator domain = map_.begin();
+ domain != map_.end(); ++domain) {
+ if (domain->first == POLICY_DOMAIN_CHROME)
+ continue;
+ if (!domain->second.empty())
+ return true;
+ }
+ return false;
+}
+
+void SchemaMap::GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const {
+ GetNamespacesNotInOther(older, added);
+ older->GetNamespacesNotInOther(this, removed);
+}
+
+void SchemaMap::GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const {
+ list->clear();
+ for (DomainMap::const_iterator domain = map_.begin();
+ domain != map_.end(); ++domain) {
+ const ComponentMap& components = domain->second;
+ for (ComponentMap::const_iterator comp = components.begin();
+ comp != components.end(); ++comp) {
+ PolicyNamespace ns(domain->first, comp->first);
+ if (!other->GetSchema(ns))
+ list->push_back(ns);
+ }
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_map.h b/components/policy/core/common/schema_map.h
new file mode 100644
index 0000000..e91886e
--- /dev/null
+++ b/components/policy/core/common/schema_map.h
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyBundle;
+
+typedef std::map<std::string, Schema> ComponentMap;
+typedef std::map<PolicyDomain, ComponentMap> DomainMap;
+
+// Contains a mapping of policy namespaces (domain + component ID) to its
+// corresponding Schema.
+class POLICY_EXPORT SchemaMap : public base::RefCountedThreadSafe<SchemaMap> {
+ public:
+ SchemaMap();
+ // Takes ownership of |map| (its contents will be swapped).
+ explicit SchemaMap(DomainMap& map);
+
+ const DomainMap& GetDomains() const;
+
+ const ComponentMap* GetComponents(PolicyDomain domain) const;
+
+ const Schema* GetSchema(const PolicyNamespace& ns) const;
+
+ // Removes all the policies in |bundle| that don't match the known schemas.
+ // Unknown components are also dropped.
+ void FilterBundle(PolicyBundle* bundle) const;
+
+ // Returns true if this map contains at least one component of a domain other
+ // than POLICY_DOMAIN_CHROME.
+ bool HasComponents() const;
+
+ void GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<SchemaMap>;
+
+ void GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const;
+
+ ~SchemaMap();
+
+ DomainMap map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchemaMap);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
diff --git a/components/policy/core/common/schema_map_unittest.cc b/components/policy/core/common/schema_map_unittest.cc
new file mode 100644
index 0000000..66da2f9
--- /dev/null
+++ b/components/policy/core/common/schema_map_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/schema.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"string\": { \"type\": \"string\" },"
+ " \"integer\": { \"type\": \"integer\" },"
+ " \"boolean\": { \"type\": \"boolean\" },"
+ " \"null\": { \"type\": \"null\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"object\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"a\": { \"type\": \"string\" },"
+ " \"b\": { \"type\": \"integer\" }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+} // namespace
+
+class SchemaMapTest : public testing::Test {
+ protected:
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ if (!schema.valid())
+ ADD_FAILURE() << error;
+ return schema;
+ }
+
+ scoped_refptr<SchemaMap> CreateTestMap() {
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map["extension-1"] = schema;
+ component_map["extension-2"] = schema;
+ component_map["legacy-extension"] = Schema();
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+
+ return new SchemaMap(domain_map);
+ }
+};
+
+TEST_F(SchemaMapTest, Empty) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_TRUE(map->GetDomains().empty());
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(map->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, HasComponents) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_FALSE(map->HasComponents());
+
+ // The Chrome schema does not count as a component.
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map[""] = schema;
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_CHROME] = component_map;
+ map = new SchemaMap(domain_map);
+ EXPECT_FALSE(map->HasComponents());
+
+ // An extension schema does.
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+ map = new SchemaMap(domain_map);
+ EXPECT_TRUE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, Lookups) {
+ scoped_refptr<SchemaMap> map = CreateTestMap();
+ ASSERT_TRUE(map);
+ EXPECT_TRUE(map->HasComponents());
+
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "extension-1")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "legacy-extension")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-3")));
+
+ const Schema* schema =
+ map->GetSchema(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-1"));
+ ASSERT_TRUE(schema);
+ EXPECT_TRUE(schema->valid());
+
+ schema = map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "legacy-extension"));
+ ASSERT_TRUE(schema);
+ EXPECT_FALSE(schema->valid());
+}
+
+TEST_F(SchemaMapTest, FilterBundle) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["abc"] = schema;
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
+
+ PolicyBundle bundle;
+ schema_map->FilterBundle(&bundle);
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+
+ // The Chrome namespace isn't filtered.
+ PolicyBundle expected_bundle;
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ expected_bundle.Get(chrome_ns).Set("ChromePolicy",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value"),
+ NULL);
+ bundle.CopyFrom(expected_bundle);
+
+ // Unknown components are filtered out.
+ PolicyNamespace another_extension_ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
+ bundle.Get(another_extension_ns).Set(
+ "AnotherExtensionPolicy",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value"),
+ NULL);
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "abc");
+ PolicyMap& map = expected_bundle.Get(extension_ns);
+ base::ListValue list;
+ list.AppendString("a");
+ list.AppendString("b");
+ map.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ list.DeepCopy(), NULL);
+ map.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(true), NULL);
+ map.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(1), NULL);
+ map.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateNullValue(), NULL);
+ map.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateDoubleValue(1.2), NULL);
+ base::DictionaryValue dict;
+ dict.SetString("a", "b");
+ dict.SetInteger("b", 2);
+ map.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ dict.DeepCopy(), NULL);
+ map.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value"), NULL);
+
+ bundle.MergeFrom(expected_bundle);
+ bundle.Get(extension_ns).Set("Unexpected",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("to-be-removed"),
+ NULL);
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ // Mismatched types are also removed.
+ bundle.Clear();
+ PolicyMap& badmap = bundle.Get(extension_ns);
+ badmap.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(false), NULL);
+ badmap.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(0), NULL);
+ badmap.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(false), NULL);
+ badmap.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(false), NULL);
+ badmap.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(false), NULL);
+ badmap.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(false), NULL);
+ badmap.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ NULL,
+ new ExternalDataFetcher(base::WeakPtr<ExternalDataManager>(),
+ std::string()));
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+}
+
+TEST_F(SchemaMapTest, LegacyComponents) {
+ std::string error;
+ Schema schema = Schema::Parse(
+ "{"
+ " \"type\":\"object\","
+ " \"properties\": {"
+ " \"String\": { \"type\": \"string\" }"
+ " }"
+ "}", &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(domain_map);
+
+ // |bundle| contains policies loaded by a policy provider.
+ PolicyBundle bundle;
+
+ // Known components with schemas are filtered.
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
+ bundle.Get(extension_ns).Set("String",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value 1"),
+ NULL);
+
+ // Known components without a schema are not filtered.
+ PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
+ bundle.Get(without_schema_ns).Set("Schemaless",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value 2"),
+ NULL);
+
+ // The Chrome namespace isn't filtered.
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("ChromePolicy",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value 3"),
+ NULL);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.MergeFrom(bundle);
+
+ // Unknown policies of known components with a schema are removed.
+ bundle.Get(extension_ns).Set("Surprise",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value 4"),
+ NULL);
+
+ // Unknown components are removed.
+ PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
+ bundle.Get(unknown_ns).Set("Surprise",
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value 5"),
+ NULL);
+
+ schema_map->FilterBundle(&bundle);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+}
+
+TEST_F(SchemaMapTest, GetChanges) {
+ DomainMap map;
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> older = new SchemaMap(map);
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> newer = new SchemaMap(map);
+
+ PolicyNamespaceList removed;
+ PolicyNamespaceList added;
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ EXPECT_TRUE(added.empty());
+
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ map[POLICY_DOMAIN_EXTENSIONS]["xyz"] = Schema();
+ newer = new SchemaMap(map);
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), added[0]);
+
+ older = newer;
+ map[POLICY_DOMAIN_EXTENSIONS]["abc"] = Schema();
+ newer = new SchemaMap(map);
+ newer->GetChanges(older, &removed, &added);
+ ASSERT_EQ(2u, removed.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), removed[0]);
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), removed[1]);
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), added[0]);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry.cc b/components/policy/core/common/schema_registry.cc
new file mode 100644
index 0000000..16f71e5
--- /dev/null
+++ b/components/policy/core/common/schema_registry.cc
@@ -0,0 +1,175 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include "base/logging.h"
+
+namespace policy {
+
+SchemaRegistry::Observer::~Observer() {}
+
+SchemaRegistry::SchemaRegistry() : schema_map_(new SchemaMap) {
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ domains_ready_[i] = false;
+#if !defined(ENABLE_EXTENSIONS)
+ domains_ready_[POLICY_DOMAIN_EXTENSIONS] = true;
+#endif
+}
+
+SchemaRegistry::~SchemaRegistry() {}
+
+void SchemaRegistry::RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema) {
+ ComponentMap map;
+ map[ns.component_id] = schema;
+ RegisterComponents(ns.domain, map);
+}
+
+void SchemaRegistry::RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) {
+ // Assume that a schema was updated if the namespace was already registered
+ // before.
+ DomainMap map(schema_map_->GetDomains());
+ for (ComponentMap::const_iterator it = components.begin();
+ it != components.end(); ++it) {
+ map[domain][it->first] = it->second;
+ }
+ schema_map_ = new SchemaMap(map);
+ Notify(true);
+}
+
+void SchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DomainMap map(schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ schema_map_ = new SchemaMap(map);
+ Notify(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SchemaRegistry::IsReady() const {
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i) {
+ if (!domains_ready_[i])
+ return false;
+ }
+ return true;
+}
+
+void SchemaRegistry::SetReady(PolicyDomain domain) {
+ if (domains_ready_[domain])
+ return;
+ domains_ready_[domain] = true;
+ if (IsReady())
+ FOR_EACH_OBSERVER(Observer, observers_, OnSchemaRegistryReady());
+}
+
+void SchemaRegistry::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void SchemaRegistry::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void SchemaRegistry::Notify(bool has_new_schemas) {
+ FOR_EACH_OBSERVER(
+ Observer, observers_, OnSchemaRegistryUpdated(has_new_schemas));
+}
+
+bool SchemaRegistry::HasObservers() const {
+ return observers_.might_have_observers();
+}
+
+CombinedSchemaRegistry::CombinedSchemaRegistry()
+ : own_schema_map_(new SchemaMap) {
+ // The combined registry is always ready, since it can always start tracking
+ // another registry that is not ready yet and going from "ready" to "not
+ // ready" is not allowed.
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ SetReady(static_cast<PolicyDomain>(i));
+}
+
+CombinedSchemaRegistry::~CombinedSchemaRegistry() {}
+
+void CombinedSchemaRegistry::Track(SchemaRegistry* registry) {
+ registries_.insert(registry);
+ registry->AddObserver(this);
+ // Recombine the maps only if the |registry| has any components other than
+ // POLICY_DOMAIN_CHROME.
+ if (registry->schema_map()->HasComponents())
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::Untrack(SchemaRegistry* registry) {
+ registry->RemoveObserver(this);
+ if (registries_.erase(registry) != 0) {
+ if (registry->schema_map()->HasComponents())
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::RegisterComponents(
+ PolicyDomain domain,
+ const ComponentMap& components) {
+ DomainMap map(own_schema_map_->GetDomains());
+ for (ComponentMap::const_iterator it = components.begin();
+ it != components.end(); ++it) {
+ map[domain][it->first] = it->second;
+ }
+ own_schema_map_ = new SchemaMap(map);
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DomainMap map(own_schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ own_schema_map_ = new SchemaMap(map);
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
+ Combine(has_new_schemas);
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryReady() {
+ // Ignore.
+}
+
+void CombinedSchemaRegistry::Combine(bool has_new_schemas) {
+ // If two registries publish a Schema for the same component then it's
+ // undefined which version gets in the combined registry.
+ //
+ // The common case is that both registries want policy for the same component,
+ // and the Schemas should be the same; in that case this makes no difference.
+ //
+ // But if the Schemas are different then one of the components is out of date.
+ // In that case the policy loaded will be valid only for one of them, until
+ // the outdated components are updated. This is a known limitation of the
+ // way policies are loaded currently, but isn't a problem worth fixing for
+ // the time being.
+ DomainMap map(own_schema_map_->GetDomains());
+ for (std::set<SchemaRegistry*>::const_iterator reg_it = registries_.begin();
+ reg_it != registries_.end(); ++reg_it) {
+ const DomainMap& reg_domain_map = (*reg_it)->schema_map()->GetDomains();
+ for (DomainMap::const_iterator domain_it = reg_domain_map.begin();
+ domain_it != reg_domain_map.end(); ++domain_it) {
+ const ComponentMap& reg_component_map = domain_it->second;
+ for (ComponentMap::const_iterator comp_it = reg_component_map.begin();
+ comp_it != reg_component_map.end(); ++comp_it) {
+ map[domain_it->first][comp_it->first] = comp_it->second;
+ }
+ }
+ }
+ schema_map_ = new SchemaMap(map);
+ Notify(has_new_schemas);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/schema_registry.h b/components/policy/core/common/schema_registry.h
new file mode 100644
index 0000000..5f56589
--- /dev/null
+++ b/components/policy/core/common/schema_registry.h
@@ -0,0 +1,115 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/threading/non_thread_safe.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+
+namespace policy {
+
+class SchemaMap;
+
+// Holds the main reference to the current SchemaMap, and allows a list of
+// observers to get notified whenever it is updated.
+// This object is not thread safe and must be used from the owner's thread,
+// usually UI.
+class POLICY_EXPORT SchemaRegistry : public base::NonThreadSafe {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked whenever schemas are registered or unregistered.
+ // |has_new_schemas| is true if a new component has been registered since
+ // the last update; this allows observers to ignore updates when
+ // components are unregistered but still get a handle to the current map
+ // (e.g. for periodic reloads).
+ virtual void OnSchemaRegistryUpdated(bool has_new_schemas) = 0;
+
+ // Invoked when all policy domains become ready.
+ virtual void OnSchemaRegistryReady() = 0;
+
+ protected:
+ virtual ~Observer();
+ };
+
+ SchemaRegistry();
+ virtual ~SchemaRegistry();
+
+ const scoped_refptr<SchemaMap>& schema_map() const { return schema_map_; }
+
+ // Register a single component.
+ void RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema);
+
+ // Register a list of components for a given domain.
+ virtual void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components);
+
+ virtual void UnregisterComponent(const PolicyNamespace& ns);
+
+ // Returns true if all domains have registered the initial components.
+ bool IsReady() const;
+
+ // This indicates that the initial components for |domain| have all been
+ // registered. It must be invoked at least once for each policy domain;
+ // subsequent calls for the same domain are ignored.
+ void SetReady(PolicyDomain domain);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ bool HasObservers() const;
+
+ protected:
+ void Notify(bool has_new_schemas);
+
+ scoped_refptr<SchemaMap> schema_map_;
+
+ private:
+ ObserverList<Observer, true> observers_;
+ bool domains_ready_[POLICY_DOMAIN_SIZE];
+
+ DISALLOW_COPY_AND_ASSIGN(SchemaRegistry);
+};
+
+// A registry that combines the maps of other registries.
+class POLICY_EXPORT CombinedSchemaRegistry : public SchemaRegistry,
+ public SchemaRegistry::Observer {
+ public:
+ CombinedSchemaRegistry();
+ virtual ~CombinedSchemaRegistry();
+
+ void Track(SchemaRegistry* registry);
+ void Untrack(SchemaRegistry* registry);
+
+ virtual void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) OVERRIDE;
+
+ virtual void UnregisterComponent(const PolicyNamespace& ns) OVERRIDE;
+
+ virtual void OnSchemaRegistryUpdated(bool has_new_schemas) OVERRIDE;
+
+ virtual void OnSchemaRegistryReady() OVERRIDE;
+
+ private:
+ void Combine(bool has_new_schemas);
+
+ std::set<SchemaRegistry*> registries_;
+ scoped_refptr<SchemaMap> own_schema_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(CombinedSchemaRegistry);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
diff --git a/components/policy/core/common/schema_registry_unittest.cc b/components/policy/core/common/schema_registry_unittest.cc
new file mode 100644
index 0000000..94e245e
--- /dev/null
+++ b/components/policy/core/common/schema_registry_unittest.cc
@@ -0,0 +1,242 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Mock;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"string\": { \"type\": \"string\" },"
+ " \"integer\": { \"type\": \"integer\" },"
+ " \"boolean\": { \"type\": \"boolean\" },"
+ " \"null\": { \"type\": \"null\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"object\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"a\": { \"type\": \"string\" },"
+ " \"b\": { \"type\": \"integer\" }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+class MockSchemaRegistryObserver : public SchemaRegistry::Observer {
+ public:
+ MockSchemaRegistryObserver() {}
+ virtual ~MockSchemaRegistryObserver() {}
+
+ MOCK_METHOD1(OnSchemaRegistryUpdated, void(bool));
+ MOCK_METHOD0(OnSchemaRegistryReady, void());
+};
+
+} // namespace
+
+TEST(SchemaRegistryTest, Notifications) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ SchemaRegistry registry;
+ EXPECT_FALSE(registry.HasObservers());
+ registry.AddObserver(&observer);
+ EXPECT_TRUE(registry.HasObservers());
+
+ ASSERT_TRUE(registry.schema_map());
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Re-register also triggers notifications, because the Schema might have
+ // changed.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ // Registering multiple components at once issues only one notification.
+ ComponentMap components;
+ components["abc"] = schema;
+ components["def"] = schema;
+ components["xyz"] = schema;
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponents(POLICY_DOMAIN_EXTENSIONS, components);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ registry.RemoveObserver(&observer);
+ EXPECT_FALSE(registry.HasObservers());
+}
+
+TEST(SchemaRegistryTest, IsReady) {
+ SchemaRegistry registry;
+ MockSchemaRegistryObserver observer;
+ registry.AddObserver(&observer);
+
+ EXPECT_FALSE(registry.IsReady());
+#if defined(ENABLE_EXTENSIONS)
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetReady(POLICY_DOMAIN_EXTENSIONS);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(registry.IsReady());
+#endif
+ EXPECT_CALL(observer, OnSchemaRegistryReady());
+ registry.SetReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+
+ CombinedSchemaRegistry combined;
+ EXPECT_TRUE(combined.IsReady());
+
+ registry.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, Combined) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ SchemaRegistry registry1;
+ SchemaRegistry registry2;
+ CombinedSchemaRegistry combined;
+ combined.AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ registry1.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Starting to track a registry issues notifications when it comes with new
+ // schemas.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.Track(&registry1);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding a new empty registry does not trigger notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ combined.Track(&registry2);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding the same component to the combined registry itself triggers
+ // notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding components to the sub-registries triggers notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry2.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // If the same component is published in 2 sub-registries then the combined
+ // registry publishes one of them.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry1.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ ASSERT_EQ(1u, combined.schema_map()->GetDomains().size());
+ ASSERT_TRUE(combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ ASSERT_EQ(
+ 2u,
+ combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS)->size());
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at the combined registry.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ combined.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at registry2.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry2.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)).Times(2);
+ registry1.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""),
+ schema);
+ registry2.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "hij"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Untracking |registry1| doesn't trigger an update nofitication, because it
+ // doesn't contain any components.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ combined.Untrack(&registry1);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ combined.Untrack(&registry2);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ combined.RemoveObserver(&observer);
+}
+
+} // namespace policy