diff options
author | danno@chromium.org <danno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-09 13:53:18 +0000 |
---|---|---|
committer | danno@chromium.org <danno@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-09 13:53:18 +0000 |
commit | ba99ca24c0ba8f0e154dbd74d8a43a55736630e1 (patch) | |
tree | 69d9ab71f38afeb5a78bc49267b9f5d20fe15cf1 /chrome/browser | |
parent | ea4a1c6aa08d16edba3128fb7810d63bcb3e1ef0 (diff) | |
download | chromium_src-ba99ca24c0ba8f0e154dbd74d8a43a55736630e1.zip chromium_src-ba99ca24c0ba8f0e154dbd74d8a43a55736630e1.tar.gz chromium_src-ba99ca24c0ba8f0e154dbd74d8a43a55736630e1.tar.bz2 |
Refactor FileBasedPolicyProvider, introduce AsynchronousPolicyProvider.
Create a superclass of FileBasedPolicyProvider that abstracts how a provider can provide policy on the UI thread that is loaded from the FILE thread.
BUG=65094
TEST=AsynchronousPolicyProvider.*
Review URL: http://codereview.chromium.org/5562002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68735 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
21 files changed, 953 insertions, 534 deletions
diff --git a/chrome/browser/policy/asynchronous_policy_loader.cc b/chrome/browser/policy/asynchronous_policy_loader.cc new file mode 100644 index 0000000..ea27bb7 --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_loader.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2010 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 "chrome/browser/policy/asynchronous_policy_loader.h" + +#include "base/message_loop.h" +#include "base/task.h" +#include "chrome/browser/browser_thread.h" + +namespace policy { + +AsynchronousPolicyLoader::AsynchronousPolicyLoader( + AsynchronousPolicyProvider::Delegate* delegate) + : delegate_(delegate), + provider_(NULL), + origin_loop_(MessageLoop::current()) {} + +void AsynchronousPolicyLoader::Init() { + policy_.reset(delegate_->Load()); +} + +void AsynchronousPolicyLoader::Stop() { + delegate_.reset(); +} + +void AsynchronousPolicyLoader::SetProvider( + AsynchronousPolicyProvider* provider) { + provider_ = provider; +} + +// Manages the life cycle of a new policy map during until it's life cycle is +// taken over by the policy loader. +class UpdatePolicyTask : public Task { + public: + UpdatePolicyTask(scoped_refptr<AsynchronousPolicyLoader> loader, + DictionaryValue* new_policy) + : loader_(loader), + new_policy_(new_policy) {} + + virtual void Run() { + loader_->UpdatePolicy(new_policy_.release()); + } + + private: + scoped_refptr<AsynchronousPolicyLoader> loader_; + scoped_ptr<DictionaryValue> new_policy_; + DISALLOW_COPY_AND_ASSIGN(UpdatePolicyTask); +}; + +void AsynchronousPolicyLoader::Reload() { + if (delegate_.get()) { + DictionaryValue* new_policy = delegate_->Load(); + PostUpdatePolicyTask(new_policy); + } +} + +void AsynchronousPolicyLoader::PostUpdatePolicyTask( + DictionaryValue* new_policy) { + origin_loop_->PostTask(FROM_HERE, new UpdatePolicyTask(this, new_policy)); +} + +void AsynchronousPolicyLoader::UpdatePolicy(DictionaryValue* new_policy_raw) { + DCHECK(policy_.get()); + if (!policy_->Equals(new_policy_raw)) { + policy_.reset(new_policy_raw); + // TODO(danno): Change the notification between the provider and the + // PrefStore into a notification mechanism, removing the need for the + // WeakPtr for the provider. + if (provider_) + provider_->NotifyStoreOfPolicyChange(); + } +} + +} // namespace policy diff --git a/chrome/browser/policy/asynchronous_policy_loader.h b/chrome/browser/policy/asynchronous_policy_loader.h new file mode 100644 index 0000000..2600847 --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_loader.h @@ -0,0 +1,90 @@ +// Copyright (c) 2010 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 CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_LOADER_H_ +#define CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_LOADER_H_ +#pragma once + +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/values.h" +#include "chrome/browser/policy/asynchronous_policy_provider.h" + +namespace policy { + +class ConfigurationPolicyProvider; + +// Used by the implementation of asynchronous policy provider to manage the +// tasks on the file thread that do the heavy lifting of loading policies. +class AsynchronousPolicyLoader + : public base::RefCountedThreadSafe<AsynchronousPolicyLoader> { + public: + explicit AsynchronousPolicyLoader( + AsynchronousPolicyProvider::Delegate* delegate); + + // Triggers initial policy load. + virtual void Init(); + + // Reloads policy, sending notification of changes if necessary. Must be + // called on the file thread. + virtual void Reload(); + + // Stops any pending reload tasks. + virtual void Stop(); + + // Associates a provider with the loader to allow the loader to notify the + // provider when policy changes. + void SetProvider(AsynchronousPolicyProvider* provider); + + const DictionaryValue* policy() const { return policy_.get(); } + + protected: + friend class UpdatePolicyTask; + + // AsynchronousPolicyLoader objects should only be deleted by + // RefCountedThreadSafe. + friend class base::RefCountedThreadSafe<AsynchronousPolicyLoader>; + virtual ~AsynchronousPolicyLoader() {} + + // Schedules a call to UpdatePolicy on |origin_loop_|. + void PostUpdatePolicyTask(DictionaryValue* new_policy); + + // Replaces the existing policy to value map with a new one, sending + // notification to the provider if there is a policy change. Must be called on + // |origin_loop_| so that it's safe to call back into the provider, which is + // not thread-safe. + void UpdatePolicy(DictionaryValue* new_policy); + + AsynchronousPolicyProvider::Delegate* delegate() { + return delegate_.get(); + } + + AsynchronousPolicyProvider* provider() { + return provider_; + } + + private: + friend class AsynchronousPolicyLoaderTest; + + // Provides the low-level mechanics for loading policy. + scoped_ptr<AsynchronousPolicyProvider::Delegate> delegate_; + + // Current policy. + scoped_ptr<DictionaryValue> policy_; + + // The provider this loader is associated with. Access only on the thread that + // called the constructor. See |origin_loop_| below. + AsynchronousPolicyProvider* provider_; + + // The message loop on which this object was constructed. Recorded so that + // it's possible to call back into the non thread safe provider to fire the + // notification. + MessageLoop* origin_loop_; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousPolicyLoader); +}; + +} // namespace policy + +#endif // CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_LOADER_H_ diff --git a/chrome/browser/policy/asynchronous_policy_loader_unittest.cc b/chrome/browser/policy/asynchronous_policy_loader_unittest.cc new file mode 100644 index 0000000..4995431 --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_loader_unittest.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2010 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 "chrome/browser/policy/asynchronous_policy_loader.h" +#include "chrome/browser/policy/asynchronous_policy_provider.h" +#include "chrome/browser/policy/asynchronous_policy_test_base.h" +#include "chrome/browser/policy/mock_configuration_policy_provider.h" +#include "chrome/common/notification_observer_mock.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +namespace policy { + +class AsynchronousPolicyLoaderTest : public AsynchronousPolicyTestBase { + public: + AsynchronousPolicyLoaderTest() {} + virtual ~AsynchronousPolicyLoaderTest() {} + + virtual void SetUp() { + AsynchronousPolicyTestBase::SetUp(); + mock_provider_.reset(new MockConfigurationPolicyProvider()); + } + + protected: + scoped_ptr<MockConfigurationPolicyProvider> mock_provider_; + + private: + DISALLOW_COPY_AND_ASSIGN(AsynchronousPolicyLoaderTest); +}; + +ACTION(CreateTestDictionary) { + return new DictionaryValue(); +} + +ACTION_P(CreateSequencedTestDictionary, number) { + DictionaryValue* test_dictionary = new DictionaryValue(); + test_dictionary->SetInteger("id", ++(*number)); + return test_dictionary; +} + +ACTION(RescheduleImmediatePolicyReload) { + *arg1 = base::TimeDelta(); + return false; +} + +TEST_F(AsynchronousPolicyLoaderTest, InitialLoad) { + DictionaryValue* template_dict(new DictionaryValue()); + EXPECT_CALL(*delegate_, Load()).WillOnce(Return(template_dict)); + scoped_refptr<AsynchronousPolicyLoader> loader = + new AsynchronousPolicyLoader(delegate_.release()); + loader->Init(); + const DictionaryValue* loaded_dict(loader->policy()); + EXPECT_TRUE(loaded_dict->Equals(template_dict)); +} + +// Verify that the fallback policy requests are made. +TEST_F(AsynchronousPolicyLoaderTest, InitialLoadWithFallback) { + int dictionary_number = 0; + InSequence s; + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number)); + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number)); + scoped_refptr<AsynchronousPolicyLoader> loader = + new AsynchronousPolicyLoader(delegate_.release()); + loader->Init(); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); + + const DictionaryValue* loaded_dict(loader->policy()); + int loaded_number; + EXPECT_TRUE(loaded_dict->GetInteger("id", &loaded_number)); + EXPECT_EQ(dictionary_number, loaded_number); +} + +// Ensure that calling stop on the loader stops subsequent reloads from +// happening. +TEST_F(AsynchronousPolicyLoaderTest, Stop) { + ON_CALL(*delegate_, Load()).WillByDefault(CreateTestDictionary()); + EXPECT_CALL(*delegate_, Load()).Times(1); + scoped_refptr<AsynchronousPolicyLoader> loader = + new AsynchronousPolicyLoader(delegate_.release()); + loader->Init(); + loop_.RunAllPending(); + loader->Stop(); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); +} + +// Verifies that the provider is notified upon policy reload, but only +// if the policy changed. +TEST_F(AsynchronousPolicyLoaderTest, ProviderNotificationOnPolicyChange) { + InSequence s; + NotificationObserverMock observer; + // NotificationService service; + NotificationRegistrar registrar; + registrar.Add(&observer, + NotificationType::POLICY_CHANGED, + NotificationService::AllSources()); + int dictionary_number_1 = 0; + int dictionary_number_2 = 0; + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number_1)); + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number_2)); + EXPECT_CALL(observer, Observe(_, _, _)).Times(0); + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number_2)); + EXPECT_CALL(observer, Observe(_, _, _)).Times(1); + EXPECT_CALL(*delegate_, Load()).WillOnce( + CreateSequencedTestDictionary(&dictionary_number_1)); + scoped_refptr<AsynchronousPolicyLoader> loader = + new AsynchronousPolicyLoader(delegate_.release()); + AsynchronousPolicyProvider provider(NULL, loader); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); +} + +} // namespace policy diff --git a/chrome/browser/policy/asynchronous_policy_provider.cc b/chrome/browser/policy/asynchronous_policy_provider.cc new file mode 100644 index 0000000..709568f --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_provider.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2010 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 "chrome/browser/policy/asynchronous_policy_provider.h" + +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/policy/asynchronous_policy_loader.h" + +namespace policy { + +AsynchronousPolicyProvider::AsynchronousPolicyProvider( + const PolicyDefinitionList* policy_list, + scoped_refptr<AsynchronousPolicyLoader> loader) + : ConfigurationPolicyProvider(policy_list), + loader_(loader) { + // TODO(danno): This explicit registration of the provider shouldn't be + // necessary. Instead, the PrefStore should explicitly observe preference + // changes that are reported during the policy change. + loader_->SetProvider(this); + loader_->Init(); +} + +AsynchronousPolicyProvider::~AsynchronousPolicyProvider() { + DCHECK(CalledOnValidThread()); + loader_->Stop(); + loader_->SetProvider(NULL); +} + +bool AsynchronousPolicyProvider::Provide( + ConfigurationPolicyStoreInterface* store) { + DCHECK(CalledOnValidThread()); + DCHECK(loader_->policy()); + DecodePolicyValueTree(loader_->policy(), store); + return true; +} + +scoped_refptr<AsynchronousPolicyLoader> AsynchronousPolicyProvider::loader() { + return loader_; +} + +} // namespace policy diff --git a/chrome/browser/policy/asynchronous_policy_provider.h b/chrome/browser/policy/asynchronous_policy_provider.h new file mode 100644 index 0000000..0f6b649 --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_provider.h @@ -0,0 +1,58 @@ +// Copyright (c) 2010 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 CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_PROVIDER_H_ +#define CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_PROVIDER_H_ +#pragma once + +#include "base/non_thread_safe.h" +#include "base/ref_counted.h" +#include "base/weak_ptr.h" +#include "chrome/browser/policy/configuration_policy_provider.h" + +namespace policy { + +class AsynchronousPolicyLoader; + +// Policy provider that loads policy asynchronously. Providers should subclass +// from this class if loading the policy requires disk access or must for some +// other reason be performed on the file thread. The actual logic for loading +// policy is handled by a delegate passed at construction time. +class AsynchronousPolicyProvider + : public ConfigurationPolicyProvider, + public base::SupportsWeakPtr<AsynchronousPolicyProvider>, + public NonThreadSafe { + public: + // Must be implemented by subclasses of the asynchronous policy provider to + // provide the implementation details of how policy is loaded. + class Delegate { + public: + virtual ~Delegate() {} + + virtual DictionaryValue* Load() = 0; + }; + + // Assumes ownership of |loader|. + AsynchronousPolicyProvider( + const PolicyDefinitionList* policy_list, + scoped_refptr<AsynchronousPolicyLoader> loader); + virtual ~AsynchronousPolicyProvider(); + + // ConfigurationPolicyProvider implementation. + virtual bool Provide(ConfigurationPolicyStoreInterface* store); + + // For tests to trigger reloads. + scoped_refptr<AsynchronousPolicyLoader> loader(); + + protected: + // The loader object used internally. + scoped_refptr<AsynchronousPolicyLoader> loader_; + + private: + DISALLOW_COPY_AND_ASSIGN(AsynchronousPolicyProvider); +}; + +} // namespace policy + +#endif // CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_PROVIDER_H_ diff --git a/chrome/browser/policy/asynchronous_policy_provider_unittest.cc b/chrome/browser/policy/asynchronous_policy_provider_unittest.cc new file mode 100644 index 0000000..64306eb --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_provider_unittest.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2010 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 "chrome/browser/policy/asynchronous_policy_loader.h" +#include "chrome/browser/policy/asynchronous_policy_provider.h" +#include "chrome/browser/policy/asynchronous_policy_test_base.h" +#include "chrome/browser/policy/configuration_policy_pref_store.h" +#include "chrome/browser/policy/mock_configuration_policy_store.h" +#include "chrome/common/policy_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +namespace policy { + +TEST_F(AsynchronousPolicyTestBase, Provide) { + InSequence s; + DictionaryValue* policies = new DictionaryValue(); + policies->SetBoolean(policy::key::kSyncDisabled, true); + EXPECT_CALL(*delegate_, Load()).WillOnce(Return(policies)); + EXPECT_CALL(*store_, Apply(policy::kPolicySyncDisabled, _)).Times(1); + AsynchronousPolicyProvider provider( + ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), + new AsynchronousPolicyLoader(delegate_.release())); + provider.Provide(store_.get()); +} + +TEST_F(AsynchronousPolicyTestBase, ProvideAfterRefresh) { + InSequence s; + DictionaryValue* original_policies = new DictionaryValue(); + original_policies->SetBoolean(policy::key::kSyncDisabled, true); + EXPECT_CALL(*delegate_, Load()).WillOnce(Return(original_policies)); + DictionaryValue* refresh_policies = new DictionaryValue(); + refresh_policies->SetBoolean( + policy::key::kJavascriptEnabled, + true); + EXPECT_CALL(*delegate_, Load()).WillOnce(Return(refresh_policies)); + AsynchronousPolicyLoader* loader = new AsynchronousPolicyLoader( + delegate_.release()); + AsynchronousPolicyProvider provider( + ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), + loader); + loop_.RunAllPending(); + loader->Reload(); + loop_.RunAllPending(); + EXPECT_CALL(*store_, Apply(policy::kPolicyJavascriptEnabled, _)).Times(1); + provider.Provide(store_.get()); +} + +} // namespace policy diff --git a/chrome/browser/policy/asynchronous_policy_test_base.h b/chrome/browser/policy/asynchronous_policy_test_base.h new file mode 100644 index 0000000..0281afd --- /dev/null +++ b/chrome/browser/policy/asynchronous_policy_test_base.h @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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 CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_TEST_BASE_H_ +#define CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_TEST_BASE_H_ +#pragma once + +#include "base/message_loop.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/policy/asynchronous_policy_provider.h" +#include "chrome/browser/policy/mock_configuration_policy_store.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace policy { + +// A delegate for testing that can feed arbitrary information to the loader. +class ProviderDelegateMock : public AsynchronousPolicyProvider::Delegate { + public: + ProviderDelegateMock() : AsynchronousPolicyProvider::Delegate() {} + virtual ~ProviderDelegateMock() {} + + MOCK_METHOD0(Load, DictionaryValue*()); + + private: + DISALLOW_COPY_AND_ASSIGN(ProviderDelegateMock); +}; + +class AsynchronousPolicyTestBase : public testing::Test { + public: + AsynchronousPolicyTestBase() + : ui_thread_(BrowserThread::UI, &loop_), + file_thread_(BrowserThread::FILE, &loop_) {} + + virtual ~AsynchronousPolicyTestBase() {} + + virtual void SetUp() { + delegate_.reset(new ProviderDelegateMock()); + store_.reset(new MockConfigurationPolicyStore); + } + + virtual void TearDown() { + loop_.RunAllPending(); + } + + protected: + MessageLoop loop_; + + // The mocks that are used in the test must outlive the scope of the test + // because they still get accessed in the RunAllPending of the TearDown. + scoped_ptr<MockConfigurationPolicyStore> store_; + scoped_ptr<ProviderDelegateMock> delegate_; + + private: + BrowserThread ui_thread_; + BrowserThread file_thread_; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousPolicyTestBase); +}; + +} // namespace policy + +#endif // CHROME_BROWSER_POLICY_ASYNCHRONOUS_POLICY_TEST_BASE_H_ diff --git a/chrome/browser/policy/config_dir_policy_provider.cc b/chrome/browser/policy/config_dir_policy_provider.cc index f134aeb..be6f586 100644 --- a/chrome/browser/policy/config_dir_policy_provider.cc +++ b/chrome/browser/policy/config_dir_policy_provider.cc @@ -12,11 +12,12 @@ namespace policy { -ConfigDirPolicyLoader::ConfigDirPolicyLoader(const FilePath& config_dir) - : FileBasedPolicyProvider::Delegate(config_dir) { +ConfigDirPolicyProviderDelegate::ConfigDirPolicyProviderDelegate( + const FilePath& config_dir) + : FileBasedPolicyProvider::ProviderDelegate(config_dir) { } -DictionaryValue* ConfigDirPolicyLoader::Load() { +DictionaryValue* ConfigDirPolicyProviderDelegate::Load() { // Enumerate the files and sort them lexicographically. std::set<FilePath> files; file_util::FileEnumerator file_enumerator(config_file_path(), false, @@ -49,7 +50,7 @@ DictionaryValue* ConfigDirPolicyLoader::Load() { return policy; } -base::Time ConfigDirPolicyLoader::GetLastModification() { +base::Time ConfigDirPolicyProviderDelegate::GetLastModification() { base::Time last_modification = base::Time(); base::PlatformFileInfo file_info; @@ -78,8 +79,9 @@ base::Time ConfigDirPolicyLoader::GetLastModification() { ConfigDirPolicyProvider::ConfigDirPolicyProvider( const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list, const FilePath& config_dir) - : FileBasedPolicyProvider(policy_list, - new ConfigDirPolicyLoader(config_dir)) { + : FileBasedPolicyProvider( + policy_list, + new ConfigDirPolicyProviderDelegate(config_dir)) { } } // namespace policy diff --git a/chrome/browser/policy/config_dir_policy_provider.h b/chrome/browser/policy/config_dir_policy_provider.h index 6096513..3647489 100644 --- a/chrome/browser/policy/config_dir_policy_provider.h +++ b/chrome/browser/policy/config_dir_policy_provider.h @@ -10,23 +10,6 @@ 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 via the PolicyLoader interface. The files -// are consulted in lexicographic file name order, so the last value read takes -// precedence in case of preference key collisions. -class ConfigDirPolicyLoader : public FileBasedPolicyProvider::Delegate { - public: - explicit ConfigDirPolicyLoader(const FilePath& config_dir); - - // FileBasedPolicyLoader::Delegate implementation. - virtual DictionaryValue* Load(); - virtual base::Time GetLastModification(); - - private: - DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyLoader); -}; - // Policy provider backed by JSON files in a configuration directory. class ConfigDirPolicyProvider : public FileBasedPolicyProvider { public: @@ -38,6 +21,24 @@ class ConfigDirPolicyProvider : public FileBasedPolicyProvider { DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyProvider); }; +// A provider delegate 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 via the ProviderDelegate +// interface. The files are consulted in lexicographic file name order, so the +// last value read takes precedence in case of preference key collisions. +class ConfigDirPolicyProviderDelegate + : public FileBasedPolicyProvider::ProviderDelegate { + public: + explicit ConfigDirPolicyProviderDelegate(const FilePath& config_dir); + + // FileBasedPolicyProvider::ProviderDelegate implementation. + virtual DictionaryValue* Load(); + virtual base::Time GetLastModification(); + + private: + DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyProviderDelegate); +}; + } // namespace policy #endif // CHROME_BROWSER_POLICY_CONFIG_DIR_POLICY_PROVIDER_H_ diff --git a/chrome/browser/policy/config_dir_policy_provider_unittest.cc b/chrome/browser/policy/config_dir_policy_provider_unittest.cc index 25893da..7197f490 100644 --- a/chrome/browser/policy/config_dir_policy_provider_unittest.cc +++ b/chrome/browser/policy/config_dir_policy_provider_unittest.cc @@ -8,6 +8,7 @@ #include "base/path_service.h" #include "base/scoped_temp_dir.h" #include "base/string_number_conversions.h" +#include "chrome/browser/browser_thread.h" #include "chrome/browser/policy/config_dir_policy_provider.h" #include "chrome/browser/policy/configuration_policy_pref_store.h" #include "chrome/browser/policy/mock_configuration_policy_store.h" @@ -49,7 +50,7 @@ class ConfigDirPolicyLoaderTest // The preferences dictionary is expected to be empty when there are no files to // load. TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) { - ConfigDirPolicyLoader loader(test_dir()); + ConfigDirPolicyProviderDelegate loader(test_dir()); scoped_ptr<DictionaryValue> policy(loader.Load()); EXPECT_TRUE(policy.get()); EXPECT_TRUE(policy->empty()); @@ -59,7 +60,7 @@ TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) { // dictionary. TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsNonExistentDirectory) { FilePath non_existent_dir(test_dir().Append(FILE_PATH_LITERAL("not_there"))); - ConfigDirPolicyLoader loader(non_existent_dir); + ConfigDirPolicyProviderDelegate loader(non_existent_dir); scoped_ptr<DictionaryValue> policy(loader.Load()); EXPECT_TRUE(policy.get()); EXPECT_TRUE(policy->empty()); @@ -71,7 +72,7 @@ TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsSinglePref) { test_dict.SetString("HomepageLocation", "http://www.google.com"); WriteConfigFile(test_dict, "config_file"); - ConfigDirPolicyLoader loader(test_dir()); + ConfigDirPolicyProviderDelegate loader(test_dir()); scoped_ptr<DictionaryValue> policy(loader.Load()); EXPECT_TRUE(policy.get()); EXPECT_TRUE(policy->Equals(&test_dict)); @@ -93,7 +94,7 @@ TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) { for (unsigned int i = 5; i <= 8; ++i) WriteConfigFile(test_dict_bar, base::IntToString(i)); - ConfigDirPolicyLoader loader(test_dir()); + ConfigDirPolicyProviderDelegate loader(test_dir()); scoped_ptr<DictionaryValue> policy(loader.Load()); EXPECT_TRUE(policy.get()); EXPECT_TRUE(policy->Equals(&test_dict_foo)); diff --git a/chrome/browser/policy/configuration_policy_provider.cc b/chrome/browser/policy/configuration_policy_provider.cc index ce7106f..a8ca10f 100644 --- a/chrome/browser/policy/configuration_policy_provider.cc +++ b/chrome/browser/policy/configuration_policy_provider.cc @@ -24,7 +24,7 @@ void ConfigurationPolicyProvider::NotifyStoreOfPolicyChange() { } void ConfigurationPolicyProvider::DecodePolicyValueTree( - DictionaryValue* policies, + const DictionaryValue* policies, ConfigurationPolicyStoreInterface* store) { const PolicyDefinitionList* policy_list(policy_definition_list()); for (const PolicyDefinitionList::Entry* i = policy_list->begin; diff --git a/chrome/browser/policy/configuration_policy_provider.h b/chrome/browser/policy/configuration_policy_provider.h index e3989b9..6d5cc7c 100644 --- a/chrome/browser/policy/configuration_policy_provider.h +++ b/chrome/browser/policy/configuration_policy_provider.h @@ -53,7 +53,7 @@ class ConfigurationPolicyProvider { virtual void NotifyStoreOfPolicyChange(); // Decodes the value tree and writes the configuration to the given |store|. - void DecodePolicyValueTree(DictionaryValue* policies, + void DecodePolicyValueTree(const DictionaryValue* policies, ConfigurationPolicyStoreInterface* store); protected: const PolicyDefinitionList* policy_definition_list() const { diff --git a/chrome/browser/policy/configuration_policy_provider_mac.cc b/chrome/browser/policy/configuration_policy_provider_mac.cc index 62b7436..b8b6011 100644 --- a/chrome/browser/policy/configuration_policy_provider_mac.cc +++ b/chrome/browser/policy/configuration_policy_provider_mac.cc @@ -34,15 +34,15 @@ static FilePath GetManagedPolicyPath() { return path.Append(base::SysCFStringRefToUTF8(bundle_id) + ".plist"); } -MacPreferencesPolicyLoader::MacPreferencesPolicyLoader( +MacPreferencesPolicyProviderDelegate::MacPreferencesPolicyProviderDelegate( MacPreferences* preferences, const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list) - : FileBasedPolicyProvider::Delegate(GetManagedPolicyPath()), + : FileBasedPolicyProvider::ProviderDelegate(GetManagedPolicyPath()), policy_list_(policy_list), preferences_(preferences) { } -DictionaryValue* MacPreferencesPolicyLoader::Load() { +DictionaryValue* MacPreferencesPolicyProviderDelegate::Load() { preferences_->AppSynchronize(kCFPreferencesCurrentApplication); DictionaryValue* policy = new DictionaryValue; @@ -110,7 +110,7 @@ DictionaryValue* MacPreferencesPolicyLoader::Load() { return policy; } -base::Time MacPreferencesPolicyLoader::GetLastModification() { +base::Time MacPreferencesPolicyProviderDelegate::GetLastModification() { base::PlatformFileInfo file_info; if (!file_util::GetFileInfo(config_file_path(), &file_info) || file_info.is_directory) { @@ -123,14 +123,16 @@ base::Time MacPreferencesPolicyLoader::GetLastModification() { ConfigurationPolicyProviderMac::ConfigurationPolicyProviderMac( const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list) : FileBasedPolicyProvider(policy_list, - new MacPreferencesPolicyLoader(new MacPreferences, policy_list)) { + new MacPreferencesPolicyProviderDelegate(new MacPreferences, + policy_list)) { } ConfigurationPolicyProviderMac::ConfigurationPolicyProviderMac( const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list, MacPreferences* preferences) : FileBasedPolicyProvider(policy_list, - new MacPreferencesPolicyLoader(preferences, policy_list)) { + new MacPreferencesPolicyProviderDelegate(preferences, + policy_list)) { } } // namespace policy diff --git a/chrome/browser/policy/configuration_policy_provider_mac.h b/chrome/browser/policy/configuration_policy_provider_mac.h index d804500..9332e2b 100644 --- a/chrome/browser/policy/configuration_policy_provider_mac.h +++ b/chrome/browser/policy/configuration_policy_provider_mac.h @@ -13,11 +13,12 @@ namespace policy { -// A policy loader implementation that read Mac OS X's managed preferences. -class MacPreferencesPolicyLoader : public FileBasedPolicyProvider::Delegate { +// A provider delegate implementation that reads Mac OS X's managed preferences. +class MacPreferencesPolicyProviderDelegate + : public FileBasedPolicyProvider::ProviderDelegate { public: // Takes ownership of |preferences|. - MacPreferencesPolicyLoader( + MacPreferencesPolicyProviderDelegate( MacPreferences* preferences, const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list); @@ -35,7 +36,7 @@ class MacPreferencesPolicyLoader : public FileBasedPolicyProvider::Delegate { scoped_ptr<MacPreferences> preferences_; - DISALLOW_COPY_AND_ASSIGN(MacPreferencesPolicyLoader); + DISALLOW_COPY_AND_ASSIGN(MacPreferencesPolicyProviderDelegate); }; // An implementation of |ConfigurationPolicyProvider| using the mechanism diff --git a/chrome/browser/policy/file_based_policy_loader.cc b/chrome/browser/policy/file_based_policy_loader.cc new file mode 100644 index 0000000..4c33b91 --- /dev/null +++ b/chrome/browser/policy/file_based_policy_loader.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2010 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 "chrome/browser/policy/file_based_policy_loader.h" + +namespace { + +// Amount of time we wait for the files on disk to settle before trying to load +// them. This alleviates the problem of reading partially written files and +// makes it possible to batch quasi-simultaneous changes. +const int kSettleIntervalSeconds = 5; + +// The time interval for rechecking policy. This is our fallback in case the +// delegate never reports a change to the ReloadObserver. +const int kReloadIntervalMinutes = 15; + +} + +namespace policy { + +FileBasedPolicyLoader::FileBasedPolicyLoader( + FileBasedPolicyProvider::ProviderDelegate* provider_delegate) + : AsynchronousPolicyLoader(provider_delegate), + config_file_path_(provider_delegate->config_file_path()), + reload_task_(NULL), + reload_interval_(base::TimeDelta::FromMinutes(kReloadIntervalMinutes)), + settle_interval_(base::TimeDelta::FromSeconds(kSettleIntervalSeconds)) { +} + +class FileBasedPolicyWatcherDelegate : public FilePathWatcher::Delegate { + public: + explicit FileBasedPolicyWatcherDelegate( + scoped_refptr<FileBasedPolicyLoader> loader) + : loader_(loader) {} + virtual ~FileBasedPolicyWatcherDelegate() {} + + // FilePathWatcher::Delegate implementation: + void OnFilePathChanged(const FilePath& path) { + loader_->OnFilePathChanged(path); + } + + void OnError() { + loader_->OnError(); + } + + private: + scoped_refptr<FileBasedPolicyLoader> loader_; + DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyWatcherDelegate); +}; + +void FileBasedPolicyLoader::Init() { + AsynchronousPolicyLoader::Init(); + + // Initialization can happen early when the file thread is not yet available, + // but the watcher's initialization must be done on the file thread. Posting + // to a to the file directly before the file thread is initialized will cause + // the task to be forgotten. Instead, post a task to the ui thread to delay + // the remainder of initialization until threading is fully initialized. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &FileBasedPolicyLoader::InitAfterFileThreadAvailable)); +} + +void FileBasedPolicyLoader::Stop() { + AsynchronousPolicyLoader::Stop(); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, &FileBasedPolicyLoader::StopOnFileThread)); +} + +void FileBasedPolicyLoader::OnFilePathChanged( + const FilePath& path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Reload(); +} + +void FileBasedPolicyLoader::OnError() { + LOG(ERROR) << "FilePathWatcher on " << config_file_path().value() + << " failed."; +} + +void FileBasedPolicyLoader::Reload() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + if (!delegate()) + return; + + // Check the directory time in order to see whether a reload is required. + base::TimeDelta delay; + base::Time now = base::Time::Now(); + if (!IsSafeToReloadPolicy(now, &delay)) { + ScheduleReloadTask(delay); + return; + } + + // Load the policy definitions. + scoped_ptr<DictionaryValue> new_policy(delegate()->Load()); + + // Check again in case the directory has changed while reading it. + if (!IsSafeToReloadPolicy(now, &delay)) { + ScheduleReloadTask(delay); + return; + } + + PostUpdatePolicyTask(new_policy.release()); + + ScheduleFallbackReloadTask(); +} + +void FileBasedPolicyLoader::InitAfterFileThreadAvailable() { + if (provider()) { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, &FileBasedPolicyLoader::InitWatcher)); + + ScheduleFallbackReloadTask(); + } +} + +void FileBasedPolicyLoader::InitWatcher() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + watcher_.reset(new FilePathWatcher); + if (!config_file_path().empty() && + !watcher_->Watch(config_file_path(), + new FileBasedPolicyWatcherDelegate(this))) { + OnError(); + } + + // There might have been changes to the directory in the time between + // construction of the loader and initialization of the watcher. Call reload + // to detect if that is the case. + Reload(); +} + +void FileBasedPolicyLoader::StopOnFileThread() { + watcher_.reset(); + if (reload_task_) { + reload_task_->Cancel(); + reload_task_ = NULL; + } +} + +void FileBasedPolicyLoader::ScheduleReloadTask( + const base::TimeDelta& delay) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + if (reload_task_) + reload_task_->Cancel(); + + reload_task_ = + NewRunnableMethod(this, &FileBasedPolicyLoader::ReloadFromTask); + BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, reload_task_, + delay.InMilliseconds()); +} + +void FileBasedPolicyLoader::ScheduleFallbackReloadTask() { + // As a safeguard in case that the load delegate failed to timely notice a + // change in policy, schedule a reload task that'll make us recheck after a + // reasonable interval. + ScheduleReloadTask(reload_interval_); +} + +void FileBasedPolicyLoader::ReloadFromTask() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + // Drop the reference to the reload task, since the task might be the only + // referer that keeps us alive, so we should not Cancel() it. + reload_task_ = NULL; + + Reload(); +} + +bool FileBasedPolicyLoader::IsSafeToReloadPolicy( + const base::Time& now, + base::TimeDelta* delay) { + DCHECK(delay); + + // A null modification time indicates there's no data. + FileBasedPolicyProvider::ProviderDelegate* provider_delegate = + static_cast<FileBasedPolicyProvider::ProviderDelegate*>(delegate()); + base::Time last_modification(provider_delegate->GetLastModification()); + if (last_modification.is_null()) + return true; + + // If there was a change since the last recorded modification, wait some more. + if (last_modification != last_modification_file_) { + last_modification_file_ = last_modification; + last_modification_clock_ = now; + *delay = settle_interval_; + return false; + } + + // Check whether the settle interval has elapsed. + base::TimeDelta age = now - last_modification_clock_; + if (age < settle_interval_) { + *delay = settle_interval_ - age; + return false; + } + + return true; +} + +} // namespace policy diff --git a/chrome/browser/policy/file_based_policy_loader.h b/chrome/browser/policy/file_based_policy_loader.h new file mode 100644 index 0000000..8acf9b2d --- /dev/null +++ b/chrome/browser/policy/file_based_policy_loader.h @@ -0,0 +1,104 @@ +// Copyright (c) 2010 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 CHROME_BROWSER_POLICY_FILE_BASED_POLICY_LOADER_H_ +#define CHROME_BROWSER_POLICY_FILE_BASED_POLICY_LOADER_H_ +#pragma once + +#include "chrome/browser/file_path_watcher/file_path_watcher.h" +#include "chrome/browser/policy/asynchronous_policy_loader.h" +#include "chrome/browser/policy/file_based_policy_provider.h" + +namespace policy { + +// A customized asynchronous policy loader that handles loading policy from a +// file using a FilePathWatcher. The loader creates a fallback task to load +// policy periodically in case the watcher fails and retries policy loads when +// the watched file is in flux. +class FileBasedPolicyLoader : public AsynchronousPolicyLoader { + public: + FileBasedPolicyLoader( + FileBasedPolicyProvider::ProviderDelegate* provider_delegate); + + // AsynchronousPolicyLoader implementation: + virtual void Init(); + virtual void Stop(); + virtual void Reload(); + + void OnFilePathChanged(const FilePath& path); + void OnError(); + + protected: + // FileBasedPolicyLoader objects should only be deleted by + // RefCountedThreadSafe. + friend class base::RefCountedThreadSafe<AsynchronousPolicyLoader>; + virtual ~FileBasedPolicyLoader() {} + + const FilePath& config_file_path() { return config_file_path_; } + + private: + // Finishes initialization after the threading system has been fully + // intiialized. + void InitAfterFileThreadAvailable(); + + // Creates the file path watcher and configures it watch |config_file_path_|. + // Must be called on the file thread. + void InitWatcher(); + + // Cancels file path watch notification and destroys the watcher. + // Must be called on file thread. + void StopOnFileThread(); + + // Schedules a reload task to run when |delay| expires. Must be called on the + // file thread. + void ScheduleReloadTask(const base::TimeDelta& delay); + + // Schedules a reload task to run after the number of minutes specified + // in |reload_interval_minutes_|. Must be called on the file thread. + void ScheduleFallbackReloadTask(); + + // Invoked from the reload task on the file thread. + void ReloadFromTask(); + + // Checks whether policy information is safe to read. If not, returns false + // and then delays until it is considered safe to reload in |delay|. + // Must be called on the file thread. + bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay); + + // The path at which we look for configuration files. + const FilePath config_file_path_; + + // Managed with a scoped_ptr rather than being declared as an inline member to + // decouple the watcher's life cycle from the loader's. This decoupling makes + // it possible to destroy the watcher before the loader's destructor is called + // (e.g. during Stop), since |watcher_| internally holds a reference to the + // loader and keeps it alive. + scoped_ptr<FilePathWatcher> watcher_; + + // The reload task. Access only on the file thread. Holds a reference to the + // currently posted task, so we can cancel and repost it if necessary. + CancelableTask* reload_task_; + + // The interval that a policy reload will be triggered as a fallback even if + // the delegate doesn't indicate that one is needed. + const base::TimeDelta reload_interval_; + + // Settle interval. + const base::TimeDelta settle_interval_; + + // Records last known modification timestamp of |config_file_path_|. + base::Time last_modification_file_; + + // The wall clock time at which the last modification timestamp was + // recorded. It's better to not assume the file notification time and the + // wall clock times come from the same source, just in case there is some + // non-local filesystem involved. + base::Time last_modification_clock_; + + DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyLoader); +}; + +} // namespace policy + +#endif // CHROME_BROWSER_POLICY_FILE_BASED_POLICY_LOADER_H_ diff --git a/chrome/browser/policy/file_based_policy_provider.cc b/chrome/browser/policy/file_based_policy_provider.cc index 578277d..488f19f 100644 --- a/chrome/browser/policy/file_based_policy_provider.cc +++ b/chrome/browser/policy/file_based_policy_provider.cc @@ -4,241 +4,21 @@ #include "chrome/browser/policy/file_based_policy_provider.h" -#include <set> - -#include "base/logging.h" -#include "base/message_loop.h" -#include "base/task.h" -#include "base/utf_string_conversions.h" -#include "base/values.h" -#include "chrome/browser/browser_thread.h" -#include "chrome/common/json_value_serializer.h" +#include "chrome/browser/policy/file_based_policy_loader.h" namespace policy { -// Amount of time we wait for the files on disk to settle before trying to load -// it. This alleviates the problem of reading partially written files and allows -// to batch quasi-simultaneous changes. -const int kSettleIntervalSeconds = 5; - -// The time interval for rechecking policy. This is our fallback in case the -// file path watch fails or doesn't report a change. -const int kReloadIntervalMinutes = 15; - -// FileBasedPolicyProvider implementation: +FileBasedPolicyProvider::ProviderDelegate::ProviderDelegate( + const FilePath& config_file_path) + : config_file_path_(config_file_path) {} -FileBasedPolicyProvider::Delegate::~Delegate() { -} - -FileBasedPolicyProvider::Delegate::Delegate(const FilePath& config_file_path) - : config_file_path_(config_file_path) { -} +FileBasedPolicyProvider::ProviderDelegate::~ProviderDelegate() {} FileBasedPolicyProvider::FileBasedPolicyProvider( const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list, - FileBasedPolicyProvider::Delegate* delegate) - : ConfigurationPolicyProvider(policy_list) { - loader_ = new FileBasedPolicyLoader(AsWeakPtr(), - delegate, - kSettleIntervalSeconds, - kReloadIntervalMinutes); - watcher_ = new FileBasedPolicyWatcher; - watcher_->Init(loader_.get()); -} - -FileBasedPolicyProvider::~FileBasedPolicyProvider() { - loader_->Stop(); -} - -bool FileBasedPolicyProvider::Provide( - ConfigurationPolicyStoreInterface* store) { - scoped_ptr<DictionaryValue> policy(loader_->GetPolicy()); - DCHECK(policy.get()); - DecodePolicyValueTree(policy.get(), store); - return true; -} - -// FileBasedPolicyLoader implementation: - -FileBasedPolicyLoader::FileBasedPolicyLoader( - base::WeakPtr<ConfigurationPolicyProvider> provider, - FileBasedPolicyProvider::Delegate* delegate, - int settle_interval_seconds, - int reload_interval_minutes) - : delegate_(delegate), - provider_(provider), - origin_loop_(MessageLoop::current()), - reload_task_(NULL), - settle_interval_seconds_(settle_interval_seconds), - reload_interval_minutes_(reload_interval_minutes) { - // Force an initial load, so GetPolicy() works. - policy_.reset(delegate_->Load()); - DCHECK(policy_.get()); -} - -void FileBasedPolicyLoader::Stop() { - if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { - BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, - NewRunnableMethod(this, &FileBasedPolicyLoader::Stop)); - return; - } - - if (reload_task_) { - reload_task_->Cancel(); - reload_task_ = NULL; - } -} - -void FileBasedPolicyLoader::Reload() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - - // Check the directory time in order to see whether a reload is required. - base::TimeDelta delay; - base::Time now = base::Time::Now(); - if (!IsSafeToReloadPolicy(now, &delay)) { - ScheduleReloadTask(delay); - return; - } - - // Load the policy definitions. - scoped_ptr<DictionaryValue> new_policy(delegate_->Load()); - - // Check again in case the directory has changed while reading it. - if (!IsSafeToReloadPolicy(now, &delay)) { - ScheduleReloadTask(delay); - return; - } - - // Replace policy definition. - bool changed = false; - { - AutoLock lock(lock_); - changed = !policy_->Equals(new_policy.get()); - policy_.reset(new_policy.release()); - } - - // There's a change, report it! - if (changed) { - VLOG(0) << "Policy reload from " << config_file_path().value() - << " succeeded."; - origin_loop_->PostTask(FROM_HERE, - NewRunnableMethod(this, &FileBasedPolicyLoader::NotifyPolicyChanged)); - } - - // As a safeguard in case the file watcher fails, schedule a reload task - // that'll make us recheck after a reasonable interval. - ScheduleReloadTask(base::TimeDelta::FromMinutes(reload_interval_minutes_)); -} - -DictionaryValue* FileBasedPolicyLoader::GetPolicy() { - AutoLock lock(lock_); - return static_cast<DictionaryValue*>(policy_->DeepCopy()); -} - -void FileBasedPolicyLoader::OnFilePathChanged(const FilePath& path) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - Reload(); -} - -void FileBasedPolicyLoader::OnError() { - LOG(ERROR) << "FilePathWatcher on " << config_file_path().value() - << " failed."; -} - -FileBasedPolicyLoader::~FileBasedPolicyLoader() { -} - -bool FileBasedPolicyLoader::IsSafeToReloadPolicy(const base::Time& now, - base::TimeDelta* delay) { - DCHECK(delay); - - // A null modification time indicates there's no data. - base::Time last_modification(delegate_->GetLastModification()); - if (last_modification.is_null()) - return true; - - // If there was a change since the last recorded modification, wait some more. - base::TimeDelta settleInterval( - base::TimeDelta::FromSeconds(settle_interval_seconds_)); - if (last_modification != last_modification_file_) { - last_modification_file_ = last_modification; - last_modification_clock_ = now; - *delay = settleInterval; - return false; - } - - // Check whether the settle interval has elapsed. - base::TimeDelta age = now - last_modification_clock_; - if (age < settleInterval) { - *delay = settleInterval - age; - return false; - } - - return true; -} - -void FileBasedPolicyLoader::ScheduleReloadTask(const base::TimeDelta& delay) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - - if (reload_task_) - reload_task_->Cancel(); - - reload_task_ = - NewRunnableMethod(this, &FileBasedPolicyLoader::ReloadFromTask); - BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, reload_task_, - delay.InMilliseconds()); -} - -void FileBasedPolicyLoader::NotifyPolicyChanged() { - DCHECK_EQ(origin_loop_, MessageLoop::current()); - if (provider_) - provider_->NotifyStoreOfPolicyChange(); -} - -void FileBasedPolicyLoader::ReloadFromTask() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - - // Drop the reference to the reload task, since the task might be the only - // referer that keeps us alive, so we should not Cancel() it. - reload_task_ = NULL; - - Reload(); -} - -// FileBasedPolicyWatcher implementation: - -FileBasedPolicyWatcher::FileBasedPolicyWatcher() { -} - -void FileBasedPolicyWatcher::Init(FileBasedPolicyLoader* loader) { - // Initialization can happen early when the file thread is not yet available. - // So post a task to ourselves on the UI thread which will run after threading - // is up and schedule watch initialization on the file thread. - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - NewRunnableMethod(this, - &FileBasedPolicyWatcher::InitWatcher, - scoped_refptr<FileBasedPolicyLoader>(loader))); -} - -FileBasedPolicyWatcher::~FileBasedPolicyWatcher() { -} - -void FileBasedPolicyWatcher::InitWatcher( - const scoped_refptr<FileBasedPolicyLoader>& loader) { - if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { - BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, - NewRunnableMethod(this, &FileBasedPolicyWatcher::InitWatcher, loader)); - return; - } - - if (!loader->config_file_path().empty() && - !watcher_.Watch(loader->config_file_path(), loader.get())) - loader->OnError(); - - // There might have been changes to the directory in the time between - // construction of the loader and initialization of the watcher. Call reload - // to detect if that is the case. - loader->Reload(); -} + FileBasedPolicyProvider::ProviderDelegate* delegate) + : AsynchronousPolicyProvider( + policy_list, + new FileBasedPolicyLoader(delegate)) {} } // namespace policy diff --git a/chrome/browser/policy/file_based_policy_provider.h b/chrome/browser/policy/file_based_policy_provider.h index 7b1807a..c94e1d4 100644 --- a/chrome/browser/policy/file_based_policy_provider.h +++ b/chrome/browser/policy/file_based_policy_provider.h @@ -6,39 +6,25 @@ #define CHROME_BROWSER_POLICY_FILE_BASED_POLICY_PROVIDER_H_ #pragma once -#include "base/basictypes.h" #include "base/file_path.h" -#include "base/lock.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" #include "base/time.h" -#include "base/weak_ptr.h" -#include "chrome/browser/file_path_watcher/file_path_watcher.h" -#include "chrome/browser/policy/configuration_policy_provider.h" - -class CancelableTask; -class DictionaryValue; -class MessageLoop; +#include "chrome/browser/policy/asynchronous_policy_provider.h" namespace policy { -class FileBasedPolicyLoader; -class FileBasedPolicyWatcher; - // File based policy provider that coordinates watching and reloading policy // information from the configuration path. Actual logic for loading policy // information is handled by a delegate passed at construction time. -class FileBasedPolicyProvider - : public ConfigurationPolicyProvider, - public base::SupportsWeakPtr<FileBasedPolicyProvider> { +class FileBasedPolicyProvider : public AsynchronousPolicyProvider { public: + // Delegate interface for actual policy loading from the system. - class Delegate { + class ProviderDelegate : public AsynchronousPolicyProvider::Delegate { public: - virtual ~Delegate(); + explicit ProviderDelegate(const FilePath& config_file_path); + virtual ~ProviderDelegate(); - // Loads the policy information. Ownership of the return value is - // transferred to the caller. + // AsynchronousPolicyProvider::Delegate implementation: virtual DictionaryValue* Load() = 0; // Gets the last modification timestamp for the policy information from the @@ -48,152 +34,21 @@ class FileBasedPolicyProvider const FilePath& config_file_path() { return config_file_path_; } - protected: - explicit Delegate(const FilePath& config_file_path); - private: - // The path at which we look for configuration files. const FilePath config_file_path_; - DISALLOW_COPY_AND_ASSIGN(Delegate); + DISALLOW_COPY_AND_ASSIGN(ProviderDelegate); }; // Assumes ownership of |delegate|. FileBasedPolicyProvider(const PolicyDefinitionList* policy_list, - Delegate* delegate); - virtual ~FileBasedPolicyProvider(); - - // ConfigurationPolicyProvider implementation. - virtual bool Provide(ConfigurationPolicyStoreInterface* store); + ProviderDelegate* delegate); + virtual ~FileBasedPolicyProvider() {} private: - // Watches for changes to the configuration directory. - scoped_refptr<FileBasedPolicyWatcher> watcher_; - - // The loader object we use internally. - scoped_refptr<FileBasedPolicyLoader> loader_; - DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyProvider); }; -// FilePathWatcher delegate implementation that handles change notifications for -// the configuration file or directory. It keeps the authorative version of the -// currently effective policy dictionary and updates it as appropriate. The -// actual loading logic is handled by a delegate. -class FileBasedPolicyLoader : public FilePathWatcher::Delegate { - public: - // Creates a new loader that'll load its data from |config_file_path|. - // Assumes ownership of |delegate|, which provides the actual loading logic. - // The parameters |settle_interval_seconds| and |reload_interval_minutes| - // specify the time to wait before reading the file contents after a change - // and the period for checking |config_file_path| for changes, respectively. - FileBasedPolicyLoader(base::WeakPtr<ConfigurationPolicyProvider> provider, - FileBasedPolicyProvider::Delegate* delegate, - int settle_interval_seconds, - int reload_interval_minutes); - - // Stops any pending reload tasks. - void Stop(); - - // Reloads the policies and sends out a notification, if appropriate. Must be - // called on the file thread. - void Reload(); - - // Gets the current dictionary value object. Ownership of the returned value - // is transferred to the caller. - DictionaryValue* GetPolicy(); - - const FilePath& config_file_path() { return delegate_->config_file_path(); } - - // FilePathWatcher::Delegate implementation: - virtual void OnFilePathChanged(const FilePath& path); - virtual void OnError(); - - private: - // FileBasedPolicyLoader objects should only be deleted by - // RefCountedThreadSafe. - friend class base::RefCountedThreadSafe<FileBasedPolicyLoader>; - virtual ~FileBasedPolicyLoader(); - - // Checks whether reading policy information is safe to do. If not, returns - // false and the delay until it is considered safe to reload in |delay|. - bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay); - - // Schedules a reload task to run when |delay| expires. Must be called on the - // file thread. - void ScheduleReloadTask(const base::TimeDelta& delay); - - // Notifies the policy provider to send out a policy changed notification. - // Must be called on |origin_loop_|. - void NotifyPolicyChanged(); - - // Invoked from the reload task on the file thread. - void ReloadFromTask(); - - // The delegate. - scoped_ptr<FileBasedPolicyProvider::Delegate> delegate_; - - // The provider this loader is associated with. Access only on the thread that - // called the constructor. See |origin_loop_| below. - base::WeakPtr<ConfigurationPolicyProvider> provider_; - - // The message loop on which this object was constructed and |provider_| - // received on. Recorded so we can call back into the non thread safe provider - // to fire the notification. - MessageLoop* origin_loop_; - - // Records last known modification timestamp of |config_file_path_|. - base::Time last_modification_file_; - - // The wall clock time at which the last modification timestamp was recorded. - // It's better to not assume the file notification time and the wall clock - // times come from the same source, just in case there is some non-local - // filesystem involved. - base::Time last_modification_clock_; - - // Protects |policy_|. - Lock lock_; - - // The current policy definition. - scoped_ptr<DictionaryValue> policy_; - - // The reload task. Access only on the file thread. Holds a reference to the - // currently posted task, so we can cancel and repost it if necessary. - CancelableTask* reload_task_; - - // Settle and reload intervals. - const int settle_interval_seconds_; - const int reload_interval_minutes_; - - DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyLoader); -}; - -// Wraps a FilePathWatcher for the configuration path and takes care of -// initializing the watcher object on the file thread. -class FileBasedPolicyWatcher - : public base::RefCountedThreadSafe<FileBasedPolicyWatcher> { - public: - FileBasedPolicyWatcher(); - - // Runs initialization. This is in a separate method since we need to post a - // task (which cannot be done from the constructor). - void Init(FileBasedPolicyLoader* loader); - - private: - // FileBasedPolicyWatcher objects should only be deleted by - // RefCountedThreadSafe. - friend class base::RefCountedThreadSafe<FileBasedPolicyWatcher>; - virtual ~FileBasedPolicyWatcher(); - - // Actually sets up the watch with the FilePathWatcher code. - void InitWatcher(const scoped_refptr<FileBasedPolicyLoader>& loader); - - // Wrapped watcher that takes care of the actual watching. - FilePathWatcher watcher_; - - DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyWatcher); -}; - } // namespace policy #endif // CHROME_BROWSER_POLICY_FILE_BASED_POLICY_PROVIDER_H_ diff --git a/chrome/browser/policy/file_based_policy_provider_unittest.cc b/chrome/browser/policy/file_based_policy_provider_unittest.cc index c78c586..1bb2185 100644 --- a/chrome/browser/policy/file_based_policy_provider_unittest.cc +++ b/chrome/browser/policy/file_based_policy_provider_unittest.cc @@ -2,130 +2,80 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "chrome/browser/policy/asynchronous_policy_loader.h" +#include "chrome/browser/policy/asynchronous_policy_test_base.h" #include "chrome/browser/policy/configuration_policy_pref_store.h" +#include "chrome/browser/policy/configuration_policy_store_interface.h" #include "chrome/browser/policy/file_based_policy_provider.h" +#include "chrome/common/policy_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -using testing::Mock; +using testing::_; +using testing::InSequence; +using testing::Return; namespace policy { -// Shorter reload intervals for testing FileBasedPolicyLoader. -const int kSettleIntervalSecondsForTesting = 0; -const int kReloadIntervalMinutesForTesting = 1; - -// A delegate for testing that can feed arbitrary information to the loader. -class TestDelegate : public FileBasedPolicyProvider::Delegate { - public: - TestDelegate() - : FileBasedPolicyProvider::Delegate(FilePath(FILE_PATH_LITERAL("fake"))) { - } - - // FileBasedPolicyProvider::Delegate implementation: - virtual DictionaryValue* Load() { - return static_cast<DictionaryValue*>(dict_.DeepCopy()); - } - - virtual base::Time GetLastModification() { - return last_modification_; - } - - DictionaryValue* dict() { return &dict_; } - void set_last_modification(const base::Time& last_modification) { - last_modification_ = last_modification; - } - - private: - DictionaryValue dict_; - base::Time last_modification_; -}; - -// A mock provider that allows us to capture reload notifications. -class MockPolicyProvider : public ConfigurationPolicyProvider, - public base::SupportsWeakPtr<MockPolicyProvider> { +class FileBasedPolicyProviderDelegateMock + : public FileBasedPolicyProvider::ProviderDelegate { public: - explicit MockPolicyProvider() - : ConfigurationPolicyProvider( - ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList()) { - } - - virtual bool Provide(ConfigurationPolicyStoreInterface* store) { - return true; - } - - MOCK_METHOD0(NotifyStoreOfPolicyChange, void()); + FileBasedPolicyProviderDelegateMock() + : FileBasedPolicyProvider::ProviderDelegate(FilePath()) {} + MOCK_METHOD0(Load, DictionaryValue*()); + MOCK_METHOD0(GetLastModification, base::Time()); }; -class FileBasedPolicyLoaderTest : public testing::Test { - protected: - FileBasedPolicyLoaderTest() - : ui_thread_(BrowserThread::UI, &loop_), - file_thread_(BrowserThread::FILE, &loop_) {} - - virtual void TearDown() { - loop_.RunAllPending(); - } - - MessageLoop loop_; - - private: - BrowserThread ui_thread_; - BrowserThread file_thread_; -}; - -TEST_F(FileBasedPolicyLoaderTest, BasicLoad) { - TestDelegate* test_delegate = new TestDelegate; - test_delegate->dict()->SetString("HomepageLocation", "http://www.google.com"); - - scoped_refptr<FileBasedPolicyLoader> loader( - new FileBasedPolicyLoader(base::WeakPtr<FileBasedPolicyProvider>(), - test_delegate, - kSettleIntervalSecondsForTesting, - kReloadIntervalMinutesForTesting)); - scoped_ptr<DictionaryValue> policy(loader->GetPolicy()); - EXPECT_TRUE(policy.get()); - EXPECT_EQ(1U, policy->size()); - - std::string str_value; - EXPECT_TRUE(policy->GetString("HomepageLocation", &str_value)); - EXPECT_EQ("http://www.google.com", str_value); - - loader->Stop(); +TEST_F(AsynchronousPolicyTestBase, ProviderInit) { + base::Time last_modified; + FileBasedPolicyProviderDelegateMock* provider_delegate = + new FileBasedPolicyProviderDelegateMock(); + EXPECT_CALL(*provider_delegate, GetLastModification()).WillRepeatedly( + Return(last_modified)); + InSequence s; + EXPECT_CALL(*provider_delegate, Load()).WillOnce(Return( + new DictionaryValue)); + DictionaryValue* policies = new DictionaryValue(); + policies->SetBoolean(policy::key::kSyncDisabled, true); + // A second call to Load gets triggered during the provider's construction + // when the file watcher is initialized, since this file may have changed + // between the initial load and creating watcher. + EXPECT_CALL(*provider_delegate, Load()).WillOnce(Return(policies)); + FileBasedPolicyProvider provider( + ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), + provider_delegate); + loop_.RunAllPending(); + EXPECT_CALL(*store_, Apply(policy::kPolicySyncDisabled, _)).Times(1); + provider.Provide(store_.get()); } -TEST_F(FileBasedPolicyLoaderTest, TestRefresh) { - MockPolicyProvider provider; - TestDelegate* test_delegate = new TestDelegate; - - scoped_refptr<FileBasedPolicyLoader> loader( - new FileBasedPolicyLoader(provider.AsWeakPtr(), - test_delegate, - kSettleIntervalSecondsForTesting, - kReloadIntervalMinutesForTesting)); - scoped_ptr<DictionaryValue> policy(loader->GetPolicy()); - EXPECT_TRUE(policy.get()); - EXPECT_EQ(0U, policy->size()); - - test_delegate->dict()->SetString("HomepageLocation", "http://www.google.com"); - - EXPECT_CALL(provider, NotifyStoreOfPolicyChange()).Times(1); - loader->OnFilePathChanged(FilePath(FILE_PATH_LITERAL("fake"))); - - // Run the loop. The refresh should be handled immediately since the settle - // interval has been disabled. +TEST_F(AsynchronousPolicyTestBase, ProviderRefresh) { + base::Time last_modified; + FileBasedPolicyProviderDelegateMock* provider_delegate = + new FileBasedPolicyProviderDelegateMock(); + EXPECT_CALL(*provider_delegate, GetLastModification()).WillRepeatedly( + Return(last_modified)); + InSequence s; + EXPECT_CALL(*provider_delegate, Load()).WillOnce(Return( + new DictionaryValue)); + FileBasedPolicyProvider file_based_provider( + ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), + provider_delegate); + // A second call to Load gets triggered during the provider's construction + // when the file watcher is initialized, since this file may have changed + // between the initial load and creating watcher. + EXPECT_CALL(*provider_delegate, Load()).WillOnce(Return( + new DictionaryValue)); loop_.RunAllPending(); - Mock::VerifyAndClearExpectations(&provider); - - policy.reset(loader->GetPolicy()); - EXPECT_TRUE(policy.get()); - EXPECT_EQ(1U, policy->size()); - - std::string str_value; - EXPECT_TRUE(policy->GetString("HomepageLocation", &str_value)); - EXPECT_EQ("http://www.google.com", str_value); - - loader->Stop(); + // A third and final call to Load is made by the explicit Reload. This + // should be the one that provides the current policy. + DictionaryValue* policies = new DictionaryValue(); + policies->SetBoolean(policy::key::kSyncDisabled, true); + EXPECT_CALL(*provider_delegate, Load()).WillOnce(Return(policies)); + file_based_provider.loader()->Reload(); + loop_.RunAllPending(); + EXPECT_CALL(*store_, Apply(policy::kPolicySyncDisabled, _)).Times(1); + file_based_provider.Provide(store_.get()); } } // namespace policy diff --git a/chrome/browser/policy/mock_configuration_policy_provider.cc b/chrome/browser/policy/mock_configuration_policy_provider.cc index 4f11497..9c2db8d 100644 --- a/chrome/browser/policy/mock_configuration_policy_provider.cc +++ b/chrome/browser/policy/mock_configuration_policy_provider.cc @@ -4,6 +4,7 @@ #include "chrome/browser/policy/mock_configuration_policy_provider.h" +#include "base/stl_util-inl.h" #include "chrome/browser/policy/configuration_policy_pref_store.h" namespace policy { diff --git a/chrome/browser/policy/mock_configuration_policy_provider.h b/chrome/browser/policy/mock_configuration_policy_provider.h index 8ba8a88..731256e 100644 --- a/chrome/browser/policy/mock_configuration_policy_provider.h +++ b/chrome/browser/policy/mock_configuration_policy_provider.h @@ -9,8 +9,8 @@ #include <map> #include <utility> -#include "base/stl_util-inl.h" #include "chrome/browser/policy/configuration_policy_provider.h" +#include "testing/gmock/include/gmock/gmock.h" namespace policy { @@ -26,6 +26,8 @@ class MockConfigurationPolicyProvider : public ConfigurationPolicyProvider { // ConfigurationPolicyProvider method overrides. virtual bool Provide(ConfigurationPolicyStoreInterface* store); + MOCK_METHOD0(NotifyStoreOfPolicyChange, void()); + private: typedef std::map<ConfigurationPolicyType, Value*> PolicyMap; |