blob: ea2bc69e4c046f7a931fabf42b62f4cf005572e3 [file] [log] [blame]
// Copyright 2016 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 "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
#include "base/test/histogram_tester.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/window_open_disposition.h"
namespace {
const char kNavigationEventCleanUpHistogramName[] =
"SafeBrowsing.NavigationObserver.NavigationEventCleanUpCount";
const char kIPAddressCleanUpHistogramName[] =
"SafeBrowsing.NavigationObserver.IPAddressCleanUpCount";
}
namespace safe_browsing {
class SBNavigationObserverTest : public BrowserWithTestWindowTest {
public:
SBNavigationObserverTest() {}
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
AddTab(browser(), GURL("http://foo/0"));
navigation_observer_manager_ = new SafeBrowsingNavigationObserverManager();
navigation_observer_ = new SafeBrowsingNavigationObserver(
browser()->tab_strip_model()->GetWebContentsAt(0),
navigation_observer_manager_);
}
void TearDown() override {
delete navigation_observer_;
BrowserWithTestWindowTest::TearDown();
}
void VerifyNavigationEvent(const GURL& expected_source_url,
const GURL& expected_source_main_frame_url,
const GURL& expected_original_request_url,
const GURL& expected_destination_url,
int expected_source_tab,
int expected_target_tab,
bool expected_is_user_initiated,
bool expected_has_committed,
bool expected_has_server_redirect,
NavigationEvent* actual_nav_event) {
EXPECT_EQ(expected_source_url, actual_nav_event->source_url);
EXPECT_EQ(expected_source_main_frame_url,
actual_nav_event->source_main_frame_url);
EXPECT_EQ(expected_original_request_url,
actual_nav_event->original_request_url);
EXPECT_EQ(expected_destination_url, actual_nav_event->GetDestinationUrl());
EXPECT_EQ(expected_source_tab, actual_nav_event->source_tab_id);
EXPECT_EQ(expected_target_tab, actual_nav_event->target_tab_id);
EXPECT_EQ(expected_is_user_initiated, actual_nav_event->is_user_initiated);
EXPECT_EQ(expected_has_committed, actual_nav_event->has_committed);
EXPECT_EQ(expected_has_server_redirect,
!actual_nav_event->server_redirect_urls.empty());
}
NavigationEventList* navigation_event_list() {
return navigation_observer_manager_->navigation_event_list();
}
SafeBrowsingNavigationObserverManager::UserGestureMap* user_gesture_map() {
return &navigation_observer_manager_->user_gesture_map_;
}
SafeBrowsingNavigationObserverManager::HostToIpMap* host_to_ip_map() {
return &navigation_observer_manager_->host_to_ip_map_;
}
void RecordHostToIpMapping(const std::string& host, const std::string& ip) {
navigation_observer_manager_->RecordHostToIpMapping(host, ip);
}
std::unique_ptr<NavigationEvent> CreateNavigationEventUniquePtr(
const GURL& destination_url, const base::Time& timestamp) {
std::unique_ptr<NavigationEvent> nav_event_ptr =
base::MakeUnique<NavigationEvent>();
nav_event_ptr->original_request_url = destination_url;
nav_event_ptr->source_url = GURL("http://dummy.com");
nav_event_ptr->last_updated = timestamp;
return nav_event_ptr;
}
void CleanUpNavigationEvents() {
navigation_observer_manager_->CleanUpNavigationEvents();
}
void CleanUpIpAddresses() {
navigation_observer_manager_->CleanUpIpAddresses();
}
void CleanUpUserGestures() {
navigation_observer_manager_->CleanUpUserGestures();
}
protected:
SafeBrowsingNavigationObserverManager* navigation_observer_manager_;
SafeBrowsingNavigationObserver* navigation_observer_;
private:
DISALLOW_COPY_AND_ASSIGN(SBNavigationObserverTest);
};
TEST_F(SBNavigationObserverTest, TestNavigationEventList) {
NavigationEventList events(3);
EXPECT_EQ(nullptr,
events.FindNavigationEvent(GURL("http://invalid.com"), GURL(), -1));
EXPECT_EQ(0U, events.CleanUpNavigationEvents());
EXPECT_EQ(0U, events.Size());
// Add 2 events to the list.
base::Time now = base::Time::Now();
base::Time one_hour_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0 * 60.0);
events.RecordNavigationEvent(
CreateNavigationEventUniquePtr(GURL("http://foo1.com"), one_hour_ago));
events.RecordNavigationEvent(
CreateNavigationEventUniquePtr(GURL("http://foo1.com"), now));
EXPECT_EQ(2U, events.Size());
// FindNavigationEvent should return the latest matching event.
EXPECT_EQ(now,
events.FindNavigationEvent(GURL("http://foo1.com"), GURL(), -1)
->last_updated);
// One event should get removed.
EXPECT_EQ(1U, events.CleanUpNavigationEvents());
EXPECT_EQ(1U, events.Size());
// Add 3 more events, previously recorded events should be overridden.
events.RecordNavigationEvent(
CreateNavigationEventUniquePtr(GURL("http://foo3.com"), one_hour_ago));
events.RecordNavigationEvent(
CreateNavigationEventUniquePtr(GURL("http://foo4.com"), one_hour_ago));
events.RecordNavigationEvent(
CreateNavigationEventUniquePtr(GURL("http://foo5.com"), now));
ASSERT_EQ(3U, events.Size());
EXPECT_EQ(GURL("http://foo3.com"), events.Get(0)->original_request_url);
EXPECT_EQ(GURL("http://foo4.com"), events.Get(1)->original_request_url);
EXPECT_EQ(GURL("http://foo5.com"), events.Get(2)->original_request_url);
EXPECT_EQ(2U, events.CleanUpNavigationEvents());
EXPECT_EQ(1U, events.Size());
}
TEST_F(SBNavigationObserverTest, BasicNavigationAndCommit) {
// Navigation in current tab.
content::NavigationController* controller =
&browser()->tab_strip_model()->GetWebContentsAt(0)->GetController();
browser()->OpenURL(
content::OpenURLParams(GURL("http://foo/1"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
CommitPendingLoad(controller);
int tab_id = SessionTabHelper::IdForTab(controller->GetWebContents());
auto* nav_list = navigation_event_list();
ASSERT_EQ(1U, nav_list->Size());
VerifyNavigationEvent(GURL(), // source_url
GURL(), // source_main_frame_url
GURL("http://foo/1"), // original_request_url
GURL("http://foo/1"), // destination_url
tab_id, // source_tab_id
tab_id, // target_tab_id
true, // is_user_initiated
true, // has_committed
false, // has_server_redirect
nav_list->Get(0U));
}
TEST_F(SBNavigationObserverTest, ServerRedirect) {
auto navigation = content::NavigationSimulator::CreateRendererInitiated(
GURL("http://foo/3"),
browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
navigation->Start();
navigation->Redirect(GURL("http://redirect/1"));
navigation->Commit();
int tab_id = SessionTabHelper::IdForTab(
browser()->tab_strip_model()->GetWebContentsAt(0));
auto* nav_list = navigation_event_list();
ASSERT_EQ(1U, nav_list->Size());
VerifyNavigationEvent(GURL("http://foo/0"), // source_url
GURL("http://foo/0"), // source_main_frame_url
GURL("http://foo/3"), // original_request_url
GURL("http://redirect/1"), // destination_url
tab_id, // source_tab_id
tab_id, // target_tab_id
false, // is_user_initiated
true, // has_committed
true, // has_server_redirect
nav_list->Get(0U));
}
TEST_F(SBNavigationObserverTest, TestCleanUpStaleNavigationEvents) {
// Sets up navigation_event_list() such that it includes fresh, stale and
// invalid
// navigation events.
base::Time now = base::Time::Now(); // Fresh
base::Time one_hour_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0 * 60.0); // Stale
base::Time one_minute_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0); // Fresh
base::Time in_an_hour =
base::Time::FromDoubleT(now.ToDoubleT() + 60.0 * 60.0); // Invalid
GURL url_0("http://foo/0");
GURL url_1("http://foo/1");
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_0, in_an_hour));
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_0, one_hour_ago));
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_1, one_hour_ago));
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_1, one_hour_ago));
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_0, one_minute_ago));
navigation_event_list()->RecordNavigationEvent(
CreateNavigationEventUniquePtr(url_0, now));
ASSERT_EQ(6U, navigation_event_list()->Size());
base::HistogramTester histograms;
histograms.ExpectTotalCount(kNavigationEventCleanUpHistogramName, 0);
// Cleans up navigation events.
CleanUpNavigationEvents();
// Verifies all stale and invalid navigation events are removed.
ASSERT_EQ(2U, navigation_event_list()->Size());
EXPECT_EQ(nullptr,
navigation_event_list()->FindNavigationEvent(url_1, GURL(), -1));
EXPECT_THAT(histograms.GetAllSamples(kNavigationEventCleanUpHistogramName),
testing::ElementsAre(base::Bucket(4, 1)));
}
TEST_F(SBNavigationObserverTest, TestCleanUpStaleUserGestures) {
// Sets up user_gesture_map() such that it includes fresh, stale and invalid
// user gestures.
base::Time now = base::Time::Now(); // Fresh
base::Time three_minutes_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0 * 3); // Stale
base::Time in_an_hour =
base::Time::FromDoubleT(now.ToDoubleT() + 60.0 * 60.0); // Invalid
AddTab(browser(), GURL("http://foo/1"));
AddTab(browser(), GURL("http://foo/2"));
content::WebContents* content0 =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* content1 =
browser()->tab_strip_model()->GetWebContentsAt(1);
content::WebContents* content2 =
browser()->tab_strip_model()->GetWebContentsAt(2);
user_gesture_map()->insert(std::make_pair(content0, now));
user_gesture_map()->insert(std::make_pair(content1, three_minutes_ago));
user_gesture_map()->insert(std::make_pair(content2, in_an_hour));
ASSERT_EQ(3U, user_gesture_map()->size());
// Cleans up user_gesture_map()
CleanUpUserGestures();
// Verifies all stale and invalid user gestures are removed.
ASSERT_EQ(1U, user_gesture_map()->size());
EXPECT_NE(user_gesture_map()->end(), user_gesture_map()->find(content0));
EXPECT_EQ(now, user_gesture_map()->at(content0));
}
TEST_F(SBNavigationObserverTest, TestCleanUpStaleIPAddresses) {
// Sets up host_to_ip_map() such that it includes fresh, stale and invalid
// user gestures.
base::Time now = base::Time::Now(); // Fresh
base::Time one_hour_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0 * 60.0); // Stale
base::Time in_an_hour =
base::Time::FromDoubleT(now.ToDoubleT() + 60.0 * 60.0); // Invalid
std::string host_0 = GURL("http://foo/0").host();
std::string host_1 = GURL("http://bar/1").host();
host_to_ip_map()->insert(
std::make_pair(host_0, std::vector<ResolvedIPAddress>()));
host_to_ip_map()->at(host_0).push_back(ResolvedIPAddress(now, "1.1.1.1"));
host_to_ip_map()->at(host_0).push_back(
ResolvedIPAddress(one_hour_ago, "2.2.2.2"));
host_to_ip_map()->insert(
std::make_pair(host_1, std::vector<ResolvedIPAddress>()));
host_to_ip_map()->at(host_1).push_back(
ResolvedIPAddress(in_an_hour, "3.3.3.3"));
ASSERT_EQ(2U, host_to_ip_map()->size());
base::HistogramTester histograms;
histograms.ExpectTotalCount(kIPAddressCleanUpHistogramName, 0);
// Cleans up host_to_ip_map()
CleanUpIpAddresses();
// Verifies all stale and invalid IP addresses are removed.
ASSERT_EQ(1U, host_to_ip_map()->size());
EXPECT_EQ(host_to_ip_map()->end(), host_to_ip_map()->find(host_1));
ASSERT_EQ(1U, host_to_ip_map()->at(host_0).size());
EXPECT_EQ(now, host_to_ip_map()->at(host_0).front().timestamp);
EXPECT_THAT(histograms.GetAllSamples(kIPAddressCleanUpHistogramName),
testing::ElementsAre(base::Bucket(2, 1)));
}
TEST_F(SBNavigationObserverTest, TestRecordHostToIpMapping) {
// Setup host_to_ip_map().
base::Time now = base::Time::Now(); // Fresh
base::Time one_hour_ago =
base::Time::FromDoubleT(now.ToDoubleT() - 60.0 * 60.0); // Stale
std::string host_0 = GURL("http://foo/0").host();
host_to_ip_map()->insert(
std::make_pair(host_0, std::vector<ResolvedIPAddress>()));
host_to_ip_map()->at(host_0).push_back(ResolvedIPAddress(now, "1.1.1.1"));
host_to_ip_map()->at(host_0).push_back(
ResolvedIPAddress(one_hour_ago, "2.2.2.2"));
// Record a host-IP pair, where host is already in the map, and IP has
// never been seen before.
RecordHostToIpMapping(host_0, "3.3.3.3");
ASSERT_EQ(1U, host_to_ip_map()->size());
EXPECT_EQ(3U, host_to_ip_map()->at(host_0).size());
EXPECT_EQ("3.3.3.3", host_to_ip_map()->at(host_0).at(2).ip);
// Record a host-IP pair which is already in the map. It should simply update
// its timestamp.
ASSERT_EQ(now, host_to_ip_map()->at(host_0).at(0).timestamp);
RecordHostToIpMapping(host_0, "1.1.1.1");
ASSERT_EQ(1U, host_to_ip_map()->size());
EXPECT_EQ(3U, host_to_ip_map()->at(host_0).size());
EXPECT_LT(now, host_to_ip_map()->at(host_0).at(2).timestamp);
// Record a host-ip pair, neither of which has been seen before.
std::string host_1 = GURL("http://bar/1").host();
RecordHostToIpMapping(host_1, "9.9.9.9");
ASSERT_EQ(2U, host_to_ip_map()->size());
EXPECT_EQ(3U, host_to_ip_map()->at(host_0).size());
EXPECT_EQ(1U, host_to_ip_map()->at(host_1).size());
EXPECT_EQ("9.9.9.9", host_to_ip_map()->at(host_1).at(0).ip);
}
TEST_F(SBNavigationObserverTest, TestContentSettingChange) {
user_gesture_map()->clear();
ASSERT_EQ(0U, user_gesture_map()->size());
content::WebContents* web_content =
browser()->tab_strip_model()->GetWebContentsAt(0);
// Simulate content setting change via page info UI.
navigation_observer_->OnContentSettingChanged(
ContentSettingsPattern::FromURL(web_content->GetLastCommittedURL()),
ContentSettingsPattern::Wildcard(), CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
std::string());
// A user gesture should be recorded.
ASSERT_EQ(1U, user_gesture_map()->size());
EXPECT_NE(user_gesture_map()->end(), user_gesture_map()->find(web_content));
user_gesture_map()->clear();
ASSERT_EQ(0U, user_gesture_map()->size());
// Simulate content setting change that cannot be changed via page info UI.
navigation_observer_->OnContentSettingChanged(
ContentSettingsPattern::FromURL(web_content->GetLastCommittedURL()),
ContentSettingsPattern::Wildcard(), CONTENT_SETTINGS_TYPE_SITE_ENGAGEMENT,
std::string());
// No user gesture should be recorded.
EXPECT_EQ(0U, user_gesture_map()->size());
}
} // namespace safe_browsing