// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This test creates a fake safebrowsing service, where we can inject known- // threat urls. It then uses a real browser to go to these urls, and sends // "goback" or "proceed" commands and verifies they work. #include #include "base/bind.h" #include "base/command_line.h" #include "base/macros.h" #include "base/prefs/pref_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/test/histogram_tester.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/interstitials/security_interstitial_page_test_utils.h" #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/safe_browsing/local_database_manager.h" #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/safe_browsing/safe_browsing_util.h" #include "chrome/browser/safe_browsing/threat_details.h" #include "chrome/browser/safe_browsing/ui_manager.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "components/safe_browsing_db/database_manager.h" #include "components/safe_browsing_db/test_database_manager.h" #include "components/security_interstitials/core/controller_client.h" #include "components/security_interstitials/core/metrics_helper.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_browser_thread.h" #include "content/public/test/test_utils.h" #include "net/test/url_request/url_request_mock_http_job.h" using chrome_browser_interstitials::SecurityInterstitialIDNTest; using content::BrowserThread; using content::InterstitialPage; using content::NavigationController; using content::RenderFrameHost; using content::WebContents; namespace safe_browsing { namespace { const char kEmptyPage[] = "empty.html"; const char kMalwarePage[] = "safe_browsing/malware.html"; const char kCrossSiteMalwarePage[] = "safe_browsing/malware2.html"; const char kMalwareIframe[] = "safe_browsing/malware_iframe.html"; const char kCrossSiteIframeUrl[] = "http://example.com/cross_site_iframe.html"; const char kUnrelatedUrl[] = "https://www.google.com"; // A SafeBrowsingDatabaseManager class that allows us to inject the malicious // URLs. class FakeSafeBrowsingDatabaseManager : public TestSafeBrowsingDatabaseManager { public: FakeSafeBrowsingDatabaseManager() {} // Called on the IO thread to check if the given url is safe or not. If we // can synchronously determine that the url is safe, CheckUrl returns true. // Otherwise it returns false, and "client" is called asynchronously with the // result when it is ready. // Overrides SafeBrowsingDatabaseManager::CheckBrowseUrl. bool CheckBrowseUrl(const GURL& gurl, Client* client) override { if (badurls[gurl.spec()] == SB_THREAT_TYPE_SAFE) return true; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&FakeSafeBrowsingDatabaseManager::OnCheckBrowseURLDone, this, gurl, client)); return false; } void OnCheckBrowseURLDone(const GURL& gurl, Client* client) { std::vector expected_threats; // TODO(nparker): Remove ref to LocalSafeBrowsingDatabase by calling // client->OnCheckBrowseUrlResult(..) directly. expected_threats.push_back(SB_THREAT_TYPE_URL_MALWARE); expected_threats.push_back(SB_THREAT_TYPE_URL_PHISHING); expected_threats.push_back(SB_THREAT_TYPE_URL_UNWANTED); LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck sb_check( std::vector(1, gurl), std::vector(), client, MALWARE, expected_threats); sb_check.url_results[0] = badurls[gurl.spec()]; sb_check.OnSafeBrowsingResult(); } void SetURLThreatType(const GURL& url, SBThreatType threat_type) { badurls[url.spec()] = threat_type; } // These are called when checking URLs, so we implement them. bool IsSupported() const override { return true; } bool ChecksAreAlwaysAsync() const override { return false; } bool CanCheckResourceType( content::ResourceType /* resource_type */) const override { return true; } // Called during startup, so must not check-fail. bool CheckExtensionIDs(const std::set& extension_ids, Client* client) override { return true; } safe_browsing::ThreatSource GetThreatSource() const override { return safe_browsing::ThreatSource::LOCAL_PVER3; } private: ~FakeSafeBrowsingDatabaseManager() override {} base::hash_map badurls; DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingDatabaseManager); }; // A SafeBrowingUIManager class that allows intercepting malware details. class FakeSafeBrowsingUIManager : public SafeBrowsingUIManager { public: explicit FakeSafeBrowsingUIManager(SafeBrowsingService* service) : SafeBrowsingUIManager(service), threat_details_done_(false) {} // Overrides SafeBrowsingUIManager void SendSerializedThreatDetails(const std::string& serialized) override { // Notify the UI thread that we got a report. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&FakeSafeBrowsingUIManager::OnThreatDetailsDone, this, serialized)); } void OnThreatDetailsDone(const std::string& serialized) { if (threat_details_done_) { return; } EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); report_ = serialized; EXPECT_FALSE(threat_details_done_callback_.is_null()); if (!threat_details_done_callback_.is_null()) { threat_details_done_callback_.Run(); threat_details_done_callback_ = base::Closure(); } threat_details_done_ = true; } void set_threat_details_done_callback(const base::Closure& callback) { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); EXPECT_TRUE(threat_details_done_callback_.is_null()); threat_details_done_callback_ = callback; } std::string GetReport() { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); return report_; } protected: ~FakeSafeBrowsingUIManager() override {} private: std::string report_; base::Closure threat_details_done_callback_; bool threat_details_done_; DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingUIManager); }; class FakeSafeBrowsingService : public SafeBrowsingService { public: FakeSafeBrowsingService() : fake_database_manager_(), fake_ui_manager_() { } // Returned pointer has the same lifespan as the database_manager_ refcounted // object. FakeSafeBrowsingDatabaseManager* fake_database_manager() { return fake_database_manager_; } // Returned pointer has the same lifespan as the ui_manager_ refcounted // object. FakeSafeBrowsingUIManager* fake_ui_manager() { return fake_ui_manager_; } protected: ~FakeSafeBrowsingService() override {} SafeBrowsingDatabaseManager* CreateDatabaseManager() override { fake_database_manager_ = new FakeSafeBrowsingDatabaseManager(); return fake_database_manager_; } SafeBrowsingUIManager* CreateUIManager() override { fake_ui_manager_ = new FakeSafeBrowsingUIManager(this); return fake_ui_manager_; } SafeBrowsingProtocolManagerDelegate* GetProtocolManagerDelegate() override { // Our SafeBrowsingDatabaseManager doesn't implement this delegate. return NULL; } private: FakeSafeBrowsingDatabaseManager* fake_database_manager_; FakeSafeBrowsingUIManager* fake_ui_manager_; DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingService); }; // Factory that creates FakeSafeBrowsingService instances. class TestSafeBrowsingServiceFactory : public SafeBrowsingServiceFactory { public: TestSafeBrowsingServiceFactory() : most_recent_service_(NULL) { } ~TestSafeBrowsingServiceFactory() override {} SafeBrowsingService* CreateSafeBrowsingService() override { most_recent_service_ = new FakeSafeBrowsingService(); return most_recent_service_; } FakeSafeBrowsingService* most_recent_service() const { return most_recent_service_; } private: FakeSafeBrowsingService* most_recent_service_; }; } // namespace class TestThreatDetailsFactory : public ThreatDetailsFactory { public: TestThreatDetailsFactory() : details_() {} ~TestThreatDetailsFactory() override {} ThreatDetails* CreateThreatDetails( SafeBrowsingUIManager* delegate, WebContents* web_contents, const SafeBrowsingUIManager::UnsafeResource& unsafe_resource) override { details_ = new ThreatDetails(delegate, web_contents, unsafe_resource); return details_; } ThreatDetails* get_details() { return details_; } private: ThreatDetails* details_; }; // A SafeBrowingBlockingPage class that lets us wait until it's hidden. class TestSafeBrowsingBlockingPage : public SafeBrowsingBlockingPage { public: TestSafeBrowsingBlockingPage(SafeBrowsingUIManager* manager, WebContents* web_contents, const GURL& main_frame_url, const UnsafeResourceList& unsafe_resources) : SafeBrowsingBlockingPage(manager, web_contents, main_frame_url, unsafe_resources), wait_for_delete_(false) { // Don't wait the whole 3 seconds for the browser test. threat_details_proceed_delay_ms_ = 100; } ~TestSafeBrowsingBlockingPage() override { if (!wait_for_delete_) return; // Notify that we are gone base::MessageLoopForUI::current()->QuitWhenIdle(); wait_for_delete_ = false; } void WaitForDelete() { wait_for_delete_ = true; content::RunMessageLoop(); } // InterstitialPageDelegate methods: void CommandReceived(const std::string& command) override { SafeBrowsingBlockingPage::CommandReceived(command); } void OnProceed() override { SafeBrowsingBlockingPage::OnProceed(); } void OnDontProceed() override { SafeBrowsingBlockingPage::OnDontProceed(); } private: bool wait_for_delete_; }; class TestSafeBrowsingBlockingPageFactory : public SafeBrowsingBlockingPageFactory { public: TestSafeBrowsingBlockingPageFactory() { } ~TestSafeBrowsingBlockingPageFactory() override {} SafeBrowsingBlockingPage* CreateSafeBrowsingPage( SafeBrowsingUIManager* delegate, WebContents* web_contents, const GURL& main_frame_url, const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources) override { return new TestSafeBrowsingBlockingPage(delegate, web_contents, main_frame_url, unsafe_resources); } }; // Tests the safe browsing blocking page in a browser. class SafeBrowsingBlockingPageBrowserTest : public InProcessBrowserTest, public testing::WithParamInterface> { public: enum Visibility { VISIBILITY_ERROR = -1, HIDDEN = 0, VISIBLE = 1 }; SafeBrowsingBlockingPageBrowserTest() {} void SetUp() override { SafeBrowsingService::RegisterFactory(&factory_); SafeBrowsingBlockingPage::RegisterFactory(&blocking_page_factory_); ThreatDetails::RegisterFactory(&details_factory_); InProcessBrowserTest::SetUp(); } void TearDown() override { InProcessBrowserTest::TearDown(); SafeBrowsingBlockingPage::RegisterFactory(NULL); SafeBrowsingService::RegisterFactory(NULL); ThreatDetails::RegisterFactory(NULL); } void SetUpCommandLine(base::CommandLine* command_line) override { if (testing::get<1>(GetParam())) content::IsolateAllSitesForTesting(command_line); } void SetUpOnMainThread() override { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); } void SetURLThreatType(const GURL& url, SBThreatType threat_type) { FakeSafeBrowsingService* service = static_cast( g_browser_process->safe_browsing_service()); ASSERT_TRUE(service); service->fake_database_manager()->SetURLThreatType(url, threat_type); } // Adds a safebrowsing result of the current test threat to the fake // safebrowsing service, navigates to that page, and returns the url. GURL SetupWarningAndNavigate() { GURL url = net::URLRequestMockHTTPJob::GetMockUrl(kEmptyPage); SetURLThreatType(url, testing::get<0>(GetParam())); ui_test_utils::NavigateToURL(browser(), url); EXPECT_TRUE(WaitForReady()); return url; } // Adds two safebrowsing threat results to the fake safebrowsing service, // navigates to a page with an iframe containing the threat site, and another // cross site iframe containing another threat site, and returns the url of // the parent page. GURL SetupThreatIframeWarningAndNavigate() { GURL url = net::URLRequestMockHTTPJob::GetMockUrl(kCrossSiteMalwarePage); GURL iframe_url = net::URLRequestMockHTTPJob::GetMockUrl(kMalwareIframe); GURL cross_site_url(kCrossSiteIframeUrl); SetURLThreatType(iframe_url, testing::get<0>(GetParam())); SetURLThreatType(cross_site_url, testing::get<0>(GetParam())); ui_test_utils::NavigateToURL(browser(), url); EXPECT_TRUE(WaitForReady()); return url; } void SendCommand( security_interstitials::SecurityInterstitialCommands command) { WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); // We use InterstitialPage::GetInterstitialPage(tab) instead of // tab->GetInterstitialPage() because the tab doesn't have a pointer // to its interstital page until it gets a command from the renderer // that it has indeed displayed it -- and this sometimes happens after // NavigateToURL returns. SafeBrowsingBlockingPage* interstitial_page = static_cast( InterstitialPage::GetInterstitialPage(contents)-> GetDelegateForTesting()); ASSERT_TRUE(interstitial_page); ASSERT_EQ(SafeBrowsingBlockingPage::kTypeForTesting, interstitial_page->GetTypeForTesting()); interstitial_page->CommandReceived(base::IntToString(command)); } void AssertNoInterstitial(bool wait_for_delete) { WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); if (contents->ShowingInterstitialPage() && wait_for_delete) { // We'll get notified when the interstitial is deleted. TestSafeBrowsingBlockingPage* page = static_cast( contents->GetInterstitialPage()->GetDelegateForTesting()); ASSERT_EQ(SafeBrowsingBlockingPage::kTypeForTesting, page->GetTypeForTesting()); page->WaitForDelete(); } // Can't use InterstitialPage::GetInterstitialPage() because that // gets updated after the TestSafeBrowsingBlockingPage destructor ASSERT_FALSE(contents->ShowingInterstitialPage()); } bool YesInterstitial() { WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); InterstitialPage* interstitial_page = InterstitialPage::GetInterstitialPage( contents); return interstitial_page != NULL; } void SetReportSentCallback(const base::Closure& callback) { factory_.most_recent_service() ->fake_ui_manager() ->set_threat_details_done_callback(callback); } std::string GetReportSent() { return factory_.most_recent_service()->fake_ui_manager()->GetReport(); } void MalwareRedirectCancelAndProceed(const std::string& open_function) { GURL load_url = net::URLRequestMockHTTPJob::GetMockUrl( "safe_browsing/interstitial_cancel.html"); GURL malware_url = net::URLRequestMockHTTPJob::GetMockUrl(kMalwarePage); SetURLThreatType(malware_url, testing::get<0>(GetParam())); // Load the test page. ui_test_utils::NavigateToURL(browser(), load_url); // Trigger the safe browsing interstitial page via a redirect in // "openWin()". ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("javascript:" + open_function + "()"), CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); content::WaitForInterstitialAttach(contents); // Cancel the redirect request while interstitial page is open. browser()->tab_strip_model()->ActivateTabAt(0, true); ui_test_utils::NavigateToURL(browser(), GURL("javascript:stopWin()")); browser()->tab_strip_model()->ActivateTabAt(1, true); // Simulate the user clicking "proceed", there should be no crash. Since // clicking proceed may do nothing (see comment in RedirectCanceled // below, and crbug.com/76460), we use SendCommand to trigger the callback // directly rather than using ClickAndWaitForDetach since there might not // be a notification to wait for. SendCommand(security_interstitials::CMD_PROCEED); } content::RenderFrameHost* GetRenderFrameHost() { InterstitialPage* interstitial = InterstitialPage::GetInterstitialPage( browser()->tab_strip_model()->GetActiveWebContents()); if (!interstitial) return NULL; return interstitial->GetMainFrame(); } bool WaitForReady() { InterstitialPage* interstitial = InterstitialPage::GetInterstitialPage( browser()->tab_strip_model()->GetActiveWebContents()); if (!interstitial) return false; return content::WaitForRenderFrameReady(interstitial->GetMainFrame()); } Visibility GetVisibility(const std::string& node_id) { content::RenderFrameHost* rfh = GetRenderFrameHost(); if (!rfh) return VISIBILITY_ERROR; scoped_ptr value = content::ExecuteScriptAndGetValue( rfh, "var node = document.getElementById('" + node_id + "');\n" "if (node)\n" " node.offsetWidth > 0 && node.offsetHeight > 0;" "else\n" " 'node not found';\n"); if (!value.get()) return VISIBILITY_ERROR; bool result = false; if (!value->GetAsBoolean(&result)) return VISIBILITY_ERROR; return result ? VISIBLE : HIDDEN; } bool Click(const std::string& node_id) { content::RenderFrameHost* rfh = GetRenderFrameHost(); if (!rfh) return false; // We don't use ExecuteScriptAndGetValue for this one, since clicking // the button/link may navigate away before the injected javascript can // reply, hanging the test. rfh->ExecuteJavaScriptForTests(base::ASCIIToUTF16( "document.getElementById('" + node_id + "').click();\n")); return true; } bool ClickAndWaitForDetach(const std::string& node_id) { // We wait for interstitial_detached rather than nav_entry_committed, as // going back from a main-frame safe browsing interstitial page will not // cause a nav entry committed event. if (!Click(node_id)) return false; content::WaitForInterstitialDetach( browser()->tab_strip_model()->GetActiveWebContents()); return true; } void TestReportingDisabledAndDontProceed(const GURL& url) { SetURLThreatType(url, testing::get<0>(GetParam())); ui_test_utils::NavigateToURL(browser(), url); ASSERT_TRUE(WaitForReady()); EXPECT_EQ(HIDDEN, GetVisibility("extended-reporting-opt-in")); EXPECT_EQ(HIDDEN, GetVisibility("opt-in-checkbox")); EXPECT_EQ(HIDDEN, GetVisibility("proceed-link")); EXPECT_TRUE(Click("details-button")); EXPECT_EQ(VISIBLE, GetVisibility("help-link")); EXPECT_EQ(VISIBLE, GetVisibility("proceed-link")); EXPECT_TRUE(ClickAndWaitForDetach("primary-button")); AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(url::kAboutBlankURL), // Back to "about:blank" browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } void VerifyResource( const ClientSafeBrowsingReportRequest& report, const ClientSafeBrowsingReportRequest::Resource& actual_resource, const std::string& expected_url, const std::string& expected_parent, int expected_child_size, const std::string& expected_tag_name) { EXPECT_EQ(expected_url, actual_resource.url()); // Finds the parent url by comparing resource ids. for (auto resource : report.resources()) { if (actual_resource.parent_id() == resource.id()) { EXPECT_EQ(expected_parent, resource.url()); break; } } EXPECT_EQ(expected_child_size, actual_resource.child_ids_size()); EXPECT_EQ(expected_tag_name, actual_resource.tag_name()); } protected: TestThreatDetailsFactory details_factory_; private: TestSafeBrowsingServiceFactory factory_; TestSafeBrowsingBlockingPageFactory blocking_page_factory_; DISALLOW_COPY_AND_ASSIGN(SafeBrowsingBlockingPageBrowserTest); }; // TODO(linux_aura) https://crbug.com/163931 // TODO(win_aura) https://crbug.com/154081 #if defined(USE_AURA) && !defined(OS_CHROMEOS) #define MAYBE_RedirectInIFrameCanceled DISABLED_RedirectInIFrameCanceled #else #define MAYBE_RedirectInIFrameCanceled RedirectInIFrameCanceled #endif IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, MAYBE_RedirectInIFrameCanceled) { // 1. Test the case that redirect is a subresource. MalwareRedirectCancelAndProceed("openWinIFrame"); // If the redirect was from subresource but canceled, "proceed" will continue // with the rest of resources. AssertNoInterstitial(true); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, RedirectCanceled) { // 2. Test the case that redirect is the only resource. MalwareRedirectCancelAndProceed("openWin"); // Clicking proceed won't do anything if the main request is cancelled // already. See crbug.com/76460. EXPECT_TRUE(YesInterstitial()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, DontProceed) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif SetupWarningAndNavigate(); EXPECT_EQ(VISIBLE, GetVisibility("primary-button")); EXPECT_EQ(HIDDEN, GetVisibility("details")); EXPECT_EQ(HIDDEN, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("error-code")); EXPECT_TRUE(Click("details-button")); EXPECT_EQ(VISIBLE, GetVisibility("details")); EXPECT_EQ(VISIBLE, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("error-code")); EXPECT_TRUE(ClickAndWaitForDetach("primary-button")); AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(url::kAboutBlankURL), // Back to "about:blank" browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, Proceed) { GURL url = SetupWarningAndNavigate(); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone. EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, IframeDontProceed) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif SetupThreatIframeWarningAndNavigate(); EXPECT_EQ(VISIBLE, GetVisibility("primary-button")); EXPECT_EQ(HIDDEN, GetVisibility("details")); EXPECT_EQ(HIDDEN, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("error-code")); EXPECT_TRUE(Click("details-button")); EXPECT_EQ(VISIBLE, GetVisibility("details")); EXPECT_EQ(VISIBLE, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("error-code")); EXPECT_TRUE(ClickAndWaitForDetach("primary-button")); AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(url::kAboutBlankURL), // Back to "about:blank" browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, IframeProceed) { GURL url = SetupThreatIframeWarningAndNavigate(); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, IframeOptInAndReportThreatDetails) { // The extended reporting opt-in is presented in the interstitial for malware, // phishing, and UwS threats. This test uses malware as an example to verify // this reporting functionality. const bool expect_threat_details = SafeBrowsingBlockingPage::ShouldReportThreatDetails( testing::get<0>(GetParam())); scoped_refptr threat_report_sent_runner( new content::MessageLoopRunner); if (expect_threat_details) SetReportSentCallback(threat_report_sent_runner->QuitClosure()); // Set up testing url containing iframe and cross site iframe. GURL url = SetupThreatIframeWarningAndNavigate(); ThreatDetails* threat_details = details_factory_.get_details(); EXPECT_EQ(expect_threat_details, threat_details != nullptr); EXPECT_EQ(VISIBLE, GetVisibility("extended-reporting-opt-in")); EXPECT_TRUE(Click("opt-in-checkbox")); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean( prefs::kSafeBrowsingExtendedReportingEnabled)); EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); if (expect_threat_details) { threat_report_sent_runner->Run(); std::string serialized = GetReportSent(); ClientSafeBrowsingReportRequest report; ASSERT_TRUE(report.ParseFromString(serialized)); // Verify the report is complete. EXPECT_TRUE(report.complete()); // Do some basic verification of report contents. EXPECT_EQ(url.spec(), report.page_url()); EXPECT_EQ(net::URLRequestMockHTTPJob::GetMockUrl(kMalwareIframe).spec(), report.url()); std::vector resources; for (auto resource: report.resources()) { resources.push_back(resource); } // Sort resources based on their urls. std::sort(resources.begin(), resources.end(), [](const ClientSafeBrowsingReportRequest::Resource& a, const ClientSafeBrowsingReportRequest::Resource& b) -> bool { return a.url() < b.url(); }); ASSERT_EQ(3U, resources.size()); VerifyResource( report, resources[0], kCrossSiteIframeUrl, net::URLRequestMockHTTPJob::GetMockUrl(kCrossSiteMalwarePage).spec(), 0, "IFRAME"); VerifyResource( report, resources[1], net::URLRequestMockHTTPJob::GetMockUrl(kCrossSiteMalwarePage).spec(), net::URLRequestMockHTTPJob::GetMockUrl(kCrossSiteMalwarePage).spec(), 2, ""); VerifyResource( report, resources[2], net::URLRequestMockHTTPJob::GetMockUrl(kMalwareIframe).spec(), url.spec(), // kCrossSiteMalwarePage 0, "IFRAME"); } } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, MainFrameBlockedShouldHaveNoDOMDetailsWhenDontProceed) { const bool expect_threat_details = SafeBrowsingBlockingPage::ShouldReportThreatDetails( testing::get<0>(GetParam())); scoped_refptr threat_report_sent_runner( new content::MessageLoopRunner); if (expect_threat_details) SetReportSentCallback(threat_report_sent_runner->QuitClosure()); // Navigate to a safe page which contains multiple potential DOM details. // (Despite the name, kMalwarePage is not the page flagged as malware in this // test.) GURL safe_url(net::URLRequestMockHTTPJob::GetMockUrl(kMalwarePage)); ui_test_utils::NavigateToURL(browser(), safe_url); EXPECT_EQ(nullptr, details_factory_.get_details()); // Start navigation to bad page (kEmptyPage), which will be blocked before it // is committed. GURL url = SetupWarningAndNavigate(); ThreatDetails* threat_details = details_factory_.get_details(); EXPECT_EQ(expect_threat_details, threat_details != nullptr); // Go back. EXPECT_EQ(VISIBLE, GetVisibility("extended-reporting-opt-in")); EXPECT_TRUE(Click("opt-in-checkbox")); EXPECT_TRUE(ClickAndWaitForDetach("primary-button")); AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean( prefs::kSafeBrowsingExtendedReportingEnabled)); EXPECT_EQ(safe_url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); if (expect_threat_details) { threat_report_sent_runner->Run(); std::string serialized = GetReportSent(); ClientSafeBrowsingReportRequest report; ASSERT_TRUE(report.ParseFromString(serialized)); // Verify the report is complete. EXPECT_TRUE(report.complete()); EXPECT_EQ(url.spec(), report.page_url()); EXPECT_EQ(url.spec(), report.url()); ASSERT_EQ(1, report.resources_size()); EXPECT_EQ(url.spec(), report.resources(0).url()); } } IN_PROC_BROWSER_TEST_P( SafeBrowsingBlockingPageBrowserTest, MainFrameBlockedShouldHaveNoDOMDetailsWhenProceeding) { const bool expect_threat_details = SafeBrowsingBlockingPage::ShouldReportThreatDetails( testing::get<0>(GetParam())); scoped_refptr threat_report_sent_runner( new content::MessageLoopRunner); if (expect_threat_details) SetReportSentCallback(threat_report_sent_runner->QuitClosure()); // Navigate to a safe page which contains multiple potential DOM details. // (Despite the name, kMalwarePage is not the page flagged as malware in this // test.) ui_test_utils::NavigateToURL( browser(), net::URLRequestMockHTTPJob::GetMockUrl(kMalwarePage)); EXPECT_EQ(nullptr, details_factory_.get_details()); // Start navigation to bad page (kEmptyPage), which will be blocked before it // is committed. GURL url = SetupWarningAndNavigate(); ThreatDetails* threat_details = details_factory_.get_details(); EXPECT_EQ(expect_threat_details, threat_details != nullptr); // Proceed through the warning. EXPECT_EQ(VISIBLE, GetVisibility("extended-reporting-opt-in")); EXPECT_TRUE(Click("opt-in-checkbox")); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean( prefs::kSafeBrowsingExtendedReportingEnabled)); EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); if (expect_threat_details) { threat_report_sent_runner->Run(); std::string serialized = GetReportSent(); ClientSafeBrowsingReportRequest report; ASSERT_TRUE(report.ParseFromString(serialized)); // Verify the report is complete. EXPECT_TRUE(report.complete()); EXPECT_EQ(url.spec(), report.page_url()); EXPECT_EQ(url.spec(), report.url()); ASSERT_EQ(1, report.resources_size()); EXPECT_EQ(url.spec(), report.resources(0).url()); } } // Verifies that the "proceed anyway" link isn't available when it is disabled // by the corresponding policy. Also verifies that sending the "proceed" // command anyway doesn't advance to the unsafe site. IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, ProceedDisabled) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif // Simulate a policy disabling the "proceed anyway" link. browser()->profile()->GetPrefs()->SetBoolean( prefs::kSafeBrowsingProceedAnywayDisabled, true); SetupWarningAndNavigate(); EXPECT_EQ(VISIBLE, GetVisibility("primary-button")); EXPECT_EQ(HIDDEN, GetVisibility("details")); EXPECT_EQ(HIDDEN, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("final-paragraph")); EXPECT_TRUE(Click("details-button")); EXPECT_EQ(HIDDEN, GetVisibility("proceed-link")); EXPECT_EQ(HIDDEN, GetVisibility("final-paragraph")); SendCommand(security_interstitials::CMD_PROCEED); // The "proceed" command should go back instead, if proceeding is disabled. AssertNoInterstitial(true); EXPECT_EQ(GURL(url::kAboutBlankURL), // Back to "about:blank" browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); } // Verifies that the reporting checkbox is hidden on non-HTTP pages. // TODO(mattm): Should also verify that no report is sent, but there isn't a // good way to do that in the current design. IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, ReportingDisabled) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif browser()->profile()->GetPrefs()->SetBoolean( prefs::kSafeBrowsingExtendedReportingEnabled, true); TestReportingDisabledAndDontProceed( net::URLRequestMockHTTPJob::GetMockHttpsUrl(kEmptyPage)); } // Verifies that the reporting checkbox is hidden when opt-in is // disabled by policy. IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, ReportingDisabledByPolicy) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif browser()->profile()->GetPrefs()->SetBoolean( prefs::kSafeBrowsingExtendedReportingEnabled, true); browser()->profile()->GetPrefs()->SetBoolean( prefs::kSafeBrowsingExtendedReportingOptInAllowed, false); TestReportingDisabledAndDontProceed( net::URLRequestMockHTTPJob::GetMockUrl(kEmptyPage)); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, LearnMore) { SetupWarningAndNavigate(); EXPECT_TRUE(ClickAndWaitForDetach("help-link")); AssertNoInterstitial(false); // Assert the interstitial is gone // We are in the help page. EXPECT_EQ( testing::get<0>(GetParam()) == SB_THREAT_TYPE_URL_PHISHING ? "/transparencyreport/safebrowsing/" : "/safebrowsing/diagnostic", browser()->tab_strip_model()->GetActiveWebContents()->GetURL().path()); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, Histograms_DontProceed) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (https://crbug.com/262796). if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAshBrowserTests)) { return; } #endif base::HistogramTester histograms; std::string prefix; SBThreatType threat_type = testing::get<0>(GetParam()); if (threat_type == SB_THREAT_TYPE_URL_MALWARE) prefix = "malware"; else if (threat_type == SB_THREAT_TYPE_URL_PHISHING) prefix = "phishing"; else if (threat_type == SB_THREAT_TYPE_URL_UNWANTED) prefix = "harmful"; else NOTREACHED(); const std::string decision_histogram = "interstitial." + prefix + ".decision"; const std::string interaction_histogram = "interstitial." + prefix + ".interaction"; // TODO(nparker): Check for *.from_device as well. // Histograms should start off empty. histograms.ExpectTotalCount(decision_histogram, 0); histograms.ExpectTotalCount(interaction_histogram, 0); // After navigating to the page, the totals should be set. SetupWarningAndNavigate(); histograms.ExpectTotalCount(decision_histogram, 1); histograms.ExpectBucketCount(decision_histogram, security_interstitials::MetricsHelper::SHOW, 1); histograms.ExpectTotalCount(interaction_histogram, 1); histograms.ExpectBucketCount( interaction_histogram, security_interstitials::MetricsHelper::TOTAL_VISITS, 1); // Decision should be recorded. EXPECT_TRUE(ClickAndWaitForDetach("primary-button")); AssertNoInterstitial(false); // Assert the interstitial is gone histograms.ExpectTotalCount(decision_histogram, 2); histograms.ExpectBucketCount( decision_histogram, security_interstitials::MetricsHelper::DONT_PROCEED, 1); histograms.ExpectTotalCount(interaction_histogram, 1); histograms.ExpectBucketCount( interaction_histogram, security_interstitials::MetricsHelper::TOTAL_VISITS, 1); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, Histograms_Proceed) { base::HistogramTester histograms; std::string prefix; SBThreatType threat_type = testing::get<0>(GetParam()); if (threat_type == SB_THREAT_TYPE_URL_MALWARE) prefix = "malware"; else if (threat_type == SB_THREAT_TYPE_URL_PHISHING) prefix = "phishing"; else if (threat_type == SB_THREAT_TYPE_URL_UNWANTED) prefix = "harmful"; else NOTREACHED(); const std::string decision_histogram = "interstitial." + prefix + ".decision"; const std::string interaction_histogram = "interstitial." + prefix + ".interaction"; // Histograms should start off empty. histograms.ExpectTotalCount(decision_histogram, 0); histograms.ExpectTotalCount(interaction_histogram, 0); // After navigating to the page, the totals should be set. GURL url = SetupWarningAndNavigate(); histograms.ExpectTotalCount(decision_histogram, 1); histograms.ExpectBucketCount(decision_histogram, security_interstitials::MetricsHelper::SHOW, 1); histograms.ExpectTotalCount(interaction_histogram, 1); histograms.ExpectBucketCount( interaction_histogram, security_interstitials::MetricsHelper::TOTAL_VISITS, 1); // Decision should be recorded. EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone. histograms.ExpectTotalCount(decision_histogram, 2); histograms.ExpectBucketCount( decision_histogram, security_interstitials::MetricsHelper::PROCEED, 1); histograms.ExpectTotalCount(interaction_histogram, 1); histograms.ExpectBucketCount( interaction_histogram, security_interstitials::MetricsHelper::TOTAL_VISITS, 1); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, WhitelistRevisit) { GURL url = SetupWarningAndNavigate(); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone. EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); // Unrelated pages should not be whitelisted now. ui_test_utils::NavigateToURL(browser(), GURL(kUnrelatedUrl)); AssertNoInterstitial(false); // The whitelisted page should remain whitelisted. ui_test_utils::NavigateToURL(browser(), url); AssertNoInterstitial(false); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, WhitelistIframeRevisit) { GURL url = SetupThreatIframeWarningAndNavigate(); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); // Assert the interstitial is gone. EXPECT_EQ(url, browser()->tab_strip_model()->GetActiveWebContents()->GetURL()); // Unrelated pages should not be whitelisted now. ui_test_utils::NavigateToURL(browser(), GURL(kUnrelatedUrl)); AssertNoInterstitial(false); // The whitelisted page should remain whitelisted. ui_test_utils::NavigateToURL(browser(), url); AssertNoInterstitial(false); } IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, WhitelistUnsaved) { GURL url = SetupWarningAndNavigate(); // Navigate without making a decision. ui_test_utils::NavigateToURL(browser(), GURL(kUnrelatedUrl)); AssertNoInterstitial(false); // The non-whitelisted page should now show an interstitial. ui_test_utils::NavigateToURL(browser(), url); EXPECT_TRUE(WaitForReady()); EXPECT_TRUE(ClickAndWaitForDetach("proceed-link")); AssertNoInterstitial(true); } INSTANTIATE_TEST_CASE_P( SafeBrowsingBlockingPageBrowserTestWithThreatTypeAndIsolationSetting, SafeBrowsingBlockingPageBrowserTest, testing::Combine( testing::Values(SB_THREAT_TYPE_URL_MALWARE, // Threat types SB_THREAT_TYPE_URL_PHISHING, SB_THREAT_TYPE_URL_UNWANTED), testing::Bool())); // If isolate all sites for testing. // Test that SafeBrowsingBlockingPage properly decodes IDN URLs that are // displayed. class SafeBrowsingBlockingPageIDNTest : public SecurityInterstitialIDNTest, public testing::WithParamInterface> { protected: // SecurityInterstitialIDNTest implementation SecurityInterstitialPage* CreateInterstitial( content::WebContents* contents, const GURL& request_url) const override { const bool is_subresource = testing::get<0>(GetParam()); SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service(); SafeBrowsingBlockingPage::UnsafeResource resource; resource.url = request_url; resource.is_subresource = is_subresource; resource.threat_type = testing::get<1>(GetParam()); resource.render_process_host_id = contents->GetRenderProcessHost()->GetID(); resource.render_frame_id = contents->GetMainFrame()->GetRoutingID(); resource.threat_source = safe_browsing::ThreatSource::LOCAL_PVER3; return SafeBrowsingBlockingPage::CreateBlockingPage( sb_service->ui_manager().get(), contents, is_subresource ? GURL("http://mainframe.example.com/") : request_url, resource); } }; IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageIDNTest, SafeBrowsingBlockingPageDecodesIDN) { EXPECT_TRUE(VerifyIDNDecoded()); } INSTANTIATE_TEST_CASE_P( SafeBrowsingBlockingPageIDNTestWithThreatType, SafeBrowsingBlockingPageIDNTest, testing::Combine(testing::Values(false, true), testing::Values(SB_THREAT_TYPE_URL_MALWARE, SB_THREAT_TYPE_URL_PHISHING, SB_THREAT_TYPE_URL_UNWANTED))); } // namespace safe_browsing