Move policy code into components/policy.
The code moved to component/policy can be included on iOS, but can't depend
on chrome/. This move includes an exception for the policy protobufs:
- the generic cloud policy protobufs will be moved into a new repository,
due to a dependency from a ChromeOS package
- the user cloud policy protobuf and its decoding will move into a new component
for user policy
BUG=271392
TBR=jochen@chromium.org
Review URL: https://codereview.chromium.org/109743002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239399 0039d316-1c4b-4281-b951-d872f2087c98
CrOS-Libchrome-Original-Commit: 76b4b15804dafa5e586738bd7c8ac12fea6ddec2
diff --git a/components/policy/core/common/config_dir_policy_loader.cc b/components/policy/core/common/config_dir_policy_loader.cc
new file mode 100644
index 0000000..b8bd66a
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 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/config_dir_policy_loader.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "base/stl_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectories that contain the mandatory and recommended policies.
+const base::FilePath::CharType kMandatoryConfigDir[] =
+ FILE_PATH_LITERAL("managed");
+const base::FilePath::CharType kRecommendedConfigDir[] =
+ FILE_PATH_LITERAL("recommended");
+
+PolicyLoadStatus JsonErrorToPolicyLoadStatus(int status) {
+ switch (status) {
+ case JSONFileValueSerializer::JSON_ACCESS_DENIED:
+ case JSONFileValueSerializer::JSON_CANNOT_READ_FILE:
+ case JSONFileValueSerializer::JSON_FILE_LOCKED:
+ return POLICY_LOAD_STATUS_READ_ERROR;
+ case JSONFileValueSerializer::JSON_NO_SUCH_FILE:
+ return POLICY_LOAD_STATUS_MISSING;
+ case base::JSONReader::JSON_INVALID_ESCAPE:
+ case base::JSONReader::JSON_SYNTAX_ERROR:
+ case base::JSONReader::JSON_UNEXPECTED_TOKEN:
+ case base::JSONReader::JSON_TRAILING_COMMA:
+ case base::JSONReader::JSON_TOO_MUCH_NESTING:
+ case base::JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT:
+ case base::JSONReader::JSON_UNSUPPORTED_ENCODING:
+ case base::JSONReader::JSON_UNQUOTED_DICTIONARY_KEY:
+ return POLICY_LOAD_STATUS_PARSE_ERROR;
+ case base::JSONReader::JSON_NO_ERROR:
+ NOTREACHED();
+ return POLICY_LOAD_STATUS_STARTED;
+ }
+ NOTREACHED() << "Invalid status " << status;
+ return POLICY_LOAD_STATUS_PARSE_ERROR;
+}
+
+} // namespace
+
+ConfigDirPolicyLoader::ConfigDirPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope)
+ : AsyncPolicyLoader(task_runner), config_dir_(config_dir), scope_(scope) {}
+
+ConfigDirPolicyLoader::~ConfigDirPolicyLoader() {}
+
+void ConfigDirPolicyLoader::InitOnBackgroundThread() {
+ base::FilePathWatcher::Callback callback =
+ base::Bind(&ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
+ mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir), false,
+ callback);
+ recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir), false,
+ callback);
+}
+
+scoped_ptr<PolicyBundle> ConfigDirPolicyLoader::Load() {
+ scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+ LoadFromPath(config_dir_.Append(kMandatoryConfigDir),
+ POLICY_LEVEL_MANDATORY,
+ bundle.get());
+ LoadFromPath(config_dir_.Append(kRecommendedConfigDir),
+ POLICY_LEVEL_RECOMMENDED,
+ bundle.get());
+ return bundle.Pass();
+}
+
+base::Time ConfigDirPolicyLoader::LastModificationTime() {
+ static const base::FilePath::CharType* kConfigDirSuffixes[] = {
+ kMandatoryConfigDir,
+ kRecommendedConfigDir,
+ };
+
+ base::Time last_modification = base::Time();
+ base::PlatformFileInfo info;
+
+ for (size_t i = 0; i < arraysize(kConfigDirSuffixes); ++i) {
+ base::FilePath path(config_dir_.Append(kConfigDirSuffixes[i]));
+
+ // Skip if the file doesn't exist, or it isn't a directory.
+ if (!base::GetFileInfo(path, &info) || !info.is_directory)
+ continue;
+
+ // Enumerate the files and find the most recent modification timestamp.
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file = file_enumerator.Next();
+ !config_file.empty();
+ config_file = file_enumerator.Next()) {
+ if (base::GetFileInfo(config_file, &info) && !info.is_directory)
+ last_modification = std::max(last_modification, info.last_modified);
+ }
+ }
+
+ return last_modification;
+}
+
+void ConfigDirPolicyLoader::LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle) {
+ // Enumerate the files and sort them lexicographically.
+ std::set<base::FilePath> files;
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file_path = file_enumerator.Next();
+ !config_file_path.empty(); config_file_path = file_enumerator.Next())
+ files.insert(config_file_path);
+
+ PolicyLoadStatusSample status;
+ if (files.empty()) {
+ status.Add(POLICY_LOAD_STATUS_NO_POLICY);
+ return;
+ }
+
+ // Start with an empty dictionary and merge the files' contents.
+ // The files are processed in reverse order because |MergeFrom| gives priority
+ // to existing keys, but the ConfigDirPolicyProvider gives priority to the
+ // last file in lexicographic order.
+ for (std::set<base::FilePath>::reverse_iterator config_file_iter =
+ files.rbegin(); config_file_iter != files.rend();
+ ++config_file_iter) {
+ JSONFileValueSerializer deserializer(*config_file_iter);
+ deserializer.set_allow_trailing_comma(true);
+ int error_code = 0;
+ std::string error_msg;
+ scoped_ptr<base::Value> value(
+ deserializer.Deserialize(&error_code, &error_msg));
+ if (!value.get()) {
+ LOG(WARNING) << "Failed to read configuration file "
+ << config_file_iter->value() << ": " << error_msg;
+ status.Add(JsonErrorToPolicyLoadStatus(error_code));
+ continue;
+ }
+ base::DictionaryValue* dictionary_value = NULL;
+ if (!value->GetAsDictionary(&dictionary_value)) {
+ LOG(WARNING) << "Expected JSON dictionary in configuration file "
+ << config_file_iter->value();
+ status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ continue;
+ }
+
+ // Detach the "3rdparty" node.
+ scoped_ptr<base::Value> third_party;
+ if (dictionary_value->Remove("3rdparty", &third_party))
+ Merge3rdPartyPolicy(third_party.get(), level, bundle);
+
+ // Add chrome policy.
+ PolicyMap policy_map;
+ policy_map.LoadFrom(dictionary_value, level, scope_);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .MergeFrom(policy_map);
+ }
+}
+
+void ConfigDirPolicyLoader::Merge3rdPartyPolicy(
+ const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle) {
+ // The first-level entries in |policies| are PolicyDomains. The second-level
+ // entries are component IDs, and the third-level entries are the policies
+ // for that domain/component namespace.
+
+ const base::DictionaryValue* domains_dictionary;
+ if (!policies->GetAsDictionary(&domains_dictionary)) {
+ LOG(WARNING) << "3rdparty value is not a dictionary!";
+ return;
+ }
+
+ // Helper to lookup a domain given its string name.
+ std::map<std::string, PolicyDomain> supported_domains;
+ supported_domains["extensions"] = POLICY_DOMAIN_EXTENSIONS;
+
+ for (base::DictionaryValue::Iterator domains_it(*domains_dictionary);
+ !domains_it.IsAtEnd(); domains_it.Advance()) {
+ if (!ContainsKey(supported_domains, domains_it.key())) {
+ LOG(WARNING) << "Unsupported 3rd party policy domain: "
+ << domains_it.key();
+ continue;
+ }
+
+ const base::DictionaryValue* components_dictionary;
+ if (!domains_it.value().GetAsDictionary(&components_dictionary)) {
+ LOG(WARNING) << "3rdparty/" << domains_it.key()
+ << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyDomain domain = supported_domains[domains_it.key()];
+ for (base::DictionaryValue::Iterator components_it(*components_dictionary);
+ !components_it.IsAtEnd(); components_it.Advance()) {
+ const base::DictionaryValue* policy_dictionary;
+ if (!components_it.value().GetAsDictionary(&policy_dictionary)) {
+ LOG(WARNING) << "3rdparty/" << domains_it.key() << "/"
+ << components_it.key() << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyMap policy;
+ policy.LoadFrom(policy_dictionary, level, scope_);
+ bundle->Get(PolicyNamespace(domain, components_it.key()))
+ .MergeFrom(policy);
+ }
+ }
+}
+
+void ConfigDirPolicyLoader::OnFileUpdated(const base::FilePath& path,
+ bool error) {
+ if (!error)
+ Reload(false);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/config_dir_policy_loader.h b/components/policy/core/common/config_dir_policy_loader.h
new file mode 100644
index 0000000..054844b
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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_CONFIG_DIR_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+// A policy loader implementation backed by a set of files in a given
+// directory. The files should contain JSON-formatted policy settings. They are
+// merged together and the result is returned in a PolicyBundle.
+// The files are consulted in lexicographic file name order, so the
+// last value read takes precedence in case of policy key collisions.
+class POLICY_EXPORT ConfigDirPolicyLoader : public AsyncPolicyLoader {
+ public:
+ ConfigDirPolicyLoader(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope);
+ virtual ~ConfigDirPolicyLoader();
+
+ // AsyncPolicyLoader implementation.
+ virtual void InitOnBackgroundThread() OVERRIDE;
+ virtual scoped_ptr<PolicyBundle> Load() OVERRIDE;
+ virtual base::Time LastModificationTime() OVERRIDE;
+
+ private:
+ // Loads the policy files at |path| into the |bundle|, with the given |level|.
+ void LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle);
+
+ // Merges the 3rd party |policies| into the |bundle|, with the given |level|.
+ void Merge3rdPartyPolicy(const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle);
+
+ // Callback for the FilePathWatchers.
+ void OnFileUpdated(const base::FilePath& path, bool error);
+
+ // The directory containing the policy files.
+ base::FilePath config_dir_;
+
+ // Policies loaded by this provider will have this scope.
+ PolicyScope scope_;
+
+ // Watchers for events on the mandatory and recommended subdirectories of
+ // |config_dir_|.
+ base::FilePathWatcher mandatory_watcher_;
+ base::FilePathWatcher recommended_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyLoader);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
diff --git a/components/policy/core/common/config_dir_policy_loader_unittest.cc b/components/policy/core/common/config_dir_policy_loader_unittest.cc
new file mode 100644
index 0000000..de74964
--- /dev/null
+++ b/components/policy/core/common/config_dir_policy_loader_unittest.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 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 "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/config_dir_policy_loader.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectory of the config dir that contains mandatory policies.
+const base::FilePath::CharType kMandatoryPath[] = FILE_PATH_LITERAL("managed");
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ TestHarness();
+ virtual ~TestHarness();
+
+ virtual void SetUp() OVERRIDE;
+
+ virtual ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE;
+
+ virtual void InstallEmptyPolicy() OVERRIDE;
+ virtual void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) OVERRIDE;
+ virtual void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) OVERRIDE;
+ virtual void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) OVERRIDE;
+ virtual void InstallStringListPolicy(
+ const std::string& policy_name,
+ const base::ListValue* policy_value) OVERRIDE;
+ virtual void InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::DictionaryValue* policy_value) OVERRIDE;
+ virtual void Install3rdPartyPolicy(
+ const base::DictionaryValue* policies) OVERRIDE;
+
+ const base::FilePath& test_dir() { return test_dir_.path(); }
+
+ // JSON-encode a dictionary and write it to a file.
+ void WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name);
+
+ // Returns a unique name for a policy file. Each subsequent call returns a new
+ // name that comes lexicographically after the previous one.
+ std::string NextConfigFileName();
+
+ static PolicyProviderTestHarness* Create();
+
+ private:
+ base::ScopedTempDir test_dir_;
+ int next_policy_file_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestHarness);
+};
+
+TestHarness::TestHarness()
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE),
+ next_policy_file_index_(100) {}
+
+TestHarness::~TestHarness() {}
+
+void TestHarness::SetUp() {
+ ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ scoped_ptr<AsyncPolicyLoader> loader(new ConfigDirPolicyLoader(
+ task_runner, test_dir(), POLICY_SCOPE_MACHINE));
+ return new AsyncPolicyProvider(registry, loader.Pass());
+}
+
+void TestHarness::InstallEmptyPolicy() {
+ base::DictionaryValue dict;
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ base::DictionaryValue dict;
+ dict.SetString(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ base::DictionaryValue dict;
+ dict.SetInteger(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ base::DictionaryValue dict;
+ dict.SetBoolean(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ base::DictionaryValue dict;
+ dict.Set(policy_name, policy_value->DeepCopy());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::DictionaryValue* policy_value) {
+ base::DictionaryValue dict;
+ dict.Set(policy_name, policy_value->DeepCopy());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) {
+ base::DictionaryValue dict;
+ dict.Set("3rdparty", policies->DeepCopy());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name) {
+ std::string data;
+ JSONStringValueSerializer serializer(&data);
+ serializer.Serialize(dict);
+ const base::FilePath mandatory_dir(test_dir().Append(kMandatoryPath));
+ ASSERT_TRUE(base::CreateDirectory(mandatory_dir));
+ const base::FilePath file_path(mandatory_dir.AppendASCII(file_name));
+ ASSERT_EQ((int) data.size(),
+ file_util::WriteFile(file_path, data.c_str(), data.size()));
+}
+
+std::string TestHarness::NextConfigFileName() {
+ EXPECT_LE(next_policy_file_index_, 999);
+ return std::string("policy") + base::IntToString(next_policy_file_index_++);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+ return new TestHarness();
+}
+
+} // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+ ConfigDirPolicyLoaderTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Instantiate abstract test case for 3rd party policy reading tests.
+INSTANTIATE_TEST_CASE_P(
+ ConfigDir3rdPartyPolicyLoaderTest,
+ Configuration3rdPartyPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Some tests that exercise special functionality in ConfigDirPolicyLoader.
+class ConfigDirPolicyLoaderTest : public PolicyTestBase {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ PolicyTestBase::SetUp();
+ harness_.SetUp();
+ }
+
+ TestHarness harness_;
+};
+
+// The preferences dictionary is expected to be empty when there are no files to
+// load.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) {
+ ConfigDirPolicyLoader loader(
+ loop_.message_loop_proxy(), harness_.test_dir(), POLICY_SCOPE_MACHINE);
+ scoped_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Reading from a non-existent directory should result in an empty preferences
+// dictionary.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsNonExistentDirectory) {
+ base::FilePath non_existent_dir(
+ harness_.test_dir().Append(FILE_PATH_LITERAL("not_there")));
+ ConfigDirPolicyLoader loader(
+ loop_.message_loop_proxy(), non_existent_dir, POLICY_SCOPE_MACHINE);
+ scoped_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Test merging values from different files.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) {
+ // Write a bunch of data files in order to increase the chance to detect the
+ // provider not respecting lexicographic ordering when reading them. Since the
+ // filesystem may return files in arbitrary order, there is no way to be sure,
+ // but this is better than nothing.
+ base::DictionaryValue test_dict_bar;
+ test_dict_bar.SetString("HomepageLocation", "http://bar.com");
+ for (unsigned int i = 1; i <= 4; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::IntToString(i));
+ base::DictionaryValue test_dict_foo;
+ test_dict_foo.SetString("HomepageLocation", "http://foo.com");
+ harness_.WriteConfigFile(test_dict_foo, "9");
+ for (unsigned int i = 5; i <= 8; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::IntToString(i));
+
+ ConfigDirPolicyLoader loader(
+ loop_.message_loop_proxy(), harness_.test_dir(), POLICY_SCOPE_USER);
+ scoped_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .LoadFrom(&test_dict_foo, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ EXPECT_TRUE(bundle->Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.cc b/components/policy/core/common/mock_policy_service.cc
new file mode 100644
index 0000000..6e3e0c1
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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/mock_policy_service.h"
+
+namespace policy {
+
+MockPolicyServiceObserver::MockPolicyServiceObserver() {
+}
+
+MockPolicyServiceObserver::~MockPolicyServiceObserver() {
+}
+
+MockPolicyService::MockPolicyService() {
+}
+
+MockPolicyService::~MockPolicyService() {
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.h b/components/policy/core/common/mock_policy_service.h
new file mode 100644
index 0000000..584f093
--- /dev/null
+++ b/components/policy/core/common/mock_policy_service.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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_MOCK_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+
+#include "components/policy/core/common/policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockPolicyServiceObserver : public PolicyService::Observer {
+ public:
+ MockPolicyServiceObserver();
+ virtual ~MockPolicyServiceObserver();
+
+ MOCK_METHOD3(OnPolicyUpdated, void(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current));
+ MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
+};
+
+class MockPolicyService : public PolicyService {
+ public:
+ MockPolicyService();
+ virtual ~MockPolicyService();
+
+ MOCK_METHOD2(AddObserver, void(PolicyDomain, Observer*));
+ MOCK_METHOD2(RemoveObserver, void(PolicyDomain, Observer*));
+
+ MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
+ MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+ MOCK_METHOD1(RefreshPolicies, void(const base::Closure&));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/policy_load_status.cc b/components/policy/core/common/policy_load_status.cc
new file mode 100644
index 0000000..71c5059
--- /dev/null
+++ b/components/policy/core/common/policy_load_status.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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/policy_load_status.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+const char kHistogramName[] = "Enterprise.PolicyLoadStatus";
+
+} // namespace
+
+PolicyLoadStatusSample::PolicyLoadStatusSample()
+ : histogram_(base::LinearHistogram::FactoryGet(
+ kHistogramName, 1, POLICY_LOAD_STATUS_SIZE,
+ POLICY_LOAD_STATUS_SIZE + 1,
+ base::Histogram::kUmaTargetedHistogramFlag)) {
+ Add(POLICY_LOAD_STATUS_STARTED);
+}
+
+PolicyLoadStatusSample::~PolicyLoadStatusSample() {
+ for (int i = 0; i < POLICY_LOAD_STATUS_SIZE; ++i) {
+ if (status_bits_[i])
+ histogram_->Add(i);
+ }
+}
+
+void PolicyLoadStatusSample::Add(PolicyLoadStatus status) {
+ status_bits_[status] = true;
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_load_status.h b/components/policy/core/common/policy_load_status.h
new file mode 100644
index 0000000..5f4d017
--- /dev/null
+++ b/components/policy/core/common/policy_load_status.h
@@ -0,0 +1,67 @@
+// Copyright (c) 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_POLICY_LOAD_STATUS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
+
+#include <bitset>
+
+#include "base/basictypes.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class HistogramBase;
+}
+
+namespace policy {
+
+// UMA histogram enum for policy load status. Don't change existing constants,
+// append additional constants to the end if needed.
+enum PolicyLoadStatus {
+ // Policy load attempt started. This gets logged for each policy load attempt
+ // to get a baseline on the number of requests, and an arbitrary number of
+ // the below status codes may get added in addition.
+ POLICY_LOAD_STATUS_STARTED,
+ // System failed to determine whether there's policy.
+ POLICY_LOAD_STATUS_QUERY_FAILED,
+ // No policy present.
+ POLICY_LOAD_STATUS_NO_POLICY,
+ // Data inaccessible, such as non-local policy file.
+ POLICY_LOAD_STATUS_INACCCESSIBLE,
+ // Data missing, such as policy file not present.
+ POLICY_LOAD_STATUS_MISSING,
+ // Trying with Wow64 redirection disabled.
+ POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED,
+ // Data read error, for example file reading errors.
+ POLICY_LOAD_STATUS_READ_ERROR,
+ // Data too large to process.
+ POLICY_LOAD_STATUS_TOO_BIG,
+ // Parse error.
+ POLICY_LOAD_STATUS_PARSE_ERROR,
+
+ // This must stay last.
+ POLICY_LOAD_STATUS_SIZE
+};
+
+// A helper for generating policy load status UMA statistics that'll collect
+// histogram samples for a policy load operation and records histogram samples
+// for the status codes that were seen on destruction.
+class POLICY_EXPORT PolicyLoadStatusSample {
+ public:
+ PolicyLoadStatusSample();
+ ~PolicyLoadStatusSample();
+
+ // Adds a status code.
+ void Add(PolicyLoadStatus status);
+
+ private:
+ std::bitset<POLICY_LOAD_STATUS_SIZE> status_bits_;
+ base::HistogramBase* histogram_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyLoadStatusSample);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
diff --git a/components/policy/core/common/policy_service.cc b/components/policy/core/common/policy_service.cc
new file mode 100644
index 0000000..fe4bb30
--- /dev/null
+++ b/components/policy/core/common/policy_service.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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/policy_service.h"
+
+#include "base/values.h"
+
+namespace policy {
+
+PolicyChangeRegistrar::PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns)
+ : policy_service_(policy_service),
+ ns_(ns) {}
+
+PolicyChangeRegistrar::~PolicyChangeRegistrar() {
+ if (!callback_map_.empty())
+ policy_service_->RemoveObserver(ns_.domain, this);
+}
+
+void PolicyChangeRegistrar::Observe(const std::string& policy_name,
+ const UpdateCallback& callback) {
+ if (callback_map_.empty())
+ policy_service_->AddObserver(ns_.domain, this);
+ callback_map_[policy_name] = callback;
+}
+
+void PolicyChangeRegistrar::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ if (ns != ns_)
+ return;
+ for (CallbackMap::iterator it = callback_map_.begin();
+ it != callback_map_.end(); ++it) {
+ const Value* prev = previous.GetValue(it->first);
+ const Value* cur = current.GetValue(it->first);
+ if (!base::Value::Equals(prev, cur))
+ it->second.Run(prev, cur);
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service.h b/components/policy/core/common/policy_service.h
new file mode 100644
index 0000000..4293e5c
--- /dev/null
+++ b/components/policy/core/common/policy_service.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// The PolicyService merges policies from all available sources, taking into
+// account their priorities. Policy clients can retrieve policy for their domain
+// and register for notifications on policy updates.
+//
+// The PolicyService is available from BrowserProcess as a global singleton.
+// There is also a PolicyService for browser-wide policies available from
+// BrowserProcess as a global singleton.
+class POLICY_EXPORT PolicyService {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked whenever policies for the given |ns| namespace are modified.
+ // This is only invoked for changes that happen after AddObserver is called.
+ // |previous| contains the values of the policies before the update,
+ // and |current| contains the current values.
+ virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) = 0;
+
+ // Invoked at most once for each |domain|, when the PolicyService becomes
+ // ready. If IsInitializationComplete() is false, then this will be invoked
+ // once all the policy providers have finished loading their policies for
+ // |domain|.
+ virtual void OnPolicyServiceInitialized(PolicyDomain domain) {}
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ virtual ~PolicyService() {}
+
+ // Observes changes to all components of the given |domain|.
+ virtual void AddObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ virtual void RemoveObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ virtual const PolicyMap& GetPolicies(const PolicyNamespace& ns) const = 0;
+
+ // The PolicyService loads policy from several sources, and some require
+ // asynchronous loads. IsInitializationComplete() returns true once all
+ // sources have loaded their policies for the given |domain|.
+ // It is safe to read policy from the PolicyService even if
+ // IsInitializationComplete() is false; there will be an OnPolicyUpdated()
+ // notification once new policies become available.
+ //
+ // OnPolicyServiceInitialized() is called when IsInitializationComplete()
+ // becomes true, which happens at most once for each domain.
+ // If IsInitializationComplete() is already true for |domain| when an Observer
+ // is registered, then that Observer will not receive an
+ // OnPolicyServiceInitialized() notification.
+ virtual bool IsInitializationComplete(PolicyDomain domain) const = 0;
+
+ // Asks the PolicyService to reload policy from all available policy sources.
+ // |callback| is invoked once every source has reloaded its policies, and
+ // GetPolicies() is guaranteed to return the updated values at that point.
+ virtual void RefreshPolicies(const base::Closure& callback) = 0;
+};
+
+// A registrar that only observes changes to particular policies within the
+// PolicyMap for the given policy namespace.
+class POLICY_EXPORT PolicyChangeRegistrar : public PolicyService::Observer {
+ public:
+ typedef base::Callback<void(const Value*, const Value*)> UpdateCallback;
+
+ // Observes updates to the given (domain, component_id) namespace in the given
+ // |policy_service|, and notifies |observer| whenever any of the registered
+ // policy keys changes. Both the |policy_service| and the |observer| must
+ // outlive |this|.
+ PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns);
+
+ virtual ~PolicyChangeRegistrar();
+
+ // Will invoke |callback| whenever |policy_name| changes its value, as long
+ // as this registrar exists.
+ // Only one callback can be registed per policy name; a second call with the
+ // same |policy_name| will overwrite the previous callback.
+ void Observe(const std::string& policy_name, const UpdateCallback& callback);
+
+ // Implementation of PolicyService::Observer:
+ virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) OVERRIDE;
+
+ private:
+ typedef std::map<std::string, UpdateCallback> CallbackMap;
+
+ PolicyService* policy_service_;
+ PolicyNamespace ns_;
+ CallbackMap callback_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyChangeRegistrar);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
new file mode 100644
index 0000000..8fdd265
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 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/policy_service_impl.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+typedef PolicyServiceImpl::Providers::const_iterator Iterator;
+
+PolicyServiceImpl::PolicyServiceImpl(
+ const Providers& providers,
+ const PreprocessCallback& preprocess_callback)
+ : preprocess_callback_(preprocess_callback),
+ update_task_ptr_factory_(this) {
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
+ initialization_complete_[domain] = true;
+ providers_ = providers;
+ for (Iterator it = providers.begin(); it != providers.end(); ++it) {
+ ConfigurationPolicyProvider* provider = *it;
+ provider->AddObserver(this);
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+ initialization_complete_[domain] &=
+ provider->IsInitializationComplete(static_cast<PolicyDomain>(domain));
+ }
+ }
+ // There are no observers yet, but calls to GetPolicies() should already get
+ // the processed policy values.
+ MergeAndTriggerUpdates();
+}
+
+PolicyServiceImpl::~PolicyServiceImpl() {
+ for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+ (*it)->RemoveObserver(this);
+ STLDeleteValues(&observers_);
+}
+
+void PolicyServiceImpl::AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ Observers*& list = observers_[domain];
+ if (!list)
+ list = new Observers();
+ list->AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ ObserverMap::iterator it = observers_.find(domain);
+ if (it == observers_.end()) {
+ NOTREACHED();
+ return;
+ }
+ it->second->RemoveObserver(observer);
+ if (!it->second->might_have_observers()) {
+ delete it->second;
+ observers_.erase(it);
+ }
+}
+
+const PolicyMap& PolicyServiceImpl::GetPolicies(
+ const PolicyNamespace& ns) const {
+ return policy_bundle_.Get(ns);
+}
+
+bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const {
+ DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
+ return initialization_complete_[domain];
+}
+
+void PolicyServiceImpl::RefreshPolicies(const base::Closure& callback) {
+ if (!callback.is_null())
+ refresh_callbacks_.push_back(callback);
+
+ if (providers_.empty()) {
+ // Refresh is immediately complete if there are no providers. See the note
+ // on OnUpdatePolicy() about why this is a posted task.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+ } else {
+ // Some providers might invoke OnUpdatePolicy synchronously while handling
+ // RefreshPolicies. Mark all as pending before refreshing.
+ for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+ refresh_pending_.insert(*it);
+ for (Iterator it = providers_.begin(); it != providers_.end(); ++it)
+ (*it)->RefreshPolicies();
+ }
+}
+
+void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(1, std::count(providers_.begin(), providers_.end(), provider));
+ refresh_pending_.erase(provider);
+
+ // Note: a policy change may trigger further policy changes in some providers.
+ // For example, disabling SigninAllowed would cause the CloudPolicyManager to
+ // drop all its policies, which makes this method enter again for that
+ // provider.
+ //
+ // Therefore this update is posted asynchronously, to prevent reentrancy in
+ // MergeAndTriggerUpdates. Also, cancel a pending update if there is any,
+ // since both will produce the same PolicyBundle.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyServiceImpl::NotifyNamespaceUpdated(
+ const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ ObserverMap::iterator iterator = observers_.find(ns.domain);
+ if (iterator != observers_.end()) {
+ FOR_EACH_OBSERVER(PolicyService::Observer,
+ *iterator->second,
+ OnPolicyUpdated(ns, previous, current));
+ }
+}
+
+void PolicyServiceImpl::MergeAndTriggerUpdates() {
+ // Merge from each provider in their order of priority.
+ PolicyBundle bundle;
+ for (Iterator it = providers_.begin(); it != providers_.end(); ++it) {
+ PolicyBundle provided_bundle;
+ provided_bundle.CopyFrom((*it)->policies());
+ if (!preprocess_callback_.is_null())
+ preprocess_callback_.Run(&provided_bundle);
+ bundle.MergeFrom(provided_bundle);
+ }
+
+ // Swap first, so that observers that call GetPolicies() see the current
+ // values.
+ policy_bundle_.Swap(&bundle);
+
+ // Only notify observers of namespaces that have been modified.
+ const PolicyMap kEmpty;
+ PolicyBundle::const_iterator it_new = policy_bundle_.begin();
+ PolicyBundle::const_iterator end_new = policy_bundle_.end();
+ PolicyBundle::const_iterator it_old = bundle.begin();
+ PolicyBundle::const_iterator end_old = bundle.end();
+ while (it_new != end_new && it_old != end_old) {
+ if (it_new->first < it_old->first) {
+ // A new namespace is available.
+ NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+ ++it_new;
+ } else if (it_old->first < it_new->first) {
+ // A previously available namespace is now gone.
+ NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+ ++it_old;
+ } else {
+ if (!it_new->second->Equals(*it_old->second)) {
+ // An existing namespace's policies have changed.
+ NotifyNamespaceUpdated(it_new->first, *it_old->second, *it_new->second);
+ }
+ ++it_new;
+ ++it_old;
+ }
+ }
+
+ // Send updates for the remaining new namespaces, if any.
+ for (; it_new != end_new; ++it_new)
+ NotifyNamespaceUpdated(it_new->first, kEmpty, *it_new->second);
+
+ // Sends updates for the remaining removed namespaces, if any.
+ for (; it_old != end_old; ++it_old)
+ NotifyNamespaceUpdated(it_old->first, *it_old->second, kEmpty);
+
+ CheckInitializationComplete();
+ CheckRefreshComplete();
+}
+
+void PolicyServiceImpl::CheckInitializationComplete() {
+ // Check if all the providers just became initialized for each domain; if so,
+ // notify that domain's observers.
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+ if (initialization_complete_[domain])
+ continue;
+
+ PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
+
+ bool all_complete = true;
+ for (Iterator it = providers_.begin(); it != providers_.end(); ++it) {
+ if (!(*it)->IsInitializationComplete(policy_domain)) {
+ all_complete = false;
+ break;
+ }
+ }
+ if (all_complete) {
+ initialization_complete_[domain] = true;
+ ObserverMap::iterator iter = observers_.find(policy_domain);
+ if (iter != observers_.end()) {
+ FOR_EACH_OBSERVER(PolicyService::Observer,
+ *iter->second,
+ OnPolicyServiceInitialized(policy_domain));
+ }
+ }
+ }
+}
+
+void PolicyServiceImpl::CheckRefreshComplete() {
+ // Invoke all the callbacks if a refresh has just fully completed.
+ if (refresh_pending_.empty() && !refresh_callbacks_.empty()) {
+ std::vector<base::Closure> callbacks;
+ callbacks.swap(refresh_callbacks_);
+ std::vector<base::Closure>::iterator it;
+ for (it = callbacks.begin(); it != callbacks.end(); ++it)
+ it->Run();
+ }
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_impl.h b/components/policy/core/common/policy_service_impl.h
new file mode 100644
index 0000000..a10e881
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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_POLICY_SERVICE_IMPL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyMap;
+
+class POLICY_EXPORT PolicyServiceImpl
+ : public PolicyService,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ typedef std::vector<ConfigurationPolicyProvider*> Providers;
+ typedef base::Callback<void(PolicyBundle*)> PreprocessCallback;
+
+ // The PolicyServiceImpl will merge policies from |providers|. |providers|
+ // must be sorted in decreasing order of priority; the first provider will
+ // have the highest priority. The PolicyServiceImpl does not take ownership of
+ // the providers, and they must outlive the PolicyServiceImpl.
+ // |preprocess_callback| will be applied every PolicyBundle before merginng.
+ PolicyServiceImpl(const Providers& providers,
+ const PreprocessCallback& preprocess_callback);
+
+ virtual ~PolicyServiceImpl();
+
+ // PolicyService overrides:
+ virtual void AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) OVERRIDE;
+ virtual const PolicyMap& GetPolicies(
+ const PolicyNamespace& ns) const OVERRIDE;
+ virtual bool IsInitializationComplete(PolicyDomain domain) const OVERRIDE;
+ virtual void RefreshPolicies(const base::Closure& callback) OVERRIDE;
+
+ private:
+ typedef ObserverList<PolicyService::Observer, true> Observers;
+ typedef std::map<PolicyDomain, Observers*> ObserverMap;
+
+ // ConfigurationPolicyProvider::Observer overrides:
+ virtual void OnUpdatePolicy(ConfigurationPolicyProvider* provider) OVERRIDE;
+
+ // Posts a task to notify observers of |ns| that its policies have changed,
+ // passing along the |previous| and the |current| policies.
+ void NotifyNamespaceUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current);
+
+ // Combines the policies from all the providers, and notifies the observers
+ // of namespaces whose policies have been modified.
+ void MergeAndTriggerUpdates();
+
+ // Checks if all providers are initialized, and notifies the observers
+ // if the service just became initialized.
+ void CheckInitializationComplete();
+
+ // Invokes all the refresh callbacks if there are no more refreshes pending.
+ void CheckRefreshComplete();
+
+ // The providers passed in the constructor, in order of decreasing priority.
+ Providers providers_;
+
+ // Maps each policy namespace to its current policies.
+ PolicyBundle policy_bundle_;
+
+ // Maps each policy domain to its observer list.
+ ObserverMap observers_;
+
+ // True if all the providers are initialized for the indexed policy domain.
+ bool initialization_complete_[POLICY_DOMAIN_SIZE];
+
+ // Set of providers that have a pending update that was triggered by a
+ // call to RefreshPolicies().
+ std::set<ConfigurationPolicyProvider*> refresh_pending_;
+
+ // Callback invoked to manipulate a PolicyBundle before it is merged.
+ PreprocessCallback preprocess_callback_;
+
+ // List of callbacks to invoke once all providers refresh after a
+ // RefreshPolicies() call.
+ std::vector<base::Closure> refresh_callbacks_;
+
+ // Used to create tasks to delay new policy updates while we may be already
+ // processing previous policy updates.
+ base::WeakPtrFactory<PolicyServiceImpl> update_task_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceImpl);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
new file mode 100644
index 0000000..e95a1fb
--- /dev/null
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright (c) 2012 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/policy_service_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kExtension[] = "extension-id";
+const char kSameLevelPolicy[] = "policy-same-level-and-scope";
+const char kDiffLevelPolicy[] = "chrome-diff-level-and-scope";
+
+void SetPolicyMapValue(const std::string& key,
+ const std::string& value,
+ PolicyBundle* bundle) {
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(key,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ new base::StringValue(value),
+ NULL);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyUpdated() with
+// their expected values.
+MATCHER_P(PolicyEquals, expected, "") {
+ return arg.Equals(*expected);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyValueUpdated()
+// with their expected values.
+MATCHER_P(ValueEquals, expected, "") {
+ return base::Value::Equals(arg, expected);
+}
+
+// Helper that fills |bundle| with test policies.
+void AddTestPolicies(PolicyBundle* bundle,
+ const char* value,
+ PolicyLevel level,
+ PolicyScope scope) {
+ PolicyMap* policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateStringValue(value), NULL);
+ policy_map->Set(kDiffLevelPolicy, level, scope,
+ base::Value::CreateStringValue(value), NULL);
+ policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, base::Value::CreateStringValue(value),
+ NULL);
+ policy_map->Set(kDiffLevelPolicy, level, scope,
+ base::Value::CreateStringValue(value), NULL);
+}
+
+// Observer class that changes the policy in the passed provider when the
+// callback is invoked.
+class ChangePolicyObserver : public PolicyService::Observer {
+ public:
+ explicit ChangePolicyObserver(MockConfigurationPolicyProvider* provider)
+ : provider_(provider),
+ observer_invoked_(false) {}
+
+ virtual void OnPolicyUpdated(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current) OVERRIDE {
+ PolicyMap new_policy;
+ new_policy.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(14), NULL);
+ provider_->UpdateChromePolicy(new_policy);
+ observer_invoked_ = true;
+ }
+
+ bool observer_invoked() const { return observer_invoked_; }
+
+ private:
+ MockConfigurationPolicyProvider* provider_;
+ bool observer_invoked_;
+};
+
+} // namespace
+
+class PolicyServiceTest : public testing::Test {
+ public:
+ PolicyServiceTest() {}
+ virtual void SetUp() OVERRIDE {
+ EXPECT_CALL(provider0_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+
+ provider0_.Init();
+ provider1_.Init();
+ provider2_.Init();
+
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(13), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_.reset(new PolicyServiceImpl(
+ providers, PolicyServiceImpl::PreprocessCallback()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ provider0_.Shutdown();
+ provider1_.Shutdown();
+ provider2_.Shutdown();
+ }
+
+ MOCK_METHOD2(OnPolicyValueUpdated, void(const base::Value*,
+ const base::Value*));
+
+ MOCK_METHOD0(OnPolicyRefresh, void());
+
+ // Returns true if the policies for namespace |ns| match |expected|.
+ bool VerifyPolicies(const PolicyNamespace& ns,
+ const PolicyMap& expected) {
+ return policy_service_->GetPolicies(ns).Equals(expected);
+ }
+
+ void RunUntilIdle() {
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+ }
+
+ protected:
+ base::MessageLoop loop_;
+ MockConfigurationPolicyProvider provider0_;
+ MockConfigurationPolicyProvider provider1_;
+ MockConfigurationPolicyProvider provider2_;
+ PolicyMap policy0_;
+ PolicyMap policy1_;
+ PolicyMap policy2_;
+ scoped_ptr<PolicyServiceImpl> policy_service_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceTest);
+};
+
+TEST_F(PolicyServiceTest, LoadsPoliciesBeforeProvidersRefresh) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(13), NULL);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, NotifyObservers) {
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+
+ PolicyMap expectedPrevious;
+ expectedPrevious.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(13), NULL);
+
+ PolicyMap expectedCurrent;
+ expectedCurrent.CopyFrom(expectedPrevious);
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(123), NULL);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(123), NULL);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ // New policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(456), NULL);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(456), NULL);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Removed policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Erase("bbb");
+ policy0_.Erase("bbb");
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Changed policy.
+ expectedPrevious.CopyFrom(expectedCurrent);
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(789), NULL);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(789), NULL);
+
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes again.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+}
+
+TEST_F(PolicyServiceTest, NotifyObserversInMultipleNamespaces) {
+ const std::string kExtension0("extension-0");
+ const std::string kExtension1("extension-1");
+ const std::string kExtension2("extension-2");
+ MockPolicyServiceObserver chrome_observer;
+ MockPolicyServiceObserver extension_observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &extension_observer);
+
+ PolicyMap previous_policy_map;
+ previous_policy_map.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(13), NULL);
+ PolicyMap policy_map;
+ policy_map.CopyFrom(previous_policy_map);
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("value"), NULL);
+
+ scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+ // The initial setup includes a policy for chrome that is now changing.
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+ .CopyFrom(policy_map);
+
+ const PolicyMap kEmptyPolicyMap;
+ EXPECT_CALL(
+ chrome_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(bundle.Pass());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ // Chrome policy stays the same, kExtension0 is gone, kExtension1 changes,
+ // and kExtension2 is new.
+ previous_policy_map.CopyFrom(policy_map);
+ bundle.reset(new PolicyBundle());
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .CopyFrom(policy_map);
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("another value"), NULL);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1))
+ .CopyFrom(policy_map);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2))
+ .CopyFrom(policy_map);
+
+ EXPECT_CALL(chrome_observer, OnPolicyUpdated(_, _, _)).Times(0);
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&kEmptyPolicyMap)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(bundle.Pass());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS,
+ &extension_observer);
+}
+
+TEST_F(PolicyServiceTest, ObserverChangesPolicy) {
+ ChangePolicyObserver observer(&provider0_);
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(123), NULL);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(1234), NULL);
+ // Should not crash.
+ provider0_.UpdateChromePolicy(policy0_);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ EXPECT_TRUE(observer.observer_invoked());
+}
+
+TEST_F(PolicyServiceTest, Priorities) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(13), NULL);
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(0), NULL);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(0), NULL);
+ policy1_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(1), NULL);
+ policy2_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(2), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ provider2_.UpdateChromePolicy(policy2_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(1), NULL);
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(2), NULL);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ base::Value::CreateIntegerValue(1), NULL);
+ provider1_.UpdateChromePolicy(policy1_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyChangeRegistrar) {
+ scoped_ptr<PolicyChangeRegistrar> registrar(new PolicyChangeRegistrar(
+ policy_service_.get(),
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+
+ // Starting to observe existing policies doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar->Observe("pre", base::Bind(
+ &PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ registrar->Observe("aaa", base::Bind(
+ &PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing it now triggers a notification.
+ base::FundamentalValue kValue0(0);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(NULL, ValueEquals(&kValue0)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue0.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing other values doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue0.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Modifying the value triggers a notification.
+ base::FundamentalValue kValue1(1);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue0),
+ ValueEquals(&kValue1)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue1.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Removing the value triggers a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue1), NULL));
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // No more notifications after destroying the registrar.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar.reset();
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue1.DeepCopy(), NULL);
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue1.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(PolicyServiceTest, RefreshPolicies) {
+ EXPECT_CALL(provider0_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider1_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider2_, RefreshPolicies()).Times(AnyNumber());
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::Bind(
+ &PolicyServiceTest::OnPolicyRefresh,
+ base::Unretained(this)));
+ // Let any queued observer tasks run.
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::FundamentalValue kValue0(0);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue0.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::FundamentalValue kValue1(1);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ kValue1.DeepCopy(), NULL);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // A provider can refresh more than once after a RefreshPolicies call, but
+ // OnPolicyRefresh should be triggered only after all providers are
+ // refreshed.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy1_.Set("bbb", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ kValue1.DeepCopy(), NULL);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // If another RefreshPolicies() call happens while waiting for a previous
+ // one to complete, then all providers must refresh again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::Bind(
+ &PolicyServiceTest::OnPolicyRefresh,
+ base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy2_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue0.DeepCopy(), NULL);
+ provider2_.UpdateChromePolicy(policy2_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Providers 0 and 1 must reload again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(2);
+ base::FundamentalValue kValue2(2);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ kValue2.DeepCopy(), NULL);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_TRUE(base::Value::Equals(&kValue2, policies.GetValue("aaa")));
+ EXPECT_TRUE(base::Value::Equals(&kValue0, policies.GetValue("bbb")));
+}
+
+TEST_F(PolicyServiceTest, NamespaceMerge) {
+ scoped_ptr<PolicyBundle> bundle0(new PolicyBundle());
+ scoped_ptr<PolicyBundle> bundle1(new PolicyBundle());
+ scoped_ptr<PolicyBundle> bundle2(new PolicyBundle());
+
+ AddTestPolicies(bundle0.get(), "bundle0",
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle1.get(), "bundle1",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle2.get(), "bundle2",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+
+ provider0_.UpdatePolicy(bundle0.Pass());
+ provider1_.UpdatePolicy(bundle1.Pass());
+ provider2_.UpdatePolicy(bundle2.Pass());
+ RunUntilIdle();
+
+ PolicyMap expected;
+ // For policies of the same level and scope, the first provider takes
+ // precedence, on every namespace.
+ expected.Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("bundle0"), NULL);
+ // For policies with different levels and scopes, the highest priority
+ // level/scope combination takes precedence, on every namespace.
+ expected.Set(kDiffLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ base::Value::CreateStringValue("bundle2"), NULL);
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())).Equals(expected));
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension)).Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyPreprocessing) {
+ // Reset the PolicyServiceImpl to one that has the preprocessor.
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ policy_service_.reset(new PolicyServiceImpl(
+ providers, base::Bind(&SetPolicyMapValue, kSameLevelPolicy, "bar")));
+
+ // Set the policy value to "foo".
+ scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
+ PolicyMap& map =
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ map.Set(kSameLevelPolicy,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("foo"),
+ NULL);
+
+ // Push the update through the provider.
+ provider0_.UpdatePolicy(bundle.Pass());
+ RunUntilIdle();
+
+ // The value should have been changed from "foo" to "bar".
+ const PolicyMap& actual = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ PolicyMap expected;
+ expected.Set(kSameLevelPolicy,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ base::Value::CreateStringValue("bar"),
+ NULL);
+ EXPECT_TRUE(actual.Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, IsInitializationComplete) {
+ // |provider0| has all domains initialized.
+ Mock::VerifyAndClearExpectations(&provider1_);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_.reset(new PolicyServiceImpl(
+ providers, PolicyServiceImpl::PreprocessCallback()));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+ // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
+ // the initialization status of that domain won't change.
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ const PolicyMap kPolicyMap;
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+ // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+ // Now initialize POLICY_DOMAIN_CHROME on all the providers.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ // Other domains are still not initialized.
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+ // Initialize the remaining domain.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.cc b/components/policy/core/common/policy_service_stub.cc
new file mode 100644
index 0000000..1f9682b
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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/policy_service_stub.h"
+
+#include "base/message_loop/message_loop.h"
+
+namespace policy {
+
+PolicyServiceStub::PolicyServiceStub() {}
+
+PolicyServiceStub::~PolicyServiceStub() {}
+
+void PolicyServiceStub::AddObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+void PolicyServiceStub::RemoveObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+const PolicyMap& PolicyServiceStub::GetPolicies(
+ const PolicyNamespace& ns) const {
+ return kEmpty_;
+};
+
+bool PolicyServiceStub::IsInitializationComplete(PolicyDomain domain) const {
+ return true;
+}
+
+void PolicyServiceStub::RefreshPolicies(const base::Closure& callback) {
+ if (!callback.is_null())
+ callback.Run();
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_service_stub.h b/components/policy/core/common/policy_service_stub.h
new file mode 100644
index 0000000..f7dee23
--- /dev/null
+++ b/components/policy/core/common/policy_service_stub.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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_POLICY_SERVICE_STUB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+
+#include "base/basictypes.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A stub implementation, that is used when ENABLE_CONFIGURATION_POLICY is not
+// set. This allows client code to compile without requiring #ifdefs.
+class POLICY_EXPORT PolicyServiceStub : public PolicyService {
+ public:
+ PolicyServiceStub();
+ virtual ~PolicyServiceStub();
+
+ virtual void AddObserver(PolicyDomain domain,
+ Observer* observer) OVERRIDE;
+
+ virtual void RemoveObserver(PolicyDomain domain,
+ Observer* observer) OVERRIDE;
+
+ virtual const PolicyMap& GetPolicies(
+ const PolicyNamespace& ns) const OVERRIDE;
+
+ virtual bool IsInitializationComplete(PolicyDomain domain) const OVERRIDE;
+
+ virtual void RefreshPolicies(const base::Closure& callback) OVERRIDE;
+ private:
+ const PolicyMap kEmpty_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyServiceStub);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
diff --git a/components/policy/core/common/policy_statistics_collector.cc b/components/policy/core/common/policy_statistics_collector.cc
new file mode 100644
index 0000000..3e6a588
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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/policy_statistics_collector.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/pref_service.h"
+#include "base/task_runner.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+
+namespace policy {
+
+const int PolicyStatisticsCollector::kStatisticsUpdateRate =
+ 24 * 60 * 60 * 1000; // 24 hours.
+
+PolicyStatisticsCollector::PolicyStatisticsCollector(
+ const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : get_details_(get_details),
+ chrome_schema_(chrome_schema),
+ policy_service_(policy_service),
+ prefs_(prefs),
+ task_runner_(task_runner) {
+}
+
+PolicyStatisticsCollector::~PolicyStatisticsCollector() {
+}
+
+void PolicyStatisticsCollector::Initialize() {
+ using base::Time;
+ using base::TimeDelta;
+
+ TimeDelta update_rate = TimeDelta::FromMilliseconds(kStatisticsUpdateRate);
+ Time last_update = Time::FromInternalValue(
+ prefs_->GetInt64(policy_prefs::kLastPolicyStatisticsUpdate));
+ TimeDelta delay = std::max(Time::Now() - last_update, TimeDelta::FromDays(0));
+ if (delay >= update_rate)
+ CollectStatistics();
+ else
+ ScheduleUpdate(update_rate - delay);
+}
+
+// static
+void PolicyStatisticsCollector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterInt64Pref(policy_prefs::kLastPolicyStatisticsUpdate, 0);
+}
+
+void PolicyStatisticsCollector::RecordPolicyUse(int id) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Enterprise.Policies", id);
+}
+
+void PolicyStatisticsCollector::CollectStatistics() {
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+
+ // Collect statistics.
+ for (Schema::Iterator it(chrome_schema_.GetPropertiesIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ if (policies.Get(it.key())) {
+ const PolicyDetails* details = get_details_.Run(it.key());
+ if (details)
+ RecordPolicyUse(details->id);
+ else
+ NOTREACHED();
+ }
+ }
+
+ // Take care of next update.
+ prefs_->SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now().ToInternalValue());
+ ScheduleUpdate(base::TimeDelta::FromMilliseconds(kStatisticsUpdateRate));
+}
+
+void PolicyStatisticsCollector::ScheduleUpdate(base::TimeDelta delay) {
+ update_callback_.Reset(base::Bind(
+ &PolicyStatisticsCollector::CollectStatistics,
+ base::Unretained(this)));
+ task_runner_->PostDelayedTask(FROM_HERE, update_callback_.callback(), delay);
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_statistics_collector.h b/components/policy/core/common/policy_statistics_collector.h
new file mode 100644
index 0000000..f062ea3
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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_POLICY_STATISTICS_COLLECTOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+
+#include "base/basictypes.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace policy {
+
+class PolicyService;
+
+// Manages regular updates of policy usage UMA histograms.
+class POLICY_EXPORT PolicyStatisticsCollector {
+ public:
+ // Policy usage statistics update rate, in milliseconds.
+ static const int kStatisticsUpdateRate;
+
+ // Neither |policy_service| nor |prefs| can be NULL and must stay valid
+ // throughout the lifetime of PolicyStatisticsCollector.
+ PolicyStatisticsCollector(const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+ virtual ~PolicyStatisticsCollector();
+
+ // Completes initialization and starts periodical statistic updates.
+ void Initialize();
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+ // protected virtual for mocking.
+ virtual void RecordPolicyUse(int id);
+
+ private:
+ void CollectStatistics();
+ void ScheduleUpdate(base::TimeDelta delay);
+
+ GetChromePolicyDetailsCallback get_details_;
+ Schema chrome_schema_;
+ PolicyService* policy_service_;
+ PrefService* prefs_;
+
+ base::CancelableClosure update_callback_;
+
+ const scoped_refptr<base::TaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyStatisticsCollector);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
diff --git a/components/policy/core/common/policy_statistics_collector_unittest.cc b/components/policy/core/common/policy_statistics_collector_unittest.cc
new file mode 100644
index 0000000..3c1a0ca
--- /dev/null
+++ b/components/policy/core/common/policy_statistics_collector_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 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 <cstring>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_statistics_collector.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+using testing::ReturnRef;
+
+// Arbitrary policy names used for testing.
+const char kTestPolicy1[] = "Test Policy 1";
+const char kTestPolicy2[] = "Test Policy 2";
+
+const int kTestPolicy1Id = 42;
+const int kTestPolicy2Id = 123;
+
+const char kTestChromeSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"Test Policy 1\": { \"type\": \"string\" },"
+ " \"Test Policy 2\": { \"type\": \"string\" }"
+ " }"
+ "}";
+
+const PolicyDetails kTestPolicyDetails[] = {
+ // is_deprecated is_device_policy id max_external_data_size
+ { false, false, kTestPolicy1Id, 0 },
+ { false, false, kTestPolicy2Id, 0 },
+};
+
+class TestPolicyStatisticsCollector : public PolicyStatisticsCollector {
+ public:
+ TestPolicyStatisticsCollector(
+ const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : PolicyStatisticsCollector(get_details,
+ chrome_schema,
+ policy_service,
+ prefs,
+ task_runner) {}
+
+ MOCK_METHOD1(RecordPolicyUse, void(int));
+};
+
+} // namespace
+
+class PolicyStatisticsCollectorTest : public testing::Test {
+ protected:
+ PolicyStatisticsCollectorTest()
+ : update_delay_(base::TimeDelta::FromMilliseconds(
+ PolicyStatisticsCollector::kStatisticsUpdateRate)),
+ task_runner_(new base::TestSimpleTaskRunner()) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ std::string error;
+ chrome_schema_ = Schema::Parse(kTestChromeSchema, &error);
+ ASSERT_TRUE(chrome_schema_.valid()) << error;
+
+ policy_details_.SetDetails(kTestPolicy1, &kTestPolicyDetails[0]);
+ policy_details_.SetDetails(kTestPolicy2, &kTestPolicyDetails[1]);
+
+ prefs_.registry()->RegisterInt64Pref(
+ policy_prefs::kLastPolicyStatisticsUpdate, 0);
+
+ // Set up default function behaviour.
+ EXPECT_CALL(policy_service_,
+ GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())))
+ .WillRepeatedly(ReturnRef(policy_map_));
+
+ // Arbitrary negative value (so it'll be different from |update_delay_|).
+ last_delay_ = base::TimeDelta::FromDays(-1);
+ policy_map_.Clear();
+ policy_statistics_collector_.reset(new TestPolicyStatisticsCollector(
+ policy_details_.GetCallback(),
+ chrome_schema_,
+ &policy_service_,
+ &prefs_,
+ task_runner_));
+ }
+
+ void SetPolicy(const std::string& name) {
+ policy_map_.Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ base::Value::CreateBooleanValue(true), NULL);
+ }
+
+ base::TimeDelta GetFirstDelay() const {
+ if (task_runner_->GetPendingTasks().empty()) {
+ ADD_FAILURE();
+ return base::TimeDelta();
+ }
+ return task_runner_->GetPendingTasks().front().delay;
+ }
+
+ const base::TimeDelta update_delay_;
+
+ base::TimeDelta last_delay_;
+
+ PolicyDetailsMap policy_details_;
+ Schema chrome_schema_;
+ TestingPrefServiceSimple prefs_;
+ MockPolicyService policy_service_;
+ PolicyMap policy_map_;
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ scoped_ptr<TestPolicyStatisticsCollector> policy_statistics_collector_;
+};
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPending) {
+ SetPolicy(kTestPolicy1);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_.get(),
+ RecordPolicyUse(kTestPolicy1Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+ EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPendingVeryOld) {
+ SetPolicy(kTestPolicy1);
+
+ // Must not be 0.0 (read comment for Time::FromDoubleT).
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::FromDoubleT(1.0).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_.get(),
+ RecordPolicyUse(kTestPolicy1Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+ EXPECT_EQ(update_delay_, GetFirstDelay());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectLater) {
+ SetPolicy(kTestPolicy1);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_ / 2).ToInternalValue());
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+ EXPECT_LT(GetFirstDelay(), update_delay_);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, MultiplePolicies) {
+ SetPolicy(kTestPolicy1);
+ SetPolicy(kTestPolicy2);
+
+ prefs_.SetInt64(policy_prefs::kLastPolicyStatisticsUpdate,
+ (base::Time::Now() - update_delay_).ToInternalValue());
+
+ EXPECT_CALL(*policy_statistics_collector_.get(),
+ RecordPolicyUse(kTestPolicy1Id));
+ EXPECT_CALL(*policy_statistics_collector_.get(),
+ RecordPolicyUse(kTestPolicy2Id));
+
+ policy_statistics_collector_->Initialize();
+ EXPECT_EQ(1u, task_runner_->GetPendingTasks().size());
+}
+
+} // namespace policy
diff --git a/components/policy/core/common/policy_test_utils.cc b/components/policy/core/common/policy_test_utils.cc
new file mode 100644
index 0000000..b80867a
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.cc
@@ -0,0 +1,139 @@
+// 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/policy_test_utils.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace policy {
+
+PolicyDetailsMap::PolicyDetailsMap() {}
+
+PolicyDetailsMap::~PolicyDetailsMap() {}
+
+GetChromePolicyDetailsCallback PolicyDetailsMap::GetCallback() const {
+ return base::Bind(&PolicyDetailsMap::Lookup, base::Unretained(this));
+}
+
+void PolicyDetailsMap::SetDetails(const std::string& policy,
+ const PolicyDetails* details) {
+ map_[policy] = details;
+}
+
+const PolicyDetails* PolicyDetailsMap::Lookup(const std::string& policy) const {
+ PolicyDetailsMapping::const_iterator it = map_.find(policy);
+ return it == map_.end() ? NULL : it->second;
+}
+
+bool PolicyServiceIsEmpty(const PolicyService* service) {
+ const PolicyMap& map = service->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ if (!map.empty()) {
+ base::DictionaryValue dict;
+ for (PolicyMap::const_iterator it = map.begin(); it != map.end(); ++it)
+ dict.SetWithoutPathExpansion(it->first, it->second.value->DeepCopy());
+ LOG(WARNING) << "There are pre-existing policies in this machine: " << dict;
+ }
+ return map.empty();
+}
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os,
+ const policy::PolicyBundle& bundle) {
+ os << "{" << std::endl;
+ for (policy::PolicyBundle::const_iterator iter = bundle.begin();
+ iter != bundle.end(); ++iter) {
+ os << " \"" << iter->first << "\": " << *iter->second << "," << std::endl;
+ }
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope) {
+ switch (scope) {
+ case policy::POLICY_SCOPE_USER: {
+ os << "POLICY_SCOPE_USER";
+ break;
+ }
+ case policy::POLICY_SCOPE_MACHINE: {
+ os << "POLICY_SCOPE_MACHINE";
+ break;
+ }
+ default: {
+ os << "POLICY_SCOPE_UNKNOWN(" << int(scope) << ")";
+ }
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level) {
+ switch (level) {
+ case policy::POLICY_LEVEL_RECOMMENDED: {
+ os << "POLICY_LEVEL_RECOMMENDED";
+ break;
+ }
+ case policy::POLICY_LEVEL_MANDATORY: {
+ os << "POLICY_LEVEL_MANDATORY";
+ break;
+ }
+ default: {
+ os << "POLICY_LEVEL_UNKNOWN(" << int(level) << ")";
+ }
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain) {
+ switch (domain) {
+ case policy::POLICY_DOMAIN_CHROME: {
+ os << "POLICY_DOMAIN_CHROME";
+ break;
+ }
+ case policy::POLICY_DOMAIN_EXTENSIONS: {
+ os << "POLICY_DOMAIN_EXTENSIONS";
+ break;
+ }
+ default: {
+ os << "POLICY_DOMAIN_UNKNOWN(" << int(domain) << ")";
+ }
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies) {
+ os << "{" << std::endl;
+ for (policy::PolicyMap::const_iterator iter = policies.begin();
+ iter != policies.end(); ++iter) {
+ os << " \"" << iter->first << "\": " << iter->second << "," << std::endl;
+ }
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e) {
+ std::string value;
+ base::JSONWriter::WriteWithOptions(e.value,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &value);
+ os << "{" << std::endl
+ << " \"level\": " << e.level << "," << std::endl
+ << " \"scope\": " << e.scope << "," << std::endl
+ << " \"value\": " << value
+ << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns) {
+ os << ns.domain << "/" << ns.component_id;
+ return os;
+}
diff --git a/components/policy/core/common/policy_test_utils.h b/components/policy/core/common/policy_test_utils.h
new file mode 100644
index 0000000..e2c0c73
--- /dev/null
+++ b/components/policy/core/common/policy_test_utils.h
@@ -0,0 +1,60 @@
+// 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_POLICY_TEST_UTILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+
+#include <map>
+#include <ostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+class PolicyBundle;
+struct PolicyNamespace;
+
+// A mapping of policy names to PolicyDetails that can be used to set the
+// PolicyDetails for test policies.
+class PolicyDetailsMap {
+ public:
+ PolicyDetailsMap();
+ ~PolicyDetailsMap();
+
+ // The returned callback's lifetime is tied to |this| object.
+ GetChromePolicyDetailsCallback GetCallback() const;
+
+ // Does not take ownership of |details|.
+ void SetDetails(const std::string& policy, const PolicyDetails* details);
+
+ private:
+ typedef std::map<std::string, const PolicyDetails*> PolicyDetailsMapping;
+
+ const PolicyDetails* Lookup(const std::string& policy) const;
+
+ PolicyDetailsMapping map_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyDetailsMap);
+};
+
+// Returns true if |service| is not serving any policies. Otherwise logs the
+// current policies and returns false.
+bool PolicyServiceIsEmpty(const PolicyService* service);
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle);
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope);
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level);
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns);
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
diff --git a/components/policy/core/common/schema_registry.h b/components/policy/core/common/schema_registry.h
index 5f56589..b9f7fa8 100644
--- a/components/policy/core/common/schema_registry.h
+++ b/components/policy/core/common/schema_registry.h
@@ -15,6 +15,7 @@
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
namespace policy {