diff options
author | dconnelly@chromium.org <dconnelly@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 17:50:47 +0000 |
---|---|---|
committer | dconnelly@chromium.org <dconnelly@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-22 17:50:47 +0000 |
commit | f39e98eb6e1f501e11eb596879a59c1420411500 (patch) | |
tree | 40c1211b431e3af82a9ae996d3949436e196373b /components/policy/core/common | |
parent | a404789baf4191b0c3fd3b0d5b050c6937c68d87 (diff) | |
download | chromium_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/policy/core/common')
-rw-r--r-- | components/policy/core/common/schema_map.cc | 105 | ||||
-rw-r--r-- | components/policy/core/common/schema_map.h | 65 | ||||
-rw-r--r-- | components/policy/core/common/schema_map_unittest.cc | 313 | ||||
-rw-r--r-- | components/policy/core/common/schema_registry.cc | 175 | ||||
-rw-r--r-- | components/policy/core/common/schema_registry.h | 115 | ||||
-rw-r--r-- | components/policy/core/common/schema_registry_unittest.cc | 242 |
6 files changed, 1015 insertions, 0 deletions
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(®istry1); + Mock::VerifyAndClearExpectations(&observer); + + // Adding a new empty registry does not trigger notifications. + EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0); + combined.Track(®istry2); + 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(®istry1); + Mock::VerifyAndClearExpectations(&observer); + + EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); + combined.Untrack(®istry2); + Mock::VerifyAndClearExpectations(&observer); + + combined.RemoveObserver(&observer); +} + +} // namespace policy |