Commit e123e372 authored by Luke Zielinski's avatar Luke Zielinski Committed by Commit Bot

Basic structure for Suspicious Site trigger (ie: SWAN).

Added the skeleton trigger class, defined the associated ReportType
and ThreatType enums, and plugged the class into TriggerCreator.

This CL is a no-op. The trigger requires quota in order to be instantiated
and the default quota is 0, so it does not get created at the moment.

Bug: 817377
Change-Id: Ied66b2ecf9dc4eaf59b1d3e87d761996eb8a9d85
Reviewed-on: https://chromium-review.googlesource.com/941362Reviewed-by: default avatarMustafa Emre Acer <meacer@chromium.org>
Reviewed-by: default avatarJialiu Lin <jialiul@chromium.org>
Reviewed-by: default avatarVarun Khaneja <vakh@chromium.org>
Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Luke Z <lpz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541786}
parent 4ab948cd
......@@ -120,6 +120,7 @@ static_library("safe_browsing") {
"//components/safe_browsing/db:whitelist_checker_client",
"//components/safe_browsing/password_protection",
"//components/safe_browsing/triggers:ad_sampler_trigger",
"//components/safe_browsing/triggers:suspicious_site_trigger",
"//components/safe_browsing/triggers:trigger_throttler",
"//components/safe_browsing/triggers:triggers",
]
......
......@@ -10,6 +10,7 @@
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/triggers/ad_sampler_trigger.h"
#include "components/safe_browsing/triggers/suspicious_site_trigger.h"
#include "components/safe_browsing/triggers/trigger_manager.h"
#include "components/safe_browsing/triggers/trigger_throttler.h"
#include "components/security_interstitials/core/base_safe_browsing_error_ui.h"
......@@ -53,6 +54,14 @@ void TriggerCreator::MaybeCreateTriggersForWebContents(
HistoryServiceFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS));
}
if (trigger_manager->CanStartDataCollection(options,
TriggerType::SUSPICIOUS_SITE)) {
safe_browsing::SuspiciousSiteTrigger::CreateForWebContents(
web_contents, trigger_manager, profile->GetPrefs(),
profile->GetRequestContext(),
HistoryServiceFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS));
}
}
} // namespace safe_browsing
......@@ -294,6 +294,7 @@ SecurityStateTabHelper::GetMaliciousContentStatus() const {
case safe_browsing::SB_THREAT_TYPE_SUBRESOURCE_FILTER:
case safe_browsing::SB_THREAT_TYPE_CSD_WHITELIST:
case safe_browsing::SB_THREAT_TYPE_AD_SAMPLE:
case safe_browsing::SB_THREAT_TYPE_SUSPICIOUS_SITE:
// These threat types are not currently associated with
// interstitials, and thus resources with these threat types are
// not ever whitelisted or pending whitelisting.
......
......@@ -89,6 +89,8 @@ ClientSafeBrowsingReportRequest::ReportType GetReportTypeFromSBThreatType(
return ClientSafeBrowsingReportRequest::AD_SAMPLE;
case SB_THREAT_TYPE_PASSWORD_REUSE:
return ClientSafeBrowsingReportRequest::URL_PASSWORD_PROTECTION_PHISHING;
case SB_THREAT_TYPE_SUSPICIOUS_SITE:
return ClientSafeBrowsingReportRequest::URL_SUSPICIOUS;
default: // Gated by SafeBrowsingBlockingPage::ShouldReportThreatDetails.
NOTREACHED() << "We should not send report for threat type "
<< threat_type;
......
......@@ -134,6 +134,9 @@ enum SBThreatType {
// A sample of an ad was collected
SB_THREAT_TYPE_AD_SAMPLE,
// The page loaded a resource from the Suspicious Site list.
SB_THREAT_TYPE_SUSPICIOUS_SITE,
};
using SBThreatTypeSet = base::flat_set<SBThreatType>;
......
......@@ -977,6 +977,7 @@ message ClientSafeBrowsingReportRequest {
URL_PASSWORD_PROTECTION_PHISHING = 12;
DANGEROUS_DOWNLOAD_OPENED = 13;
AD_SAMPLE = 14;
URL_SUSPICIOUS = 15;
}
message HTTPHeader {
......
......@@ -50,6 +50,23 @@ source_set("ad_sampler_trigger") {
]
}
source_set("suspicious_site_trigger") {
sources = [
"suspicious_site_trigger.cc",
"suspicious_site_trigger.h",
]
deps = [
":trigger_throttler",
":triggers",
"//base:base",
"//components/history/core/browser:browser",
"//components/prefs:prefs",
"//components/safe_browsing:features",
"//content/public/browser",
"//net:net",
]
}
source_set("unit_tests") {
testonly = true
sources = [
......
include_rules = [
"+content/public/test",
"+components/history/core/browser",
"+components/prefs",
"+content/public/test",
]
// Copyright 2018 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/safe_browsing/triggers/suspicious_site_trigger.h"
#include "components/history/core/browser/history_service.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/triggers/trigger_manager.h"
#include "content/public/browser/web_contents.h"
#include "net/url_request/url_request_context_getter.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(safe_browsing::SuspiciousSiteTrigger);
namespace safe_browsing {
SuspiciousSiteTrigger::SuspiciousSiteTrigger(
content::WebContents* web_contents,
TriggerManager* trigger_manager,
PrefService* prefs,
net::URLRequestContextGetter* request_context,
history::HistoryService* history_service)
: content::WebContentsObserver(web_contents),
trigger_manager_(trigger_manager),
prefs_(prefs),
request_context_(request_context),
history_service_(history_service) {}
SuspiciousSiteTrigger::~SuspiciousSiteTrigger() {}
// static
void SuspiciousSiteTrigger::CreateForWebContents(
content::WebContents* web_contents,
TriggerManager* trigger_manager,
PrefService* prefs,
net::URLRequestContextGetter* request_context,
history::HistoryService* history_service) {
if (!FromWebContents(web_contents)) {
web_contents->SetUserData(UserDataKey(),
base::WrapUnique(new SuspiciousSiteTrigger(
web_contents, trigger_manager, prefs,
request_context, history_service)));
}
}
} // namespace safe_browsing
// Copyright 2018 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.
#ifndef COMPONENTS_SAFE_BROWSING_TRIGGERS_SUSPICIOUS_SITE_TRIGGER_H_
#define COMPONENTS_SAFE_BROWSING_TRIGGERS_SUSPICIOUS_SITE_TRIGGER_H_
#include "base/macros.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
class PrefService;
namespace history {
class HistoryService;
}
namespace net {
class URLRequestContextGetter;
}
namespace safe_browsing {
class TriggerManager;
// This class watches tab-level events such as the start and end of a page
// load, and also listens for events from the SuspiciousSiteURLThrottle that
// indicate there was a hit on the suspicious site list. This trigger is
// repsonsible for creating reports about the page at the right time, based on
// the sequence of such events.
class SuspiciousSiteTrigger
: public content::WebContentsObserver,
public content::WebContentsUserData<SuspiciousSiteTrigger> {
public:
~SuspiciousSiteTrigger() override;
static void CreateForWebContents(
content::WebContents* web_contents,
TriggerManager* trigger_manager,
PrefService* prefs,
net::URLRequestContextGetter* request_context,
history::HistoryService* history_service);
private:
friend class content::WebContentsUserData<SuspiciousSiteTrigger>;
SuspiciousSiteTrigger(content::WebContents* web_contents,
TriggerManager* trigger_manager,
PrefService* prefs,
net::URLRequestContextGetter* request_context,
history::HistoryService* history_service);
// TriggerManager gets called if this trigger detects a suspicious site and
// wants to collect data abou tit. Not owned.
TriggerManager* trigger_manager_;
PrefService* prefs_;
net::URLRequestContextGetter* request_context_;
history::HistoryService* history_service_;
DISALLOW_COPY_AND_ASSIGN(SuspiciousSiteTrigger);
};
} // namespace safe_browsing
#endif // COMPONENTS_SAFE_BROWSING_TRIGGERS_SUSPICIOUS_SITE_TRIGGER_H_
......@@ -27,11 +27,14 @@ bool TriggerNeedsScout(const TriggerType trigger_type) {
// Security interstitials only need legacy SBER opt-in.
return false;
case TriggerType::AD_SAMPLE:
// Ad samples need Scout-level opt-in.
// Ad samples need Scout-level opt-in (background data collection).
return true;
case TriggerType::GAIA_PASSWORD_REUSE:
// Gaia password reuses only need legacy SBER opt-in.
return false;
case TriggerType::SUSPICIOUS_SITE:
// Suspicious sites need Scout-level opt-in (background data collection).
return true;
}
// By default, require Scout so we are more restrictive on data collection.
return true;
......@@ -52,6 +55,10 @@ bool TriggerNeedsOptInForCollection(const TriggerType trigger_type) {
// while the trigger runs, so we require opt-in for collection to avoid
// overheads.
return true;
case TriggerType::SUSPICIOUS_SITE:
// Suspicious site collection happens in the background so the user must
// already be opted in before the trigger is allowed to run.
return true;
}
// By default, require opt-in for all triggers.
return true;
......@@ -62,7 +69,7 @@ bool CanSendReport(const SBErrorOptions& error_display_options,
// If the |kAdSamplerCollectButDontSendFeature| feature is enabled then we
// will overlook other checks to force the report to be created (which is safe
// because we ensure it will be discarded downstream).
// TODO(crbug.com/776893): Remote the feature and this logic.
// TODO(crbug.com/776893): Remove the feature and this logic.
if (trigger_type == TriggerType::AD_SAMPLE &&
base::FeatureList::IsEnabled(kAdSamplerCollectButDontSendFeature)) {
return true;
......
......@@ -182,8 +182,8 @@ size_t TriggerThrottler::GetDailyQuotaForTrigger(
case TriggerType::GAIA_PASSWORD_REUSE:
return kUnlimitedTriggerQuota;
case TriggerType::AD_SAMPLE:
// These triggers have quota configured via Finch, lookup the value in
// |trigger_quota_list|. If it's not found, return the default quota.
// Ad Samples have a non-zero default quota, but it can be overwritten
// through Finch.
if (TryFindQuotaForTrigger(trigger_type, trigger_type_and_quota_list_,
&quota_from_finch)) {
return quota_from_finch;
......@@ -191,6 +191,13 @@ size_t TriggerThrottler::GetDailyQuotaForTrigger(
return kAdSamplerTriggerDefaultQuota;
}
break;
case TriggerType::SUSPICIOUS_SITE:
// Suspicious Sites are disabled unless they are configured through Finch.
if (TryFindQuotaForTrigger(trigger_type, trigger_type_and_quota_list_,
&quota_from_finch)) {
return quota_from_finch;
}
break;
}
// By default, unhandled or unconfigured trigger types have no quota.
......
......@@ -26,6 +26,7 @@ enum class TriggerType {
SECURITY_INTERSTITIAL = 1,
AD_SAMPLE = 2,
GAIA_PASSWORD_REUSE = 3,
SUSPICIOUS_SITE = 4,
};
struct TriggerTypeHash {
......@@ -61,7 +62,7 @@ class TriggerThrottler {
private:
friend class TriggerThrottlerTest;
FRIEND_TEST_ALL_PREFIXES(TriggerThrottlerTestFinch, AdSamplerDefaultQuota);
friend class TriggerThrottlerTestFinch;
// Called to periodically clean-up the list of event timestamps.
void CleanupOldEvents();
......
......@@ -138,26 +138,41 @@ TEST_F(TriggerThrottlerTest, TriggerQuotaResetsAfterOneDay) {
ElementsAre(advanced_ts));
}
TEST(TriggerThrottlerTestFinch, ConfigureQuotaViaFinch) {
// Make sure that setting the quota param via Finch params works as expected.
class TriggerThrottlerTestFinch : public ::testing::Test {
public:
std::unique_ptr<base::FeatureList> SetupQuotaInFinch(
const TriggerType trigger_type,
const std::string& group_name,
int quota) {
base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name, group_name);
std::map<std::string, std::string> feature_params;
feature_params[std::string(safe_browsing::kTriggerTypeAndQuotaParam)] =
base::StringPrintf("%d,%d", trigger_type, quota);
base::AssociateFieldTrialParams(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name, group_name,
feature_params);
std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
feature_list->InitializeFromCommandLine(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name, std::string());
feature_list->AssociateReportingFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
return feature_list;
}
size_t GetDailyQuotaForTrigger(const TriggerThrottler& throttler,
const TriggerType trigger_type) {
return throttler.GetDailyQuotaForTrigger(trigger_type);
}
};
TEST_F(TriggerThrottlerTestFinch, ConfigureQuotaViaFinch) {
base::FieldTrialList field_trial_list(nullptr);
base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
"Group_ConfigureQuotaViaFinch");
std::map<std::string, std::string> feature_params;
feature_params[std::string(safe_browsing::kTriggerTypeAndQuotaParam)] =
base::StringPrintf("%d,%d", TriggerType::AD_SAMPLE, 3);
base::AssociateFieldTrialParams(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
"Group_ConfigureQuotaViaFinch", feature_params);
std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
feature_list->InitializeFromCommandLine(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name, std::string());
feature_list->AssociateReportingFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatureList(std::move(feature_list));
scoped_feature_list.InitWithFeatureList(SetupQuotaInFinch(
TriggerType::AD_SAMPLE, "Group_ConfigureQuotaViaFinch", 3));
// Make sure that setting the quota param via Finch params works as expected.
// The throttler has been configured (above) to allow ad samples to fire three
// times per day.
......@@ -175,35 +190,38 @@ TEST(TriggerThrottlerTestFinch, ConfigureQuotaViaFinch) {
EXPECT_FALSE(throttler.TriggerCanFire(TriggerType::AD_SAMPLE));
}
TEST(TriggerThrottlerTestFinch, AdSamplerDefaultQuota) {
TEST_F(TriggerThrottlerTestFinch, AdSamplerDefaultQuota) {
// Make sure that the ad sampler gets its own default quota when no finch
// config exists, but the quota can be overwritten through Finch.
TriggerThrottler throttler_default;
EXPECT_EQ(kAdSamplerTriggerDefaultQuota,
throttler_default.GetDailyQuotaForTrigger(TriggerType::AD_SAMPLE));
GetDailyQuotaForTrigger(throttler_default, TriggerType::AD_SAMPLE));
EXPECT_TRUE(throttler_default.TriggerCanFire(TriggerType::AD_SAMPLE));
size_t quota_from_finch = 4;
base::FieldTrialList field_trial_list(nullptr);
base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
"Group_AdSamplerDefaultQuota");
std::map<std::string, std::string> feature_params;
feature_params[std::string(safe_browsing::kTriggerTypeAndQuotaParam)] =
base::StringPrintf("%d,%zu", TriggerType::AD_SAMPLE, quota_from_finch);
base::AssociateFieldTrialParams(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
"Group_AdSamplerDefaultQuota", feature_params);
std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
feature_list->InitializeFromCommandLine(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name, std::string());
feature_list->AssociateReportingFieldTrial(
safe_browsing::kTriggerThrottlerDailyQuotaFeature.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatureList(std::move(feature_list));
scoped_feature_list.InitWithFeatureList(SetupQuotaInFinch(
TriggerType::AD_SAMPLE, "Group_AdSamplerDefaultQuota", 4));
TriggerThrottler throttler_finch;
EXPECT_EQ(4u,
GetDailyQuotaForTrigger(throttler_finch, TriggerType::AD_SAMPLE));
}
TEST_F(TriggerThrottlerTestFinch, SuspiciousSiteTriggerDefaultQuota) {
// Ensure that suspicious site trigger is disabled by default.
TriggerThrottler throttler_default;
EXPECT_EQ(0u, GetDailyQuotaForTrigger(throttler_default,
TriggerType::SUSPICIOUS_SITE));
EXPECT_FALSE(throttler_default.TriggerCanFire(TriggerType::SUSPICIOUS_SITE));
base::FieldTrialList field_trial_list(nullptr);
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatureList(
SetupQuotaInFinch(TriggerType::SUSPICIOUS_SITE,
"Group_SuspiciousSiteTriggerDefaultQuota", 5));
TriggerThrottler throttler_finch;
EXPECT_EQ(quota_from_finch,
throttler_finch.GetDailyQuotaForTrigger(TriggerType::AD_SAMPLE));
EXPECT_EQ(5u, GetDailyQuotaForTrigger(throttler_finch,
TriggerType::SUSPICIOUS_SITE));
}
} // namespace safe_browsing
......@@ -47,6 +47,8 @@ bool UnsafeResource::IsMainPageLoadBlocked() const {
case safe_browsing::SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE:
// Ad sampling happens in the background.
case safe_browsing::SB_THREAT_TYPE_AD_SAMPLE:
// Suspicious site collection happens in the background
case safe_browsing::SB_THREAT_TYPE_SUSPICIOUS_SITE:
return false;
default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment