blob: 668b9d9de719ff95e86419c1cb5e8d8b35d04403 [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 "components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/test/histogram_tester.h"
#include "components/safe_browsing_db/util.h"
#include "components/subresource_filter/content/browser/content_activation_list_utils.h"
#include "components/subresource_filter/content/browser/subresource_filter_client.h"
#include "components/subresource_filter/content/common/subresource_filter_messages.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "components/subresource_filter/core/browser/subresource_filter_features_test_support.h"
#include "components/subresource_filter/core/common/activation_list.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "net/base/net_errors.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace subresource_filter {
using ActivationDecision =
ContentSubresourceFilterDriverFactory::ActivationDecision;
namespace {
const char kExampleUrlWithParams[] = "https://example.com/soceng?q=engsoc";
const char kExampleUrl[] = "https://example.com";
const char kExampleLoginUrl[] = "https://example.com/login";
const char kUrlA[] = "https://example_a.com";
const char kUrlB[] = "https://example_b.com";
const char kUrlC[] = "https://example_c.com";
const char kUrlD[] = "https://example_d.com";
const char kSubframeName[] = "Child";
const char kMatchesPatternHistogramName[] =
"SubresourceFilter.PageLoad.RedirectChainMatchPattern.";
const char kNavigationChainSize[] =
"SubresourceFilter.PageLoad.RedirectChainLength.";
// Human readable representation of expected redirect chain match patterns.
// The explanations for the buckets given for the following redirect chain:
// A->B->C->D, where A is initial URL and D is a final URL.
enum RedirectChainMatchPattern {
EMPTY, // No histograms were recorded.
F0M0L1, // D is a Safe Browsing match.
F0M1L0, // B or C, or both are Safe Browsing matches.
F0M1L1, // B or C, or both and D are Safe Browsing matches.
F1M0L0, // A is Safe Browsing match
F1M0L1, // A and D are Safe Browsing matches.
F1M1L0, // B and/or C and A are Safe Browsing matches.
F1M1L1, // B and/or C and A and D are Safe Browsing matches.
NO_REDIRECTS_HIT, // Redirect chain consists of single URL, aka no redirects
// has happened, and this URL was a Safe Browsing hit.
NUM_HIT_PATTERNS,
};
std::string GetSuffixForList(const ActivationList& type) {
switch (type) {
case ActivationList::SOCIAL_ENG_ADS_INTERSTITIAL:
return "SocialEngineeringAdsInterstitial";
case ActivationList::PHISHING_INTERSTITIAL:
return "PhishingInterstital";
case ActivationList::SUBRESOURCE_FILTER:
return "SubresourceFilterOnly";
case ActivationList::NONE:
return std::string();
}
return std::string();
}
struct ActivationListTestData {
ActivationDecision expected_activation_decision;
const char* const activation_list;
safe_browsing::SBThreatType threat_type;
safe_browsing::ThreatPatternType threat_type_metadata;
};
const ActivationListTestData kActivationListTestData[] = {
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED, "",
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListSocialEngineeringAdsInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::NONE},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListSocialEngineeringAdsInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::MALWARE_LANDING},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListSocialEngineeringAdsInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::MALWARE_DISTRIBUTION},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_API_ABUSE,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_BLACKLISTED_RESOURCE,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_BINARY_MALWARE_URL,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_UNWANTED,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_MALWARE,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_SAFE,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::NONE},
{ActivationDecision::ACTIVATED,
subresource_filter::kActivationListSocialEngineeringAdsInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
{ActivationDecision::ACTIVATED,
subresource_filter::kActivationListPhishingInterstitial,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS},
};
struct ActivationScopeTestData {
ActivationDecision expected_activation_decision;
bool url_matches_activation_list;
const char* const activation_scope;
};
const ActivationScopeTestData kActivationScopeTestData[] = {
{ActivationDecision::ACTIVATED, false /* url_matches_activation_list */,
kActivationScopeAllSites},
{ActivationDecision::ACTIVATED, true /* url_matches_activation_list */,
kActivationScopeAllSites},
{ActivationDecision::ACTIVATION_DISABLED,
true /* url_matches_activation_list */, kActivationScopeNoSites},
{ActivationDecision::ACTIVATED, true /* url_matches_activation_list */,
kActivationScopeActivationList},
{ActivationDecision::ACTIVATION_LIST_NOT_MATCHED,
false /* url_matches_activation_list */, kActivationScopeActivationList},
};
struct ActivationLevelTestData {
ActivationDecision expected_activation_decision;
const char* const activation_level;
};
const ActivationLevelTestData kActivationLevelTestData[] = {
{ActivationDecision::ACTIVATED, kActivationLevelDryRun},
{ActivationDecision::ACTIVATED, kActivationLevelEnabled},
{ActivationDecision::ACTIVATION_DISABLED, kActivationLevelDisabled},
};
class MockSubresourceFilterClient : public SubresourceFilterClient {
public:
MockSubresourceFilterClient() {}
~MockSubresourceFilterClient() override = default;
bool IsWhitelistedByContentSettings(const GURL& url) override {
return false;
}
void WhitelistByContentSettings(const GURL& url) override {}
MOCK_METHOD1(ToggleNotificationVisibility, void(bool));
private:
DISALLOW_COPY_AND_ASSIGN(MockSubresourceFilterClient);
};
} // namespace
class ContentSubresourceFilterDriverFactoryTest
: public content::RenderViewHostTestHarness {
public:
ContentSubresourceFilterDriverFactoryTest() {}
~ContentSubresourceFilterDriverFactoryTest() override {}
// content::RenderViewHostImplTestHarness:
void SetUp() override {
RenderViewHostTestHarness::SetUp();
client_ = new MockSubresourceFilterClient();
ContentSubresourceFilterDriverFactory::CreateForWebContents(
web_contents(), base::WrapUnique(client()));
// Add a subframe.
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
rfh_tester->InitializeRenderFrameIfNeeded();
rfh_tester->AppendChild(kSubframeName);
}
ContentSubresourceFilterDriverFactory* factory() {
return ContentSubresourceFilterDriverFactory::FromWebContents(
web_contents());
}
MockSubresourceFilterClient* client() { return client_; }
content::RenderFrameHost* GetSubframeRFH() {
for (content::RenderFrameHost* rfh : web_contents()->GetAllFrames()) {
if (rfh->GetFrameName() == kSubframeName)
return rfh;
}
return nullptr;
}
void ExpectActivationSignalForFrame(content::RenderFrameHost* rfh,
bool expect_activation) {
content::MockRenderProcessHost* render_process_host =
static_cast<content::MockRenderProcessHost*>(rfh->GetProcess());
const IPC::Message* message =
render_process_host->sink().GetFirstMessageMatching(
SubresourceFilterMsg_ActivateForNextCommittedLoad::ID);
ASSERT_EQ(expect_activation, !!message);
if (expect_activation) {
std::tuple<ActivationLevel, bool> args;
SubresourceFilterMsg_ActivateForNextCommittedLoad::Read(message, &args);
EXPECT_NE(ActivationLevel::DISABLED, std::get<0>(args));
}
render_process_host->sink().ClearMessages();
}
void BlacklistURLWithRedirectsNavigateAndCommit(
const std::vector<bool>& blacklisted_urls,
const std::vector<GURL>& navigation_chain,
safe_browsing::SBThreatType threat_type,
safe_browsing::ThreatPatternType threat_type_metadata,
const content::Referrer& referrer,
ui::PageTransition transition,
RedirectChainMatchPattern expected_pattern,
ActivationDecision expected_activation_decision) {
const bool expected_activation =
expected_activation_decision == ActivationDecision::ACTIVATED;
base::HistogramTester tester;
EXPECT_CALL(*client(), ToggleNotificationVisibility(false)).Times(1);
std::unique_ptr<content::NavigationSimulator> navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(
navigation_chain.front(), main_rfh());
navigation_simulator->SetReferrer(referrer);
navigation_simulator->SetTransition(transition);
navigation_simulator->Start();
if (blacklisted_urls.front()) {
factory()->OnMainResourceMatchedSafeBrowsingBlacklist(
navigation_chain.front(), navigation_chain, threat_type,
threat_type_metadata);
}
::testing::Mock::VerifyAndClearExpectations(client());
for (size_t i = 1; i < navigation_chain.size(); ++i) {
const GURL url = navigation_chain[i];
if (i < blacklisted_urls.size() && blacklisted_urls[i]) {
factory()->OnMainResourceMatchedSafeBrowsingBlacklist(
url, navigation_chain, threat_type, threat_type_metadata);
}
navigation_simulator->Redirect(url);
}
navigation_simulator->Commit();
ExpectActivationSignalForFrame(main_rfh(), expected_activation);
EXPECT_EQ(expected_activation_decision,
factory()->GetActivationDecisionForLastCommittedPageLoad());
// Re-create a subframe now that the frame has navigated.
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
rfh_tester->AppendChild(kSubframeName);
ActivationList activation_list =
GetListForThreatTypeAndMetadata(threat_type, threat_type_metadata);
const std::string suffix(GetSuffixForList(activation_list));
size_t all_pattern =
tester.GetTotalCountsForPrefix(kMatchesPatternHistogramName).size();
size_t all_chain_size =
tester.GetTotalCountsForPrefix(kNavigationChainSize).size();
if (expected_pattern != EMPTY) {
EXPECT_THAT(tester.GetAllSamples(kMatchesPatternHistogramName + suffix),
::testing::ElementsAre(base::Bucket(expected_pattern, 1)));
EXPECT_THAT(
tester.GetAllSamples(kNavigationChainSize + suffix),
::testing::ElementsAre(base::Bucket(navigation_chain.size(), 1)));
// Check that we recorded only what is needed.
EXPECT_EQ(1u, all_pattern);
EXPECT_EQ(1u, all_chain_size);
} else {
EXPECT_EQ(0u, all_pattern);
EXPECT_EQ(0u, all_chain_size);
}
}
void NavigateAndCommitSubframe(const GURL& url, bool expected_activation) {
EXPECT_CALL(*client(), ToggleNotificationVisibility(::testing::_)).Times(0);
content::NavigationSimulator::NavigateAndCommitFromDocument(
url, GetSubframeRFH());
ExpectActivationSignalForFrame(GetSubframeRFH(), expected_activation);
::testing::Mock::VerifyAndClearExpectations(client());
}
void NavigateAndExpectActivation(
const std::vector<bool>& blacklisted_urls,
const std::vector<GURL>& navigation_chain,
safe_browsing::SBThreatType threat_type,
safe_browsing::ThreatPatternType threat_type_metadata,
const content::Referrer& referrer,
ui::PageTransition transition,
RedirectChainMatchPattern expected_pattern,
ActivationDecision expected_activation_decision) {
const bool expected_activation =
expected_activation_decision == ActivationDecision::ACTIVATED;
BlacklistURLWithRedirectsNavigateAndCommit(
blacklisted_urls, navigation_chain, threat_type, threat_type_metadata,
referrer, transition, expected_pattern, expected_activation_decision);
NavigateAndCommitSubframe(GURL(kExampleLoginUrl), expected_activation);
}
void NavigateAndExpectActivation(
const std::vector<bool>& blacklisted_urls,
const std::vector<GURL>& navigation_chain,
RedirectChainMatchPattern expected_pattern,
ActivationDecision expected_activation_decision) {
NavigateAndExpectActivation(
blacklisted_urls, navigation_chain,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS,
content::Referrer(), ui::PAGE_TRANSITION_LINK, expected_pattern,
expected_activation_decision);
}
void EmulateDidDisallowFirstSubresourceMessage() {
factory()->OnMessageReceived(
SubresourceFilterHostMsg_DidDisallowFirstSubresource(
main_rfh()->GetRoutingID()),
main_rfh());
}
void EmulateFailedNavigationAndExpectNoActivation(const GURL& url) {
EXPECT_CALL(*client(), ToggleNotificationVisibility(false)).Times(1);
// With browser-side navigation enabled, ReadyToCommitNavigation is invoked
// even for failed navigations. This is correctly simulated by
// NavigationSimulator. Make sure no activation message is sent in this
// case.
content::NavigationSimulator::NavigateAndFailFromDocument(
url, net::ERR_TIMED_OUT, main_rfh());
ExpectActivationSignalForFrame(main_rfh(), false);
::testing::Mock::VerifyAndClearExpectations(client());
}
void EmulateInPageNavigation(
const std::vector<bool>& blacklisted_urls,
RedirectChainMatchPattern expected_pattern,
ActivationDecision expected_activation_decision) {
// This test examines the navigation with the following sequence of events:
// DidStartProvisional(main, "example.com")
// ReadyToCommitNavigation(“example.com”)
// DidCommitProvisional(main, "example.com")
// DidStartProvisional(sub, "example.com/login")
// DidCommitProvisional(sub, "example.com/login")
// DidCommitProvisional(main, "example.com#ref")
NavigateAndExpectActivation(blacklisted_urls, {GURL(kExampleUrl)},
expected_pattern, expected_activation_decision);
EXPECT_CALL(*client(), ToggleNotificationVisibility(::testing::_)).Times(0);
std::unique_ptr<content::NavigationSimulator> navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(GURL(kExampleUrl),
main_rfh());
navigation_simulator->CommitSameDocument();
ExpectActivationSignalForFrame(main_rfh(), false);
::testing::Mock::VerifyAndClearExpectations(client());
}
private:
static bool expected_measure_performance() {
const double rate = GetPerformanceMeasurementRate();
// Note: The case when 0 < rate < 1 is not deterministic, don't test it.
EXPECT_TRUE(rate == 0 || rate == 1);
return rate == 1;
}
// Owned by the factory.
MockSubresourceFilterClient* client_;
DISALLOW_COPY_AND_ASSIGN(ContentSubresourceFilterDriverFactoryTest);
};
class ContentSubresourceFilterDriverFactoryThreatTypeTest
: public ContentSubresourceFilterDriverFactoryTest,
public ::testing::WithParamInterface<ActivationListTestData> {
public:
ContentSubresourceFilterDriverFactoryThreatTypeTest() {}
~ContentSubresourceFilterDriverFactoryThreatTypeTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(ContentSubresourceFilterDriverFactoryThreatTypeTest);
};
class ContentSubresourceFilterDriverFactoryActivationScopeTest
: public ContentSubresourceFilterDriverFactoryTest,
public ::testing::WithParamInterface<ActivationScopeTestData> {
public:
ContentSubresourceFilterDriverFactoryActivationScopeTest() {}
~ContentSubresourceFilterDriverFactoryActivationScopeTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(
ContentSubresourceFilterDriverFactoryActivationScopeTest);
};
class ContentSubresourceFilterDriverFactoryActivationLevelTest
: public ContentSubresourceFilterDriverFactoryTest,
public ::testing::WithParamInterface<ActivationLevelTestData> {
public:
ContentSubresourceFilterDriverFactoryActivationLevelTest() {}
~ContentSubresourceFilterDriverFactoryActivationLevelTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(
ContentSubresourceFilterDriverFactoryActivationLevelTest);
};
TEST_F(ContentSubresourceFilterDriverFactoryTest,
ActivateForFrameHostDisabledFeature) {
// Activation scope is set to NONE => no activation should happen even if URL
// which is visited was a SB hit.
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_DISABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites,
kActivationListSocialEngineeringAdsInterstitial);
const GURL url(kExampleUrlWithParams);
NavigateAndExpectActivation({true}, {url}, NO_REDIRECTS_HIT,
ActivationDecision::ACTIVATION_DISABLED);
factory()->AddHostOfURLToWhitelistSet(url);
NavigateAndExpectActivation({true}, {url}, NO_REDIRECTS_HIT,
ActivationDecision::ACTIVATION_DISABLED);
}
TEST_F(ContentSubresourceFilterDriverFactoryTest, NoActivationWhenNoMatch) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeActivationList,
kActivationListSocialEngineeringAdsInterstitial);
NavigateAndExpectActivation({false}, {GURL(kExampleUrl)}, EMPTY,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED);
}
TEST_F(ContentSubresourceFilterDriverFactoryTest,
SpecialCaseNavigationAllSitesEnabled) {
// Check that when the experiment is enabled for all site, the activation
// signal is always sent.
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites);
EmulateInPageNavigation({false}, EMPTY, ActivationDecision::ACTIVATED);
}
TEST_F(ContentSubresourceFilterDriverFactoryTest,
SpecialCaseNavigationActivationListEnabled) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeActivationList,
kActivationListSocialEngineeringAdsInterstitial);
EmulateInPageNavigation({true}, NO_REDIRECTS_HIT,
ActivationDecision::ACTIVATED);
}
TEST_F(ContentSubresourceFilterDriverFactoryTest,
SpecialCaseNavigationActivationListEnabledWithPerformanceMeasurement) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeActivationList,
kActivationListSocialEngineeringAdsInterstitial,
"1" /* performance_measurement_rate */);
EmulateInPageNavigation({true}, NO_REDIRECTS_HIT,
ActivationDecision::ACTIVATED);
}
TEST_F(ContentSubresourceFilterDriverFactoryTest, FailedNavigation) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites);
const GURL url(kExampleUrl);
NavigateAndExpectActivation({false}, {url}, EMPTY,
ActivationDecision::ACTIVATED);
EmulateFailedNavigationAndExpectNoActivation(url);
}
// TODO(melandory): refactor the test so it no longer require the current
// activation list to be matching.
TEST_F(ContentSubresourceFilterDriverFactoryTest, RedirectPatternTest) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeActivationList,
kActivationListSocialEngineeringAdsInterstitial);
struct RedirectRedirectChainMatchPatternTestData {
std::vector<bool> blacklisted_urls;
std::vector<GURL> navigation_chain;
RedirectChainMatchPattern hit_expected_pattern;
ActivationDecision expected_activation_decision;
} kRedirectRedirectChainMatchPatternTestData[] = {
{{false},
{GURL(kUrlA)},
EMPTY,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{true}, {GURL(kUrlA)}, NO_REDIRECTS_HIT, ActivationDecision::ACTIVATED},
{{false, false},
{GURL(kUrlA), GURL(kUrlB)},
EMPTY,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{false, true},
{GURL(kUrlA), GURL(kUrlB)},
F0M0L1,
ActivationDecision::ACTIVATED},
{{true, false},
{GURL(kUrlA), GURL(kUrlB)},
F1M0L0,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{true, true},
{GURL(kUrlA), GURL(kUrlB)},
F1M0L1,
ActivationDecision::ACTIVATED},
{{false, false, false},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
EMPTY,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{false, false, true},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F0M0L1,
ActivationDecision::ACTIVATED},
{{false, true, false},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F0M1L0,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{false, true, true},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F0M1L1,
ActivationDecision::ACTIVATED},
{{true, false, false},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F1M0L0,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{true, false, true},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F1M0L1,
ActivationDecision::ACTIVATED},
{{true, true, false},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F1M1L0,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
{{true, true, true},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC)},
F1M1L1,
ActivationDecision::ACTIVATED},
{{false, true, false, false},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC), GURL(kUrlD)},
F0M1L0,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED},
};
for (size_t i = 0U; i < arraysize(kRedirectRedirectChainMatchPatternTestData);
++i) {
auto test_data = kRedirectRedirectChainMatchPatternTestData[i];
NavigateAndExpectActivation(
test_data.blacklisted_urls, test_data.navigation_chain,
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS,
content::Referrer(), ui::PAGE_TRANSITION_LINK,
test_data.hit_expected_pattern, test_data.expected_activation_decision);
NavigateAndExpectActivation(
{false}, {GURL("https://dummy.com")}, EMPTY,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED);
#if defined(GOOGLE_CHROME_BUILD)
NavigateAndExpectActivation(
test_data.blacklisted_urls, test_data.navigation_chain,
safe_browsing::SB_THREAT_TYPE_SUBRESOURCE_FILTER,
safe_browsing::ThreatPatternType::NONE, content::Referrer(),
ui::PAGE_TRANSITION_LINK, test_data.hit_expected_pattern,
ActivationDecision::ACTIVATION_LIST_NOT_MATCHED);
#endif
}
}
TEST_F(ContentSubresourceFilterDriverFactoryTest, NotificationVisibility) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites);
NavigateAndExpectActivation({false}, {GURL(kExampleUrl)}, EMPTY,
ActivationDecision::ACTIVATED);
EXPECT_CALL(*client(), ToggleNotificationVisibility(true)).Times(1);
EmulateDidDisallowFirstSubresourceMessage();
}
TEST_F(ContentSubresourceFilterDriverFactoryTest,
SuppressNotificationVisibility) {
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites, "" /* activation_lists */,
"" /* performance_measurement_rate */,
"true" /* suppress_notifications */);
NavigateAndExpectActivation({false}, {GURL(kExampleUrl)}, EMPTY,
ActivationDecision::ACTIVATED);
EXPECT_CALL(*client(), ToggleNotificationVisibility(::testing::_)).Times(0);
EmulateDidDisallowFirstSubresourceMessage();
}
TEST_F(ContentSubresourceFilterDriverFactoryTest, WhitelistSiteOnReload) {
const struct {
content::Referrer referrer;
ui::PageTransition transition;
ActivationDecision expected_activation_decision;
} kTestCases[] = {
{content::Referrer(), ui::PAGE_TRANSITION_LINK,
ActivationDecision::ACTIVATED},
{content::Referrer(GURL(kUrlA), blink::WebReferrerPolicyDefault),
ui::PAGE_TRANSITION_LINK, ActivationDecision::ACTIVATED},
{content::Referrer(GURL(kExampleUrl), blink::WebReferrerPolicyDefault),
ui::PAGE_TRANSITION_LINK, ActivationDecision::URL_WHITELISTED},
{content::Referrer(), ui::PAGE_TRANSITION_RELOAD,
ActivationDecision::URL_WHITELISTED}};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(::testing::Message("referrer = \"")
<< test_case.referrer.url << "\""
<< " transition = \"" << test_case.transition << "\"");
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeAllSites, "" /* activation_lists */,
"" /* performance_measurement_rate */, "" /* suppress_notifications */,
"true" /* whitelist_site_on_reload */);
NavigateAndExpectActivation(
{false}, {GURL(kExampleUrl)},
safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
safe_browsing::ThreatPatternType::SOCIAL_ENGINEERING_ADS,
test_case.referrer, test_case.transition, EMPTY,
test_case.expected_activation_decision);
// Verify that if the first URL failed to activate, subsequent same-origin
// navigations also fail to activate.
NavigateAndExpectActivation({false}, {GURL(kExampleUrlWithParams)}, EMPTY,
test_case.expected_activation_decision);
}
}
TEST_P(ContentSubresourceFilterDriverFactoryActivationLevelTest,
ActivateForFrameState) {
const ActivationLevelTestData& test_data = GetParam();
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, test_data.activation_level,
kActivationScopeActivationList,
kActivationListSocialEngineeringAdsInterstitial);
const GURL url(kExampleUrlWithParams);
NavigateAndExpectActivation({true}, {url}, NO_REDIRECTS_HIT,
test_data.expected_activation_decision);
factory()->AddHostOfURLToWhitelistSet(url);
NavigateAndExpectActivation(
{true}, {GURL(kExampleUrlWithParams)}, NO_REDIRECTS_HIT,
GetMaximumActivationLevel() == ActivationLevel::DISABLED
? ActivationDecision::ACTIVATION_DISABLED
: ActivationDecision::URL_WHITELISTED);
}
TEST_P(ContentSubresourceFilterDriverFactoryThreatTypeTest,
ActivateForTheListType) {
// Sets up the experiment in a way that the activation decision depends on the
// list for which the Safe Browsing hit has happened.
const ActivationListTestData& test_data = GetParam();
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
kActivationScopeActivationList, test_data.activation_list);
const GURL test_url("https://example.com/nonsoceng?q=engsocnon");
std::vector<GURL> navigation_chain;
ActivationList effective_list = GetListForThreatTypeAndMetadata(
test_data.threat_type, test_data.threat_type_metadata);
NavigateAndExpectActivation(
{false, false, false, true},
{GURL(kUrlA), GURL(kUrlB), GURL(kUrlC), test_url}, test_data.threat_type,
test_data.threat_type_metadata, content::Referrer(),
ui::PAGE_TRANSITION_LINK,
effective_list != ActivationList::NONE ? F0M0L1 : EMPTY,
test_data.expected_activation_decision);
};
TEST_P(ContentSubresourceFilterDriverFactoryActivationScopeTest,
ActivateForScopeType) {
const ActivationScopeTestData& test_data = GetParam();
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
test_data.activation_scope,
kActivationListSocialEngineeringAdsInterstitial);
const GURL test_url(kExampleUrlWithParams);
RedirectChainMatchPattern expected_pattern =
test_data.url_matches_activation_list ? NO_REDIRECTS_HIT : EMPTY;
NavigateAndExpectActivation({test_data.url_matches_activation_list},
{test_url}, expected_pattern,
test_data.expected_activation_decision);
if (test_data.url_matches_activation_list) {
factory()->AddHostOfURLToWhitelistSet(test_url);
NavigateAndExpectActivation(
{test_data.url_matches_activation_list}, {GURL(kExampleUrlWithParams)},
expected_pattern,
GetCurrentActivationScope() == ActivationScope::NO_SITES
? ActivationDecision::ACTIVATION_DISABLED
: ActivationDecision::URL_WHITELISTED);
}
};
// Only main frames with http/https schemes should activate, unless the
// activation scope is for all sites.
TEST_P(ContentSubresourceFilterDriverFactoryActivationScopeTest,
ActivateForSupportedUrlScheme) {
const ActivationScopeTestData& test_data = GetParam();
base::FieldTrialList field_trial_list(nullptr);
testing::ScopedSubresourceFilterFeatureToggle scoped_feature_toggle(
base::FeatureList::OVERRIDE_ENABLE_FEATURE, kActivationLevelEnabled,
test_data.activation_scope,
kActivationListSocialEngineeringAdsInterstitial);
// data URLs are also not supported, but not listed here, as it's not possible
// for a page to redirect to them after https://crbug.com/594215 is fixed.
const char* unsupported_urls[] = {"ftp://example.com/", "chrome://settings",
"chrome-extension://some-extension",
"file:///var/www/index.html"};
const char* supported_urls[] = {"http://example.test",
"https://example.test"};
for (auto* url : unsupported_urls) {
SCOPED_TRACE(url);
RedirectChainMatchPattern expected_pattern = EMPTY;
NavigateAndExpectActivation(
{test_data.url_matches_activation_list}, {GURL(url)}, expected_pattern,
GetCurrentActivationScope() == ActivationScope::NO_SITES
? ActivationDecision::ACTIVATION_DISABLED
: ActivationDecision::UNSUPPORTED_SCHEME);
}
for (auto* url : supported_urls) {
SCOPED_TRACE(url);
RedirectChainMatchPattern expected_pattern =
test_data.url_matches_activation_list ? NO_REDIRECTS_HIT : EMPTY;
NavigateAndExpectActivation({test_data.url_matches_activation_list},
{GURL(url)}, expected_pattern,
test_data.expected_activation_decision);
}
};
INSTANTIATE_TEST_CASE_P(NoSocEngHit,
ContentSubresourceFilterDriverFactoryThreatTypeTest,
::testing::ValuesIn(kActivationListTestData));
INSTANTIATE_TEST_CASE_P(
ActivationScopeTest,
ContentSubresourceFilterDriverFactoryActivationScopeTest,
::testing::ValuesIn(kActivationScopeTestData));
INSTANTIATE_TEST_CASE_P(
ActivationLevelTest,
ContentSubresourceFilterDriverFactoryActivationLevelTest,
::testing::ValuesIn(kActivationLevelTestData));
} // namespace subresource_filter