From d769050f460f7926381fb37444d588f24349d946 Mon Sep 17 00:00:00 2001 From: "joaodasilva@chromium.org" Date: Thu, 12 Dec 2013 17:48:01 +0000 Subject: Move UserCloudPolicyManager and UserCloudPolicyStore into the policy component. This will enable using those classes on the iOS build. BUG=271392 TBR=jochen@chromium.org Review URL: https://codereview.chromium.org/105573005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@240359 0039d316-1c4b-4281-b951-d872f2087c98 --- components/components_tests.gyp | 11 + components/policy.gypi | 20 ++ components/policy/core/common/cloud/DEPS | 7 + .../common/cloud/mock_user_cloud_policy_store.cc | 15 + .../common/cloud/mock_user_cloud_policy_store.h | 38 +++ .../core/common/cloud/user_cloud_policy_manager.cc | 101 ++++++ .../core/common/cloud/user_cloud_policy_manager.h | 97 ++++++ .../cloud/user_cloud_policy_manager_unittest.cc | 96 ++++++ .../core/common/cloud/user_cloud_policy_store.cc | 254 ++++++++++++++ .../core/common/cloud/user_cloud_policy_store.h | 93 ++++++ .../common/cloud/user_cloud_policy_store_base.cc | 51 +++ .../common/cloud/user_cloud_policy_store_base.h | 57 ++++ .../cloud/user_cloud_policy_store_unittest.cc | 367 +++++++++++++++++++++ 13 files changed, 1207 insertions(+) create mode 100644 components/policy/core/common/cloud/DEPS create mode 100644 components/policy/core/common/cloud/mock_user_cloud_policy_store.cc create mode 100644 components/policy/core/common/cloud/mock_user_cloud_policy_store.h create mode 100644 components/policy/core/common/cloud/user_cloud_policy_manager.cc create mode 100644 components/policy/core/common/cloud/user_cloud_policy_manager.h create mode 100644 components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc create mode 100644 components/policy/core/common/cloud/user_cloud_policy_store.cc create mode 100644 components/policy/core/common/cloud/user_cloud_policy_store.h create mode 100644 components/policy/core/common/cloud/user_cloud_policy_store_base.cc create mode 100644 components/policy/core/common/cloud/user_cloud_policy_store_base.h create mode 100644 components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc (limited to 'components') diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 49e32a3..2c1eea3 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -189,6 +189,9 @@ }], ['configuration_policy==1', { 'dependencies': [ + # TODO(joaodasilva): remove this dependency. This is needed to + # get the include path for policy_constants.h. + '../chrome/app/policy/cloud_policy_codegen.gyp:policy_test_support', 'components.gyp:policy_component_test_support', ], 'sources': [ @@ -208,6 +211,8 @@ 'policy/core/common/cloud/external_policy_data_updater_unittest.cc', 'policy/core/common/cloud/rate_limiter_unittest.cc', 'policy/core/common/cloud/resource_cache_unittest.cc', + 'policy/core/common/cloud/user_cloud_policy_manager_unittest.cc', + 'policy/core/common/cloud/user_cloud_policy_store_unittest.cc', 'policy/core/common/cloud/user_info_fetcher_unittest.cc', 'policy/core/common/config_dir_policy_loader_unittest.cc', 'policy/core/common/forwarding_policy_provider_unittest.cc', @@ -236,6 +241,12 @@ 'policy/core/common/config_dir_policy_loader_unittest.cc', ], }], + ['chromeos==1', { + 'sources!': [ + 'policy/core/common/cloud/user_cloud_policy_manager_unittest.cc', + 'policy/core/common/cloud/user_cloud_policy_store_unittest.cc', + ], + }], ], }], ], diff --git a/components/policy.gypi b/components/policy.gypi index af9db89..a69a1f1 100644 --- a/components/policy.gypi +++ b/components/policy.gypi @@ -83,6 +83,12 @@ 'policy/core/common/cloud/resource_cache.h', 'policy/core/common/cloud/system_policy_request_context.cc', 'policy/core/common/cloud/system_policy_request_context.h', + 'policy/core/common/cloud/user_cloud_policy_manager.cc', + 'policy/core/common/cloud/user_cloud_policy_manager.h', + 'policy/core/common/cloud/user_cloud_policy_store.cc', + 'policy/core/common/cloud/user_cloud_policy_store.h', + 'policy/core/common/cloud/user_cloud_policy_store_base.cc', + 'policy/core/common/cloud/user_cloud_policy_store_base.h', 'policy/core/common/cloud/user_info_fetcher.cc', 'policy/core/common/cloud/user_info_fetcher.h', 'policy/core/common/cloud/user_policy_request_context.cc', @@ -170,6 +176,10 @@ 'sources!': [ 'policy/core/common/cloud/cloud_policy_client_registration_helper.cc', 'policy/core/common/cloud/cloud_policy_client_registration_helper.h', + 'policy/core/common/cloud/user_cloud_policy_manager.cc', + 'policy/core/common/cloud/user_cloud_policy_manager.h', + 'policy/core/common/cloud/user_cloud_policy_store.cc', + 'policy/core/common/cloud/user_cloud_policy_store.h', ], }], ], @@ -251,6 +261,8 @@ 'policy/core/common/cloud/mock_cloud_policy_store.h', 'policy/core/common/cloud/mock_device_management_service.cc', 'policy/core/common/cloud/mock_device_management_service.h', + 'policy/core/common/cloud/mock_user_cloud_policy_store.cc', + 'policy/core/common/cloud/mock_user_cloud_policy_store.h', 'policy/core/common/cloud/policy_builder.cc', 'policy/core/common/cloud/policy_builder.h', 'policy/core/common/configuration_policy_provider_test.cc', @@ -264,6 +276,14 @@ 'policy/core/common/preferences_mock_mac.cc', 'policy/core/common/preferences_mock_mac.h', ], + 'conditions': [ + ['chromeos==1', { + 'sources!': [ + 'policy/core/common/cloud/mock_user_cloud_policy_store.cc', + 'policy/core/common/cloud/mock_user_cloud_policy_store.h', + ], + }], + ], }, ], }], diff --git a/components/policy/core/common/cloud/DEPS b/components/policy/core/common/cloud/DEPS new file mode 100644 index 0000000..458b8b5 --- /dev/null +++ b/components/policy/core/common/cloud/DEPS @@ -0,0 +1,7 @@ +# TODO(joaodasilva): remove this DEPS exception after moving chrome/app/policy +# into the component. +specific_include_rules = { + 'user_cloud_policy_store_unittest\.cc': [ + "+policy/policy_constants.h", + ], +} diff --git a/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc b/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc new file mode 100644 index 0000000..74166ca --- /dev/null +++ b/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc @@ -0,0 +1,15 @@ +// 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/cloud/mock_user_cloud_policy_store.h" + +namespace policy { + +MockUserCloudPolicyStore::MockUserCloudPolicyStore() + : UserCloudPolicyStore(base::FilePath(), + scoped_refptr()) {} + +MockUserCloudPolicyStore::~MockUserCloudPolicyStore() {} + +} // namespace policy diff --git a/components/policy/core/common/cloud/mock_user_cloud_policy_store.h b/components/policy/core/common/cloud/mock_user_cloud_policy_store.h new file mode 100644 index 0000000..8fae220 --- /dev/null +++ b/components/policy/core/common/cloud/mock_user_cloud_policy_store.h @@ -0,0 +1,38 @@ +// 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_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_ +#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_ + +#include "components/policy/core/common/cloud/user_cloud_policy_store.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace policy { + +class MockUserCloudPolicyStore : public UserCloudPolicyStore { + public: + MockUserCloudPolicyStore(); + virtual ~MockUserCloudPolicyStore(); + + MOCK_METHOD1(Store, void(const enterprise_management::PolicyFetchResponse&)); + MOCK_METHOD0(Load, void(void)); + MOCK_METHOD0(LoadImmediately, void(void)); + MOCK_METHOD0(Clear, void(void)); + + // Publish the protected members. + using CloudPolicyStore::NotifyStoreLoaded; + using CloudPolicyStore::NotifyStoreError; + + using CloudPolicyStore::policy_map_; + using CloudPolicyStore::policy_; + using CloudPolicyStore::status_; + using UserCloudPolicyStore::signin_username_; + + private: + DISALLOW_COPY_AND_ASSIGN(MockUserCloudPolicyStore); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_ diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.cc b/components/policy/core/common/cloud/user_cloud_policy_manager.cc new file mode 100644 index 0000000..6e47339 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_manager.cc @@ -0,0 +1,101 @@ +// 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/cloud/user_cloud_policy_manager.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/sequenced_task_runner.h" +#include "components/policy/core/common/cloud/cloud_external_data_manager.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "components/policy/core/common/cloud/cloud_policy_service.h" +#include "components/policy/core/common/cloud/user_cloud_policy_store.h" +#include "components/policy/core/common/policy_pref_names.h" +#include "components/policy/core/common/policy_types.h" +#include "net/url_request/url_request_context_getter.h" + +namespace em = enterprise_management; + +namespace policy { + +UserCloudPolicyManager::UserCloudPolicyManager( + scoped_ptr store, + const base::FilePath& component_policy_cache_path, + scoped_ptr external_data_manager, + const scoped_refptr& task_runner, + const scoped_refptr& file_task_runner, + const scoped_refptr& io_task_runner) + : CloudPolicyManager( + PolicyNamespaceKey(GetChromeUserPolicyType(), std::string()), + store.get(), + task_runner, + file_task_runner, + io_task_runner), + store_(store.Pass()), + component_policy_cache_path_(component_policy_cache_path), + external_data_manager_(external_data_manager.Pass()) {} + +UserCloudPolicyManager::~UserCloudPolicyManager() {} + +void UserCloudPolicyManager::Shutdown() { + if (external_data_manager_) + external_data_manager_->Disconnect(); + CloudPolicyManager::Shutdown(); +} + +void UserCloudPolicyManager::SetSigninUsername(const std::string& username) { + store_->SetSigninUsername(username); +} + +void UserCloudPolicyManager::Connect( + PrefService* local_state, + scoped_refptr request_context, + scoped_ptr client) { + core()->Connect(client.Pass()); + core()->StartRefreshScheduler(); + core()->TrackRefreshDelayPref(local_state, + policy_prefs::kUserPolicyRefreshRate); + if (external_data_manager_) + external_data_manager_->Connect(request_context); + + CreateComponentCloudPolicyService(component_policy_cache_path_, + request_context); +} + +// static +scoped_ptr +UserCloudPolicyManager::CreateCloudPolicyClient( + DeviceManagementService* device_management_service, + scoped_refptr request_context) { + return make_scoped_ptr( + new CloudPolicyClient( + std::string(), + std::string(), + USER_AFFILIATION_NONE, + NULL, + device_management_service, + request_context)).Pass(); +} + +void UserCloudPolicyManager::DisconnectAndRemovePolicy() { + if (external_data_manager_) + external_data_manager_->Disconnect(); + core()->Disconnect(); + + // store_->Clear() will publish the updated, empty policy. The component + // policy service must be cleared before OnStoreLoaded() is issued, so that + // component policies are also empty at CheckAndPublishPolicy(). + ClearAndDestroyComponentCloudPolicyService(); + + // When the |store_| is cleared, it informs the |external_data_manager_| that + // all external data references have been removed, causing the + // |external_data_manager_| to clear its cache as well. + store_->Clear(); +} + +bool UserCloudPolicyManager::IsClientRegistered() const { + return client() && client()->is_registered(); +} + +} // namespace policy diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.h b/components/policy/core/common/cloud/user_cloud_policy_manager.h new file mode 100644 index 0000000..5c4068e --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_manager.h @@ -0,0 +1,97 @@ +// 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_CLOUD_USER_CLOUD_POLICY_MANAGER_H_ +#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_MANAGER_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "components/policy/core/common/cloud/cloud_policy_manager.h" +#include "components/policy/policy_export.h" + +class PrefService; + +namespace base { +class SequencedTaskRunner; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace policy { + +class CloudExternalDataManager; +class DeviceManagementService; +class UserCloudPolicyStore; + +// UserCloudPolicyManager handles initialization of user policy. +class POLICY_EXPORT UserCloudPolicyManager : public CloudPolicyManager { + public: + // |task_runner| is the runner for policy refresh tasks. + // |file_task_runner| is used for file operations. Currently this must be + // the FILE BrowserThread. + // |io_task_runner| is used for network IO. Currently this must be the IO + // BrowserThread. + UserCloudPolicyManager( + scoped_ptr store, + const base::FilePath& component_policy_cache_path, + scoped_ptr external_data_manager, + const scoped_refptr& task_runner, + const scoped_refptr& file_task_runner, + const scoped_refptr& io_task_runner); + virtual ~UserCloudPolicyManager(); + + // ConfigurationPolicyProvider overrides: + virtual void Shutdown() OVERRIDE; + + void SetSigninUsername(const std::string& username); + + // Initializes the cloud connection. |local_state| must stay valid until this + // object is deleted or DisconnectAndRemovePolicy() gets called. Virtual for + // mocking. + virtual void Connect( + PrefService* local_state, + scoped_refptr request_context, + scoped_ptr client); + + // Shuts down the UserCloudPolicyManager (removes and stops refreshing the + // cached cloud policy). This is typically called when a profile is being + // disassociated from a given user (e.g. during signout). No policy will be + // provided by this object until the next time Initialize() is invoked. + void DisconnectAndRemovePolicy(); + + // Returns true if the underlying CloudPolicyClient is already registered. + // Virtual for mocking. + virtual bool IsClientRegistered() const; + + // Creates a CloudPolicyClient for this client. Used in situations where + // callers want to create a DMToken without actually initializing the + // profile's policy infrastructure. + static scoped_ptr CreateCloudPolicyClient( + DeviceManagementService* device_management_service, + scoped_refptr request_context); + + private: + // Typed pointer to the store owned by UserCloudPolicyManager. Note that + // CloudPolicyManager only keeps a plain CloudPolicyStore pointer. + scoped_ptr store_; + + // Path where policy for components will be cached. + base::FilePath component_policy_cache_path_; + + // Manages external data referenced by policies. + scoped_ptr external_data_manager_; + + DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyManager); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_MANAGER_H_ diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc b/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc new file mode 100644 index 0000000..0602b28 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc @@ -0,0 +1,96 @@ +// 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/cloud/user_cloud_policy_manager.h" + +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "components/policy/core/common/cloud/cloud_external_data_manager.h" +#include "components/policy/core/common/cloud/mock_user_cloud_policy_store.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/schema_registry.h" +#include "net/url_request/url_request_context_getter.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace em = enterprise_management; + +using testing::AnyNumber; +using testing::AtLeast; +using testing::Mock; +using testing::_; + +namespace policy { +namespace { + +class UserCloudPolicyManagerTest : public testing::Test { + protected: + UserCloudPolicyManagerTest() : store_(NULL) {} + + virtual void SetUp() OVERRIDE { + // Set up a policy map for testing. + policy_map_.Set("key", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, + base::Value::CreateStringValue("value"), NULL); + expected_bundle_.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) + .CopyFrom(policy_map_); + } + + virtual void TearDown() OVERRIDE { + if (manager_) { + manager_->RemoveObserver(&observer_); + manager_->Shutdown(); + } + } + + void CreateManager() { + store_ = new MockUserCloudPolicyStore(); + EXPECT_CALL(*store_, Load()); + manager_.reset(new UserCloudPolicyManager( + scoped_ptr(store_), + base::FilePath(), + scoped_ptr(), + loop_.message_loop_proxy(), + loop_.message_loop_proxy(), + loop_.message_loop_proxy())); + manager_->Init(&schema_registry_); + manager_->AddObserver(&observer_); + Mock::VerifyAndClearExpectations(store_); + } + + // Required by the refresh scheduler that's created by the manager. + base::MessageLoop loop_; + + // Convenience policy objects. + PolicyMap policy_map_; + PolicyBundle expected_bundle_; + + // Policy infrastructure. + SchemaRegistry schema_registry_; + MockConfigurationPolicyObserver observer_; + MockUserCloudPolicyStore* store_; // Not owned. + scoped_ptr manager_; + + private: + DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyManagerTest); +}; + +TEST_F(UserCloudPolicyManagerTest, DisconnectAndRemovePolicy) { + // Load policy, make sure it goes away when DisconnectAndRemovePolicy() is + // called. + CreateManager(); + store_->policy_map_.CopyFrom(policy_map_); + EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get())); + store_->NotifyStoreLoaded(); + EXPECT_TRUE(expected_bundle_.Equals(manager_->policies())); + EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME)); + EXPECT_CALL(*store_, Clear()); + manager_->DisconnectAndRemovePolicy(); + EXPECT_FALSE(manager_->core()->service()); +} + +} // namespace +} // namespace policy diff --git a/components/policy/core/common/cloud/user_cloud_policy_store.cc b/components/policy/core/common/cloud/user_cloud_policy_store.cc new file mode 100644 index 0000000..c800328 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_store.cc @@ -0,0 +1,254 @@ +// 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/cloud/user_cloud_policy_store.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/location.h" +#include "base/task_runner_util.h" +#include "policy/proto/cloud_policy.pb.h" +#include "policy/proto/device_management_backend.pb.h" + +namespace em = enterprise_management; + +namespace policy { + +enum PolicyLoadStatus { + // Policy blob was successfully loaded and parsed. + LOAD_RESULT_SUCCESS, + + // No previously stored policy was found. + LOAD_RESULT_NO_POLICY_FILE, + + // Could not load the previously stored policy due to either a parse or + // file read error. + LOAD_RESULT_LOAD_ERROR, +}; + +// Struct containing the result of a policy load - if |status| == +// LOAD_RESULT_SUCCESS, |policy| is initialized from the policy file on disk. +struct PolicyLoadResult { + PolicyLoadStatus status; + em::PolicyFetchResponse policy; +}; + +namespace { + +// Subdirectory in the user's profile for storing user policies. +const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy"); +// File in the above directory for storing user policy data. +const base::FilePath::CharType kPolicyCacheFile[] = + FILE_PATH_LITERAL("User Policy"); + +// Loads policy from the backing file. Returns a PolicyLoadResult with the +// results of the fetch. +policy::PolicyLoadResult LoadPolicyFromDisk(const base::FilePath& path) { + policy::PolicyLoadResult result; + // If the backing file does not exist, just return. + if (!base::PathExists(path)) { + result.status = policy::LOAD_RESULT_NO_POLICY_FILE; + return result; + } + std::string data; + if (!base::ReadFileToString(path, &data) || + !result.policy.ParseFromArray(data.c_str(), data.size())) { + LOG(WARNING) << "Failed to read or parse policy data from " << path.value(); + result.status = policy::LOAD_RESULT_LOAD_ERROR; + return result; + } + + result.status = policy::LOAD_RESULT_SUCCESS; + return result; +} + +// Stores policy to the backing file (must be called via a task on +// the background thread). +void StorePolicyToDiskOnBackgroundThread( + const base::FilePath& path, + const em::PolicyFetchResponse& policy) { + DVLOG(1) << "Storing policy to " << path.value(); + std::string data; + if (!policy.SerializeToString(&data)) { + DLOG(WARNING) << "Failed to serialize policy data"; + return; + } + + if (!base::CreateDirectory(path.DirName())) { + DLOG(WARNING) << "Failed to create directory " << path.DirName().value(); + return; + } + + int size = data.size(); + if (file_util::WriteFile(path, data.c_str(), size) != size) { + DLOG(WARNING) << "Failed to write " << path.value(); + } +} + +} // namespace + +UserCloudPolicyStore::UserCloudPolicyStore( + const base::FilePath& path, + scoped_refptr background_task_runner) + : UserCloudPolicyStoreBase(background_task_runner), + weak_factory_(this), + backing_file_path_(path) {} + +UserCloudPolicyStore::~UserCloudPolicyStore() {} + +// static +scoped_ptr UserCloudPolicyStore::Create( + const base::FilePath& profile_path, + scoped_refptr background_task_runner) { + base::FilePath path = + profile_path.Append(kPolicyDir).Append(kPolicyCacheFile); + return make_scoped_ptr( + new UserCloudPolicyStore(path, background_task_runner)); +} + +void UserCloudPolicyStore::SetSigninUsername(const std::string& username) { + signin_username_ = username; +} + +void UserCloudPolicyStore::LoadImmediately() { + DVLOG(1) << "Initiating immediate policy load from disk"; + // Cancel any pending Load/Store/Validate operations. + weak_factory_.InvalidateWeakPtrs(); + // Load the policy from disk... + PolicyLoadResult result = LoadPolicyFromDisk(backing_file_path_); + // ...and install it, reporting success/failure to any observers. + PolicyLoaded(false, result); +} + +void UserCloudPolicyStore::Clear() { + background_task_runner()->PostTask( + FROM_HERE, + base::Bind( + base::IgnoreResult(&base::DeleteFile), backing_file_path_, false)); + policy_.reset(); + policy_map_.Clear(); + NotifyStoreLoaded(); +} + +void UserCloudPolicyStore::Load() { + DVLOG(1) << "Initiating policy load from disk"; + // Cancel any pending Load/Store/Validate operations. + weak_factory_.InvalidateWeakPtrs(); + + // Start a new Load operation and have us get called back when it is + // complete. + base::PostTaskAndReplyWithResult( + background_task_runner(), + FROM_HERE, + base::Bind(&LoadPolicyFromDisk, backing_file_path_), + base::Bind(&UserCloudPolicyStore::PolicyLoaded, + weak_factory_.GetWeakPtr(), true)); +} + +void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background, + PolicyLoadResult result) { + switch (result.status) { + case LOAD_RESULT_LOAD_ERROR: + status_ = STATUS_LOAD_ERROR; + NotifyStoreError(); + break; + + case LOAD_RESULT_NO_POLICY_FILE: + DVLOG(1) << "No policy found on disk"; + NotifyStoreLoaded(); + break; + + case LOAD_RESULT_SUCCESS: { + // Found policy on disk - need to validate it before it can be used. + scoped_ptr cloud_policy( + new em::PolicyFetchResponse(result.policy)); + Validate(cloud_policy.Pass(), + validate_in_background, + base::Bind( + &UserCloudPolicyStore::InstallLoadedPolicyAfterValidation, + weak_factory_.GetWeakPtr())); + break; + } + default: + NOTREACHED(); + } +} + +void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation( + UserCloudPolicyValidator* validator) { + validation_status_ = validator->status(); + if (!validator->success()) { + DVLOG(1) << "Validation failed: status=" << validation_status_; + status_ = STATUS_VALIDATION_ERROR; + NotifyStoreError(); + return; + } + + DVLOG(1) << "Validation succeeded - installing policy with dm_token: " << + validator->policy_data()->request_token(); + DVLOG(1) << "Device ID: " << validator->policy_data()->device_id(); + + InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); + status_ = STATUS_OK; + NotifyStoreLoaded(); +} + +void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) { + // Stop any pending requests to store policy, then validate the new policy + // before storing it. + weak_factory_.InvalidateWeakPtrs(); + scoped_ptr policy_copy( + new em::PolicyFetchResponse(policy)); + Validate(policy_copy.Pass(), + true, + base::Bind(&UserCloudPolicyStore::StorePolicyAfterValidation, + weak_factory_.GetWeakPtr())); +} + +void UserCloudPolicyStore::Validate( + scoped_ptr policy, + bool validate_in_background, + const UserCloudPolicyValidator::CompletionCallback& callback) { + // Configure the validator. + scoped_ptr validator = CreateValidator( + policy.Pass(), + CloudPolicyValidatorBase::TIMESTAMP_NOT_BEFORE); + + // Validate the username if the user is signed in. + if (!signin_username_.empty()) + validator->ValidateUsername(signin_username_); + + if (validate_in_background) { + // Start validation in the background. The Validator will free itself once + // validation is complete. + validator.release()->StartValidation(callback); + } else { + // Run validation immediately and invoke the callback with the results. + validator->RunValidation(); + callback.Run(validator.get()); + } +} + +void UserCloudPolicyStore::StorePolicyAfterValidation( + UserCloudPolicyValidator* validator) { + validation_status_ = validator->status(); + DVLOG(1) << "Policy validation complete: status = " << validation_status_; + if (!validator->success()) { + status_ = STATUS_VALIDATION_ERROR; + NotifyStoreError(); + return; + } + + // Persist the validated policy (just fire a task - don't bother getting a + // reply because we can't do anything if it fails). + background_task_runner()->PostTask( + FROM_HERE, + base::Bind(&StorePolicyToDiskOnBackgroundThread, + backing_file_path_, *validator->policy())); + InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); + status_ = STATUS_OK; + NotifyStoreLoaded(); +} + +} // namespace policy diff --git a/components/policy/core/common/cloud/user_cloud_policy_store.h b/components/policy/core/common/cloud/user_cloud_policy_store.h new file mode 100644 index 0000000..4dc7e24 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_store.h @@ -0,0 +1,93 @@ +// 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_CLOUD_USER_CLOUD_POLICY_STORE_H_ +#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "components/policy/core/common/cloud/user_cloud_policy_store_base.h" +#include "components/policy/policy_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace policy { + +// Implements a cloud policy store that is stored in a simple file in the user's +// profile directory. This is used on (non-chromeos) platforms that do not have +// a secure storage implementation. +class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { + public: + // Creates a policy store associated with a signed-in (or in the progress of + // it) user. + UserCloudPolicyStore( + const base::FilePath& policy_file, + scoped_refptr background_task_runner); + virtual ~UserCloudPolicyStore(); + + // Factory method for creating a UserCloudPolicyStore for a profile with path + // |profile_path|. + static scoped_ptr Create( + const base::FilePath& profile_path, + scoped_refptr background_task_runner); + + // Sets the username from signin for validation of the policy. + void SetSigninUsername(const std::string& username); + + // Loads policy immediately on the current thread. Virtual for mocks. + virtual void LoadImmediately(); + + // Deletes any existing policy blob and notifies observers via OnStoreLoaded() + // that the blob has changed. Virtual for mocks. + virtual void Clear(); + + // CloudPolicyStore implementation. + virtual void Load() OVERRIDE; + virtual void Store( + const enterprise_management::PolicyFetchResponse& policy) OVERRIDE; + + protected: + std::string signin_username_; + + private: + // Callback invoked when a new policy has been loaded from disk. If + // |validate_in_background| is true, then policy is validated via a background + // thread. + void PolicyLoaded(bool validate_in_background, + struct PolicyLoadResult policy_load_result); + + // Starts policy blob validation. |callback| is invoked once validation is + // complete. If |validate_in_background| is true, then the validation work + // occurs on a background thread (results are sent back to the calling + // thread). + void Validate( + scoped_ptr policy, + bool validate_in_background, + const UserCloudPolicyValidator::CompletionCallback& callback); + + // Callback invoked to install a just-loaded policy after validation has + // finished. + void InstallLoadedPolicyAfterValidation(UserCloudPolicyValidator* validator); + + // Callback invoked to store the policy after validation has finished. + void StorePolicyAfterValidation(UserCloudPolicyValidator* validator); + + // WeakPtrFactory used to create callbacks for validating and storing policy. + base::WeakPtrFactory weak_factory_; + + // Path to file where we store persisted policy. + base::FilePath backing_file_path_; + + DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStore); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_H_ diff --git a/components/policy/core/common/cloud/user_cloud_policy_store_base.cc b/components/policy/core/common/cloud/user_cloud_policy_store_base.cc new file mode 100644 index 0000000..de5adee --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_store_base.cc @@ -0,0 +1,51 @@ +// 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/cloud/user_cloud_policy_store_base.h" + +#include "components/policy/core/common/cloud/cloud_external_data_manager.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "components/policy/core/common/policy_map.h" +#include "policy/proto/cloud_policy.pb.h" + +namespace policy { + +// Decodes a CloudPolicySettings object into a policy map. The implementation is +// generated code in policy/cloud_policy_generated.cc. +void DecodePolicy(const enterprise_management::CloudPolicySettings& policy, + base::WeakPtr external_data_manager, + PolicyMap* policies); + +UserCloudPolicyStoreBase::UserCloudPolicyStoreBase( + scoped_refptr background_task_runner) + : background_task_runner_(background_task_runner) {} + +UserCloudPolicyStoreBase::~UserCloudPolicyStoreBase() { +} + +scoped_ptr UserCloudPolicyStoreBase::CreateValidator( + scoped_ptr policy, + CloudPolicyValidatorBase::ValidateTimestampOption timestamp_option) { + // Configure the validator. + UserCloudPolicyValidator* validator = + UserCloudPolicyValidator::Create(policy.Pass(), background_task_runner_); + validator->ValidatePolicyType(GetChromeUserPolicyType()); + validator->ValidateAgainstCurrentPolicy( + policy_.get(), + timestamp_option, + CloudPolicyValidatorBase::DM_TOKEN_REQUIRED); + validator->ValidatePayload(); + return scoped_ptr(validator); +} + +void UserCloudPolicyStoreBase::InstallPolicy( + scoped_ptr policy_data, + scoped_ptr payload) { + // Decode the payload. + policy_map_.Clear(); + DecodePolicy(*payload, external_data_manager(), &policy_map_); + policy_ = policy_data.Pass(); +} + +} // namespace policy diff --git a/components/policy/core/common/cloud/user_cloud_policy_store_base.h b/components/policy/core/common/cloud/user_cloud_policy_store_base.h new file mode 100644 index 0000000..1dd9743 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_store_base.h @@ -0,0 +1,57 @@ +// 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_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_ +#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "components/policy/core/common/cloud/cloud_policy_store.h" +#include "components/policy/core/common/cloud/cloud_policy_validator.h" +#include "components/policy/policy_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace policy { + +// Base class that implements common cross-platform UserCloudPolicyStore +// functionality. +class POLICY_EXPORT UserCloudPolicyStoreBase : public CloudPolicyStore { + public: + explicit UserCloudPolicyStoreBase( + scoped_refptr background_task_runner); + virtual ~UserCloudPolicyStoreBase(); + + protected: + // Creates a validator configured to validate a user policy. The caller owns + // the resulting object until StartValidation() is invoked. + scoped_ptr CreateValidator( + scoped_ptr policy, + CloudPolicyValidatorBase::ValidateTimestampOption option); + + // Sets |policy_data| and |payload| as the active policy. + void InstallPolicy( + scoped_ptr policy_data, + scoped_ptr payload); + + scoped_refptr background_task_runner() const { + return background_task_runner_; + } + + private: + // Task runner for background file operations. + scoped_refptr background_task_runner_; + + DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreBase); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_ diff --git a/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc b/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc new file mode 100644 index 0000000..0220251 --- /dev/null +++ b/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc @@ -0,0 +1,367 @@ +// 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/cloud/user_cloud_policy_store.h" + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "components/policy/core/common/cloud/mock_cloud_external_data_manager.h" +#include "components/policy/core/common/cloud/mock_cloud_policy_store.h" +#include "components/policy/core/common/cloud/policy_builder.h" +#include "net/url_request/url_request_context_getter.h" +#include "policy/policy_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::AllOf; +using testing::Eq; +using testing::Mock; +using testing::Property; +using testing::Sequence; + +namespace policy { + +namespace { + +void RunUntilIdle() { + base::RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +class UserCloudPolicyStoreTest : public testing::Test { + public: + UserCloudPolicyStoreTest() : loop_(base::MessageLoop::TYPE_UI) {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); + store_.reset( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + external_data_manager_.reset(new MockCloudExternalDataManager); + external_data_manager_->SetPolicyStore(store_.get()); + store_->SetSigninUsername(PolicyBuilder::kFakeUsername); + store_->AddObserver(&observer_); + + policy_.payload().mutable_passwordmanagerenabled()->set_value(true); + policy_.payload().mutable_urlblacklist()->mutable_value()->add_entries( + "chromium.org"); + + policy_.Build(); + } + + virtual void TearDown() OVERRIDE { + store_->RemoveObserver(&observer_); + external_data_manager_.reset(); + store_.reset(); + RunUntilIdle(); + } + + base::FilePath policy_file() { + return tmp_dir_.path().AppendASCII("policy"); + } + + // Verifies that store_->policy_map() has the appropriate entries. + void VerifyPolicyMap(CloudPolicyStore* store) { + EXPECT_EQ(2U, store->policy_map().size()); + const PolicyMap::Entry* entry = + store->policy_map().Get(key::kPasswordManagerEnabled); + ASSERT_TRUE(entry); + EXPECT_TRUE(base::FundamentalValue(true).Equals(entry->value)); + ASSERT_TRUE(store->policy_map().Get(key::kURLBlacklist)); + } + + // Install an expectation on |observer_| for an error code. + void ExpectError(CloudPolicyStore* store, CloudPolicyStore::Status error) { + EXPECT_CALL(observer_, + OnStoreError(AllOf(Eq(store), + Property(&CloudPolicyStore::status, + Eq(error))))); + } + + UserPolicyBuilder policy_; + MockCloudPolicyStoreObserver observer_; + scoped_ptr store_; + scoped_ptr external_data_manager_; + + // CloudPolicyValidator() requires a FILE thread so declare one here. Both + // |ui_thread_| and |file_thread_| share the same MessageLoop |loop_| so + // callers can use RunLoop to manage both virtual threads. + base::MessageLoop loop_; + + base::ScopedTempDir tmp_dir_; + + DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreTest); +}; + +TEST_F(UserCloudPolicyStoreTest, LoadWithNoFile) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Load(); + RunUntilIdle(); + + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); +} + +TEST_F(UserCloudPolicyStoreTest, LoadWithInvalidFile) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Create a bogus file. + ASSERT_TRUE(base::CreateDirectory(policy_file().DirName())); + std::string bogus_data = "bogus_data"; + int size = bogus_data.size(); + ASSERT_EQ(size, file_util::WriteFile(policy_file(), + bogus_data.c_str(), + bogus_data.size())); + + ExpectError(store_.get(), CloudPolicyStore::STATUS_LOAD_ERROR); + store_->Load(); + RunUntilIdle(); + + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); +} + +TEST_F(UserCloudPolicyStoreTest, LoadImmediatelyWithNoFile) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->LoadImmediately(); // Should load without running the message loop. + + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); +} + +TEST_F(UserCloudPolicyStoreTest, LoadImmediatelyWithInvalidFile) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Create a bogus file. + ASSERT_TRUE(base::CreateDirectory(policy_file().DirName())); + std::string bogus_data = "bogus_data"; + int size = bogus_data.size(); + ASSERT_EQ(size, file_util::WriteFile(policy_file(), + bogus_data.c_str(), + bogus_data.size())); + + ExpectError(store_.get(), CloudPolicyStore::STATUS_LOAD_ERROR); + store_->LoadImmediately(); // Should load without running the message loop. + + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); +} + +TEST_F(UserCloudPolicyStoreTest, Store) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Store a simple policy and make sure it ends up as the currently active + // policy. + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Store(policy_.policy()); + RunUntilIdle(); + + // Policy should be decoded and stored. + ASSERT_TRUE(store_->policy()); + EXPECT_EQ(policy_.policy_data().SerializeAsString(), + store_->policy()->SerializeAsString()); + VerifyPolicyMap(store_.get()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); +} + +TEST_F(UserCloudPolicyStoreTest, StoreThenClear) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Store a simple policy and make sure the file exists. + // policy. + Sequence s1; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s1); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s1); + store_->Store(policy_.policy()); + RunUntilIdle(); + + EXPECT_TRUE(store_->policy()); + EXPECT_FALSE(store_->policy_map().empty()); + + Mock::VerifyAndClearExpectations(external_data_manager_.get()); + Mock::VerifyAndClearExpectations(&observer_); + + // Policy file should exist. + ASSERT_TRUE(base::PathExists(policy_file())); + + Sequence s2; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s2); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s2); + store_->Clear(); + RunUntilIdle(); + + // Policy file should not exist. + ASSERT_TRUE(!base::PathExists(policy_file())); + + // Policy should be gone. + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); +} + +TEST_F(UserCloudPolicyStoreTest, StoreTwoTimes) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Store a simple policy then store a second policy before the first one + // finishes validating, and make sure the second policy ends up as the active + // policy. + Sequence s1; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s1); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s1); + + UserPolicyBuilder first_policy; + first_policy.payload().mutable_passwordmanagerenabled()->set_value(false); + first_policy.Build(); + store_->Store(first_policy.policy()); + RunUntilIdle(); + + Mock::VerifyAndClearExpectations(external_data_manager_.get()); + Mock::VerifyAndClearExpectations(&observer_); + + Sequence s2; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s2); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s2); + + store_->Store(policy_.policy()); + RunUntilIdle(); + + // Policy should be decoded and stored. + ASSERT_TRUE(store_->policy()); + EXPECT_EQ(policy_.policy_data().SerializeAsString(), + store_->policy()->SerializeAsString()); + VerifyPolicyMap(store_.get()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); +} + +TEST_F(UserCloudPolicyStoreTest, StoreThenLoad) { + // Store a simple policy and make sure it can be read back in. + // policy. + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Store(policy_.policy()); + RunUntilIdle(); + + // Now, make sure the policy can be read back in from a second store. + scoped_ptr store2( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + store2->SetSigninUsername(PolicyBuilder::kFakeUsername); + store2->AddObserver(&observer_); + EXPECT_CALL(observer_, OnStoreLoaded(store2.get())); + store2->Load(); + RunUntilIdle(); + + ASSERT_TRUE(store2->policy()); + EXPECT_EQ(policy_.policy_data().SerializeAsString(), + store2->policy()->SerializeAsString()); + VerifyPolicyMap(store2.get()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store2->status()); + store2->RemoveObserver(&observer_); +} + +TEST_F(UserCloudPolicyStoreTest, StoreThenLoadImmediately) { + // Store a simple policy and make sure it can be read back in. + // policy. + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Store(policy_.policy()); + RunUntilIdle(); + + // Now, make sure the policy can be read back in from a second store. + scoped_ptr store2( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + store2->SetSigninUsername(PolicyBuilder::kFakeUsername); + store2->AddObserver(&observer_); + EXPECT_CALL(observer_, OnStoreLoaded(store2.get())); + store2->LoadImmediately(); // Should load without running the message loop. + + ASSERT_TRUE(store2->policy()); + EXPECT_EQ(policy_.policy_data().SerializeAsString(), + store2->policy()->SerializeAsString()); + VerifyPolicyMap(store2.get()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store2->status()); + store2->RemoveObserver(&observer_); +} + +TEST_F(UserCloudPolicyStoreTest, StoreValidationError) { + // Create an invalid policy (no policy type). + policy_.policy_data().clear_policy_type(); + policy_.Build(); + + // Store policy. + ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); + store_->Store(policy_.policy()); + RunUntilIdle(); + ASSERT_FALSE(store_->policy()); +} + +TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { + // Force a validation error by changing the username after policy is stored. + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Store(policy_.policy()); + RunUntilIdle(); + + // Sign out, and sign back in as a different user, and try to load the profile + // data (should fail due to mismatched username). + scoped_ptr store2( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + store2->SetSigninUsername("foobar@foobar.com"); + store2->AddObserver(&observer_); + ExpectError(store2.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); + store2->Load(); + RunUntilIdle(); + + ASSERT_FALSE(store2->policy()); + store2->RemoveObserver(&observer_); + + // Sign out - we should be able to load the policy (don't check usernames + // when signed out). + scoped_ptr store3( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + store3->AddObserver(&observer_); + EXPECT_CALL(observer_, OnStoreLoaded(store3.get())); + store3->Load(); + RunUntilIdle(); + + ASSERT_TRUE(store3->policy()); + store3->RemoveObserver(&observer_); + + // Now start a signin as a different user - this should fail validation. + scoped_ptr store4( + new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + store4->SetSigninUsername("foobar@foobar.com"); + store4->AddObserver(&observer_); + ExpectError(store4.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); + store4->Load(); + RunUntilIdle(); + + ASSERT_FALSE(store4->policy()); + store4->RemoveObserver(&observer_); +} + +} // namespace + +} // namespace policy -- cgit v1.1