// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/policy/cloud/component_cloud_policy_service.h" #include "base/callback.h" #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop.h" #include "base/pickle.h" #include "base/run_loop.h" #include "base/sha1.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/values.h" #include "chrome/browser/policy/cloud/cloud_policy_constants.h" #include "chrome/browser/policy/cloud/mock_cloud_policy_client.h" #include "chrome/browser/policy/cloud/mock_cloud_policy_store.h" #include "chrome/browser/policy/cloud/policy_builder.h" #include "chrome/browser/policy/cloud/resource_cache.h" #include "chrome/browser/policy/external_data_fetcher.h" #include "chrome/browser/policy/policy_domain_descriptor.h" #include "chrome/browser/policy/policy_map.h" #include "chrome/browser/policy/policy_types.h" #include "chrome/browser/policy/proto/cloud/chrome_extension_policy.pb.h" #include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h" #include "components/policy/core/common/schema.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/test_browser_thread.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_context.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::Mock; namespace policy { namespace { const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const char kTestExtension2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; const char kTestDownload[] = "http://example.com/getpolicy?id=123"; const char kTestDownload2[] = "http://example.com/getpolicy?id=456"; const char kTestPolicy[] = "{" " \"Name\": {" " \"Value\": \"disabled\"" " }," " \"Second\": {" " \"Value\": \"maybe\"," " \"Level\": \"Recommended\"" " }" "}"; const char kTestSchema[] = "{" " \"type\": \"object\"," " \"properties\": {" " \"Name\": { \"type\": \"string\" }," " \"Second\": { \"type\": \"string\" }" " }" "}"; class MockComponentCloudPolicyDelegate : public ComponentCloudPolicyService::Delegate { public: virtual ~MockComponentCloudPolicyDelegate() {} MOCK_METHOD0(OnComponentCloudPolicyRefreshNeeded, void()); MOCK_METHOD0(OnComponentCloudPolicyUpdated, void()); }; class TestURLRequestContextGetter : public net::URLRequestContextGetter { public: explicit TestURLRequestContextGetter( scoped_refptr task_runner) : task_runner_(task_runner) {} virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE { return NULL; } virtual scoped_refptr GetNetworkTaskRunner() const OVERRIDE { return task_runner_; } private: virtual ~TestURLRequestContextGetter() {} scoped_refptr task_runner_; }; } // namespace class ComponentCloudPolicyServiceTest : public testing::Test { protected: ComponentCloudPolicyServiceTest() : ui_thread_(content::BrowserThread::UI, &loop_), file_thread_(content::BrowserThread::FILE, &loop_) {} virtual void SetUp() OVERRIDE { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); cache_ = new ResourceCache(temp_dir_.path(), loop_.message_loop_proxy()); service_.reset(new ComponentCloudPolicyService( &delegate_, &store_, make_scoped_ptr(cache_), loop_.message_loop_proxy(), loop_.message_loop_proxy())); builder_.policy_data().set_policy_type( dm_protocol::kChromeExtensionPolicyType); builder_.policy_data().set_settings_entity_id(kTestExtension); builder_.payload().set_download_url(kTestDownload); builder_.payload().set_secure_hash(base::SHA1HashString(kTestPolicy)); expected_policy_.Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, base::Value::CreateStringValue("disabled"), NULL); expected_policy_.Set("Second", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER, base::Value::CreateStringValue("maybe"), NULL); // A NULL |request_context_| is enough to construct the updater, but // ComponentCloudPolicyService::Backend::LoadStore() tests the pointer when // Connect() is called before the store was loaded. request_context_ = new TestURLRequestContextGetter(loop_.message_loop_proxy()); } virtual void TearDown() OVERRIDE { // The service cleans up its backend on the FILE thread. service_.reset(); RunUntilIdle(); } void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } void LoadStore() { EXPECT_FALSE(store_.is_initialized()); EXPECT_FALSE(service_->is_initialized()); em::PolicyData* data = new em::PolicyData(); data->set_username(ComponentPolicyBuilder::kFakeUsername); data->set_request_token(ComponentPolicyBuilder::kFakeToken); store_.policy_.reset(data); EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); store_.NotifyStoreLoaded(); RunUntilIdle(); Mock::VerifyAndClearExpectations(&delegate_); EXPECT_TRUE(service_->is_initialized()); } void PopulateCache() { Pickle pickle; pickle.WriteString(kTestExtension); pickle.WriteString(kTestExtension2); std::string data(reinterpret_cast(pickle.data()), pickle.size()); EXPECT_TRUE( cache_->Store(ComponentCloudPolicyService::kComponentNamespaceCache, dm_protocol::kChromeExtensionPolicyType, data)); EXPECT_TRUE(cache_->Store( "extension-policy", kTestExtension, CreateSerializedResponse())); EXPECT_TRUE( cache_->Store("extension-policy-data", kTestExtension, kTestPolicy)); builder_.policy_data().set_settings_entity_id(kTestExtension2); EXPECT_TRUE(cache_->Store( "extension-policy", kTestExtension2, CreateSerializedResponse())); EXPECT_TRUE( cache_->Store("extension-policy-data", kTestExtension2, kTestPolicy)); EXPECT_TRUE(cache_->Store("unrelated", "stuff", "here")); } scoped_ptr CreateResponse() { builder_.Build(); return make_scoped_ptr(new em::PolicyFetchResponse(builder_.policy())); } std::string CreateSerializedResponse() { builder_.Build(); return builder_.GetBlob(); } scoped_ptr CreateTestSchema() { std::string error; scoped_ptr schema = SchemaOwner::Parse(kTestSchema, &error); EXPECT_TRUE(schema) << error; return schema.Pass(); } base::MessageLoop loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread file_thread_; base::ScopedTempDir temp_dir_; scoped_refptr request_context_; net::TestURLFetcherFactory fetcher_factory_; MockComponentCloudPolicyDelegate delegate_; // |cache_| is owned by the |service_| and is invalid once the |service_| // is destroyed. ResourceCache* cache_; MockCloudPolicyClient client_; MockCloudPolicyStore store_; scoped_ptr service_; ComponentPolicyBuilder builder_; PolicyMap expected_policy_; }; TEST_F(ComponentCloudPolicyServiceTest, InitializeWithoutCredentials) { EXPECT_FALSE(service_->is_initialized()); // Run the background task to initialize the backend. RunUntilIdle(); // Still waiting for the |store_|. EXPECT_FALSE(service_->is_initialized()); // Initialize with a store without credentials. EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); store_.NotifyStoreLoaded(); RunUntilIdle(); EXPECT_TRUE(service_->is_initialized()); Mock::VerifyAndClearExpectations(&delegate_); const PolicyBundle empty_bundle; EXPECT_TRUE(service_->policy().Equals(empty_bundle)); } TEST_F(ComponentCloudPolicyServiceTest, InitializationWithCachedComponents) { // Store a previous list of components. Pickle pickle; pickle.WriteString("aa"); pickle.WriteString("bb"); pickle.WriteString("cc"); std::string data(reinterpret_cast(pickle.data()), pickle.size()); EXPECT_TRUE( cache_->Store(ComponentCloudPolicyService::kComponentNamespaceCache, dm_protocol::kChromeExtensionPolicyType, data)); // Store some garbage in another domain; it won't be fetched. EXPECT_TRUE(cache_->Store( ComponentCloudPolicyService::kComponentNamespaceCache, "garbage", data)); // Connect a client before initialization is complete. service_->Connect(&client_, request_context_); EXPECT_TRUE(client_.namespaces_to_fetch_.empty()); // Now load the backend. LoadStore(); // The cached namespaces were added to the client. std::set set; set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, "aa")); set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, "bb")); set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, "cc")); EXPECT_EQ(set, client_.namespaces_to_fetch_); // And the bad components were wiped from the cache. std::map contents; cache_->LoadAllSubkeys(ComponentCloudPolicyService::kComponentNamespaceCache, &contents); ASSERT_EQ(1u, contents.size()); EXPECT_EQ(std::string(dm_protocol::kChromeExtensionPolicyType), contents.begin()->first); } TEST_F(ComponentCloudPolicyServiceTest, ConnectAfterRegister) { // Add some components. scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension, CreateTestSchema()); descriptor->RegisterComponent(kTestExtension2, CreateTestSchema()); service_->RegisterPolicyDomain(descriptor); // Now connect the client. EXPECT_TRUE(client_.namespaces_to_fetch_.empty()); service_->Connect(&client_, request_context_); EXPECT_TRUE(client_.namespaces_to_fetch_.empty()); // It receives the namespaces once the backend is initialized. LoadStore(); std::set set; set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension)); set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension2)); EXPECT_EQ(set, client_.namespaces_to_fetch_); // The current components were also persisted to the cache. std::map contents; cache_->LoadAllSubkeys(ComponentCloudPolicyService::kComponentNamespaceCache, &contents); ASSERT_EQ(1u, contents.size()); EXPECT_EQ(std::string(dm_protocol::kChromeExtensionPolicyType), contents.begin()->first); const std::string data(contents.begin()->second); Pickle pickle(data.data(), data.size()); PickleIterator pickit(pickle); std::string value0; std::string value1; std::string tmp; ASSERT_TRUE(pickit.ReadString(&value0)); ASSERT_TRUE(pickit.ReadString(&value1)); EXPECT_FALSE(pickit.ReadString(&tmp)); std::set unpickled; unpickled.insert(value0); unpickled.insert(value1); std::set expected_components; expected_components.insert(kTestExtension); expected_components.insert(kTestExtension2); EXPECT_EQ(expected_components, unpickled); } TEST_F(ComponentCloudPolicyServiceTest, StoreReadyAfterConnectAndRegister) { // Add some previous data to the cache. PopulateCache(); // Add some components. scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension, CreateTestSchema()); service_->RegisterPolicyDomain(descriptor); // And connect the client. Make the client have some policies, with a new // download_url. builder_.payload().set_download_url(kTestDownload2); client_.SetPolicy(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension), *CreateResponse()); service_->Connect(&client_, request_context_); // Now make the store ready. LoadStore(); // The components that were registed before have their caches purged. std::map contents; cache_->LoadAllSubkeys("extension-policy", &contents); EXPECT_EQ(1u, contents.size()); // kTestExtension2 was purged. EXPECT_TRUE(ContainsKey(contents, kTestExtension)); cache_->LoadAllSubkeys("unrelated", &contents); EXPECT_EQ(1u, contents.size()); // unrelated keys are not purged. EXPECT_TRUE(ContainsKey(contents, "stuff")); // The policy responses that the client already had start updating. net::TestURLFetcher* fetcher = fetcher_factory_.GetFetcherByID(0); ASSERT_TRUE(fetcher); // Expect the URL of the client's PolicyFetchResponse, not the cached URL. EXPECT_EQ(GURL(kTestDownload2), fetcher->GetOriginalURL()); } TEST_F(ComponentCloudPolicyServiceTest, ConnectThenRegisterThenStoreReady) { // Connect right after creating the service. service_->Connect(&client_, request_context_); // Now register the current components, before the backend has been // initialized. EXPECT_TRUE(client_.namespaces_to_fetch_.empty()); scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension, CreateTestSchema()); service_->RegisterPolicyDomain(descriptor); EXPECT_TRUE(client_.namespaces_to_fetch_.empty()); // Now load the store. The client gets the namespaces. LoadStore(); std::set set; set.insert(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension)); EXPECT_EQ(set, client_.namespaces_to_fetch_); } TEST_F(ComponentCloudPolicyServiceTest, FetchPolicy) { // Initialize the store and create the backend, and connect the client. LoadStore(); // A refresh is not needed, because no components were found. EXPECT_CALL(delegate_, OnComponentCloudPolicyRefreshNeeded()).Times(0); service_->Connect(&client_, request_context_); Mock::VerifyAndClearExpectations(&delegate_); // Register the components to fetch. scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension, CreateTestSchema()); EXPECT_CALL(delegate_, OnComponentCloudPolicyRefreshNeeded()); service_->RegisterPolicyDomain(descriptor); Mock::VerifyAndClearExpectations(&delegate_); // Send back a fake policy fetch response. client_.SetPolicy(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension), *CreateResponse()); service_->OnPolicyFetched(&client_); RunUntilIdle(); // That should have triggered the download fetch. net::TestURLFetcher* fetcher = fetcher_factory_.GetFetcherByID(0); ASSERT_TRUE(fetcher); EXPECT_EQ(GURL(kTestDownload), fetcher->GetOriginalURL()); fetcher->set_response_code(200); fetcher->SetResponseString(kTestPolicy); fetcher->delegate()->OnURLFetchComplete(fetcher); EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); RunUntilIdle(); Mock::VerifyAndClearExpectations(&delegate_); // The policy is now being served. PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension); PolicyBundle expected_bundle; expected_bundle.Get(ns).CopyFrom(expected_policy_); EXPECT_TRUE(service_->policy().Equals(expected_bundle)); } TEST_F(ComponentCloudPolicyServiceTest, LoadAndPurgeCache) { // Insert data in the cache. PopulateCache(); // Load the initial cache. LoadStore(); EXPECT_CALL(delegate_, OnComponentCloudPolicyRefreshNeeded()); service_->Connect(&client_, request_context_); Mock::VerifyAndClearExpectations(&delegate_); PolicyBundle expected_bundle; PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension); expected_bundle.Get(ns).CopyFrom(expected_policy_); ns.component_id = kTestExtension2; expected_bundle.Get(ns).CopyFrom(expected_policy_); EXPECT_TRUE(service_->policy().Equals(expected_bundle)); // Now purge one of the extensions. EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); // The service will start updating the components that are registered, which // starts by fetching policy for them. scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension2, CreateTestSchema()); service_->RegisterPolicyDomain(descriptor); RunUntilIdle(); Mock::VerifyAndClearExpectations(&delegate_); ns.component_id = kTestExtension; expected_bundle.Get(ns).Clear(); EXPECT_TRUE(service_->policy().Equals(expected_bundle)); std::map contents; cache_->LoadAllSubkeys("extension-policy", &contents); EXPECT_EQ(1u, contents.size()); EXPECT_TRUE(ContainsKey(contents, kTestExtension2)); cache_->LoadAllSubkeys("unrelated", &contents); EXPECT_EQ(1u, contents.size()); EXPECT_TRUE(ContainsKey(contents, "stuff")); } TEST_F(ComponentCloudPolicyServiceTest, UpdateCredentials) { // Do the same as LoadStore() but without the initial credentials. EXPECT_FALSE(store_.is_initialized()); EXPECT_FALSE(service_->is_initialized()); store_.policy_.reset(new em::PolicyData()); // No credentials. EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); store_.NotifyStoreLoaded(); RunUntilIdle(); Mock::VerifyAndClearExpectations(&delegate_); EXPECT_TRUE(service_->is_initialized()); // Connect the client and register an extension. service_->Connect(&client_, request_context_); EXPECT_CALL(delegate_, OnComponentCloudPolicyRefreshNeeded()); scoped_refptr descriptor = new PolicyDomainDescriptor( POLICY_DOMAIN_EXTENSIONS); descriptor->RegisterComponent(kTestExtension, CreateTestSchema()); service_->RegisterPolicyDomain(descriptor); Mock::VerifyAndClearExpectations(&delegate_); // Send the response to the service. The response data will be rejected, // because the store doesn't have the updated credentials yet. client_.SetPolicy(PolicyNamespaceKey(dm_protocol::kChromeExtensionPolicyType, kTestExtension), *CreateResponse()); service_->OnPolicyFetched(&client_); RunUntilIdle(); // The policy was not verified, and no download is started. net::TestURLFetcher* fetcher = fetcher_factory_.GetFetcherByID(0); EXPECT_FALSE(fetcher); // Now update the |store_| with the updated policy, which includes // credentials. The responses in the |client_| will be reloaded. em::PolicyData* data = new em::PolicyData(); data->set_username(ComponentPolicyBuilder::kFakeUsername); data->set_request_token(ComponentPolicyBuilder::kFakeToken); store_.policy_.reset(data); store_.NotifyStoreLoaded(); RunUntilIdle(); // The extension policy was validated this time, and the download is started. fetcher = fetcher_factory_.GetFetcherByID(0); ASSERT_TRUE(fetcher); EXPECT_EQ(GURL(kTestDownload), fetcher->GetOriginalURL()); fetcher->set_response_code(200); fetcher->SetResponseString(kTestPolicy); fetcher->delegate()->OnURLFetchComplete(fetcher); EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); RunUntilIdle(); Mock::VerifyAndClearExpectations(&delegate_); // The policy is now being served. PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension); PolicyBundle expected_bundle; expected_bundle.Get(ns).CopyFrom(expected_policy_); EXPECT_TRUE(service_->policy().Equals(expected_bundle)); } } // namespace policy