// Copyright 2015 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 #include #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_util.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/metrics/field_trial.h" #include "base/test/mock_entropy_provider.h" #include "chrome/browser/extensions/component_loader.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service_test_with_install.h" #include "chrome/browser/extensions/extension_sync_data.h" #include "chrome/browser/extensions/extension_sync_service.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/updater/extension_updater.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/sync_helper.h" #include "chrome/test/base/testing_profile.h" #include "components/browser_sync/browser/profile_sync_service.h" #include "components/crx_file/id_util.h" #include "components/variations/variations_associated_data.h" #include "extensions/browser/app_sorting.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/management_policy.h" #include "extensions/browser/test_management_policy.h" #include "extensions/common/constants.h" #include "extensions/common/extension_builder.h" #include "extensions/common/manifest_url_handlers.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/value_builder.h" #include "sync/api/fake_sync_change_processor.h" #include "sync/api/sync_change_processor_wrapper_for_test.h" #include "sync/api/sync_data.h" #include "sync/api/sync_error_factory_mock.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(ENABLE_SUPERVISED_USERS) #include "chrome/browser/supervised_user/permission_request_creator.h" #include "chrome/browser/supervised_user/supervised_user_constants.h" #include "chrome/browser/supervised_user/supervised_user_service.h" #include "chrome/browser/supervised_user/supervised_user_service_factory.h" #endif using extensions::AppSorting; using extensions::Extension; using extensions::ExtensionPrefs; using extensions::ExtensionSyncData; using extensions::ExtensionSystem; using extensions::Manifest; using extensions::PermissionSet; using syncer::SyncChange; using syncer::SyncChangeList; namespace { const char good0[] = "behllobkkfkfnphdnhnkndlbkcpglgmj"; const char good2[] = "bjafgdebaacbbbecmhlhpofkepfkgcpa"; const char good_crx[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; const char page_action[] = "obcimlgaoabeegjmmpldobjndiealpln"; const char theme2_crx[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf"; SyncChangeList MakeSyncChangeList(const std::string& id, const sync_pb::EntitySpecifics& specifics, SyncChange::SyncChangeType change_type) { syncer::SyncData sync_data = syncer::SyncData::CreateLocalData(id, "Name", specifics); return SyncChangeList(1, SyncChange(FROM_HERE, change_type, sync_data)); } // This is a FakeSyncChangeProcessor specialization that maintains a store of // SyncData items in the superclass' data_ member variable, treating it like a // map keyed by the extension id from the SyncData. Each instance of this class // should only be used for one model type (which should be either extensions or // apps) to match how the real sync system handles things. class StatefulChangeProcessor : public syncer::FakeSyncChangeProcessor { public: explicit StatefulChangeProcessor(syncer::ModelType expected_type) : expected_type_(expected_type) { EXPECT_TRUE(expected_type == syncer::ModelType::EXTENSIONS || expected_type == syncer::ModelType::APPS); } ~StatefulChangeProcessor() override {} // We let our parent class, FakeSyncChangeProcessor, handle saving the // changes for us, but in addition we "apply" these changes by treating // the FakeSyncChangeProcessor's SyncDataList as a map keyed by extension // id. syncer::SyncError ProcessSyncChanges( const tracked_objects::Location& from_here, const syncer::SyncChangeList& change_list) override { syncer::FakeSyncChangeProcessor::ProcessSyncChanges(from_here, change_list); for (const auto& change : change_list) { syncer::SyncData sync_data = change.sync_data(); EXPECT_EQ(expected_type_, sync_data.GetDataType()); scoped_ptr modified = ExtensionSyncData::CreateFromSyncData(sync_data); // Start by removing any existing entry for this extension id. syncer::SyncDataList& data_list = data(); for (auto iter = data_list.begin(); iter != data_list.end(); ++iter) { scoped_ptr existing = ExtensionSyncData::CreateFromSyncData(*iter); if (existing->id() == modified->id()) { data_list.erase(iter); break; } } // Now add in the new data for this id, if appropriate. if (change.change_type() == SyncChange::ACTION_ADD || change.change_type() == SyncChange::ACTION_UPDATE) { data_list.push_back(sync_data); } else if (change.change_type() != SyncChange::ACTION_DELETE) { ADD_FAILURE() << "Unexpected change type " << change.change_type(); } } return syncer::SyncError(); } // We override this to help catch the error of trying to use a single // StatefulChangeProcessor to process changes for both extensions and apps // sync data. syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override { EXPECT_EQ(expected_type_, type); return FakeSyncChangeProcessor::GetAllSyncData(type); } // This is a helper to vend a wrapped version of this object suitable for // passing in to MergeDataAndStartSyncing, which takes a // scoped_ptr, since in tests we typically don't want to // give up ownership of a local change processor. scoped_ptr GetWrapped() { return make_scoped_ptr(new syncer::SyncChangeProcessorWrapperForTest(this)); } protected: // The expected ModelType of changes that this processor will see. syncer::ModelType expected_type_; DISALLOW_COPY_AND_ASSIGN(StatefulChangeProcessor); }; } // namespace class ExtensionServiceSyncTest : public extensions::ExtensionServiceTestWithInstall { public: void MockSyncStartFlare(bool* was_called, syncer::ModelType* model_type_passed_in, syncer::ModelType model_type) { *was_called = true; *model_type_passed_in = model_type; } // Helper to call MergeDataAndStartSyncing with no server data and dummy // change processor / error factory. void StartSyncing(syncer::ModelType type) { ASSERT_TRUE(type == syncer::EXTENSIONS || type == syncer::APPS); extension_sync_service()->MergeDataAndStartSyncing( type, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); } protected: // Paths to some of the fake extensions. base::FilePath good0_path() { return data_dir() .AppendASCII("good") .AppendASCII("Extensions") .AppendASCII(good0) .AppendASCII("1.0.0.0"); } ExtensionSyncService* extension_sync_service() { return ExtensionSyncService::Get(profile()); } }; TEST_F(ExtensionServiceSyncTest, DeferredSyncStartupPreInstalledComponent) { InitializeEmptyExtensionService(); bool flare_was_called = false; syncer::ModelType triggered_type(syncer::UNSPECIFIED); base::WeakPtrFactory factory(this); extension_sync_service()->SetSyncStartFlareForTesting( base::Bind(&ExtensionServiceSyncTest::MockSyncStartFlare, factory.GetWeakPtr(), &flare_was_called, // Safe due to WeakPtrFactory scope. &triggered_type)); // Safe due to WeakPtrFactory scope. // Install a component extension. std::string manifest; ASSERT_TRUE(base::ReadFileToString( good0_path().Append(extensions::kManifestFilename), &manifest)); service()->component_loader()->Add(manifest, good0_path()); ASSERT_FALSE(service()->is_ready()); service()->Init(); ASSERT_TRUE(service()->is_ready()); // Extensions added before service is_ready() don't trigger sync startup. EXPECT_FALSE(flare_was_called); ASSERT_EQ(syncer::UNSPECIFIED, triggered_type); } TEST_F(ExtensionServiceSyncTest, DeferredSyncStartupPreInstalledNormal) { InitializeGoodInstalledExtensionService(); bool flare_was_called = false; syncer::ModelType triggered_type(syncer::UNSPECIFIED); base::WeakPtrFactory factory(this); extension_sync_service()->SetSyncStartFlareForTesting( base::Bind(&ExtensionServiceSyncTest::MockSyncStartFlare, factory.GetWeakPtr(), &flare_was_called, // Safe due to WeakPtrFactory scope. &triggered_type)); // Safe due to WeakPtrFactory scope. ASSERT_FALSE(service()->is_ready()); service()->Init(); ASSERT_EQ(3u, loaded_.size()); ASSERT_TRUE(service()->is_ready()); // Extensions added before service is_ready() don't trigger sync startup. EXPECT_FALSE(flare_was_called); ASSERT_EQ(syncer::UNSPECIFIED, triggered_type); } TEST_F(ExtensionServiceSyncTest, DeferredSyncStartupOnInstall) { InitializeEmptyExtensionService(); service()->Init(); ASSERT_TRUE(service()->is_ready()); bool flare_was_called = false; syncer::ModelType triggered_type(syncer::UNSPECIFIED); base::WeakPtrFactory factory(this); extension_sync_service()->SetSyncStartFlareForTesting( base::Bind(&ExtensionServiceSyncTest::MockSyncStartFlare, factory.GetWeakPtr(), &flare_was_called, // Safe due to WeakPtrFactory scope. &triggered_type)); // Safe due to WeakPtrFactory scope. base::FilePath path = data_dir().AppendASCII("good.crx"); InstallCRX(path, INSTALL_NEW); EXPECT_TRUE(flare_was_called); EXPECT_EQ(syncer::EXTENSIONS, triggered_type); // Reset. flare_was_called = false; triggered_type = syncer::UNSPECIFIED; // Once sync starts, flare should no longer be invoked. extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); path = data_dir().AppendASCII("page_action.crx"); InstallCRX(path, INSTALL_NEW); EXPECT_FALSE(flare_was_called); ASSERT_EQ(syncer::UNSPECIFIED, triggered_type); } TEST_F(ExtensionServiceSyncTest, DisableExtensionFromSync) { // Start the extensions service with one external extension already installed. base::FilePath source_install_dir = data_dir().AppendASCII("good").AppendASCII("Extensions"); base::FilePath pref_path = source_install_dir.DirName().Append(chrome::kPreferencesFilename); InitializeInstalledExtensionService(pref_path, source_install_dir); // The user has enabled sync. ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile()); sync_service->SetFirstSetupComplete(); service()->Init(); ASSERT_TRUE(service()->is_ready()); ASSERT_EQ(3u, loaded_.size()); // We start enabled. const Extension* extension = service()->GetExtensionById(good0, true); ASSERT_TRUE(extension); ASSERT_TRUE(service()->IsExtensionEnabled(good0)); // Sync starts up. extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); // Then sync data arrives telling us to disable |good0|. ExtensionSyncData disable_good_crx(*extension, false, Extension::DISABLE_USER_ACTION, false, false, ExtensionSyncData::BOOLEAN_UNSET); SyncChangeList list( 1, disable_good_crx.GetSyncChange(SyncChange::ACTION_UPDATE)); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); ASSERT_FALSE(service()->IsExtensionEnabled(good0)); } TEST_F(ExtensionServiceSyncTest, IgnoreSyncChangesWhenLocalStateIsMoreRecent) { // Start the extension service with three extensions already installed. base::FilePath source_install_dir = data_dir().AppendASCII("good").AppendASCII("Extensions"); base::FilePath pref_path = source_install_dir.DirName().Append(chrome::kPreferencesFilename); InitializeInstalledExtensionService(pref_path, source_install_dir); // The user has enabled sync. ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile()); sync_service->SetFirstSetupComplete(); // Make sure ExtensionSyncService is created, so it'll be notified of changes. extension_sync_service(); service()->Init(); ASSERT_TRUE(service()->is_ready()); ASSERT_EQ(3u, loaded_.size()); ASSERT_TRUE(service()->IsExtensionEnabled(good0)); ASSERT_TRUE(service()->IsExtensionEnabled(good2)); // Disable and re-enable good0 before first sync data arrives. service()->DisableExtension(good0, Extension::DISABLE_USER_ACTION); ASSERT_FALSE(service()->IsExtensionEnabled(good0)); service()->EnableExtension(good0); ASSERT_TRUE(service()->IsExtensionEnabled(good0)); // Disable good2 before first sync data arrives (good1 is considered // non-syncable because it has plugin permission). service()->DisableExtension(good2, Extension::DISABLE_USER_ACTION); ASSERT_FALSE(service()->IsExtensionEnabled(good2)); const Extension* extension0 = service()->GetExtensionById(good0, true); const Extension* extension2 = service()->GetExtensionById(good2, true); ASSERT_TRUE(extensions::sync_helper::IsSyncable(extension0)); ASSERT_TRUE(extensions::sync_helper::IsSyncable(extension2)); // Now sync data comes in that says to disable good0 and enable good2. ExtensionSyncData disable_good0(*extension0, false, Extension::DISABLE_USER_ACTION, false, false, ExtensionSyncData::BOOLEAN_UNSET); ExtensionSyncData enable_good2(*extension2, true, Extension::DISABLE_NONE, false, false, ExtensionSyncData::BOOLEAN_UNSET); syncer::SyncDataList sync_data; sync_data.push_back(disable_good0.GetSyncData()); sync_data.push_back(enable_good2.GetSyncData()); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, sync_data, make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); // Both sync changes should be ignored, since the local state was changed // before sync started, and so the local state is considered more recent. EXPECT_TRUE(service()->IsExtensionEnabled(good0)); EXPECT_FALSE(service()->IsExtensionEnabled(good2)); } TEST_F(ExtensionServiceSyncTest, DontSelfNotify) { // Start the extension service with three extensions already installed. base::FilePath source_install_dir = data_dir().AppendASCII("good").AppendASCII("Extensions"); base::FilePath pref_path = source_install_dir.DirName().Append(chrome::kPreferencesFilename); InitializeInstalledExtensionService(pref_path, source_install_dir); // The user has enabled sync. ProfileSyncServiceFactory::GetForProfile(profile())->SetFirstSetupComplete(); // Make sure ExtensionSyncService is created, so it'll be notified of changes. extension_sync_service(); service()->Init(); ASSERT_TRUE(service()->is_ready()); ASSERT_EQ(3u, loaded_.size()); ASSERT_TRUE(service()->IsExtensionEnabled(good0)); syncer::FakeSyncChangeProcessor* processor = new syncer::FakeSyncChangeProcessor; extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(processor), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); processor->changes().clear(); // Simulate various incoming sync changes, and make sure they don't result in // any outgoing changes. { const Extension* extension = service()->GetExtensionById(good0, true); ASSERT_TRUE(extension); // Disable the extension. ExtensionSyncData data(*extension, false, Extension::DISABLE_USER_ACTION, false, false, ExtensionSyncData::BOOLEAN_UNSET); SyncChangeList list(1, data.GetSyncChange(SyncChange::ACTION_UPDATE)); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(processor->changes().empty()); } { const Extension* extension = service()->GetExtensionById(good0, true); ASSERT_TRUE(extension); // Set incognito enabled to true. ExtensionSyncData data(*extension, false, Extension::DISABLE_NONE, true, false, ExtensionSyncData::BOOLEAN_UNSET); SyncChangeList list(1, data.GetSyncChange(SyncChange::ACTION_UPDATE)); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(processor->changes().empty()); } { const Extension* extension = service()->GetExtensionById(good0, true); ASSERT_TRUE(extension); // Add another disable reason. ExtensionSyncData data(*extension, false, Extension::DISABLE_USER_ACTION | Extension::DISABLE_PERMISSIONS_INCREASE, false, false, ExtensionSyncData::BOOLEAN_UNSET); SyncChangeList list(1, data.GetSyncChange(SyncChange::ACTION_UPDATE)); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(processor->changes().empty()); } { const Extension* extension = service()->GetExtensionById(good0, true); ASSERT_TRUE(extension); // Uninstall the extension. ExtensionSyncData data(*extension, false, Extension::DISABLE_USER_ACTION | Extension::DISABLE_PERMISSIONS_INCREASE, false, false, ExtensionSyncData::BOOLEAN_UNSET); SyncChangeList list(1, data.GetSyncChange(SyncChange::ACTION_DELETE)); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(processor->changes().empty()); } } TEST_F(ExtensionServiceSyncTest, GetSyncData) { InitializeEmptyExtensionService(); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); const Extension* extension = service()->GetInstalledExtension(good_crx); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_EQ(extension->id(), data->id()); EXPECT_FALSE(data->uninstalled()); EXPECT_EQ(service()->IsExtensionEnabled(good_crx), data->enabled()); EXPECT_EQ(extensions::util::IsIncognitoEnabled(good_crx, profile()), data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_UNSET, data->all_urls_enabled()); EXPECT_EQ(data->version(), *extension->version()); EXPECT_EQ(extensions::ManifestURL::GetUpdateURL(extension), data->update_url()); EXPECT_EQ(extension->name(), data->name()); } TEST_F(ExtensionServiceSyncTest, GetSyncDataDisableReasons) { InitializeEmptyExtensionService(); const Extension* extension = InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_TRUE(data->enabled()); EXPECT_TRUE(data->supports_disable_reasons()); EXPECT_EQ(Extension::DISABLE_NONE, data->disable_reasons()); } // Syncable disable reason, should propagate to sync. service()->DisableExtension(good_crx, Extension::DISABLE_USER_ACTION); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_FALSE(data->enabled()); EXPECT_TRUE(data->supports_disable_reasons()); EXPECT_EQ(Extension::DISABLE_USER_ACTION, data->disable_reasons()); } service()->EnableExtension(good_crx); // Non-syncable disable reason. The sync data should still say "enabled". service()->DisableExtension(good_crx, Extension::DISABLE_RELOAD); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_TRUE(data->enabled()); EXPECT_TRUE(data->supports_disable_reasons()); EXPECT_EQ(Extension::DISABLE_NONE, data->disable_reasons()); } service()->EnableExtension(good_crx); // Both a syncable and a non-syncable disable reason, only the former should // propagate to sync. service()->DisableExtension( good_crx, Extension::DISABLE_USER_ACTION | Extension::DISABLE_RELOAD); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_FALSE(data->enabled()); EXPECT_TRUE(data->supports_disable_reasons()); EXPECT_EQ(Extension::DISABLE_USER_ACTION, data->disable_reasons()); } service()->EnableExtension(good_crx); } TEST_F(ExtensionServiceSyncTest, GetSyncDataTerminated) { InitializeEmptyExtensionService(); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); TerminateExtension(good_crx); const Extension* extension = service()->GetInstalledExtension(good_crx); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_EQ(extension->id(), data->id()); EXPECT_FALSE(data->uninstalled()); EXPECT_EQ(service()->IsExtensionEnabled(good_crx), data->enabled()); EXPECT_EQ(extensions::util::IsIncognitoEnabled(good_crx, profile()), data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_UNSET, data->all_urls_enabled()); EXPECT_EQ(data->version(), *extension->version()); EXPECT_EQ(extensions::ManifestURL::GetUpdateURL(extension), data->update_url()); EXPECT_EQ(extension->name(), data->name()); } TEST_F(ExtensionServiceSyncTest, GetSyncDataFilter) { InitializeEmptyExtensionService(); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); const Extension* extension = service()->GetInstalledExtension(good_crx); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 0U); } TEST_F(ExtensionServiceSyncTest, GetSyncExtensionDataUserSettings) { InitializeEmptyExtensionService(); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); const Extension* extension = service()->GetInstalledExtension(good_crx); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_TRUE(data->enabled()); EXPECT_FALSE(data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_UNSET, data->all_urls_enabled()); } service()->DisableExtension(good_crx, Extension::DISABLE_USER_ACTION); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_FALSE(data->enabled()); EXPECT_FALSE(data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_UNSET, data->all_urls_enabled()); } extensions::util::SetIsIncognitoEnabled(good_crx, profile(), true); extensions::util::SetAllowedScriptingOnAllUrls( good_crx, profile(), false); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_FALSE(data->enabled()); EXPECT_TRUE(data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_FALSE, data->all_urls_enabled()); } service()->EnableExtension(good_crx); extensions::util::SetAllowedScriptingOnAllUrls( good_crx, profile(), true); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(list.size(), 1U); scoped_ptr data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(data.get()); EXPECT_TRUE(data->enabled()); EXPECT_TRUE(data->incognito_enabled()); EXPECT_EQ(ExtensionSyncData::BOOLEAN_TRUE, data->all_urls_enabled()); } } TEST_F(ExtensionServiceSyncTest, SyncForUninstalledExternalExtension) { InitializeEmptyExtensionService(); InstallCRXWithLocation( data_dir().AppendASCII("good.crx"), Manifest::EXTERNAL_PREF, INSTALL_NEW); const Extension* extension = service()->GetInstalledExtension(good_crx); ASSERT_TRUE(extension); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); StartSyncing(syncer::APPS); UninstallExtension(good_crx, false); EXPECT_TRUE( ExtensionPrefs::Get(profile())->IsExternalExtensionUninstalled(good_crx)); sync_pb::EntitySpecifics specifics; sync_pb::AppSpecifics* app_specifics = specifics.mutable_app(); sync_pb::ExtensionSpecifics* extension_specifics = app_specifics->mutable_extension(); extension_specifics->set_id(good_crx); extension_specifics->set_version("1.0"); extension_specifics->set_enabled(true); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE( ExtensionPrefs::Get(profile())->IsExternalExtensionUninstalled(good_crx)); } TEST_F(ExtensionServiceSyncTest, GetSyncAppDataUserSettings) { InitializeEmptyExtensionService(); const Extension* app = PackAndInstallCRX(data_dir().AppendASCII("app"), INSTALL_NEW); ASSERT_TRUE(app); ASSERT_TRUE(app->is_app()); extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); syncer::StringOrdinal initial_ordinal = syncer::StringOrdinal::CreateInitialOrdinal(); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::APPS); ASSERT_EQ(list.size(), 1U); scoped_ptr app_sync_data = ExtensionSyncData::CreateFromSyncData(list[0]); EXPECT_TRUE(initial_ordinal.Equals(app_sync_data->app_launch_ordinal())); EXPECT_TRUE(initial_ordinal.Equals(app_sync_data->page_ordinal())); } AppSorting* sorting = ExtensionSystem::Get(profile())->app_sorting(); sorting->SetAppLaunchOrdinal(app->id(), initial_ordinal.CreateAfter()); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::APPS); ASSERT_EQ(list.size(), 1U); scoped_ptr app_sync_data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(app_sync_data.get()); EXPECT_TRUE(initial_ordinal.LessThan(app_sync_data->app_launch_ordinal())); EXPECT_TRUE(initial_ordinal.Equals(app_sync_data->page_ordinal())); } sorting->SetPageOrdinal(app->id(), initial_ordinal.CreateAfter()); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::APPS); ASSERT_EQ(list.size(), 1U); scoped_ptr app_sync_data = ExtensionSyncData::CreateFromSyncData(list[0]); ASSERT_TRUE(app_sync_data.get()); EXPECT_TRUE(initial_ordinal.LessThan(app_sync_data->app_launch_ordinal())); EXPECT_TRUE(initial_ordinal.LessThan(app_sync_data->page_ordinal())); } } // TODO (rdevlin.cronin): The OnExtensionMoved() method has been removed from // ExtensionService, so this test probably needs a new home. Unfortunately, it // relies pretty heavily on things like InitializeExtension[Sync]Service() and // PackAndInstallCRX(). When we clean up a bit more, this should move out. TEST_F(ExtensionServiceSyncTest, GetSyncAppDataUserSettingsOnExtensionMoved) { InitializeEmptyExtensionService(); const size_t kAppCount = 3; const Extension* apps[kAppCount]; apps[0] = PackAndInstallCRX(data_dir().AppendASCII("app1"), INSTALL_NEW); apps[1] = PackAndInstallCRX(data_dir().AppendASCII("app2"), INSTALL_NEW); apps[2] = PackAndInstallCRX(data_dir().AppendASCII("app4"), INSTALL_NEW); for (size_t i = 0; i < kAppCount; ++i) { ASSERT_TRUE(apps[i]); ASSERT_TRUE(apps[i]->is_app()); } extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); ExtensionSystem::Get(service()->GetBrowserContext()) ->app_sorting() ->OnExtensionMoved(apps[0]->id(), apps[1]->id(), apps[2]->id()); { syncer::SyncDataList list = extension_sync_service()->GetAllSyncData(syncer::APPS); ASSERT_EQ(list.size(), 3U); scoped_ptr data[kAppCount]; for (size_t i = 0; i < kAppCount; ++i) { data[i] = ExtensionSyncData::CreateFromSyncData(list[i]); ASSERT_TRUE(data[i].get()); } // The sync data is not always in the same order our apps were installed in, // so we do that sorting here so we can make sure the values are changed as // expected. syncer::StringOrdinal app_launch_ordinals[kAppCount]; for (size_t i = 0; i < kAppCount; ++i) { for (size_t j = 0; j < kAppCount; ++j) { if (apps[i]->id() == data[j]->id()) app_launch_ordinals[i] = data[j]->app_launch_ordinal(); } } EXPECT_TRUE(app_launch_ordinals[1].LessThan(app_launch_ordinals[0])); EXPECT_TRUE(app_launch_ordinals[0].LessThan(app_launch_ordinals[2])); } } TEST_F(ExtensionServiceSyncTest, GetSyncDataList) { InitializeEmptyExtensionService(); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); InstallCRX(data_dir().AppendASCII("page_action.crx"), INSTALL_NEW); InstallCRX(data_dir().AppendASCII("theme.crx"), INSTALL_NEW); InstallCRX(data_dir().AppendASCII("theme2.crx"), INSTALL_NEW); extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); service()->DisableExtension(page_action, Extension::DISABLE_USER_ACTION); TerminateExtension(theme2_crx); EXPECT_EQ(0u, extension_sync_service()->GetAllSyncData(syncer::APPS).size()); EXPECT_EQ( 2u, extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS).size()); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataUninstall) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(good_crx); ext_specifics->set_version("1.0"); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_DELETE); // Should do nothing. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->GetExtensionById(good_crx, true)); // Install the extension. base::FilePath extension_path = data_dir().AppendASCII("good.crx"); InstallCRX(extension_path, INSTALL_NEW); EXPECT_TRUE(service()->GetExtensionById(good_crx, true)); // Should uninstall the extension. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->GetExtensionById(good_crx, true)); // Should again do nothing. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->GetExtensionById(good_crx, true)); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataWrongType) { InitializeEmptyExtensionService(); StartSyncing(syncer::EXTENSIONS); StartSyncing(syncer::APPS); // Install the extension. base::FilePath extension_path = data_dir().AppendASCII("good.crx"); InstallCRX(extension_path, INSTALL_NEW); EXPECT_TRUE(service()->GetExtensionById(good_crx, true)); sync_pb::EntitySpecifics specifics; sync_pb::AppSpecifics* app_specifics = specifics.mutable_app(); sync_pb::ExtensionSpecifics* extension_specifics = app_specifics->mutable_extension(); extension_specifics->set_id(good_crx); extension_specifics->set_version( service()->GetInstalledExtension(good_crx)->version()->GetString()); { extension_specifics->set_enabled(true); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_DELETE); // Should do nothing extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->GetExtensionById(good_crx, true)); } { extension_specifics->set_enabled(false); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); // Should again do nothing. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->GetExtensionById(good_crx, false)); } } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataSettings) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_FALSE(extensions::util::IsIncognitoEnabled(good_crx, profile())); EXPECT_FALSE(extensions::util::HasSetAllowedScriptingOnAllUrls( good_crx, profile())); const bool kDefaultAllowedScripting = extensions::util::DefaultAllowedScriptingOnAllUrls(); EXPECT_EQ(kDefaultAllowedScripting, extensions::util::AllowedScriptingOnAllUrls(good_crx, profile())); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(good_crx); ext_specifics->set_version( service()->GetInstalledExtension(good_crx)->version()->GetString()); ext_specifics->set_enabled(false); { SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->IsExtensionEnabled(good_crx)); EXPECT_FALSE(extensions::util::IsIncognitoEnabled(good_crx, profile())); EXPECT_FALSE(extensions::util::HasSetAllowedScriptingOnAllUrls( good_crx, profile())); EXPECT_EQ(kDefaultAllowedScripting, extensions::util::AllowedScriptingOnAllUrls(good_crx, profile())); } { ext_specifics->set_enabled(true); ext_specifics->set_incognito_enabled(true); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::IsIncognitoEnabled(good_crx, profile())); } { ext_specifics->set_enabled(false); ext_specifics->set_incognito_enabled(true); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::IsIncognitoEnabled(good_crx, profile())); } { ext_specifics->set_enabled(true); ext_specifics->set_all_urls_enabled(!kDefaultAllowedScripting); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::HasSetAllowedScriptingOnAllUrls( good_crx, profile())); EXPECT_EQ(!kDefaultAllowedScripting, extensions::util::AllowedScriptingOnAllUrls(good_crx, profile())); } { ext_specifics->set_all_urls_enabled(kDefaultAllowedScripting); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::HasSetAllowedScriptingOnAllUrls( good_crx, profile())); EXPECT_EQ(kDefaultAllowedScripting, extensions::util::AllowedScriptingOnAllUrls(good_crx, profile())); } EXPECT_FALSE(service()->pending_extension_manager()->IsIdPending(good_crx)); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataNewExtension) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); const base::FilePath path = data_dir().AppendASCII("good.crx"); const ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); struct TestCase { const char* name; // For failure output only. bool sync_enabled; // The "enabled" flag coming in from Sync. // The disable reason(s) coming in from Sync, or -1 for "not set". int sync_disable_reasons; // The disable reason(s) that should be set on the installed extension. // This will usually be the same as |sync_disable_reasons|, but see the // "Legacy" case. int expect_disable_reasons; // Whether the extension's permissions should be auto-granted during // installation. bool expect_permissions_granted; } test_cases[] = { // Standard case: Extension comes in enabled; permissions should be granted // during installation. { "Standard", true, 0, 0, true }, // If the extension comes in disabled, its permissions should still be // granted (the user already approved them on another machine). { "Disabled", false, Extension::DISABLE_USER_ACTION, Extension::DISABLE_USER_ACTION, true }, // Legacy case (set_id(good_crx); ext_specifics->set_version(base::Version("1").GetString()); ext_specifics->set_enabled(test_case.sync_enabled); if (test_case.sync_disable_reasons != -1) ext_specifics->set_disable_reasons(test_case.sync_disable_reasons); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); ASSERT_TRUE(service()->pending_extension_manager()->IsIdPending(good_crx)); UpdateExtension(good_crx, path, test_case.sync_enabled ? ENABLED : DISABLED); EXPECT_EQ(test_case.expect_disable_reasons, prefs->GetDisableReasons(good_crx)); scoped_ptr permissions = prefs->GetGrantedPermissions(good_crx); EXPECT_EQ(test_case.expect_permissions_granted, !permissions->IsEmpty()); ASSERT_FALSE(service()->pending_extension_manager()->IsIdPending(good_crx)); // Remove the extension again, so we can install it again for the next case. UninstallExtension(good_crx, false, test_case.sync_enabled ? Extension::ENABLED : Extension::DISABLED); } } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataTerminatedExtension) { InitializeExtensionServiceWithUpdater(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); TerminateExtension(good_crx); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_FALSE(extensions::util::IsIncognitoEnabled(good_crx, profile())); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(good_crx); ext_specifics->set_version( service()->GetInstalledExtension(good_crx)->version()->GetString()); ext_specifics->set_enabled(false); ext_specifics->set_incognito_enabled(true); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::IsIncognitoEnabled(good_crx, profile())); EXPECT_FALSE(service()->pending_extension_manager()->IsIdPending(good_crx)); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataVersionCheck) { InitializeExtensionServiceWithUpdater(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_FALSE(extensions::util::IsIncognitoEnabled(good_crx, profile())); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(good_crx); ext_specifics->set_enabled(true); const base::Version installed_version = *service()->GetInstalledExtension(good_crx)->version(); { ext_specifics->set_version(installed_version.GetString()); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); // Should do nothing if extension version == sync version. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->updater()->WillCheckSoon()); // Make sure the version we'll send back to sync didn't change. syncer::SyncDataList data = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(1u, data.size()); scoped_ptr extension_data = ExtensionSyncData::CreateFromSyncData(data[0]); ASSERT_TRUE(extension_data); EXPECT_EQ(installed_version, extension_data->version()); } // Should do nothing if extension version > sync version. { ext_specifics->set_version("0.0.0.0"); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_FALSE(service()->updater()->WillCheckSoon()); // Make sure the version we'll send back to sync didn't change. syncer::SyncDataList data = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(1u, data.size()); scoped_ptr extension_data = ExtensionSyncData::CreateFromSyncData(data[0]); ASSERT_TRUE(extension_data); EXPECT_EQ(installed_version, extension_data->version()); } // Should kick off an update if extension version < sync version. { const base::Version new_version("9.9.9.9"); ext_specifics->set_version(new_version.GetString()); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->updater()->WillCheckSoon()); // Make sure that we'll send the NEW version back to sync, even though we // haven't actually updated yet. This is to prevent the data in sync from // flip-flopping back and forth until all clients are up to date. syncer::SyncDataList data = extension_sync_service()->GetAllSyncData(syncer::EXTENSIONS); ASSERT_EQ(1u, data.size()); scoped_ptr extension_data = ExtensionSyncData::CreateFromSyncData(data[0]); ASSERT_TRUE(extension_data); EXPECT_EQ(new_version, extension_data->version()); } EXPECT_FALSE(service()->pending_extension_manager()->IsIdPending(good_crx)); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataNotInstalled) { InitializeExtensionServiceWithUpdater(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(good_crx); ext_specifics->set_enabled(false); ext_specifics->set_incognito_enabled(true); ext_specifics->set_update_url("http://www.google.com/"); ext_specifics->set_version("1.2.3.4"); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); EXPECT_TRUE(service()->IsExtensionEnabled(good_crx)); EXPECT_FALSE(extensions::util::IsIncognitoEnabled(good_crx, profile())); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(service()->updater()->WillCheckSoon()); EXPECT_FALSE(service()->IsExtensionEnabled(good_crx)); EXPECT_TRUE(extensions::util::IsIncognitoEnabled(good_crx, profile())); const extensions::PendingExtensionInfo* info; EXPECT_TRUE( (info = service()->pending_extension_manager()->GetById(good_crx))); EXPECT_EQ(ext_specifics->update_url(), info->update_url().spec()); EXPECT_TRUE(info->is_from_sync()); EXPECT_EQ(Manifest::INTERNAL, info->install_source()); // TODO(akalin): Figure out a way to test |info.ShouldAllowInstall()|. } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataEnableDisable) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); const ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); struct TestCase { const char* name; // For failure output only. // Set of disable reasons before any Sync data comes in. If this is != 0, // the extension is disabled. int previous_disable_reasons; bool sync_enable; // The enabled flag coming in from Sync. // The disable reason(s) coming in from Sync, or -1 for "not set". int sync_disable_reasons; // The expected set of disable reasons after processing the Sync update. The // extension should be disabled iff this is != 0. int expect_disable_reasons; } test_cases[] = { { "NopEnable", 0, true, 0, 0 }, { "NopDisable", Extension::DISABLE_USER_ACTION, false, Extension::DISABLE_USER_ACTION, Extension::DISABLE_USER_ACTION }, { "Enable", Extension::DISABLE_USER_ACTION, true, 0, 0 }, { "Disable", 0, false, Extension::DISABLE_USER_ACTION, Extension::DISABLE_USER_ACTION }, { "AddDisableReason", Extension::DISABLE_REMOTE_INSTALL, false, Extension::DISABLE_REMOTE_INSTALL | Extension::DISABLE_USER_ACTION, Extension::DISABLE_REMOTE_INSTALL | Extension::DISABLE_USER_ACTION }, { "RemoveDisableReason", Extension::DISABLE_REMOTE_INSTALL | Extension::DISABLE_USER_ACTION, false, Extension::DISABLE_USER_ACTION, Extension::DISABLE_USER_ACTION }, { "PreserveLocalDisableReason", Extension::DISABLE_RELOAD, true, 0, Extension::DISABLE_RELOAD }, { "PreserveOnlyLocalDisableReason", Extension::DISABLE_USER_ACTION | Extension::DISABLE_RELOAD, true, 0, Extension::DISABLE_RELOAD }, // Interaction with Chrome clients <=M44, which don't sync disable_reasons // at all (any existing reasons are preserved). { "M44Enable", Extension::DISABLE_USER_ACTION, true, -1, 0 }, // An M44 client enables an extension that had been disabled on a new // client. The disable reasons are still be there, but should be ignored. { "M44ReEnable", Extension::DISABLE_USER_ACTION, true, Extension::DISABLE_USER_ACTION, 0 }, { "M44Disable", 0, false, -1, Extension::DISABLE_USER_ACTION }, { "M44ReDisable", 0, false, 0, Extension::DISABLE_USER_ACTION }, { "M44AlreadyDisabledByUser", Extension::DISABLE_USER_ACTION, false, -1, Extension::DISABLE_USER_ACTION}, { "M44AlreadyDisabledWithOtherReason", Extension::DISABLE_REMOTE_INSTALL, false, -1, Extension::DISABLE_REMOTE_INSTALL | Extension::DISABLE_USER_ACTION }, }; for (const TestCase& test_case : test_cases) { SCOPED_TRACE(test_case.name); std::string id; std::string version; // Don't keep |extension| around longer than necessary. { const Extension* extension = InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); // The extension should now be installed and enabled. ASSERT_TRUE(extension); id = extension->id(); version = extension->VersionString(); } ASSERT_TRUE(registry()->enabled_extensions().Contains(id)); // Disable it if the test case says so. if (test_case.previous_disable_reasons) { service()->DisableExtension(id, test_case.previous_disable_reasons); ASSERT_TRUE(registry()->disabled_extensions().Contains(id)); } // Now a sync update comes in. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_enabled(test_case.sync_enable); ext_specifics->set_version(version); if (test_case.sync_disable_reasons != -1) ext_specifics->set_disable_reasons(test_case.sync_disable_reasons); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // Check expectations. const bool expect_enabled = !test_case.expect_disable_reasons; EXPECT_EQ(expect_enabled, service()->IsExtensionEnabled(id)); EXPECT_EQ(test_case.expect_disable_reasons, prefs->GetDisableReasons(id)); // Remove the extension again, so we can install it again for the next case. UninstallExtension(id, false, expect_enabled ? Extension::ENABLED : Extension::DISABLED); } } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataDeferredEnable) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); base::FilePath base_path = data_dir().AppendASCII("permissions_increase"); base::FilePath pem_path = base_path.AppendASCII("permissions.pem"); base::FilePath path = base_path.AppendASCII("v1"); const Extension* extension = PackAndInstallCRX(path, pem_path, INSTALL_NEW); // The extension must now be installed and enabled. ASSERT_TRUE(extension); ASSERT_TRUE(registry()->enabled_extensions().Contains(extension->id())); // Save the id, as the extension object will be destroyed during updating. std::string id = extension->id(); // Update to a new version with increased permissions. path = base_path.AppendASCII("v2"); PackCRXAndUpdateExtension(id, path, pem_path, DISABLED); // Now a sync update comes in, telling us to re-enable a *newer* version. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_version("3"); ext_specifics->set_enabled(true); ext_specifics->set_disable_reasons(Extension::DISABLE_NONE); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // Since the version didn't match, the extension should still be disabled. EXPECT_TRUE(registry()->disabled_extensions().Contains(id)); // After we update to the matching version, the extension should get enabled. path = base_path.AppendASCII("v3"); PackCRXAndUpdateExtension(id, path, pem_path, ENABLED); } TEST_F(ExtensionServiceSyncTest, ProcessSyncDataPermissionApproval) { // This is the update URL specified in the test extension. Setting it here is // necessary to make it considered syncable. base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryUpdateURL, "http://localhost/autoupdate/updates.xml"); InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); const base::FilePath base_path = data_dir().AppendASCII("permissions_increase"); const base::FilePath pem_path = base_path.AppendASCII("permissions.pem"); const base::FilePath path_v1 = base_path.AppendASCII("v1"); const base::FilePath path_v2 = base_path.AppendASCII("v2"); base::ScopedTempDir crx_dir; ASSERT_TRUE(crx_dir.CreateUniqueTempDir()); const base::FilePath crx_path_v1 = crx_dir.path().AppendASCII("temp1.crx"); PackCRX(path_v1, pem_path, crx_path_v1); const base::FilePath crx_path_v2 = crx_dir.path().AppendASCII("temp2.crx"); PackCRX(path_v2, pem_path, crx_path_v2); const std::string v1("1"); const std::string v2("2"); const ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); struct TestCase { const char* name; // For failure output only. const std::string& sync_version; // The version coming in from Sync. // The disable reason(s) coming in from Sync, or -1 for "not set". int sync_disable_reasons; // The expected set of disable reasons after processing the Sync update. The // extension should be enabled iff this is 0. int expect_disable_reasons; // Whether the extension's permissions should be auto-granted. bool expect_permissions_granted; } test_cases[] = { // Sync tells us to re-enable an older version. No permissions should be // granted, since we can't be sure if the user actually approved the right // set of permissions. { "OldVersion", v1, 0, Extension::DISABLE_PERMISSIONS_INCREASE, false }, // Legacy case: Sync tells us to re-enable the extension, but doesn't // specify disable reasons. No permissions should be granted. { "Legacy", v2, -1, Extension::DISABLE_PERMISSIONS_INCREASE, false }, // Sync tells us to re-enable the extension and explicitly removes the // disable reasons. Now the extension should have its permissions granted. { "GrantPermissions", v2, 0, Extension::DISABLE_NONE, true }, }; for (const TestCase& test_case : test_cases) { SCOPED_TRACE(test_case.name); std::string id; // Don't keep |extension| around longer than necessary (it'll be destroyed // during updating). { const Extension* extension = InstallCRX(crx_path_v1, INSTALL_NEW); // The extension should now be installed and enabled. ASSERT_TRUE(extension); ASSERT_EQ(v1, extension->VersionString()); id = extension->id(); } ASSERT_TRUE(registry()->enabled_extensions().Contains(id)); scoped_ptr granted_permissions_v1 = prefs->GetGrantedPermissions(id); // Update to a new version with increased permissions. UpdateExtension(id, crx_path_v2, DISABLED); // Now the extension should be disabled due to a permissions increase. { const Extension* extension = registry()->disabled_extensions().GetByID(id); ASSERT_TRUE(extension); ASSERT_EQ(v2, extension->VersionString()); } ASSERT_TRUE(prefs->HasDisableReason( id, Extension::DISABLE_PERMISSIONS_INCREASE)); // No new permissions should have been granted. scoped_ptr granted_permissions_v2 = prefs->GetGrantedPermissions(id); ASSERT_EQ(*granted_permissions_v1, *granted_permissions_v2); // Now a sync update comes in. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_enabled(true); ext_specifics->set_version(test_case.sync_version); if (test_case.sync_disable_reasons != -1) ext_specifics->set_disable_reasons(test_case.sync_disable_reasons); SyncChangeList list = MakeSyncChangeList(good_crx, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // Check expectations. const bool expect_enabled = !test_case.expect_disable_reasons; EXPECT_EQ(expect_enabled, service()->IsExtensionEnabled(id)); EXPECT_EQ(test_case.expect_disable_reasons, prefs->GetDisableReasons(id)); scoped_ptr granted_permissions = prefs->GetGrantedPermissions(id); if (test_case.expect_permissions_granted) { scoped_ptr active_permissions = prefs->GetActivePermissions(id); EXPECT_EQ(*granted_permissions, *active_permissions); } else { EXPECT_EQ(*granted_permissions, *granted_permissions_v1); } // Remove the extension again, so we can install it again for the next case. UninstallExtension(id, false, expect_enabled ? Extension::ENABLED : Extension::DISABLED); } } // Regression test for crbug.com/558299 TEST_F(ExtensionServiceSyncTest, DontSyncThemes) { InitializeEmptyExtensionService(); // The user has enabled sync. ProfileSyncServiceFactory::GetForProfile(profile())->SetFirstSetupComplete(); // Make sure ExtensionSyncService is created, so it'll be notified of changes. extension_sync_service(); service()->Init(); ASSERT_TRUE(service()->is_ready()); syncer::FakeSyncChangeProcessor* processor = new syncer::FakeSyncChangeProcessor; extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(processor), make_scoped_ptr(new syncer::SyncErrorFactoryMock)); processor->changes().clear(); // Sanity check: Installing an extension should result in a sync change. InstallCRX(data_dir().AppendASCII("good.crx"), INSTALL_NEW); EXPECT_EQ(1u, processor->changes().size()); processor->changes().clear(); // Installing a theme should not result in a sync change (themes are handled // separately by ThemeSyncableService). InstallCRX(data_dir().AppendASCII("theme.crx"), INSTALL_NEW); EXPECT_TRUE(processor->changes().empty()); } #if defined(ENABLE_SUPERVISED_USERS) class ExtensionServiceTestSupervised : public ExtensionServiceSyncTest, public SupervisedUserService::Delegate { public: ExtensionServiceTestSupervised() : field_trial_list_(new base::MockEntropyProvider()) {} void SetUp() override { ExtensionServiceSyncTest::SetUp(); // This is the update URL specified in the permissions test extension. // Setting it here is necessary to make the extension considered syncable. base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryUpdateURL, "http://localhost/autoupdate/updates.xml"); } void TearDown() override { supervised_user_service()->SetDelegate(nullptr); ExtensionServiceSyncTest::TearDown(); } protected: void InitNeedCustodianApprovalFieldTrial(bool enabled) { // Group name doesn't matter. base::FieldTrialList::CreateFieldTrial( "SupervisedUserExtensionPermissionIncrease", "group"); std::map params; params["legacy_supervised_user"] = enabled ? "true" : "false"; params["child_account"] = enabled ? "true" : "false"; variations::AssociateVariationParams( "SupervisedUserExtensionPermissionIncrease", "group", params); } void InitServices(bool profile_is_supervised) { ExtensionServiceInitParams params = CreateDefaultInitParams(); params.profile_is_supervised = profile_is_supervised; InitializeExtensionService(params); StartSyncing(syncer::EXTENSIONS); supervised_user_service()->SetDelegate(this); supervised_user_service()->Init(); } std::string InstallPermissionsTestExtension() { const std::string version("1"); const Extension* extension = PackAndInstallCRX(dir_path(version), pem_path(), INSTALL_NEW, Extension::WAS_INSTALLED_BY_CUSTODIAN); // The extension must now be installed and enabled. EXPECT_TRUE(extension); EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id())); EXPECT_EQ(version, extension->VersionString()); return extension->id(); } void UpdatePermissionsTestExtension(const std::string& id, const std::string& version, UpdateState expected_state) { PackCRXAndUpdateExtension(id, dir_path(version), pem_path(), expected_state); const Extension* extension = registry()->GetInstalledExtension(id); ASSERT_TRUE(extension); // The version should have been updated. EXPECT_EQ(version, extension->VersionString()); } SupervisedUserService* supervised_user_service() { return SupervisedUserServiceFactory::GetForProfile(profile()); } static std::string UpdateRequestId(const std::string& extension_id, const std::string& version) { return SupervisedUserService::GetExtensionUpdateRequestId( extension_id, base::Version(version)); } private: // This prevents the legacy supervised user init code from running. bool SetActive(bool active) override { return true; } base::FilePath base_path() const { return data_dir().AppendASCII("permissions_increase"); } base::FilePath dir_path(const std::string& version) const { return base_path().AppendASCII("v" + version); } base::FilePath pem_path() const { return base_path().AppendASCII("permissions.pem"); } base::FieldTrialList field_trial_list_; }; class MockPermissionRequestCreator : public PermissionRequestCreator { public: MockPermissionRequestCreator() {} ~MockPermissionRequestCreator() override {} bool IsEnabled() const override { return true; } void CreateURLAccessRequest(const GURL& url_requested, const SuccessCallback& callback) override { FAIL(); } MOCK_METHOD2(CreateExtensionUpdateRequest, void(const std::string& id, const SupervisedUserService::SuccessCallback& callback)); private: DISALLOW_COPY_AND_ASSIGN(MockPermissionRequestCreator); }; TEST_F(ExtensionServiceTestSupervised, InstallOnlyAllowedByCustodian) { InitServices(true /* profile_is_supervised */); base::FilePath path1 = data_dir().AppendASCII("good.crx"); base::FilePath path2 = data_dir().AppendASCII("good2048.crx"); const Extension* extensions[] = { InstallCRX(path1, INSTALL_FAILED), InstallCRX(path2, INSTALL_NEW, Extension::WAS_INSTALLED_BY_CUSTODIAN) }; // Only the extension with the "installed by custodian" flag should have been // installed and enabled. EXPECT_FALSE(extensions[0]); ASSERT_TRUE(extensions[1]); EXPECT_TRUE(registry()->enabled_extensions().Contains(extensions[1]->id())); } TEST_F(ExtensionServiceTestSupervised, PreinstalledExtension) { InitServices(false /* profile_is_supervised */); // Install an extension. base::FilePath path = data_dir().AppendASCII("good.crx"); const Extension* extension = InstallCRX(path, INSTALL_NEW); std::string id = extension->id(); // Now make the profile supervised. profile()->AsTestingProfile()->SetSupervisedUserId( supervised_users::kChildAccountSUID); // The extension should not be enabled anymore. EXPECT_FALSE(registry()->enabled_extensions().Contains(id)); } TEST_F(ExtensionServiceTestSupervised, UpdateWithoutPermissionIncrease) { InitServices(true /* profile_is_supervised */); base::FilePath base_path = data_dir().AppendASCII("autoupdate"); base::FilePath pem_path = base_path.AppendASCII("key.pem"); const Extension* extension = PackAndInstallCRX(base_path.AppendASCII("v1"), pem_path, INSTALL_NEW, Extension::WAS_INSTALLED_BY_CUSTODIAN); // The extension must now be installed and enabled. ASSERT_TRUE(extension); ASSERT_TRUE(registry()->enabled_extensions().Contains(extension->id())); // Save the id, as the extension object will be destroyed during updating. std::string id = extension->id(); std::string old_version = extension->VersionString(); // Update to a new version. PackCRXAndUpdateExtension(id, base_path.AppendASCII("v2"), pem_path, ENABLED); // The extension should still be there and enabled. extension = registry()->enabled_extensions().GetByID(id); ASSERT_TRUE(extension); // The version should have changed. EXPECT_NE(extension->VersionString(), old_version); } TEST_F(ExtensionServiceTestSupervised, UpdateWithPermissionIncreaseNoApproval) { InitNeedCustodianApprovalFieldTrial(false); InitServices(true /* profile_is_supervised */); MockPermissionRequestCreator* creator = new MockPermissionRequestCreator; supervised_user_service()->AddPermissionRequestCreator( make_scoped_ptr(creator)); std::string id = InstallPermissionsTestExtension(); // Update to a new version with increased permissions. // Since we don't require the custodian's approval, no permission request // should be created. const std::string version2("2"); EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version2), testing::_)) .Times(0); UpdatePermissionsTestExtension(id, version2, DISABLED); } TEST_F(ExtensionServiceTestSupervised, UpdateWithPermissionIncreaseApprovalOldVersion) { InitNeedCustodianApprovalFieldTrial(true); InitServices(true /* profile_is_supervised */); MockPermissionRequestCreator* creator = new MockPermissionRequestCreator; supervised_user_service()->AddPermissionRequestCreator( make_scoped_ptr(creator)); const std::string version1("1"); const std::string version2("2"); std::string id = InstallPermissionsTestExtension(); // Update to a new version with increased permissions. EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version2), testing::_)); UpdatePermissionsTestExtension(id, version2, DISABLED); // Simulate a custodian approval for re-enabling the extension coming in // through Sync, but set the old version. This can happen when there already // was a pending request for an earlier version of the extension. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_enabled(true); ext_specifics->set_disable_reasons(Extension::DISABLE_NONE); ext_specifics->set_installed_by_custodian(true); ext_specifics->set_version(version1); // Attempting to re-enable an old version should result in a permission // request for the current version. EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version2), testing::_)); SyncChangeList list = MakeSyncChangeList(id, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // The re-enable should be ignored, since the version doesn't match. EXPECT_FALSE(registry()->enabled_extensions().Contains(id)); EXPECT_FALSE(extension_sync_service()->HasPendingReenable( id, base::Version(version1))); EXPECT_FALSE(extension_sync_service()->HasPendingReenable( id, base::Version(version2))); } TEST_F(ExtensionServiceTestSupervised, UpdateWithPermissionIncreaseApprovalMatchingVersion) { InitNeedCustodianApprovalFieldTrial(true); InitServices(true /* profile_is_supervised */); MockPermissionRequestCreator* creator = new MockPermissionRequestCreator; supervised_user_service()->AddPermissionRequestCreator( make_scoped_ptr(creator)); std::string id = InstallPermissionsTestExtension(); // Update to a new version with increased permissions. const std::string version2("2"); EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version2), testing::_)); UpdatePermissionsTestExtension(id, version2, DISABLED); // Simulate a custodian approval for re-enabling the extension coming in // through Sync. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_enabled(true); ext_specifics->set_disable_reasons(Extension::DISABLE_NONE); ext_specifics->set_installed_by_custodian(true); ext_specifics->set_version(version2); SyncChangeList list = MakeSyncChangeList(id, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // The extension should have gotten re-enabled. EXPECT_TRUE(registry()->enabled_extensions().Contains(id)); } TEST_F(ExtensionServiceTestSupervised, UpdateWithPermissionIncreaseApprovalNewVersion) { InitNeedCustodianApprovalFieldTrial(true); InitServices(true /* profile_is_supervised */); MockPermissionRequestCreator* creator = new MockPermissionRequestCreator; supervised_user_service()->AddPermissionRequestCreator( make_scoped_ptr(creator)); std::string id = InstallPermissionsTestExtension(); // Update to a new version with increased permissions. const std::string version2("2"); EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version2), testing::_)); UpdatePermissionsTestExtension(id, version2, DISABLED); // Simulate a custodian approval for re-enabling the extension coming in // through Sync. Set a newer version than we have installed. const std::string version3("3"); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_enabled(true); ext_specifics->set_disable_reasons(Extension::DISABLE_NONE); ext_specifics->set_installed_by_custodian(true); ext_specifics->set_version(version3); // This should *not* result in a new permission request. EXPECT_CALL(*creator, CreateExtensionUpdateRequest( UpdateRequestId(id, version3), testing::_)) .Times(0); SyncChangeList list = MakeSyncChangeList(id, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // The re-enable should be delayed until the extension is updated to the // matching version. EXPECT_FALSE(registry()->enabled_extensions().Contains(id)); EXPECT_TRUE(extension_sync_service()->HasPendingReenable( id, base::Version(version3))); // Update to the matching version. Now the extension should get enabled. UpdatePermissionsTestExtension(id, version3, ENABLED); } TEST_F(ExtensionServiceSyncTest, SyncUninstallByCustodianSkipsPolicy) { InitializeEmptyExtensionService(); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), make_scoped_ptr(new syncer::FakeSyncChangeProcessor()), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); // Install two extensions. base::FilePath path1 = data_dir().AppendASCII("good.crx"); base::FilePath path2 = data_dir().AppendASCII("good2048.crx"); const Extension* extensions[] = { InstallCRX(path1, INSTALL_NEW), InstallCRX(path2, INSTALL_NEW, Extension::WAS_INSTALLED_BY_CUSTODIAN) }; // Add a policy provider that will disallow any changes. extensions::TestManagementPolicyProvider provider( extensions::TestManagementPolicyProvider::PROHIBIT_MODIFY_STATUS); ExtensionSystem::Get( browser_context())->management_policy()->RegisterProvider(&provider); // Create a sync deletion for each extension. SyncChangeList list; for (size_t i = 0; i < arraysize(extensions); i++) { const std::string& id = extensions[i]->id(); sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_version("1.0"); ext_specifics->set_installed_by_custodian( extensions[i]->was_installed_by_custodian()); syncer::SyncData sync_data = syncer::SyncData::CreateLocalData(id, "Name", specifics); list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_DELETE, sync_data)); } // Save the extension ids, as uninstalling destroys the Extension instance. std::string extension_ids[] = { extensions[0]->id(), extensions[1]->id() }; // Now apply the uninstallations. extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); // Uninstalling the extension without installed_by_custodian should have been // blocked by policy, so it should still be there. EXPECT_TRUE(registry()->enabled_extensions().Contains(extension_ids[0])); // But installed_by_custodian should result in bypassing the policy check. EXPECT_FALSE( registry()->GenerateInstalledExtensionsSet()->Contains(extension_ids[1])); } TEST_F(ExtensionServiceSyncTest, SyncExtensionHasAllhostsWithheld) { InitializeEmptyExtensionService(); StartSyncing(syncer::EXTENSIONS); // Create an extension that needs all-hosts. const std::string kName("extension"); scoped_refptr extension = extensions::ExtensionBuilder() .SetLocation(Manifest::INTERNAL) .SetManifest( extensions::DictionaryBuilder() .Set("name", kName) .Set("description", "foo") .Set("manifest_version", 2) .Set("version", "1.0") .Set("permissions", extensions::ListBuilder().Append("*://*/*").Build()) .Build()) .SetID(crx_file::id_util::GenerateId(kName)) .Build(); // Install and enable it. service()->AddExtension(extension.get()); service()->GrantPermissionsAndEnableExtension(extension.get()); const std::string id = extension->id(); EXPECT_TRUE(registry()->enabled_extensions().GetByID(id)); // Simulate a sync node coming in where the extension had all-hosts withheld. // This means that it should have all-hosts withheld on this machine, too. sync_pb::EntitySpecifics specifics; sync_pb::ExtensionSpecifics* ext_specifics = specifics.mutable_extension(); ext_specifics->set_id(id); ext_specifics->set_name(kName); ext_specifics->set_version("1.0"); ext_specifics->set_all_urls_enabled(false); ext_specifics->set_enabled(true); SyncChangeList list = MakeSyncChangeList(id, specifics, SyncChange::ACTION_UPDATE); extension_sync_service()->ProcessSyncChanges(FROM_HERE, list); EXPECT_TRUE(registry()->enabled_extensions().GetByID(id)); EXPECT_FALSE(extensions::util::AllowedScriptingOnAllUrls(id, profile())); EXPECT_TRUE(extensions::util::HasSetAllowedScriptingOnAllUrls(id, profile())); EXPECT_FALSE(extensions::util::AllowedScriptingOnAllUrls(id, profile())); } #endif // defined(ENABLE_SUPERVISED_USERS) // Tests sync behavior in the case of an item that starts out as an app and // gets updated to become an extension. TEST_F(ExtensionServiceSyncTest, AppToExtension) { InitializeEmptyExtensionService(); service()->Init(); ASSERT_TRUE(service()->is_ready()); // Install v1, which is an app. const Extension* v1 = InstallCRX(data_dir().AppendASCII("sync_datatypes").AppendASCII("v1.crx"), INSTALL_NEW); EXPECT_TRUE(v1->is_app()); EXPECT_FALSE(v1->is_extension()); std::string id = v1->id(); StatefulChangeProcessor extensions_processor(syncer::ModelType::EXTENSIONS); StatefulChangeProcessor apps_processor(syncer::ModelType::APPS); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, syncer::SyncDataList(), extensions_processor.GetWrapped(), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, syncer::SyncDataList(), apps_processor.GetWrapped(), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); // Check the app/extension change processors to be sure the right data was // added. EXPECT_TRUE(extensions_processor.changes().empty()); EXPECT_TRUE(extensions_processor.data().empty()); EXPECT_EQ(1u, apps_processor.data().size()); ASSERT_EQ(1u, apps_processor.changes().size()); const SyncChange& app_change = apps_processor.changes()[0]; EXPECT_EQ(SyncChange::ACTION_ADD, app_change.change_type()); scoped_ptr app_data = ExtensionSyncData::CreateFromSyncData(app_change.sync_data()); EXPECT_TRUE(app_data->is_app()); EXPECT_EQ(id, app_data->id()); EXPECT_EQ(*v1->version(), app_data->version()); // Update the app to v2, which is an extension. const Extension* v2 = InstallCRX(data_dir().AppendASCII("sync_datatypes").AppendASCII("v2.crx"), INSTALL_UPDATED); EXPECT_FALSE(v2->is_app()); EXPECT_TRUE(v2->is_extension()); EXPECT_EQ(id, v2->id()); // Make sure we saw an extension item added. ASSERT_EQ(1u, extensions_processor.changes().size()); const SyncChange& extension_change = extensions_processor.changes()[0]; EXPECT_EQ(SyncChange::ACTION_ADD, extension_change.change_type()); scoped_ptr extension_data = ExtensionSyncData::CreateFromSyncData(extension_change.sync_data()); EXPECT_FALSE(extension_data->is_app()); EXPECT_EQ(id, extension_data->id()); EXPECT_EQ(*v2->version(), extension_data->version()); // Get the current data from the change processors to use as the input to // the following call to MergeDataAndStartSyncing. This simulates what should // happen with sync. syncer::SyncDataList extensions_data = extensions_processor.GetAllSyncData(syncer::EXTENSIONS); syncer::SyncDataList apps_data = apps_processor.GetAllSyncData(syncer::APPS); // Stop syncing, then start again. extension_sync_service()->StopSyncing(syncer::EXTENSIONS); extension_sync_service()->StopSyncing(syncer::APPS); extension_sync_service()->MergeDataAndStartSyncing( syncer::EXTENSIONS, extensions_data, extensions_processor.GetWrapped(), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); extension_sync_service()->MergeDataAndStartSyncing( syncer::APPS, apps_data, apps_processor.GetWrapped(), make_scoped_ptr(new syncer::SyncErrorFactoryMock())); // Make sure we saw an app item deleted. bool found_delete = false; for (const auto& change : apps_processor.changes()) { if (change.change_type() == SyncChange::ACTION_DELETE) { scoped_ptr data = ExtensionSyncData::CreateFromSyncChange(change); if (data->id() == id) { found_delete = true; break; } } } EXPECT_TRUE(found_delete); // Make sure there is one extension, and there are no more apps. EXPECT_EQ(1u, extensions_processor.data().size()); EXPECT_TRUE(apps_processor.data().empty()); }