// Copyright 2017 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 "extensions/browser/extension_registrar.h" #include #include "base/location.h" #include "base/macros.h" #include "base/optional.h" #include "base/threading/sequenced_task_runner_handle.h" #include "content/public/browser/browser_context.h" #include "content/public/test/test_notification_tracker.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extensions_test.h" #include "extensions/browser/notification_types.h" #include "extensions/browser/runtime_data.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/browser/test_extensions_browser_client.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { namespace { using testing::Return; using testing::_; using LoadErrorBehavior = ExtensionRegistrar::LoadErrorBehavior; class TestExtensionSystem : public MockExtensionSystem { public: explicit TestExtensionSystem(content::BrowserContext* context) : MockExtensionSystem(context), runtime_data_(ExtensionRegistry::Get(context)) {} ~TestExtensionSystem() override {} // MockExtensionSystem: void RegisterExtensionWithRequestContexts( const Extension* extension, const base::Closure& callback) override { base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); } RuntimeData* runtime_data() override { return &runtime_data_; } private: RuntimeData runtime_data_; DISALLOW_COPY_AND_ASSIGN(TestExtensionSystem); }; class TestExtensionRegistrarDelegate : public ExtensionRegistrar::Delegate { public: TestExtensionRegistrarDelegate() = default; ~TestExtensionRegistrarDelegate() override = default; // ExtensionRegistrar::Delegate: MOCK_METHOD2(PreAddExtension, void(const Extension* extension, const Extension* old_extension)); MOCK_METHOD1(PostActivateExtension, void(scoped_refptr extension)); MOCK_METHOD1(PostDeactivateExtension, void(scoped_refptr extension)); MOCK_METHOD3(LoadExtensionForReload, void(const ExtensionId& extension_id, const base::FilePath& path, LoadErrorBehavior load_error_behavior)); MOCK_METHOD1(CanEnableExtension, bool(const Extension* extension)); MOCK_METHOD1(CanDisableExtension, bool(const Extension* extension)); MOCK_METHOD1(ShouldBlockExtension, bool(const Extension* extension)); private: DISALLOW_COPY_AND_ASSIGN(TestExtensionRegistrarDelegate); }; } // namespace class ExtensionRegistrarTest : public ExtensionsTest { public: ExtensionRegistrarTest() = default; ~ExtensionRegistrarTest() override = default; void SetUp() override { ExtensionsTest::SetUp(); extensions_browser_client()->set_extension_system_factory(&factory_); extension_ = ExtensionBuilder("extension").Build(); registrar_.emplace(browser_context(), delegate()); notification_tracker_.ListenFor( extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED, content::Source(browser_context())); notification_tracker_.ListenFor( extensions::NOTIFICATION_EXTENSION_REMOVED, content::Source(browser_context())); // Mock defaults. ON_CALL(delegate_, CanEnableExtension(extension_.get())) .WillByDefault(Return(true)); ON_CALL(delegate_, CanDisableExtension(extension_.get())) .WillByDefault(Return(true)); ON_CALL(delegate_, ShouldBlockExtension(extension_.get())) .WillByDefault(Return(false)); EXPECT_CALL(delegate_, PostActivateExtension(_)).Times(0); EXPECT_CALL(delegate_, PostDeactivateExtension(_)).Times(0); } protected: // Boilerplate to verify the mock's expected calls. With a SCOPED_TRACE at the // call site, this includes the caller's function in the Gtest trace on // failure. Otherwise, the failures are unhelpfully listed at the end of the // test. void VerifyMock() { EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&delegate_)); // Re-add the expectations for functions that must not be called. EXPECT_CALL(delegate_, PostActivateExtension(_)).Times(0); EXPECT_CALL(delegate_, PostDeactivateExtension(_)).Times(0); } // Adds the extension as enabled and verifies the result. void AddEnabledExtension() { SCOPED_TRACE("AddEnabledExtension"); ExtensionRegistry* extension_registry = ExtensionRegistry::Get(browser_context()); EXPECT_CALL(delegate_, PostActivateExtension(extension_)); registrar_->AddExtension(extension_); ExpectInSet(ExtensionRegistry::ENABLED); EXPECT_FALSE(IsExtensionReady()); TestExtensionRegistryObserver(extension_registry).WaitForExtensionReady(); EXPECT_TRUE(IsExtensionReady()); EXPECT_EQ(disable_reason::DISABLE_NONE, ExtensionPrefs::Get(browser_context()) ->GetDisableReasons(extension()->id())); VerifyMock(); } // Adds the extension as disabled and verifies the result. void AddDisabledExtension() { SCOPED_TRACE("AddDisabledExtension"); ExtensionPrefs::Get(browser_context()) ->SetExtensionDisabled(extension_->id(), disable_reason::DISABLE_USER_ACTION); registrar_->AddExtension(extension_); ExpectInSet(ExtensionRegistry::DISABLED); EXPECT_FALSE(IsExtensionReady()); EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED)); } // Adds the extension as blacklisted and verifies the result. void AddBlacklistedExtension() { SCOPED_TRACE("AddBlacklistedExtension"); ExtensionPrefs::Get(browser_context()) ->SetExtensionBlacklistState(extension_->id(), BLACKLISTED_MALWARE); registrar_->AddExtension(extension_); ExpectInSet(ExtensionRegistry::BLACKLISTED); EXPECT_FALSE(IsExtensionReady()); EXPECT_EQ(0u, notification_tracker_.size()); } // Adds the extension as blocked and verifies the result. void AddBlockedExtension() { SCOPED_TRACE("AddBlockedExtension"); registrar_->AddExtension(extension_); ExpectInSet(ExtensionRegistry::BLOCKED); EXPECT_FALSE(IsExtensionReady()); EXPECT_EQ(0u, notification_tracker_.size()); } // Removes an enabled extension and verifies the result. void RemoveEnabledExtension() { SCOPED_TRACE("RemoveEnabledExtension"); // Calling RemoveExtension removes its entry from the enabled list and // removes the extension. EXPECT_CALL(delegate_, PostDeactivateExtension(extension_)); registrar_->RemoveExtension(extension_->id(), UnloadedExtensionReason::UNINSTALL); ExpectInSet(ExtensionRegistry::NONE); // Removing an enabled extension should trigger a notification. EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_REMOVED)); VerifyMock(); } // Removes a disabled extension and verifies the result. void RemoveDisabledExtension() { SCOPED_TRACE("RemoveDisabledExtension"); // Calling RemoveExtension removes its entry from the disabled list and // removes the extension. registrar_->RemoveExtension(extension_->id(), UnloadedExtensionReason::UNINSTALL); ExpectInSet(ExtensionRegistry::NONE); // Removing a disabled extension should trigger a notification. EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_REMOVED)); } // Removes a blacklisted extension and verifies the result. void RemoveBlacklistedExtension() { SCOPED_TRACE("RemoveBlacklistedExtension"); // Calling RemoveExtension removes the extension. // TODO(michaelpg): Blacklisted extensions shouldn't need to be // "deactivated". See crbug.com/708230. EXPECT_CALL(delegate_, PostDeactivateExtension(extension_)); registrar_->RemoveExtension(extension_->id(), UnloadedExtensionReason::UNINSTALL); // RemoveExtension does not un-blacklist the extension. ExpectInSet(ExtensionRegistry::BLACKLISTED); // Removing a blacklisted extension should trigger a notification. EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_REMOVED)); VerifyMock(); } // Removes a blocked extension and verifies the result. void RemoveBlockedExtension() { SCOPED_TRACE("RemoveBlockedExtension"); // Calling RemoveExtension removes the extension. // TODO(michaelpg): Blocked extensions shouldn't need to be // "deactivated". See crbug.com/708230. EXPECT_CALL(delegate_, PostDeactivateExtension(extension_)); registrar_->RemoveExtension(extension_->id(), UnloadedExtensionReason::UNINSTALL); // RemoveExtension does not un-block the extension. ExpectInSet(ExtensionRegistry::BLOCKED); // Removing a blocked extension should trigger a notification. EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_REMOVED)); VerifyMock(); } void EnableExtension() { SCOPED_TRACE("EnableExtension"); ExtensionRegistry* extension_registry = ExtensionRegistry::Get(browser_context()); EXPECT_CALL(delegate_, PostActivateExtension(extension_)); registrar_->EnableExtension(extension_->id()); ExpectInSet(ExtensionRegistry::ENABLED); EXPECT_FALSE(IsExtensionReady()); TestExtensionRegistryObserver(extension_registry).WaitForExtensionReady(); ExpectInSet(ExtensionRegistry::ENABLED); EXPECT_TRUE(IsExtensionReady()); VerifyMock(); } void DisableEnabledExtension() { SCOPED_TRACE("DisableEnabledExtension"); EXPECT_CALL(delegate_, PostDeactivateExtension(extension_)); registrar_->DisableExtension(extension_->id(), disable_reason::DISABLE_USER_ACTION); ExpectInSet(ExtensionRegistry::DISABLED); EXPECT_FALSE(IsExtensionReady()); VerifyMock(); } void DisableTerminatedExtension() { SCOPED_TRACE("DisableTerminatedExtension"); // PostDeactivateExtension should not be called. registrar_->DisableExtension(extension_->id(), disable_reason::DISABLE_USER_ACTION); ExpectInSet(ExtensionRegistry::DISABLED); EXPECT_FALSE(IsExtensionReady()); } void TerminateExtension() { SCOPED_TRACE("TerminateExtension"); EXPECT_CALL(delegate_, PostDeactivateExtension(extension_)); registrar_->TerminateExtension(extension_->id()); ExpectInSet(ExtensionRegistry::TERMINATED); EXPECT_FALSE(IsExtensionReady()); VerifyMock(); } void UntrackTerminatedExtension() { SCOPED_TRACE("UntrackTerminatedExtension"); registrar()->UntrackTerminatedExtension(extension()->id()); ExpectInSet(ExtensionRegistry::NONE); EXPECT_TRUE(notification_tracker_.Check1AndReset( extensions::NOTIFICATION_EXTENSION_REMOVED)); } // Directs ExtensionRegistrar to reload the extension and verifies the // delegate is invoked correctly. void ReloadEnabledExtension() { SCOPED_TRACE("ReloadEnabledExtension"); EXPECT_CALL(delegate_, PostDeactivateExtension(extension())); EXPECT_CALL(delegate_, LoadExtensionForReload(extension()->id(), extension()->path(), LoadErrorBehavior::kNoisy)); registrar()->ReloadExtension(extension()->id(), LoadErrorBehavior::kNoisy); VerifyMock(); // ExtensionRegistrar should have disabled the extension in preparation for // a reload. ExpectInSet(ExtensionRegistry::DISABLED); EXPECT_EQ(disable_reason::DISABLE_RELOAD, ExtensionPrefs::Get(browser_context()) ->GetDisableReasons(extension()->id())); } // Directs ExtensionRegistrar to reload the terminated extension and verifies // the delegate is invoked correctly. void ReloadTerminatedExtension() { SCOPED_TRACE("ReloadTerminatedExtension"); EXPECT_CALL(delegate_, LoadExtensionForReload(extension()->id(), extension()->path(), LoadErrorBehavior::kNoisy)); registrar()->ReloadExtension(extension()->id(), LoadErrorBehavior::kNoisy); VerifyMock(); // The extension should remain in the terminated set until the reload // completes successfully. ExpectInSet(ExtensionRegistry::TERMINATED); // Unlike when reloading an enabled extension, the extension hasn't been // disabled and shouldn't have the DISABLE_RELOAD disable reason. EXPECT_EQ(disable_reason::DISABLE_NONE, ExtensionPrefs::Get(browser_context()) ->GetDisableReasons(extension()->id())); } // Verifies that the extension is in the given set in the ExtensionRegistry // and not in other sets. void ExpectInSet(ExtensionRegistry::IncludeFlag set_id) { ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); EXPECT_EQ(set_id == ExtensionRegistry::ENABLED, registry->enabled_extensions().Contains(extension_->id())); EXPECT_EQ(set_id == ExtensionRegistry::DISABLED, registry->disabled_extensions().Contains(extension_->id())); EXPECT_EQ(set_id == ExtensionRegistry::TERMINATED, registry->terminated_extensions().Contains(extension_->id())); EXPECT_EQ(set_id == ExtensionRegistry::BLACKLISTED, registry->blacklisted_extensions().Contains(extension_->id())); EXPECT_EQ(set_id == ExtensionRegistry::BLOCKED, registry->blocked_extensions().Contains(extension_->id())); } bool IsExtensionReady() { return ExtensionRegistry::Get(browser_context()) ->ready_extensions() .Contains(extension_->id()); } ExtensionRegistrar* registrar() { return ®istrar_.value(); } TestExtensionRegistrarDelegate* delegate() { return &delegate_; } scoped_refptr extension() const { return extension_; } private: MockExtensionSystemFactory factory_; // Use NiceMock to allow uninteresting calls, so the delegate can be queried // any number of times. We will explicitly disallow unexpected calls to // PostActivateExtension/PostDeactivateExtension with EXPECT_CALL statements. testing::NiceMock delegate_; scoped_refptr extension_; content::TestNotificationTracker notification_tracker_; // Initialized in SetUp(). base::Optional registrar_; DISALLOW_COPY_AND_ASSIGN(ExtensionRegistrarTest); }; TEST_F(ExtensionRegistrarTest, Basic) { AddEnabledExtension(); RemoveEnabledExtension(); } TEST_F(ExtensionRegistrarTest, AlreadyEnabled) { AddEnabledExtension(); // As the extension is already enabled, this is a no-op. registrar()->EnableExtension(extension()->id()); ExpectInSet(ExtensionRegistry::ENABLED); EXPECT_TRUE(IsExtensionReady()); RemoveEnabledExtension(); } TEST_F(ExtensionRegistrarTest, Disable) { AddEnabledExtension(); // Disable the extension before removing it. DisableEnabledExtension(); RemoveDisabledExtension(); } TEST_F(ExtensionRegistrarTest, DisableAndEnable) { AddEnabledExtension(); // Disable then enable the extension. DisableEnabledExtension(); EnableExtension(); RemoveEnabledExtension(); } TEST_F(ExtensionRegistrarTest, AddDisabled) { // An extension can be added as disabled, then removed. AddDisabledExtension(); RemoveDisabledExtension(); // An extension can be added as disabled, then enabled. AddDisabledExtension(); EnableExtension(); RemoveEnabledExtension(); } TEST_F(ExtensionRegistrarTest, AddForceEnabled) { // Prevent the extension from being disabled. ON_CALL(*delegate(), CanDisableExtension(extension().get())) .WillByDefault(Return(false)); AddEnabledExtension(); // Extension cannot be disabled. registrar()->DisableExtension(extension()->id(), disable_reason::DISABLE_USER_ACTION); ExpectInSet(ExtensionRegistry::ENABLED); } TEST_F(ExtensionRegistrarTest, AddForceDisabled) { // Prevent the extension from being enabled. ON_CALL(*delegate(), CanEnableExtension(extension().get())) .WillByDefault(Return(false)); AddDisabledExtension(); // Extension cannot be enabled. registrar()->EnableExtension(extension()->id()); ExpectInSet(ExtensionRegistry::DISABLED); } TEST_F(ExtensionRegistrarTest, AddBlacklisted) { AddBlacklistedExtension(); // A blacklisted extension cannot be enabled/disabled/reloaded. registrar()->EnableExtension(extension()->id()); ExpectInSet(ExtensionRegistry::BLACKLISTED); registrar()->DisableExtension(extension()->id(), disable_reason::DISABLE_USER_ACTION); ExpectInSet(ExtensionRegistry::BLACKLISTED); registrar()->ReloadExtension(extension()->id(), LoadErrorBehavior::kQuiet); ExpectInSet(ExtensionRegistry::BLACKLISTED); RemoveBlacklistedExtension(); } TEST_F(ExtensionRegistrarTest, AddBlocked) { // Block extensions. ON_CALL(*delegate(), ShouldBlockExtension(extension().get())) .WillByDefault(Return(true)); // A blocked extension can be added. AddBlockedExtension(); // Extension cannot be enabled/disabled. registrar()->EnableExtension(extension()->id()); ExpectInSet(ExtensionRegistry::BLOCKED); registrar()->DisableExtension(extension()->id(), disable_reason::DISABLE_USER_ACTION); ExpectInSet(ExtensionRegistry::BLOCKED); RemoveBlockedExtension(); } TEST_F(ExtensionRegistrarTest, TerminateExtension) { AddEnabledExtension(); TerminateExtension(); // RemoveExtension only handles enabled or disabled extensions. registrar()->RemoveExtension(extension()->id(), UnloadedExtensionReason::UNINSTALL); ExpectInSet(ExtensionRegistry::TERMINATED); UntrackTerminatedExtension(); } TEST_F(ExtensionRegistrarTest, DisableTerminatedExtension) { AddEnabledExtension(); TerminateExtension(); DisableTerminatedExtension(); RemoveDisabledExtension(); } TEST_F(ExtensionRegistrarTest, EnableTerminatedExtension) { AddEnabledExtension(); TerminateExtension(); // Enable the terminated extension. UntrackTerminatedExtension(); AddEnabledExtension(); RemoveEnabledExtension(); } TEST_F(ExtensionRegistrarTest, ReloadExtension) { AddEnabledExtension(); ReloadEnabledExtension(); // Add the now-reloaded extension back into the registrar. AddEnabledExtension(); } TEST_F(ExtensionRegistrarTest, RemoveReloadedExtension) { AddEnabledExtension(); ReloadEnabledExtension(); // Simulate the delegate failing to load the extension and removing it // instead. RemoveDisabledExtension(); // Attempting to reload it silently fails. registrar()->ReloadExtension(extension()->id(), LoadErrorBehavior::kQuiet); ExpectInSet(ExtensionRegistry::NONE); } TEST_F(ExtensionRegistrarTest, ReloadTerminatedExtension) { AddEnabledExtension(); TerminateExtension(); // Reload the terminated extension. ReloadTerminatedExtension(); // Complete the reload by adding the extension. Expect the extension to be // enabled once re-added to the registrar, since ExtensionPrefs shouldn't say // it's disabled. AddEnabledExtension(); } } // namespace extensions