diff options
Diffstat (limited to 'chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc')
-rw-r--r-- | chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc | 687 |
1 files changed, 687 insertions, 0 deletions
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc new file mode 100644 index 00000000000..1dd0581e4c9 --- /dev/null +++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc @@ -0,0 +1,687 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/policy/core/common/cloud/component_cloud_policy_service.h" + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "base/callback.h" +#include "base/containers/contains.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/raw_ptr.h" +#include "base/run_loop.h" +#include "base/task/single_thread_task_runner.h" +#include "base/test/task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/values.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "components/policy/core/common/cloud/mock_cloud_policy_client.h" +#include "components/policy/core/common/cloud/mock_cloud_policy_store.h" +#include "components/policy/core/common/cloud/resource_cache.h" +#include "components/policy/core/common/cloud/test/policy_builder.h" +#include "components/policy/core/common/external_data_fetcher.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/core/common/policy_types.h" +#include "components/policy/core/common/schema.h" +#include "components/policy/core/common/schema_map.h" +#include "components/policy/proto/chrome_extension_policy.pb.h" +#include "components/policy/proto/device_management_backend.pb.h" +#include "crypto/rsa_private_key.h" +#include "crypto/sha2.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_network_connection_tracker.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace em = enterprise_management; + +using testing::AtLeast; +using testing::Mock; +using testing::Return; + +namespace policy { + +namespace { + +const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +const char kTestExtension2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +const char kTestDownload[] = "http://example.com/getpolicy?id=123"; + +const char kTestPolicy[] = + "{" + " \"Name\": {" + " \"Value\": \"disabled\"" + " }," + " \"Second\": {" + " \"Value\": \"maybe\"," + " \"Level\": \"Recommended\"" + " }" + "}"; + +const char kInvalidTestPolicy[] = + "{" + " \"Name\": {" + " \"Value\": \"published\"" + " }," + " \"Undeclared Name\": {" + " \"Value\": \"not published\"" + " }" + "}"; + +const char kTestSchema[] = + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"Name\": { \"type\": \"string\" }," + " \"Second\": { \"type\": \"string\" }" + " }" + "}"; + +class MockComponentCloudPolicyDelegate + : public ComponentCloudPolicyService::Delegate { + public: + ~MockComponentCloudPolicyDelegate() override {} + + MOCK_METHOD0(OnComponentCloudPolicyUpdated, void()); +}; + +} // namespace + +class ComponentCloudPolicyServiceTest : public testing::Test { + protected: + ComponentCloudPolicyServiceTest() + : cache_(nullptr), + client_(nullptr), + core_(dm_protocol::kChromeUserPolicyType, + std::string(), + &store_, + base::ThreadTaskRunnerHandle::Get(), + network::TestNetworkConnectionTracker::CreateGetter()) { + builder_.SetDefaultSigningKey(); + 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(crypto::SHA256HashString(kTestPolicy)); + + public_key_ = builder_.GetPublicSigningKeyAsString(); + + expected_policy_.Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, + POLICY_SOURCE_CLOUD, base::Value("disabled"), nullptr); + expected_policy_.Set("Second", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER, + POLICY_SOURCE_CLOUD, base::Value("maybe"), nullptr); + } + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + owned_cache_ = std::make_unique<ResourceCache>( + temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(), + /* max_cache_size */ absl::nullopt); + cache_ = owned_cache_.get(); + } + + void TearDown() override { + // The service cleans up its backend on the background thread. + service_.reset(); + RunUntilIdle(); + } + + void RunUntilIdle() { base::RunLoop().RunUntilIdle(); } + + void Connect() { + client_ = new MockCloudPolicyClient( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &loader_factory_)); + service_ = std::make_unique<ComponentCloudPolicyService>( + dm_protocol::kChromeExtensionPolicyType, &delegate_, ®istry_, &core_, + client_, std::move(owned_cache_), base::ThreadTaskRunnerHandle::Get()); + + client_->SetDMToken(ComponentCloudPolicyBuilder::kFakeToken); + EXPECT_EQ(1u, client_->types_to_fetch_.size()); + core_.Connect(std::unique_ptr<CloudPolicyClient>(client_)); + EXPECT_EQ(2u, client_->types_to_fetch_.size()); + + // Also initialize the refresh scheduler, so that calls to + // core()->RefreshSoon() trigger a FetchPolicy() call on the mock |client_|. + // The |service_| should never trigger new fetches. + EXPECT_CALL(*client_, FetchPolicy()); + core_.StartRefreshScheduler(); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + } + + void LoadStore() { + auto policy_data = std::make_unique<em::PolicyData>(); + policy_data->set_username(PolicyBuilder::kFakeUsername); + policy_data->set_request_token(PolicyBuilder::kFakeToken); + policy_data->set_device_id(PolicyBuilder::kFakeDeviceId); + policy_data->set_public_key_version(PolicyBuilder::kFakePublicKeyVersion); + store_.set_policy_data_for_testing(std::move(policy_data)); + store_.policy_signature_public_key_ = public_key_; + store_.NotifyStoreLoaded(); + RunUntilIdle(); + EXPECT_TRUE(store_.is_initialized()); + } + + void InitializeRegistry() { + registry_.RegisterComponent( + PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension), + CreateTestSchema()); + registry_.SetAllDomainsReady(); + } + + void PopulateCache() { + EXPECT_FALSE(cache_ + ->Store("extension-policy", kTestExtension, + CreateSerializedResponse()) + .empty()); + EXPECT_FALSE( + cache_->Store("extension-policy-data", kTestExtension, kTestPolicy) + .empty()); + + builder_.policy_data().set_settings_entity_id(kTestExtension2); + EXPECT_FALSE(cache_ + ->Store("extension-policy", kTestExtension2, + CreateSerializedResponse()) + .empty()); + EXPECT_FALSE( + cache_->Store("extension-policy-data", kTestExtension2, kTestPolicy) + .empty()); + builder_.policy_data().set_settings_entity_id(kTestExtension); + } + + std::unique_ptr<em::PolicyFetchResponse> CreateResponse() { + builder_.Build(); + return std::make_unique<em::PolicyFetchResponse>(builder_.policy()); + } + + std::string CreateSerializedResponse() { + builder_.Build(); + return builder_.GetBlob(); + } + + Schema CreateTestSchema() { + std::string error; + Schema schema = Schema::Parse(kTestSchema, &error); + EXPECT_TRUE(schema.valid()) << error; + return schema; + } + + const PolicyNamespace kTestExtensionNS = + PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension); + const PolicyNamespace kTestExtensionNS2 = + PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension2); + + base::test::SingleThreadTaskEnvironment task_environment_; + base::ScopedTempDir temp_dir_; + network::TestURLLoaderFactory loader_factory_; + MockComponentCloudPolicyDelegate delegate_; + // |cache_| is owned by the |service_| and is invalid once the |service_| + // is destroyed. + std::unique_ptr<ResourceCache> owned_cache_; + raw_ptr<ResourceCache> cache_; + raw_ptr<MockCloudPolicyClient> client_; + MockCloudPolicyStore store_; + CloudPolicyCore core_; + SchemaRegistry registry_; + std::unique_ptr<ComponentCloudPolicyService> service_; + ComponentCloudPolicyBuilder builder_; + PolicyMap expected_policy_; + std::string public_key_; +}; + +TEST_F(ComponentCloudPolicyServiceTest, InitializeStoreThenRegistry) { + Connect(); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(0); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_FALSE(service_->is_initialized()); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + InitializeRegistry(); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_TRUE(service_->is_initialized()); + + const PolicyBundle empty_bundle; + EXPECT_TRUE(service_->policy().Equals(empty_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, InitializeRegistryThenStore) { + Connect(); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(0); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + InitializeRegistry(); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_FALSE(service_->is_initialized()); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_TRUE(service_->is_initialized()); + EXPECT_EQ(2u, client_->types_to_fetch_.size()); + const PolicyBundle empty_bundle; + EXPECT_TRUE(service_->policy().Equals(empty_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, InitializeWithCachedPolicy) { + PopulateCache(); + Connect(); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + InitializeRegistry(); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + EXPECT_TRUE(service_->is_initialized()); + EXPECT_EQ(2u, client_->types_to_fetch_.size()); + + // Policies for both extensions are stored in the cache. + std::map<std::string, std::string> contents; + cache_->LoadAllSubkeys("extension-policy", &contents); + ASSERT_EQ(2u, contents.size()); + EXPECT_TRUE(base::Contains(contents, kTestExtension)); + EXPECT_TRUE(base::Contains(contents, kTestExtension2)); + + // Policy for extension 1 is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); + + // Register extension 2. Its policy gets loaded without any additional + // policy fetches. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema()); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + // Policies for both extensions are being served now. + expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, FetchPolicy) { + Connect(); + // Initialize the store. A refresh is not needed, because no components are + // registered yet. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + registry_.SetAllDomainsReady(); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_TRUE(service_->is_initialized()); + + // Register the components to fetch. The |service_| issues a new update + // because the new schema may filter different policies from the store. + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + // Send back a fake policy fetch response. + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + + // That should have triggered the download fetch. + EXPECT_TRUE(loader_factory_.IsPending(kTestDownload)); + loader_factory_.AddResponse(kTestDownload, kTestPolicy); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The policy is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, FetchPolicyBeforeStoreLoaded) { + Connect(); + // A fake policy is fetched. + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + + // Initialize the store. A refresh is not needed, because no components are + // registered yet. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + registry_.SetAllDomainsReady(); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_TRUE(service_->is_initialized()); + + // Register the components to fetch. The |service_| issues a new update + // because the new schema may filter different policies from the store. + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + // The download started. + EXPECT_TRUE(loader_factory_.IsPending(kTestDownload)); + loader_factory_.AddResponse(kTestDownload, kTestPolicy); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The policy is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, + FetchPolicyWithCachedPolicyBeforeStoreLoaded) { + PopulateCache(); + Connect(); + // A fake policy is fetched. + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + + // Initialize the store. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(AtLeast(1)); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + InitializeRegistry(); + LoadStore(); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + EXPECT_TRUE(service_->is_initialized()); + + // Only policy for extension 1 is served. Policy for extension 2, which was in + // the cache initially, is now dropped. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, LoadCacheAndDeleteExtensions) { + Connect(); + // Insert data in the cache. + PopulateCache(); + registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema()); + InitializeRegistry(); + + // Load the initial cache. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); + + // Now purge one of the extensions. This generates a notification after an + // immediate filtering. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + registry_.UnregisterComponent(kTestExtensionNS); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The policy served for extension 1 becomes empty. + expected_bundle.Get(kTestExtensionNS).Clear(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); + + // The cache still keeps policies for both extensions. + std::map<std::string, std::string> contents; + cache_->LoadAllSubkeys("extension-policy", &contents); + EXPECT_EQ(2u, contents.size()); + EXPECT_TRUE(base::Contains(contents, kTestExtension)); + EXPECT_TRUE(base::Contains(contents, kTestExtension2)); +} + +TEST_F(ComponentCloudPolicyServiceTest, SignInAfterStartup) { + registry_.SetAllDomainsReady(); + + // Initialize the store without credentials. + EXPECT_FALSE(store_.is_initialized()); + store_.NotifyStoreLoaded(); + RunUntilIdle(); + + // Register an extension. + registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema()); + RunUntilIdle(); + + // Now signin. The service will finish loading its backend (which is empty + // for now, because there are no credentials) and issue a notification. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + Connect(); + Mock::VerifyAndClearExpectations(&delegate_); + + // Send the response to the service. The response data will be ignored, + // because the store doesn't have the updated credentials yet. + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + + // The policy was ignored and no download is started because the store + // doesn't have credentials. + EXPECT_EQ(0, loader_factory_.NumPending()); + + // Now update the |store_| with the updated policy, which includes + // credentials. The responses in the |client_| will be reloaded. + LoadStore(); + + // The extension policy was validated this time, and the download is started. + ASSERT_EQ(1, loader_factory_.NumPending()); + EXPECT_TRUE(loader_factory_.IsPending(kTestDownload)); + loader_factory_.AddResponse(kTestDownload, kTestPolicy); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The policy is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, SignOut) { + // Initialize everything and serve policy for a component. + PopulateCache(); + LoadStore(); + InitializeRegistry(); + + // The initial, cached policy will be served once the backend is initialized. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + Connect(); + Mock::VerifyAndClearExpectations(&delegate_); + // Policy for extension 1 is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); + // The cache still contains policies for both extensions. + std::map<std::string, std::string> contents; + cache_->LoadAllSubkeys("extension-policy", &contents); + ASSERT_EQ(2u, contents.size()); + + // Signing out removes all of the component policies from the service and + // from the cache. It does not trigger a refresh. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + core_.Disconnect(); + store_.set_policy_data_for_testing(std::make_unique<em::PolicyData>()); + Mock::VerifyAndClearExpectations(&store_); + store_.NotifyStoreLoaded(); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + const PolicyBundle empty_bundle; + EXPECT_TRUE(service_->policy().Equals(empty_bundle)); + cache_->LoadAllSubkeys("extension-policy", &contents); + ASSERT_EQ(0u, contents.size()); +} + +TEST_F(ComponentCloudPolicyServiceTest, LoadInvalidPolicyFromCache) { + // Put the invalid test policy in the cache. One of its policies will be + // loaded, the other should be filtered out by the schema. + builder_.payload().set_secure_hash( + crypto::SHA256HashString(kInvalidTestPolicy)); + EXPECT_FALSE(cache_ + ->Store("extension-policy", kTestExtension, + CreateSerializedResponse()) + .empty()); + EXPECT_FALSE( + cache_->Store("extension-policy-data", kTestExtension, kInvalidTestPolicy) + .empty()); + + LoadStore(); + InitializeRegistry(); + + // The initial, cached policy will be served once the backend is initialized. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + Connect(); + Mock::VerifyAndClearExpectations(&delegate_); + + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) + .Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, + POLICY_SOURCE_CLOUD, base::Value("published"), nullptr); + // The second policy should be invalid. + expected_bundle.Get(kTestExtensionNS) + .Set("Undeclared Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, + POLICY_SOURCE_CLOUD, base::Value("not published"), nullptr); + expected_bundle.Get(kTestExtensionNS) + .GetMutable("Undeclared Name") + ->SetInvalid(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, PurgeWhenServerRemovesPolicy) { + // Initialize with cached policy. + PopulateCache(); + Connect(); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + EXPECT_CALL(*client_, FetchPolicy()).Times(0); + registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema()); + InitializeRegistry(); + LoadStore(); + Mock::VerifyAndClearExpectations(client_); + Mock::VerifyAndClearExpectations(&delegate_); + + EXPECT_TRUE(service_->is_initialized()); + EXPECT_EQ(2u, client_->types_to_fetch_.size()); + + // Verify that policy for 2 extensions has been loaded from the cache. + std::map<std::string, std::string> contents; + cache_->LoadAllSubkeys("extension-policy", &contents); + ASSERT_EQ(2u, contents.size()); + EXPECT_TRUE(base::Contains(contents, kTestExtension)); + EXPECT_TRUE(base::Contains(contents, kTestExtension2)); + + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); + + // Receive an updated fetch response from the server. There is no response for + // extension 2, so it will be dropped from the cache. This triggers an + // immediate notification to the delegate. + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The cache should have dropped the entries for the second extension. + contents.clear(); + cache_->LoadAllSubkeys("extension-policy", &contents); + ASSERT_EQ(1u, contents.size()); + EXPECT_TRUE(base::Contains(contents, kTestExtension)); + EXPECT_FALSE(base::Contains(contents, kTestExtension2)); + + // And the service isn't publishing policy for the second extension anymore. + expected_bundle.Clear(); + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +TEST_F(ComponentCloudPolicyServiceTest, KeyRotation) { + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + Connect(); + LoadStore(); + InitializeRegistry(); + RunUntilIdle(); + EXPECT_TRUE(service_->is_initialized()); + + // Send back a fake policy fetch response with the new signing key. + const int kNewPublicKeyVersion = PolicyBuilder::kFakePublicKeyVersion + 1; + std::unique_ptr<crypto::RSAPrivateKey> new_signing_key = + PolicyBuilder::CreateTestOtherSigningKey(); + builder_.SetSigningKey(*new_signing_key); + builder_.policy_data().set_public_key_version(kNewPublicKeyVersion); + client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension, + *CreateResponse()); + service_->OnPolicyFetched(client_); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&store_); + + // The download fetch shouldn't have been triggered, as the component policy + // validation should fail due to the old key returned by the store. + ASSERT_EQ(0, loader_factory_.NumPending()); + + // Update the signing key in the store. + store_.policy_signature_public_key_ = builder_.GetPublicSigningKeyAsString(); + auto policy_data = std::make_unique<em::PolicyData>(*store_.policy()); + policy_data->set_public_key_version(kNewPublicKeyVersion); + store_.set_policy_data_for_testing(std::move(policy_data)); + store_.NotifyStoreLoaded(); + RunUntilIdle(); + + // The validation of the component policy should have finished successfully + // and trigger the download fetch. + EXPECT_TRUE(loader_factory_.IsPending(kTestDownload)); + loader_factory_.AddResponse(kTestDownload, kTestPolicy); + + EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(&delegate_); + + // The policy is now being served. + PolicyBundle expected_bundle; + expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone(); + EXPECT_TRUE(service_->policy().Equals(expected_bundle)); +} + +} // namespace policy |