// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <utility> #include "base/bind.h" #include "base/json/json_writer.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/values.h" #include "chrome/browser/extensions/api/storage/settings_sync_util.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_system_factory.h" #include "chrome/browser/policy/schema_registry_service.h" #include "chrome/browser/policy/schema_registry_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "components/policy/core/browser/browser_policy_connector.h" #include "components/policy/core/common/mock_configuration_policy_provider.h" #include "components/policy/core/common/policy_bundle.h" #include "components/policy/core/common/policy_map.h" #include "components/policy/core/common/policy_namespace.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/core/common/schema_registry.h" #include "extensions/browser/api/storage/settings_namespace.h" #include "extensions/browser/api/storage/storage_frontend.h" #include "extensions/browser/extension_system.h" #include "extensions/common/value_builder.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" #include "sync/api/fake_sync_change_processor.h" #include "sync/api/sync_change.h" #include "sync/api/sync_change_processor.h" #include "sync/api/sync_change_processor_wrapper_for_test.h" #include "sync/api/sync_error_factory.h" #include "sync/api/sync_error_factory_mock.h" #include "sync/api/syncable_service.h" #include "testing/gmock/include/gmock/gmock.h" namespace extensions { using settings_namespace::LOCAL; using settings_namespace::MANAGED; using settings_namespace::Namespace; using settings_namespace::SYNC; using settings_namespace::ToString; using testing::Mock; using testing::Return; using testing::_; namespace { // TODO(kalman): test both EXTENSION_SETTINGS and APP_SETTINGS. const syncer::ModelType kModelType = syncer::EXTENSION_SETTINGS; // The managed_storage extension has a key defined in its manifest, so that // its extension ID is well-known and the policy system can push policies for // the extension. const char kManagedStorageExtensionId[] = "kjmkgkdkpedkejedfhmfcenooemhbpbo"; class MockSchemaRegistryObserver : public policy::SchemaRegistry::Observer { public: MockSchemaRegistryObserver() {} virtual ~MockSchemaRegistryObserver() {} MOCK_METHOD1(OnSchemaRegistryUpdated, void(bool)); }; } // namespace class ExtensionSettingsApiTest : public ExtensionApiTest { protected: void SetUpInProcessBrowserTestFixture() override { ExtensionApiTest::SetUpInProcessBrowserTestFixture(); EXPECT_CALL(policy_provider_, IsInitializationComplete(_)) .WillRepeatedly(Return(true)); policy_provider_.SetAutoRefresh(); policy::BrowserPolicyConnector::SetPolicyProviderForTesting( &policy_provider_); } void ReplyWhenSatisfied( Namespace settings_namespace, const std::string& normal_action, const std::string& incognito_action) { MaybeLoadAndReplyWhenSatisfied( settings_namespace, normal_action, incognito_action, NULL, false); } const Extension* LoadAndReplyWhenSatisfied( Namespace settings_namespace, const std::string& normal_action, const std::string& incognito_action, const std::string& extension_dir) { return MaybeLoadAndReplyWhenSatisfied( settings_namespace, normal_action, incognito_action, &extension_dir, false); } void FinalReplyWhenSatisfied( Namespace settings_namespace, const std::string& normal_action, const std::string& incognito_action) { MaybeLoadAndReplyWhenSatisfied( settings_namespace, normal_action, incognito_action, NULL, true); } syncer::SyncableService* GetSyncableService() { return settings_sync_util::GetSyncableService(browser()->profile(), kModelType); } void InitSync(syncer::SyncChangeProcessor* sync_processor) { base::MessageLoop::current()->RunUntilIdle(); InitSyncWithSyncableService(sync_processor, GetSyncableService()); } void SendChanges(const syncer::SyncChangeList& change_list) { base::MessageLoop::current()->RunUntilIdle(); SendChangesToSyncableService(change_list, GetSyncableService()); } void SetPolicies(const base::DictionaryValue& policies) { scoped_ptr<policy::PolicyBundle> bundle(new policy::PolicyBundle()); policy::PolicyMap& policy_map = bundle->Get(policy::PolicyNamespace( policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId)); policy_map.LoadFrom(&policies, policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD); policy_provider_.UpdatePolicy(std::move(bundle)); } private: const Extension* MaybeLoadAndReplyWhenSatisfied( Namespace settings_namespace, const std::string& normal_action, const std::string& incognito_action, // May be NULL to imply not loading the extension. const std::string* extension_dir, bool is_final_action) { ExtensionTestMessageListener listener("waiting", true); ExtensionTestMessageListener listener_incognito("waiting_incognito", true); // Only load the extension after the listeners have been set up, to avoid // initialisation race conditions. const Extension* extension = NULL; if (extension_dir) { extension = LoadExtensionIncognito( test_data_dir_.AppendASCII("settings").AppendASCII(*extension_dir)); EXPECT_TRUE(extension); } EXPECT_TRUE(listener.WaitUntilSatisfied()); EXPECT_TRUE(listener_incognito.WaitUntilSatisfied()); listener.Reply( CreateMessage(settings_namespace, normal_action, is_final_action)); listener_incognito.Reply( CreateMessage(settings_namespace, incognito_action, is_final_action)); return extension; } std::string CreateMessage( Namespace settings_namespace, const std::string& action, bool is_final_action) { base::DictionaryValue message; message.SetString("namespace", ToString(settings_namespace)); message.SetString("action", action); message.SetBoolean("isFinalAction", is_final_action); std::string message_json; base::JSONWriter::Write(message, &message_json); return message_json; } void InitSyncWithSyncableService( syncer::SyncChangeProcessor* sync_processor, syncer::SyncableService* settings_service) { EXPECT_FALSE( settings_service->MergeDataAndStartSyncing( kModelType, syncer::SyncDataList(), scoped_ptr<syncer::SyncChangeProcessor>( new syncer::SyncChangeProcessorWrapperForTest( sync_processor)), scoped_ptr<syncer::SyncErrorFactory>( new syncer::SyncErrorFactoryMock())) .error() .IsSet()); } void SendChangesToSyncableService( const syncer::SyncChangeList& change_list, syncer::SyncableService* settings_service) { EXPECT_FALSE( settings_service->ProcessSyncChanges(FROM_HERE, change_list).IsSet()); } protected: policy::MockConfigurationPolicyProvider policy_provider_; }; IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SimpleTest) { ASSERT_TRUE(RunExtensionTest("settings/simple_test")) << message_; } // Structure of this test taken from IncognitoSplitMode. // Note that only split-mode incognito is tested, because spanning mode // incognito looks the same as normal mode when the only API activity comes // from background pages. IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SplitModeIncognito) { // We need 2 ResultCatchers because we'll be running the same test in both // regular and incognito mode. ResultCatcher catcher, catcher_incognito; catcher.RestrictToBrowserContext(browser()->profile()); catcher_incognito.RestrictToBrowserContext( browser()->profile()->GetOffTheRecordProfile()); LoadAndReplyWhenSatisfied(SYNC, "assertEmpty", "assertEmpty", "split_incognito"); ReplyWhenSatisfied(SYNC, "noop", "setFoo"); ReplyWhenSatisfied(SYNC, "assertFoo", "assertFoo"); ReplyWhenSatisfied(SYNC, "clear", "noop"); ReplyWhenSatisfied(SYNC, "assertEmpty", "assertEmpty"); ReplyWhenSatisfied(SYNC, "setFoo", "noop"); ReplyWhenSatisfied(SYNC, "assertFoo", "assertFoo"); ReplyWhenSatisfied(SYNC, "noop", "removeFoo"); FinalReplyWhenSatisfied(SYNC, "assertEmpty", "assertEmpty"); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, OnChangedNotificationsBetweenBackgroundPages) { // We need 2 ResultCatchers because we'll be running the same test in both // regular and incognito mode. ResultCatcher catcher, catcher_incognito; catcher.RestrictToBrowserContext(browser()->profile()); catcher_incognito.RestrictToBrowserContext( browser()->profile()->GetOffTheRecordProfile()); LoadAndReplyWhenSatisfied(SYNC, "assertNoNotifications", "assertNoNotifications", "split_incognito"); ReplyWhenSatisfied(SYNC, "noop", "setFoo"); ReplyWhenSatisfied(SYNC, "assertAddFooNotification", "assertAddFooNotification"); ReplyWhenSatisfied(SYNC, "clearNotifications", "clearNotifications"); ReplyWhenSatisfied(SYNC, "removeFoo", "noop"); FinalReplyWhenSatisfied(SYNC, "assertDeleteFooNotification", "assertDeleteFooNotification"); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SyncAndLocalAreasAreSeparate) { // We need 2 ResultCatchers because we'll be running the same test in both // regular and incognito mode. ResultCatcher catcher, catcher_incognito; catcher.RestrictToBrowserContext(browser()->profile()); catcher_incognito.RestrictToBrowserContext( browser()->profile()->GetOffTheRecordProfile()); LoadAndReplyWhenSatisfied(SYNC, "assertNoNotifications", "assertNoNotifications", "split_incognito"); ReplyWhenSatisfied(SYNC, "noop", "setFoo"); ReplyWhenSatisfied(SYNC, "assertFoo", "assertFoo"); ReplyWhenSatisfied(SYNC, "assertAddFooNotification", "assertAddFooNotification"); ReplyWhenSatisfied(LOCAL, "assertEmpty", "assertEmpty"); ReplyWhenSatisfied(LOCAL, "assertNoNotifications", "assertNoNotifications"); ReplyWhenSatisfied(SYNC, "clearNotifications", "clearNotifications"); ReplyWhenSatisfied(LOCAL, "setFoo", "noop"); ReplyWhenSatisfied(LOCAL, "assertFoo", "assertFoo"); ReplyWhenSatisfied(LOCAL, "assertAddFooNotification", "assertAddFooNotification"); ReplyWhenSatisfied(SYNC, "assertFoo", "assertFoo"); ReplyWhenSatisfied(SYNC, "assertNoNotifications", "assertNoNotifications"); ReplyWhenSatisfied(LOCAL, "clearNotifications", "clearNotifications"); ReplyWhenSatisfied(LOCAL, "noop", "removeFoo"); ReplyWhenSatisfied(LOCAL, "assertEmpty", "assertEmpty"); ReplyWhenSatisfied(LOCAL, "assertDeleteFooNotification", "assertDeleteFooNotification"); ReplyWhenSatisfied(SYNC, "assertFoo", "assertFoo"); ReplyWhenSatisfied(SYNC, "assertNoNotifications", "assertNoNotifications"); ReplyWhenSatisfied(LOCAL, "clearNotifications", "clearNotifications"); ReplyWhenSatisfied(SYNC, "removeFoo", "noop"); ReplyWhenSatisfied(SYNC, "assertEmpty", "assertEmpty"); ReplyWhenSatisfied(SYNC, "assertDeleteFooNotification", "assertDeleteFooNotification"); ReplyWhenSatisfied(LOCAL, "assertNoNotifications", "assertNoNotifications"); FinalReplyWhenSatisfied(LOCAL, "assertEmpty", "assertEmpty"); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); } // Disabled, see crbug.com/101110 IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, DISABLED_OnChangedNotificationsFromSync) { // We need 2 ResultCatchers because we'll be running the same test in both // regular and incognito mode. ResultCatcher catcher, catcher_incognito; catcher.RestrictToBrowserContext(browser()->profile()); catcher_incognito.RestrictToBrowserContext( browser()->profile()->GetOffTheRecordProfile()); const Extension* extension = LoadAndReplyWhenSatisfied(SYNC, "assertNoNotifications", "assertNoNotifications", "split_incognito"); const std::string& extension_id = extension->id(); syncer::FakeSyncChangeProcessor sync_processor; InitSync(&sync_processor); // Set "foo" to "bar" via sync. syncer::SyncChangeList sync_changes; base::StringValue bar("bar"); sync_changes.push_back(settings_sync_util::CreateAdd( extension_id, "foo", bar, kModelType)); SendChanges(sync_changes); ReplyWhenSatisfied(SYNC, "assertAddFooNotification", "assertAddFooNotification"); ReplyWhenSatisfied(SYNC, "clearNotifications", "clearNotifications"); // Remove "foo" via sync. sync_changes.clear(); sync_changes.push_back(settings_sync_util::CreateDelete( extension_id, "foo", kModelType)); SendChanges(sync_changes); FinalReplyWhenSatisfied(SYNC, "assertDeleteFooNotification", "assertDeleteFooNotification"); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); } // Disabled, see crbug.com/101110 // // TODO: boring test, already done in the unit tests. What we really should be // be testing is that the areas don't overlap. IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, DISABLED_OnChangedNotificationsFromSyncNotSentToLocal) { // We need 2 ResultCatchers because we'll be running the same test in both // regular and incognito mode. ResultCatcher catcher, catcher_incognito; catcher.RestrictToBrowserContext(browser()->profile()); catcher_incognito.RestrictToBrowserContext( browser()->profile()->GetOffTheRecordProfile()); const Extension* extension = LoadAndReplyWhenSatisfied(LOCAL, "assertNoNotifications", "assertNoNotifications", "split_incognito"); const std::string& extension_id = extension->id(); syncer::FakeSyncChangeProcessor sync_processor; InitSync(&sync_processor); // Set "foo" to "bar" via sync. syncer::SyncChangeList sync_changes; base::StringValue bar("bar"); sync_changes.push_back(settings_sync_util::CreateAdd( extension_id, "foo", bar, kModelType)); SendChanges(sync_changes); ReplyWhenSatisfied(LOCAL, "assertNoNotifications", "assertNoNotifications"); // Remove "foo" via sync. sync_changes.clear(); sync_changes.push_back(settings_sync_util::CreateDelete( extension_id, "foo", kModelType)); SendChanges(sync_changes); FinalReplyWhenSatisfied(LOCAL, "assertNoNotifications", "assertNoNotifications"); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, IsStorageEnabled) { StorageFrontend* frontend = StorageFrontend::Get(browser()->profile()); EXPECT_TRUE(frontend->IsStorageEnabled(LOCAL)); EXPECT_TRUE(frontend->IsStorageEnabled(SYNC)); EXPECT_TRUE(frontend->IsStorageEnabled(MANAGED)); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, ExtensionsSchemas) { // Verifies that the Schemas for the extensions domain are created on startup. Profile* profile = browser()->profile(); ExtensionSystem* extension_system = ExtensionSystem::Get(profile); if (!extension_system->ready().is_signaled()) { // Wait until the extension system is ready. base::RunLoop run_loop; extension_system->ready().Post(FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); ASSERT_TRUE(extension_system->ready().is_signaled()); } // This test starts without any test extensions installed. EXPECT_FALSE(GetSingleLoadedExtension()); message_.clear(); policy::SchemaRegistry* registry = policy::SchemaRegistryServiceFactory::GetForContext(profile)->registry(); ASSERT_TRUE(registry); EXPECT_FALSE(registry->schema_map()->GetSchema(policy::PolicyNamespace( policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId))); MockSchemaRegistryObserver observer; registry->AddObserver(&observer); // Install a managed extension. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); const Extension* extension = LoadExtension(test_data_dir_.AppendASCII("settings/managed_storage")); ASSERT_TRUE(extension); Mock::VerifyAndClearExpectations(&observer); registry->RemoveObserver(&observer); // Verify that its schema has been published, and verify its contents. const policy::Schema* schema = registry->schema_map()->GetSchema(policy::PolicyNamespace( policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId)); ASSERT_TRUE(schema); ASSERT_TRUE(schema->valid()); ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema->type()); ASSERT_TRUE(schema->GetKnownProperty("string-policy").valid()); EXPECT_EQ(base::Value::TYPE_STRING, schema->GetKnownProperty("string-policy").type()); ASSERT_TRUE(schema->GetKnownProperty("int-policy").valid()); EXPECT_EQ(base::Value::TYPE_INTEGER, schema->GetKnownProperty("int-policy").type()); ASSERT_TRUE(schema->GetKnownProperty("double-policy").valid()); EXPECT_EQ(base::Value::TYPE_DOUBLE, schema->GetKnownProperty("double-policy").type()); ASSERT_TRUE(schema->GetKnownProperty("boolean-policy").valid()); EXPECT_EQ(base::Value::TYPE_BOOLEAN, schema->GetKnownProperty("boolean-policy").type()); policy::Schema list = schema->GetKnownProperty("list-policy"); ASSERT_TRUE(list.valid()); ASSERT_EQ(base::Value::TYPE_LIST, list.type()); ASSERT_TRUE(list.GetItems().valid()); EXPECT_EQ(base::Value::TYPE_STRING, list.GetItems().type()); policy::Schema dict = schema->GetKnownProperty("dict-policy"); ASSERT_TRUE(dict.valid()); ASSERT_EQ(base::Value::TYPE_DICTIONARY, dict.type()); list = dict.GetKnownProperty("list"); ASSERT_TRUE(list.valid()); ASSERT_EQ(base::Value::TYPE_LIST, list.type()); dict = list.GetItems(); ASSERT_TRUE(dict.valid()); ASSERT_EQ(base::Value::TYPE_DICTIONARY, dict.type()); ASSERT_TRUE(dict.GetProperty("anything").valid()); EXPECT_EQ(base::Value::TYPE_INTEGER, dict.GetProperty("anything").type()); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, ManagedStorage) { // Set policies for the test extension. scoped_ptr<base::DictionaryValue> policy = extensions::DictionaryBuilder() .Set("string-policy", "value") .Set("int-policy", -123) .Set("double-policy", 456e7) .SetBoolean("boolean-policy", true) .Set("list-policy", extensions::ListBuilder() .Append("one") .Append("two") .Append("three") .Build()) .Set("dict-policy", extensions::DictionaryBuilder() .Set("list", extensions::ListBuilder() .Append(extensions::DictionaryBuilder() .Set("one", 1) .Set("two", 2) .Build()) .Append(extensions::DictionaryBuilder() .Set("three", 3) .Build()) .Build()) .Build()) .Build(); SetPolicies(*policy); // Now run the extension. ASSERT_TRUE(RunExtensionTest("settings/managed_storage")) << message_; } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, DISABLED_PRE_ManagedStorageEvents) { ResultCatcher catcher; // This test starts without any test extensions installed. EXPECT_FALSE(GetSingleLoadedExtension()); message_.clear(); // Set policies for the test extension. scoped_ptr<base::DictionaryValue> policy = extensions::DictionaryBuilder() .Set("constant-policy", "aaa") .Set("changes-policy", "bbb") .Set("deleted-policy", "ccc") .Build(); SetPolicies(*policy); ExtensionTestMessageListener ready_listener("ready", false); // Load the extension to install the event listener. const Extension* extension = LoadExtension( test_data_dir_.AppendASCII("settings/managed_storage_events")); ASSERT_TRUE(extension); // Wait until the extension sends the "ready" message. ASSERT_TRUE(ready_listener.WaitUntilSatisfied()); // Now change the policies and wait until the extension is done. policy = extensions::DictionaryBuilder() .Set("constant-policy", "aaa") .Set("changes-policy", "ddd") .Set("new-policy", "eee") .Build(); SetPolicies(*policy); EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, DISABLED_ManagedStorageEvents) { // This test runs after PRE_ManagedStorageEvents without having deleted the // profile, so the extension is still around. While the browser restarted the // policy went back to the empty default, and so the extension should receive // the corresponding change events. ResultCatcher catcher; // Verify that the test extension is still installed. const Extension* extension = GetSingleLoadedExtension(); ASSERT_TRUE(extension); EXPECT_EQ(kManagedStorageExtensionId, extension->id()); // Running the test again skips the onInstalled callback, and just triggers // the onChanged notification. EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); } IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, ManagedStorageDisabled) { // Disable the 'managed' namespace. StorageFrontend* frontend = StorageFrontend::Get(browser()->profile()); frontend->DisableStorageForTesting(MANAGED); EXPECT_FALSE(frontend->IsStorageEnabled(MANAGED)); // Now run the extension. ASSERT_TRUE(RunExtensionTest("settings/managed_storage_disabled")) << message_; } } // namespace extensions