| Index: chrome/browser/extensions/content_verifier_browsertest.cc
|
| diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc
|
| index f688dd4712708f4c9b1023b3580a2c2482d06acc..9b9dce766e90c29a5ad8565e7feb97a5a561404e 100644
|
| --- a/chrome/browser/extensions/content_verifier_browsertest.cc
|
| +++ b/chrome/browser/extensions/content_verifier_browsertest.cc
|
| @@ -6,52 +6,104 @@
|
| #include <set>
|
| #include <string>
|
|
|
| +#include "base/callback_helpers.h"
|
| #include "base/macros.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/run_loop.h"
|
| #include "base/scoped_observer.h"
|
| +#include "base/strings/string_split.h"
|
| +#include "base/threading/thread_task_runner_handle.h"
|
| #include "chrome/browser/extensions/extension_browsertest.h"
|
| +#include "chrome/browser/extensions/extension_service.h"
|
| #include "chrome/common/chrome_switches.h"
|
| #include "content/public/test/test_utils.h"
|
| #include "extensions/browser/content_verifier.h"
|
| #include "extensions/browser/content_verify_job.h"
|
| +#include "extensions/browser/crx_file_info.h"
|
| #include "extensions/browser/extension_prefs.h"
|
| #include "extensions/browser/extension_registry.h"
|
| #include "extensions/browser/extension_registry_observer.h"
|
| +#include "extensions/browser/external_install_info.h"
|
| +#include "extensions/browser/external_provider_interface.h"
|
| +#include "extensions/browser/management_policy.h"
|
| +#include "extensions/browser/updater/extension_downloader.h"
|
| +#include "extensions/browser/updater/extension_downloader_test_delegate.h"
|
| +#include "extensions/browser/updater/manifest_fetch_data.h"
|
| +#include "extensions/common/extension_urls.h"
|
|
|
| namespace extensions {
|
|
|
| namespace {
|
|
|
| -// Helper for observing extension unloads.
|
| -class UnloadObserver : public ExtensionRegistryObserver {
|
| +// Helper for observing extension registry events.
|
| +class RegistryObserver : public ExtensionRegistryObserver {
|
| public:
|
| - explicit UnloadObserver(ExtensionRegistry* registry) : observer_(this) {
|
| + explicit RegistryObserver(ExtensionRegistry* registry) : observer_(this) {
|
| observer_.Add(registry);
|
| }
|
| - ~UnloadObserver() override {}
|
| + ~RegistryObserver() override {}
|
| +
|
| + // Waits until we've seen an unload for extension with |id|, returning true
|
| + // if we saw one or false otherwise (typically because of test timeout).
|
| + bool WaitForUnload(const ExtensionId& id) {
|
| + if (base::ContainsKey(unloaded_, id))
|
| + return true;
|
| +
|
| + base::RunLoop run_loop;
|
| + awaited_unload_id_ = id;
|
| + quit_closure_ = run_loop.QuitClosure();
|
| + run_loop.Run();
|
| + return base::ContainsKey(unloaded_, id);
|
| + }
|
|
|
| - void WaitForUnload(const ExtensionId& id) {
|
| - if (base::ContainsKey(observed_, id))
|
| - return;
|
| + // Same as WaitForUnload, but for an install.
|
| + bool WaitForInstall(const ExtensionId& id) {
|
| + if (base::ContainsKey(installed_, id))
|
| + return true;
|
|
|
| - ASSERT_TRUE(loop_runner_.get() == NULL);
|
| - awaited_id_ = id;
|
| - loop_runner_ = new content::MessageLoopRunner();
|
| - loop_runner_->Run();
|
| + base::RunLoop run_loop;
|
| + awaited_install_id_ = id;
|
| + quit_closure_ = run_loop.QuitClosure();
|
| + run_loop.Run();
|
| + return base::ContainsKey(installed_, id);
|
| }
|
|
|
| + // ExtensionRegistryObserver
|
| void OnExtensionUnloaded(content::BrowserContext* browser_context,
|
| const Extension* extension,
|
| UnloadedExtensionInfo::Reason reason) override {
|
| - observed_.insert(extension->id());
|
| - if (awaited_id_ == extension->id())
|
| - loop_runner_->Quit();
|
| + unloaded_.insert(extension->id());
|
| + if (awaited_unload_id_ == extension->id()) {
|
| + awaited_unload_id_.clear();
|
| + base::ResetAndReturn(&quit_closure_).Run();
|
| + }
|
| + }
|
| +
|
| + void OnExtensionInstalled(content::BrowserContext* browser_context,
|
| + const Extension* extension,
|
| + bool is_update) override {
|
| + installed_.insert(extension->id());
|
| + if (awaited_install_id_ == extension->id()) {
|
| + awaited_install_id_.clear();
|
| + base::ResetAndReturn(&quit_closure_).Run();
|
| + }
|
| }
|
|
|
| private:
|
| - ExtensionId awaited_id_;
|
| - std::set<ExtensionId> observed_;
|
| - scoped_refptr<content::MessageLoopRunner> loop_runner_;
|
| - ScopedObserver<ExtensionRegistry, UnloadObserver> observer_;
|
| + // The id we're waiting for a load/install of respectively.
|
| + ExtensionId awaited_unload_id_;
|
| + ExtensionId awaited_install_id_;
|
| +
|
| + // The quit closure for stopping a running RunLoop, if we're waiting.
|
| + base::Closure quit_closure_;
|
| +
|
| + // The extension id's we've seen unloaded and installed, respectively.
|
| + std::set<ExtensionId> unloaded_;
|
| + std::set<ExtensionId> installed_;
|
| +
|
| + ScopedObserver<ExtensionRegistry, RegistryObserver> observer_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(RegistryObserver);
|
| };
|
|
|
| // Helper for forcing ContentVerifyJob's to return an error.
|
| @@ -250,6 +302,133 @@ void VerifierObserver::OnFetchComplete(const std::string& extension_id,
|
| loop_runner_->Quit();
|
| }
|
|
|
| +// This lets us intercept requests for update checks of extensions, and
|
| +// substitute a local file as a simulated response.
|
| +class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate {
|
| + public:
|
| + DownloaderTestDelegate() {}
|
| +
|
| + // This makes it so that update check requests for |extension_id| will return
|
| + // a downloaded file of |crx_path| that is claimed to have version
|
| + // |version_string|.
|
| + void AddResponse(const ExtensionId& extension_id,
|
| + const std::string& version_string,
|
| + const base::FilePath& crx_path) {
|
| + responses_[extension_id] = std::make_pair(version_string, crx_path);
|
| + }
|
| +
|
| + const std::vector<std::unique_ptr<ManifestFetchData>>& requests() {
|
| + return requests_;
|
| + }
|
| +
|
| + // ExtensionDownloaderTestDelegate:
|
| + void StartUpdateCheck(
|
| + ExtensionDownloader* downloader,
|
| + ExtensionDownloaderDelegate* delegate,
|
| + std::unique_ptr<ManifestFetchData> fetch_data) override {
|
| + requests_.push_back(std::move(fetch_data));
|
| + const ManifestFetchData* data = requests_.back().get();
|
| +
|
| + for (const auto& id : data->extension_ids()) {
|
| + if (ContainsKey(responses_, id)) {
|
| + // We use PostTask here instead of calling OnExtensionDownloadFinished
|
| + // immeditately, because the calling code isn't expecting a synchronous
|
| + // response (in non-test situations there are at least 2 network
|
| + // requests needed before a file could be returned).
|
| + base::ThreadTaskRunnerHandle::Get()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(
|
| + &ExtensionDownloaderDelegate::OnExtensionDownloadFinished,
|
| + base::Unretained(delegate),
|
| + CRXFileInfo(id, responses_[id].second),
|
| + false /* pass_file_ownership */, GURL(), responses_[id].first,
|
| + ExtensionDownloaderDelegate::PingResult(), data->request_ids(),
|
| + ExtensionDownloaderDelegate::InstallCallback()));
|
| + }
|
| + }
|
| + }
|
| +
|
| + private:
|
| + // The requests we've received.
|
| + std::vector<std::unique_ptr<ManifestFetchData>> requests_;
|
| +
|
| + // The prepared responses - this maps an extension id to a (version string,
|
| + // crx file path) pair.
|
| + std::map<std::string, std::pair<ExtensionId, base::FilePath>> responses_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DownloaderTestDelegate);
|
| +};
|
| +
|
| +// This lets us simulate the behavior of an enterprise policy that wants
|
| +// a given extension to be installed via the webstore.
|
| +class TestExternalProvider : public ExternalProviderInterface {
|
| + public:
|
| + TestExternalProvider(VisitorInterface* visitor,
|
| + const ExtensionId& extension_id)
|
| + : visitor_(visitor), extension_id_(extension_id) {}
|
| +
|
| + ~TestExternalProvider() override {}
|
| +
|
| + // ExternalProviderInterface:
|
| + void ServiceShutdown() override {}
|
| +
|
| + void VisitRegisteredExtension() override {
|
| + visitor_->OnExternalExtensionUpdateUrlFound(
|
| + ExternalInstallInfoUpdateUrl(
|
| + extension_id_, std::string() /* install_parameter */,
|
| + base::MakeUnique<GURL>(extension_urls::GetWebstoreUpdateUrl()),
|
| + Manifest::EXTERNAL_POLICY_DOWNLOAD, 0 /* creation_flags */,
|
| + true /* mark_acknowledged */),
|
| + true /* is_initial_load */);
|
| + visitor_->OnExternalProviderReady(this);
|
| + }
|
| +
|
| + bool HasExtension(const ExtensionId& id) const override {
|
| + return id == std::string("npnbmohejbjohgpjnmjagbafnjhkmgko");
|
| + }
|
| +
|
| + bool GetExtensionDetails(
|
| + const ExtensionId& id,
|
| + Manifest::Location* location,
|
| + std::unique_ptr<base::Version>* version) const override {
|
| + ADD_FAILURE() << "Unexpected GetExtensionDetails call; id:" << id;
|
| + return false;
|
| + }
|
| +
|
| + bool IsReady() const override { return true; }
|
| +
|
| + private:
|
| + VisitorInterface* visitor_;
|
| + ExtensionId extension_id_;
|
| + base::Closure quit_closure_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TestExternalProvider);
|
| +};
|
| +
|
| +// This lets us simulate a policy-installed extension being "force" installed;
|
| +// ie a user is not allowed to manually uninstall/disable it.
|
| +class ForceInstallProvider : public ManagementPolicy::Provider {
|
| + public:
|
| + explicit ForceInstallProvider(const ExtensionId& id) : id_(id) {}
|
| + ~ForceInstallProvider() override {}
|
| +
|
| + std::string GetDebugPolicyProviderName() const override {
|
| + return "ForceInstallProvider";
|
| + }
|
| +
|
| + // MananagementPolicy::Provider:
|
| + bool UserMayModifySettings(const Extension* extension,
|
| + base::string16* error) const override {
|
| + return extension->id() != id_;
|
| + }
|
| +
|
| + private:
|
| + // The extension id we want to disallow uninstall/disable for.
|
| + ExtensionId id_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ForceInstallProvider);
|
| +};
|
| +
|
| } // namespace
|
|
|
| class ContentVerifierTest : public ExtensionBrowserTest {
|
| @@ -280,7 +459,7 @@ class ContentVerifierTest : public ExtensionBrowserTest {
|
| std::string id = "npnbmohejbjohgpjnmjagbafnjhkmgko";
|
| delegate_.set_id(id);
|
| unload_observer_.reset(
|
| - new UnloadObserver(ExtensionRegistry::Get(profile())));
|
| + new RegistryObserver(ExtensionRegistry::Get(profile())));
|
| const Extension* extension = InstallExtensionFromWebstore(
|
| test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1);
|
| ASSERT_TRUE(extension);
|
| @@ -294,7 +473,7 @@ class ContentVerifierTest : public ExtensionBrowserTest {
|
| AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK,
|
| false);
|
|
|
| - unload_observer_->WaitForUnload(id);
|
| + EXPECT_TRUE(unload_observer_->WaitForUnload(id));
|
| ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
|
| int reasons = prefs->GetDisableReasons(id);
|
| EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED);
|
| @@ -306,7 +485,7 @@ class ContentVerifierTest : public ExtensionBrowserTest {
|
|
|
| protected:
|
| JobDelegate delegate_;
|
| - std::unique_ptr<UnloadObserver> unload_observer_;
|
| + std::unique_ptr<RegistryObserver> unload_observer_;
|
| GURL page_url_;
|
| };
|
|
|
| @@ -423,4 +602,64 @@ IN_PROC_BROWSER_TEST_F(ContentVerifierTest, ContentScripts) {
|
| ContentVerifyJob::SetObserverForTests(NULL);
|
| }
|
|
|
| +// Tests the case of a corrupt extension that is force-installed by policy and
|
| +// should not be allowed to be manually uninstalled/disabled by the user.
|
| +IN_PROC_BROWSER_TEST_F(ContentVerifierTest, PolicyCorrupted) {
|
| + ExtensionSystem* system = ExtensionSystem::Get(profile());
|
| + ExtensionService* service = system->extension_service();
|
| +
|
| + // The id of our test extension.
|
| + std::string id("npnbmohejbjohgpjnmjagbafnjhkmgko");
|
| +
|
| + // Setup fake policy and update check objects.
|
| + ForceInstallProvider policy(id);
|
| + DownloaderTestDelegate downloader;
|
| + system->management_policy()->RegisterProvider(&policy);
|
| + ExtensionDownloader::set_test_delegate(&downloader);
|
| + service->AddProviderForTesting(
|
| + base::MakeUnique<TestExternalProvider>(service, id));
|
| +
|
| + base::FilePath crx_path =
|
| + test_data_dir_.AppendASCII("content_verifier/v1.crx");
|
| + const Extension* extension =
|
| + InstallExtension(crx_path, 1, Manifest::EXTERNAL_POLICY_DOWNLOAD);
|
| + EXPECT_NE(extension, nullptr);
|
| +
|
| + downloader.AddResponse(id, extension->VersionString(), crx_path);
|
| +
|
| + RegistryObserver registry_observer(ExtensionRegistry::Get(profile()));
|
| + ContentVerifier* verifier = system->content_verifier();
|
| + verifier->VerifyFailed(extension->id(), ContentVerifyJob::HASH_MISMATCH);
|
| +
|
| + // Make sure the extension first got disabled due to corruption.
|
| + EXPECT_TRUE(registry_observer.WaitForUnload(id));
|
| + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
|
| + int reasons = prefs->GetDisableReasons(id);
|
| + EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED);
|
| +
|
| + // Make sure the extension then got re-installed, and that after reinstall it
|
| + // is no longer disabled due to corruption.
|
| + EXPECT_TRUE(registry_observer.WaitForInstall(id));
|
| + reasons = prefs->GetDisableReasons(id);
|
| + EXPECT_FALSE(reasons & Extension::DISABLE_CORRUPTED);
|
| +
|
| + // Make sure that the update check request properly included a parameter
|
| + // indicating that this was a corrupt policy reinstall.
|
| + bool found = false;
|
| + for (const auto& request : downloader.requests()) {
|
| + if (request->Includes(id)) {
|
| + std::string query = request->full_url().query();
|
| + for (const auto& part : base::SplitString(
|
| + query, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
|
| + if (base::StartsWith(part, "x=", base::CompareCase::SENSITIVE) &&
|
| + part.find(std::string("id%3D") + id) != std::string::npos) {
|
| + found = true;
|
| + EXPECT_NE(std::string::npos, part.find("installsource%3Dreinstall"));
|
| + }
|
| + }
|
| + }
|
| + }
|
| + EXPECT_TRUE(found);
|
| +}
|
| +
|
| } // namespace extensions
|
|
|