#include "components/safe_browsing/content/browser/ui_manager.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/values.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page_factory.h"
#include "components/safe_browsing/content/browser/safe_browsing_controller_client.h"
#include "components/safe_browsing/core/browser/db/util.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/settings_page_helper.h"
#include "components/security_interstitials/content/unsafe_resource_util.h"
#include "components/security_interstitials/core/base_safe_browsing_error_ui.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/test/browser_task_environment.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 "services/network/public/cpp/cross_thread_pending_shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using content::BrowserThread;
static const char* kGoodURL = "https://www.good.com";
static const char* kBadURL = "https://www.malware.com";
static const char* kBadURLWithPath = "https://www.malware.com/index.html";
static const char* kAnotherBadURL = "https://www.badware.com";
static const char* kLandingURL = "https://www.landing.com";
namespace safe_browsing {
class SafeBrowsingCallbackWaiter {
public:
SafeBrowsingCallbackWaiter() {}
bool callback_called() const { return callback_called_; }
bool proceed() const { return proceed_; }
bool showed_interstitial() const { return showed_interstitial_; }
void OnBlockingPageDone(bool proceed, bool showed_interstitial) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback_called_ = true;
proceed_ = proceed;
showed_interstitial_ = showed_interstitial;
loop_.Quit();
}
void OnBlockingPageDoneOnIO(bool proceed, bool showed_interstitial) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(this), proceed, showed_interstitial));
}
void WaitForCallback() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
loop_.Run();
}
private:
bool callback_called_ = false;
bool proceed_ = false;
bool showed_interstitial_ = false;
base::RunLoop loop_;
};
class TestSafeBrowsingBlockingPage : public SafeBrowsingBlockingPage {
public:
TestSafeBrowsingBlockingPage(BaseUIManager* manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const UnsafeResourceList& unsafe_resources)
: SafeBrowsingBlockingPage(
manager,
web_contents,
main_frame_url,
unsafe_resources,
std::make_unique<safe_browsing::SafeBrowsingControllerClient>(
web_contents,
std::make_unique<security_interstitials::MetricsHelper>(
unsafe_resources[0].url,
BaseBlockingPage::GetReportingInfo(unsafe_resources),
nullptr),
nullptr,
manager->app_locale(),
manager->default_safe_page(),
nullptr),
BaseSafeBrowsingErrorUI::SBErrorDisplayOptions(
BaseBlockingPage::IsMainPageLoadBlocked(unsafe_resources),
false,
false,
false,
false,
false,
false,
true,
true,
false,
false,
"cpn_safe_browsing"),
true,
nullptr,
nullptr,
nullptr,
nullptr) {
SetThreatDetailsProceedDelayForTesting(0);
DontCreateViewForTesting();
}
};
class TestSafeBrowsingBlockingPageFactory
: public SafeBrowsingBlockingPageFactory {
public:
TestSafeBrowsingBlockingPageFactory() = default;
~TestSafeBrowsingBlockingPageFactory() override = default;
SafeBrowsingBlockingPage* CreateSafeBrowsingPage(
BaseUIManager* delegate,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources,
bool should_trigger_reporting) override {
return new TestSafeBrowsingBlockingPage(delegate, web_contents,
main_frame_url, unsafe_resources);
}
#if !BUILDFLAG(IS_ANDROID)
security_interstitials::SecurityInterstitialPage* CreateEnterpriseWarnPage(
BaseUIManager* ui_manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
NOTREACHED();
return nullptr;
}
security_interstitials::SecurityInterstitialPage* CreateEnterpriseBlockPage(
BaseUIManager* ui_manager,
content::WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
NOTREACHED();
return nullptr;
}
#endif
};
class TestSafeBrowsingUIManagerDelegate
: public SafeBrowsingUIManager::Delegate {
public:
TestSafeBrowsingUIManagerDelegate() {
safe_browsing::RegisterProfilePrefs(pref_service_.registry());
}
~TestSafeBrowsingUIManagerDelegate() override = default;
std::string GetApplicationLocale() override { return "en-us"; }
void TriggerSecurityInterstitialShownExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& reason,
int net_error_code) override {}
void TriggerSecurityInterstitialProceededExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& reason,
int net_error_code) override {}
#if !BUILDFLAG(IS_ANDROID)
void TriggerUrlFilteringInterstitialExtensionEventIfDesired(
content::WebContents* web_contents,
const GURL& page_url,
const std::string& threat_type,
safe_browsing::RTLookupResponse rt_lookup_response) override {}
#endif
prerender::NoStatePrefetchContents* GetNoStatePrefetchContentsIfExists(
content::WebContents* web_contents) override {
return nullptr;
}
bool IsHostingExtension(content::WebContents* web_contents) override {
return is_hosting_extension_;
}
PrefService* GetPrefs(content::BrowserContext* browser_context) override {
return &pref_service_;
}
history::HistoryService* GetHistoryService(
content::BrowserContext* browser_context) override {
return nullptr;
}
PingManager* GetPingManager(
content::BrowserContext* browser_context) override {
return nullptr;
}
bool IsMetricsAndCrashReportingEnabled() override { return false; }
bool IsSendingOfHitReportsEnabled() override { return false; }
void set_is_hosting_extension(bool is_hosting_extension) {
is_hosting_extension_ = is_hosting_extension;
}
private:
bool is_hosting_extension_ = false;
TestingPrefServiceSimple pref_service_;
};
class SafeBrowsingUIManagerTest : public content::RenderViewHostTestHarness {
public:
SafeBrowsingUIManagerTest() {
auto ui_manager_delegate =
std::make_unique<TestSafeBrowsingUIManagerDelegate>();
raw_ui_manager_delegate_ = ui_manager_delegate.get();
ui_manager_ = new SafeBrowsingUIManager(
std::move(ui_manager_delegate),
std::make_unique<TestSafeBrowsingBlockingPageFactory>(),
GURL("chrome://new-tab-page/"));
}
~SafeBrowsingUIManagerTest() override {}
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
SafeBrowsingUIManager::CreateAllowlistForTesting(web_contents());
}
bool IsAllowlisted(security_interstitials::UnsafeResource resource) {
return ui_manager_->IsAllowlisted(resource);
}
void AddToAllowlist(security_interstitials::UnsafeResource resource) {
ui_manager_->AddToAllowlistUrlSet(
SafeBrowsingUIManager::GetMainFrameAllowlistUrlForResourceForTesting(
resource),
web_contents(), false, resource.threat_type);
}
security_interstitials::UnsafeResource MakeUnsafeResource(
const char* url,
bool is_subresource,
absl::optional<content::GlobalRenderFrameHostId>
override_primary_main_frame_id = absl::nullopt) {
const content::GlobalRenderFrameHostId primary_main_frame_id =
override_primary_main_frame_id.has_value()
? override_primary_main_frame_id.value()
: web_contents()->GetPrimaryMainFrame()->GetGlobalId();
security_interstitials::UnsafeResource resource;
resource.url = GURL(url);
resource.is_subresource = is_subresource;
resource.render_process_id = primary_main_frame_id.child_id;
resource.render_frame_id = primary_main_frame_id.frame_routing_id;
resource.threat_type = SB_THREAT_TYPE_URL_MALWARE;
return resource;
}
security_interstitials::UnsafeResource MakeUnsafeResourceAndStartNavigation(
const char* url) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(url, false );
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL(url), web_contents());
navigation->Start();
return resource;
}
void SimulateBlockingPageDone(
const std::vector<security_interstitials::UnsafeResource>& resources,
bool proceed) {
GURL main_frame_url;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
if (entry)
main_frame_url = entry->GetURL();
ui_manager_->OnBlockingPageDone(resources, proceed, web_contents(),
main_frame_url,
true );
}
protected:
SafeBrowsingUIManager* ui_manager() { return ui_manager_.get(); }
TestSafeBrowsingUIManagerDelegate* ui_manager_delegate() {
return raw_ui_manager_delegate_;
}
private:
scoped_refptr<SafeBrowsingUIManager> ui_manager_;
raw_ptr<TestSafeBrowsingUIManagerDelegate> raw_ui_manager_delegate_ = nullptr;
};
TEST_F(SafeBrowsingUIManagerTest, Allowlist) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToAllowlist(resource);
EXPECT_TRUE(IsAllowlisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresSitesNotAdded) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kGoodURL);
EXPECT_FALSE(IsAllowlisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistRemembersThreatType) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToAllowlist(resource);
EXPECT_TRUE(IsAllowlisted(resource));
SBThreatType threat_type;
content::NavigationEntry* entry =
web_contents()->GetController().GetVisibleEntry();
ASSERT_TRUE(entry);
EXPECT_TRUE(ui_manager()->IsUrlAllowlistedOrPendingForWebContents(
resource.url, resource.is_subresource, entry,
security_interstitials::GetWebContentsForResource(resource), true,
&threat_type));
EXPECT_EQ(resource.threat_type, threat_type);
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresPath) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToAllowlist(resource);
EXPECT_TRUE(IsAllowlisted(resource));
content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
security_interstitials::UnsafeResource resource_path =
MakeUnsafeResourceAndStartNavigation(kBadURLWithPath);
EXPECT_TRUE(IsAllowlisted(resource_path));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistIgnoresThreatType) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
AddToAllowlist(resource);
EXPECT_TRUE(IsAllowlisted(resource));
security_interstitials::UnsafeResource resource_phishing =
MakeUnsafeResource(kBadURL, false );
resource_phishing.threat_type = SB_THREAT_TYPE_URL_PHISHING;
EXPECT_TRUE(IsAllowlisted(resource_phishing));
}
TEST_F(SafeBrowsingUIManagerTest, AllowlistWithUnrelatedPendingLoad) {
NavigateAndCommit(GURL(kLandingURL));
auto unrelated_navigation =
content::NavigationSimulator::CreateBrowserInitiated(GURL(kGoodURL),
web_contents());
{
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true );
unrelated_navigation->Start();
AddToAllowlist(resource);
EXPECT_TRUE(IsAllowlisted(resource));
}
unrelated_navigation->Commit();
{
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true );
EXPECT_FALSE(IsAllowlisted(resource));
}
NavigateAndCommit(GURL(kLandingURL));
{
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true );
EXPECT_TRUE(IsAllowlisted(resource));
}
{
security_interstitials::UnsafeResource resource2 =
MakeUnsafeResource(kAnotherBadURL, true );
EXPECT_TRUE(IsAllowlisted(resource2));
}
}
TEST_F(SafeBrowsingUIManagerTest, UICallbackProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
}
TEST_F(SafeBrowsingUIManagerTest, UICallbackDontProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, false);
EXPECT_FALSE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_FALSE(waiter.proceed());
}
TEST_F(SafeBrowsingUIManagerTest, IOCallbackProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_sequence = content::GetIOThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
}
TEST_F(SafeBrowsingUIManagerTest, IOCallbackDontProceed) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_sequence = content::GetIOThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
SimulateBlockingPageDone(resources, false);
EXPECT_FALSE(IsAllowlisted(resource));
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_FALSE(waiter.proceed());
}
namespace {
class SecurityStateWebContentsDelegate : public content::WebContentsDelegate {
public:
SecurityStateWebContentsDelegate() {}
SecurityStateWebContentsDelegate(const SecurityStateWebContentsDelegate&) =
delete;
SecurityStateWebContentsDelegate& operator=(
const SecurityStateWebContentsDelegate&) = delete;
~SecurityStateWebContentsDelegate() override {}
bool visible_security_state_changed() const {
return visible_security_state_changed_;
}
void ClearVisibleSecurityStateChanged() {
visible_security_state_changed_ = false;
}
void VisibleSecurityStateChanged(content::WebContents* source) override {
visible_security_state_changed_ = true;
}
private:
bool visible_security_state_changed_ = false;
};
}
TEST_F(SafeBrowsingUIManagerTest,
VisibleSecurityStateChangedForUnsafeSubresource) {
SecurityStateWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, true );
resource.threat_source = safe_browsing::ThreatSource::REMOTE;
NavigateAndCommit(GURL("http://example.test"));
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
ui_manager()->DisplayBlockingPage(resource);
EXPECT_TRUE(delegate.visible_security_state_changed());
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDoneOnIO,
base::Unretained(&waiter));
resource.callback_sequence = content::GetIOThreadTaskRunner({});
std::vector<security_interstitials::UnsafeResource> resources;
resources.push_back(resource);
delegate.ClearVisibleSecurityStateChanged();
EXPECT_FALSE(delegate.visible_security_state_changed());
SimulateBlockingPageDone(resources, true);
EXPECT_TRUE(delegate.visible_security_state_changed());
waiter.WaitForCallback();
EXPECT_TRUE(waiter.callback_called());
EXPECT_TRUE(waiter.proceed());
EXPECT_TRUE(IsAllowlisted(resource));
}
TEST_F(SafeBrowsingUIManagerTest, ShowBlockPageNoCallback) {
SecurityStateWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false );
resource.threat_source = safe_browsing::ThreatSource::REMOTE;
ui_manager()->DisplayBlockingPage(resource);
}
TEST_F(SafeBrowsingUIManagerTest, NoInterstitialInExtensions) {
ui_manager_delegate()->set_is_hosting_extension(true);
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false );
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
ui_manager()->StartDisplayingBlockingPage(resource);
waiter.WaitForCallback();
EXPECT_FALSE(waiter.proceed());
EXPECT_FALSE(waiter.showed_interstitial());
}
TEST_F(SafeBrowsingUIManagerTest, DisplayInterstitial) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false );
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
ui_manager()->StartDisplayingBlockingPage(resource);
waiter.WaitForCallback();
EXPECT_FALSE(waiter.proceed());
EXPECT_TRUE(waiter.showed_interstitial());
}
TEST_F(SafeBrowsingUIManagerTest,
LookupMechanismExperiment_DisplayInterstitial) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false);
SafeBrowsingCallbackWaiter waiter;
resource.callback =
base::BindRepeating(&SafeBrowsingCallbackWaiter::OnBlockingPageDone,
base::Unretained(&waiter));
resource.callback_sequence = content::GetUIThreadTaskRunner({});
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(true));
ui_manager()->CheckExperimentEligibilityAndStartBlockingPage(
resource, callback.Get(), base::SequencedTaskRunner::GetCurrentDefault());
waiter.WaitForCallback();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(waiter.proceed());
EXPECT_TRUE(waiter.showed_interstitial());
}
TEST_F(SafeBrowsingUIManagerTest, CheckLookupMechanismExperimentEligibility) {
auto run_test = [this](
bool expect_is_eligible,
const security_interstitials::UnsafeResource& resource) {
base::MockCallback<base::OnceCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(expect_is_eligible));
ui_manager()->CheckLookupMechanismExperimentEligibility(
resource, callback.Get(),
base::SequencedTaskRunner::GetCurrentDefault());
base::RunLoop().RunUntilIdle();
};
{
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false);
run_test(true, resource);
}
{
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false);
AddToAllowlist(resource);
run_test(false, resource);
}
{
ui_manager_delegate()->set_is_hosting_extension(true);
security_interstitials::UnsafeResource resource =
MakeUnsafeResource(kBadURL, false);
run_test(false, resource);
}
{
const content::GlobalRenderFrameHostId primary_main_frame_id =
web_contents()->GetPrimaryMainFrame()->GetGlobalId();
DeleteContents();
security_interstitials::UnsafeResource resource = MakeUnsafeResource(
kBadURL, false,
primary_main_frame_id);
run_test(false, resource);
}
}
TEST_F(SafeBrowsingUIManagerTest, InvalidRenderFrameHostId) {
security_interstitials::UnsafeResource resource =
MakeUnsafeResourceAndStartNavigation(kBadURL);
content::GlobalRenderFrameHostId invalid_rfh_id;
resource.render_process_id = invalid_rfh_id.child_id;
resource.render_frame_id = invalid_rfh_id.frame_routing_id;
ASSERT_FALSE(security_interstitials::GetWebContentsForResource(resource));
EXPECT_FALSE(IsAllowlisted(resource));
}
}