OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <list> | 5 #include <list> |
6 #include <set> | 6 #include <set> |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/callback_helpers.h" | |
9 #include "base/macros.h" | 10 #include "base/macros.h" |
11 #include "base/memory/ptr_util.h" | |
12 #include "base/run_loop.h" | |
10 #include "base/scoped_observer.h" | 13 #include "base/scoped_observer.h" |
14 #include "base/strings/string_split.h" | |
15 #include "base/threading/thread_task_runner_handle.h" | |
11 #include "chrome/browser/extensions/extension_browsertest.h" | 16 #include "chrome/browser/extensions/extension_browsertest.h" |
17 #include "chrome/browser/extensions/extension_service.h" | |
12 #include "chrome/common/chrome_switches.h" | 18 #include "chrome/common/chrome_switches.h" |
13 #include "content/public/test/test_utils.h" | 19 #include "content/public/test/test_utils.h" |
14 #include "extensions/browser/content_verifier.h" | 20 #include "extensions/browser/content_verifier.h" |
15 #include "extensions/browser/content_verify_job.h" | 21 #include "extensions/browser/content_verify_job.h" |
22 #include "extensions/browser/crx_file_info.h" | |
16 #include "extensions/browser/extension_prefs.h" | 23 #include "extensions/browser/extension_prefs.h" |
17 #include "extensions/browser/extension_registry.h" | 24 #include "extensions/browser/extension_registry.h" |
18 #include "extensions/browser/extension_registry_observer.h" | 25 #include "extensions/browser/extension_registry_observer.h" |
26 #include "extensions/browser/external_install_info.h" | |
27 #include "extensions/browser/external_provider_interface.h" | |
28 #include "extensions/browser/management_policy.h" | |
29 #include "extensions/browser/updater/extension_downloader.h" | |
30 #include "extensions/browser/updater/extension_downloader_test_delegate.h" | |
31 #include "extensions/browser/updater/manifest_fetch_data.h" | |
32 #include "extensions/common/extension_urls.h" | |
19 | 33 |
20 namespace extensions { | 34 namespace extensions { |
21 | 35 |
22 namespace { | 36 namespace { |
23 | 37 |
24 // Helper for observing extension unloads. | 38 // Helper for observing extension registry events. |
25 class UnloadObserver : public ExtensionRegistryObserver { | 39 class RegistryObserver : public ExtensionRegistryObserver { |
26 public: | 40 public: |
27 explicit UnloadObserver(ExtensionRegistry* registry) : observer_(this) { | 41 explicit RegistryObserver(ExtensionRegistry* registry) : observer_(this) { |
28 observer_.Add(registry); | 42 observer_.Add(registry); |
29 } | 43 } |
30 ~UnloadObserver() override {} | 44 ~RegistryObserver() override {} |
31 | 45 |
32 void WaitForUnload(const ExtensionId& id) { | 46 // Waits until we've seen an unload for extension with |id|, returning true |
33 if (base::ContainsKey(observed_, id)) | 47 // if we saw one or false otherwise (typically because of test timeout). |
34 return; | 48 bool WaitForUnload(const ExtensionId& id) { |
49 if (base::ContainsKey(unloaded_, id)) | |
50 return true; | |
35 | 51 |
36 ASSERT_TRUE(loop_runner_.get() == NULL); | 52 base::RunLoop run_loop; |
37 awaited_id_ = id; | 53 awaited_unload_id_ = id; |
38 loop_runner_ = new content::MessageLoopRunner(); | 54 quit_closure_ = run_loop.QuitClosure(); |
39 loop_runner_->Run(); | 55 run_loop.Run(); |
56 return base::ContainsKey(unloaded_, id); | |
40 } | 57 } |
41 | 58 |
59 // Same as WaitForUnload, but for an install. | |
60 bool WaitForInstall(const ExtensionId& id) { | |
61 if (base::ContainsKey(installed_, id)) | |
62 return true; | |
63 | |
64 base::RunLoop run_loop; | |
65 awaited_install_id_ = id; | |
66 quit_closure_ = run_loop.QuitClosure(); | |
67 run_loop.Run(); | |
68 return base::ContainsKey(installed_, id); | |
69 } | |
70 | |
71 // ExtensionRegistryObserver | |
42 void OnExtensionUnloaded(content::BrowserContext* browser_context, | 72 void OnExtensionUnloaded(content::BrowserContext* browser_context, |
43 const Extension* extension, | 73 const Extension* extension, |
44 UnloadedExtensionInfo::Reason reason) override { | 74 UnloadedExtensionInfo::Reason reason) override { |
45 observed_.insert(extension->id()); | 75 unloaded_.insert(extension->id()); |
46 if (awaited_id_ == extension->id()) | 76 if (awaited_unload_id_ == extension->id()) { |
47 loop_runner_->Quit(); | 77 awaited_unload_id_.clear(); |
78 base::ResetAndReturn(&quit_closure_).Run(); | |
79 } | |
80 } | |
81 | |
82 void OnExtensionInstalled(content::BrowserContext* browser_context, | |
83 const Extension* extension, | |
84 bool is_update) override { | |
85 installed_.insert(extension->id()); | |
86 if (awaited_install_id_ == extension->id()) { | |
87 awaited_install_id_.clear(); | |
88 base::ResetAndReturn(&quit_closure_).Run(); | |
89 } | |
48 } | 90 } |
49 | 91 |
50 private: | 92 private: |
51 ExtensionId awaited_id_; | 93 // The id we're waiting for a load/install of respectively. |
52 std::set<ExtensionId> observed_; | 94 ExtensionId awaited_unload_id_; |
53 scoped_refptr<content::MessageLoopRunner> loop_runner_; | 95 ExtensionId awaited_install_id_; |
54 ScopedObserver<ExtensionRegistry, UnloadObserver> observer_; | 96 |
97 // The quit closure for stopping a running RunLoop, if we're waiting. | |
98 base::Closure quit_closure_; | |
99 | |
100 // The extension id's we've seen unloaded and installed, respectively. | |
101 std::set<ExtensionId> unloaded_; | |
102 std::set<ExtensionId> installed_; | |
103 | |
104 ScopedObserver<ExtensionRegistry, RegistryObserver> observer_; | |
105 | |
106 DISALLOW_COPY_AND_ASSIGN(RegistryObserver); | |
55 }; | 107 }; |
56 | 108 |
57 // Helper for forcing ContentVerifyJob's to return an error. | 109 // Helper for forcing ContentVerifyJob's to return an error. |
58 class JobDelegate : public ContentVerifyJob::TestDelegate { | 110 class JobDelegate : public ContentVerifyJob::TestDelegate { |
59 public: | 111 public: |
60 JobDelegate() | 112 JobDelegate() |
61 : fail_next_read_(false), | 113 : fail_next_read_(false), |
62 fail_next_done_(false), | 114 fail_next_done_(false), |
63 bytes_read_failed_(0), | 115 bytes_read_failed_(0), |
64 done_reading_failed_(0) {} | 116 done_reading_failed_(0) {} |
(...skipping 178 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
243 loop_runner_ = nullptr; | 295 loop_runner_ = nullptr; |
244 } | 296 } |
245 | 297 |
246 void VerifierObserver::OnFetchComplete(const std::string& extension_id, | 298 void VerifierObserver::OnFetchComplete(const std::string& extension_id, |
247 bool success) { | 299 bool success) { |
248 completed_fetches_.insert(extension_id); | 300 completed_fetches_.insert(extension_id); |
249 if (extension_id == id_to_wait_for_) | 301 if (extension_id == id_to_wait_for_) |
250 loop_runner_->Quit(); | 302 loop_runner_->Quit(); |
251 } | 303 } |
252 | 304 |
305 // This lets us intercept requests for update checks of extensions, and | |
306 // substitute a local file as a simulated response. | |
307 class DownloaderTestDelegate : public ExtensionDownloaderTestDelegate { | |
308 public: | |
309 DownloaderTestDelegate() {} | |
310 | |
311 // This makes it so that update check requests for |extension_id| will return | |
312 // a downloaded file of |crx_path| that is claimed to have version | |
313 // |version_string|. | |
314 void AddResponse(const std::string& extension_id, | |
lazyboy
2016/09/02 19:21:53
ExtensionId instead of std::string for extension i
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
315 const std::string& version_string, | |
316 const base::FilePath& crx_path) { | |
317 responses_[extension_id] = std::make_pair(version_string, crx_path); | |
318 } | |
319 | |
320 const std::vector<std::unique_ptr<ManifestFetchData>>& requests() { | |
321 return requests_; | |
322 } | |
323 | |
324 // ExtensionDownloaderTestDelegate: | |
325 void StartUpdateCheck( | |
326 ExtensionDownloader* downloader, | |
327 ExtensionDownloaderDelegate* delegate, | |
328 std::unique_ptr<ManifestFetchData> fetch_data) override { | |
329 requests_.push_back(std::move(fetch_data)); | |
330 const ManifestFetchData* data = requests_.back().get(); | |
331 | |
332 for (const auto& id : data->extension_ids()) { | |
333 if (ContainsKey(responses_, id)) { | |
334 // We use PostTask here instead of calling OnExtensionDownloadFinished | |
335 // immeditately, because the calling code isn't expecting a synchronous | |
336 // response (in non-test situations there are at least 2 network | |
337 // requests needed before a file could be returned). | |
338 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
339 FROM_HERE, | |
340 base::Bind( | |
341 &ExtensionDownloaderDelegate::OnExtensionDownloadFinished, | |
342 base::Unretained(delegate), | |
343 CRXFileInfo(id, responses_[id].second), | |
344 false /* pass_file_ownership */, GURL(), responses_[id].first, | |
345 ExtensionDownloaderDelegate::PingResult(), data->request_ids(), | |
346 ExtensionDownloaderDelegate::InstallCallback())); | |
347 } | |
348 } | |
349 } | |
350 | |
351 private: | |
352 // The requests we've received. | |
353 std::vector<std::unique_ptr<ManifestFetchData>> requests_; | |
354 | |
355 // The prepared responses - this maps an extension id to a (version string, | |
356 // crx file path) pair. | |
357 std::map<std::string, std::pair<std::string, base::FilePath>> responses_; | |
358 | |
359 DISALLOW_COPY_AND_ASSIGN(DownloaderTestDelegate); | |
360 }; | |
361 | |
362 // This lets us simulate the behavior of an enterprise policy that wants | |
363 // a given extension to be installed via the webstore. | |
364 class TestExternalProvider : public ExternalProviderInterface { | |
365 public: | |
366 TestExternalProvider(VisitorInterface* visitor, | |
367 const std::string& extension_id) | |
368 : visitor_(visitor), extension_id_(extension_id) {} | |
369 | |
370 ~TestExternalProvider() override {} | |
371 | |
372 // ExternalProviderInterface: | |
373 void ServiceShutdown() override {} | |
374 | |
375 void VisitRegisteredExtension() override { | |
376 visitor_->OnExternalExtensionUpdateUrlFound( | |
377 ExternalInstallInfoUpdateUrl( | |
378 extension_id_, std::string() /* install_parameter */, | |
379 base::MakeUnique<GURL>(extension_urls::GetWebstoreUpdateUrl()), | |
380 Manifest::EXTERNAL_POLICY_DOWNLOAD, 0 /* creation_flags */, | |
381 true /* mark_acknowledged */), | |
382 true /* is_initial_load */); | |
383 visitor_->OnExternalProviderReady(this); | |
384 } | |
385 | |
386 bool HasExtension(const std::string& id) const override { | |
387 return id == std::string("npnbmohejbjohgpjnmjagbafnjhkmgko"); | |
388 } | |
389 | |
390 bool GetExtensionDetails( | |
391 const std::string& id, | |
392 Manifest::Location* location, | |
393 std::unique_ptr<base::Version>* version) const override { | |
394 ADD_FAILURE() << "Unexpected GetExtensionDetails call; id:" << id; | |
395 return false; | |
396 } | |
397 | |
398 bool IsReady() const override { return true; } | |
399 | |
400 private: | |
401 VisitorInterface* visitor_; | |
402 std::string extension_id_; | |
403 base::Closure quit_closure_; | |
404 | |
405 DISALLOW_COPY_AND_ASSIGN(TestExternalProvider); | |
406 }; | |
407 | |
408 // This lets us simulate a policy-installed extension being "force" installed; | |
409 // ie a user is not allowed to manually uninstall/disable it. | |
410 class ForceInstallProvider : public ManagementPolicy::Provider { | |
411 public: | |
412 explicit ForceInstallProvider(const std::string& id) : id_(id) {} | |
413 ~ForceInstallProvider() override {} | |
414 | |
415 std::string GetDebugPolicyProviderName() const override { | |
416 return "ForceInstallProvider"; | |
417 } | |
418 | |
419 // MananagementPolicy::Provider: | |
420 bool UserMayModifySettings(const Extension* extension, | |
421 base::string16* error) const override { | |
422 return extension->id() != id_; | |
423 } | |
424 | |
425 private: | |
426 // The extension id we want to disallow uninstall/disable for. | |
427 std::string id_; | |
428 | |
429 DISALLOW_COPY_AND_ASSIGN(ForceInstallProvider); | |
430 }; | |
431 | |
253 } // namespace | 432 } // namespace |
254 | 433 |
255 class ContentVerifierTest : public ExtensionBrowserTest { | 434 class ContentVerifierTest : public ExtensionBrowserTest { |
256 public: | 435 public: |
257 ContentVerifierTest() {} | 436 ContentVerifierTest() {} |
258 ~ContentVerifierTest() override {} | 437 ~ContentVerifierTest() override {} |
259 | 438 |
260 void SetUpCommandLine(base::CommandLine* command_line) override { | 439 void SetUpCommandLine(base::CommandLine* command_line) override { |
261 ExtensionBrowserTest::SetUpCommandLine(command_line); | 440 ExtensionBrowserTest::SetUpCommandLine(command_line); |
262 command_line->AppendSwitchASCII( | 441 command_line->AppendSwitchASCII( |
(...skipping 10 matching lines...) Expand all Loading... | |
273 ContentVerifyJob::SetDelegateForTests(NULL); | 452 ContentVerifyJob::SetDelegateForTests(NULL); |
274 ContentVerifyJob::SetObserverForTests(NULL); | 453 ContentVerifyJob::SetObserverForTests(NULL); |
275 ExtensionBrowserTest::TearDownOnMainThread(); | 454 ExtensionBrowserTest::TearDownOnMainThread(); |
276 } | 455 } |
277 | 456 |
278 virtual void OpenPageAndWaitForUnload() { | 457 virtual void OpenPageAndWaitForUnload() { |
279 ContentVerifyJob::SetDelegateForTests(&delegate_); | 458 ContentVerifyJob::SetDelegateForTests(&delegate_); |
280 std::string id = "npnbmohejbjohgpjnmjagbafnjhkmgko"; | 459 std::string id = "npnbmohejbjohgpjnmjagbafnjhkmgko"; |
281 delegate_.set_id(id); | 460 delegate_.set_id(id); |
282 unload_observer_.reset( | 461 unload_observer_.reset( |
283 new UnloadObserver(ExtensionRegistry::Get(profile()))); | 462 new RegistryObserver(ExtensionRegistry::Get(profile()))); |
284 const Extension* extension = InstallExtensionFromWebstore( | 463 const Extension* extension = InstallExtensionFromWebstore( |
285 test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1); | 464 test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1); |
286 ASSERT_TRUE(extension); | 465 ASSERT_TRUE(extension); |
287 ASSERT_EQ(id, extension->id()); | 466 ASSERT_EQ(id, extension->id()); |
288 page_url_ = extension->GetResourceURL("page.html"); | 467 page_url_ = extension->GetResourceURL("page.html"); |
289 | 468 |
290 // This call passes false for |check_navigation_success|, because checking | 469 // This call passes false for |check_navigation_success|, because checking |
291 // for navigation success needs the WebContents to still exist after the | 470 // for navigation success needs the WebContents to still exist after the |
292 // navigation, whereas this navigation triggers an unload which destroys | 471 // navigation, whereas this navigation triggers an unload which destroys |
293 // the WebContents. | 472 // the WebContents. |
294 AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK, | 473 AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK, |
295 false); | 474 false); |
296 | 475 |
297 unload_observer_->WaitForUnload(id); | 476 EXPECT_TRUE(unload_observer_->WaitForUnload(id)); |
298 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); | 477 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); |
299 int reasons = prefs->GetDisableReasons(id); | 478 int reasons = prefs->GetDisableReasons(id); |
300 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); | 479 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); |
301 | 480 |
302 // This needs to happen before the ExtensionRegistry gets deleted, which | 481 // This needs to happen before the ExtensionRegistry gets deleted, which |
303 // happens before TearDownOnMainThread is called. | 482 // happens before TearDownOnMainThread is called. |
304 unload_observer_.reset(); | 483 unload_observer_.reset(); |
305 } | 484 } |
306 | 485 |
307 protected: | 486 protected: |
308 JobDelegate delegate_; | 487 JobDelegate delegate_; |
309 std::unique_ptr<UnloadObserver> unload_observer_; | 488 std::unique_ptr<RegistryObserver> unload_observer_; |
310 GURL page_url_; | 489 GURL page_url_; |
311 }; | 490 }; |
312 | 491 |
313 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) { | 492 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) { |
314 EXPECT_EQ(0, delegate_.bytes_read_failed()); | 493 EXPECT_EQ(0, delegate_.bytes_read_failed()); |
315 delegate_.fail_next_read(); | 494 delegate_.fail_next_read(); |
316 OpenPageAndWaitForUnload(); | 495 OpenPageAndWaitForUnload(); |
317 EXPECT_EQ(1, delegate_.bytes_read_failed()); | 496 EXPECT_EQ(1, delegate_.bytes_read_failed()); |
318 } | 497 } |
319 | 498 |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
416 DisableExtension(id); | 595 DisableExtension(id); |
417 job_observer.ExpectJobResult(id, | 596 job_observer.ExpectJobResult(id, |
418 base::FilePath(FILE_PATH_LITERAL("script.js")), | 597 base::FilePath(FILE_PATH_LITERAL("script.js")), |
419 JobObserver::Result::FAILURE); | 598 JobObserver::Result::FAILURE); |
420 EnableExtension(id); | 599 EnableExtension(id); |
421 EXPECT_TRUE(job_observer.WaitForExpectedJobs()); | 600 EXPECT_TRUE(job_observer.WaitForExpectedJobs()); |
422 | 601 |
423 ContentVerifyJob::SetObserverForTests(NULL); | 602 ContentVerifyJob::SetObserverForTests(NULL); |
424 } | 603 } |
425 | 604 |
605 // Test the case of a corrupt extension that is force-installed by policy and | |
lazyboy
2016/09/02 19:21:52
nit: Tests the ...
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
606 // should not be allowed to be manually uninstalled/disabled by the user. | |
607 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, PolicyCorrupted) { | |
lazyboy
2016/09/02 19:21:53
Nice and thorough test!
asargent_no_longer_on_chrome
2016/09/09 03:30:43
thanks!
| |
608 ExtensionSystem* system = ExtensionSystem::Get(profile()); | |
609 ExtensionService* service = system->extension_service(); | |
610 | |
611 // The id of our test extension. | |
612 std::string id("npnbmohejbjohgpjnmjagbafnjhkmgko"); | |
613 | |
614 // Setup fake policy and update check objects. | |
615 ForceInstallProvider policy(id); | |
616 DownloaderTestDelegate downloader; | |
617 system->management_policy()->RegisterProvider(&policy); | |
618 ExtensionDownloader::set_test_delegate(&downloader); | |
619 service->AddProviderForTesting( | |
620 base::MakeUnique<TestExternalProvider>(service, id)); | |
621 | |
622 base::FilePath crx_path = | |
623 test_data_dir_.AppendASCII("content_verifier/v1.crx"); | |
624 const Extension* extension = | |
625 InstallExtension(crx_path, 1, Manifest::EXTERNAL_POLICY_DOWNLOAD); | |
626 EXPECT_NE(extension, nullptr); | |
627 | |
628 downloader.AddResponse(id, extension->VersionString(), crx_path); | |
629 | |
630 RegistryObserver registry_observer(ExtensionRegistry::Get(profile())); | |
631 ContentVerifier* verifier = system->content_verifier(); | |
632 verifier->VerifyFailed(extension->id(), ContentVerifyJob::HASH_MISMATCH); | |
633 | |
634 // Make sure the extension first got disabled due to corruption. | |
635 EXPECT_TRUE(registry_observer.WaitForUnload(id)); | |
636 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); | |
637 int reasons = prefs->GetDisableReasons(id); | |
638 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED); | |
639 | |
640 // Make sure the extension then got re-installed, and that after reinstall it | |
641 // is no longer disabled due to corruption. | |
642 EXPECT_TRUE(registry_observer.WaitForInstall(id)); | |
643 reasons = prefs->GetDisableReasons(id); | |
644 EXPECT_FALSE(reasons & Extension::DISABLE_CORRUPTED); | |
645 | |
646 // Make sure that the update check request properly included a parameter | |
647 // indicating that this was a corrupt policy reinstall. | |
648 bool found = false; | |
649 for (const auto& request : downloader.requests()) { | |
650 if (request->Includes(id)) { | |
651 std::string query = request->full_url().query(); | |
652 for (const auto& part : base::SplitString( | |
653 query, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
654 if (part.find("x=") == 0 && | |
lazyboy
2016/09/02 19:21:53
base::StartWith(part, "x=")
asargent_no_longer_on_chrome
2016/09/09 03:30:43
Done.
| |
655 part.find(std::string("id%3D") + id) != std::string::npos) { | |
656 found = true; | |
657 EXPECT_NE(std::string::npos, part.find("%26cpr")); | |
658 } | |
659 } | |
660 } | |
661 } | |
662 EXPECT_TRUE(found); | |
663 } | |
664 | |
426 } // namespace extensions | 665 } // namespace extensions |
OLD | NEW |