blob: 55792b6b66b538f3f74de2d475a3cee58cfd2298 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/permissions/crowd_deny_preload_data.h"
#include <string_view>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/numerics/safe_conversions.h"
#include "base/test/task_environment.h"
#include "base/version.h"
#include "chrome/browser/permissions/crowd_deny.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
constexpr char kTestDomainAlpha[] = "alpha.com";
constexpr char kTestDomainBeta[] = "beta.com";
constexpr char kTestDomainGamma[] = "gamma.com";
constexpr char kTestDomainDelta[] = "delta.com";
constexpr char kTestSubdomainOfDelta1[] = "one.delta.com";
constexpr char kTestDomainEpsilon[] = "epsilon.com";
constexpr char kTestOriginAlpha[] = "https://alpha.com";
constexpr char kTestOriginSubdomainOfAlpha[] = "https://foo.alpha.com";
constexpr char kTestOriginBeta[] = "https://beta.com";
constexpr char kTestOriginGamma[] = "https://gamma.com";
constexpr char kTestOriginDelta[] = "https://delta.com";
constexpr char kTestOriginSubdomainOfDelta1[] = "https://one.delta.com";
constexpr char kTestOriginSubdomainOfDelta2[] = "https://foo.two.delta.com";
constexpr char kTestOriginNotSubdomainOfDelta[] = "https://notdelta.com";
constexpr char kTestOriginEpsilon[] = "https://epsilon.com";
constexpr char kTestOriginZeta[] = "https://zeta.com";
constexpr const char* kAllTestingOrigins[] = {
kTestOriginAlpha, kTestOriginBeta, kTestOriginGamma, kTestOriginDelta,
kTestOriginEpsilon};
} // namespace
class CrowdDenyPreloadDataTest : public testing::Test {
public:
using SiteReputation = chrome_browser_crowd_deny::SiteReputation;
CrowdDenyPreloadDataTest() = default;
CrowdDenyPreloadDataTest(const CrowdDenyPreloadDataTest&) = delete;
CrowdDenyPreloadDataTest& operator=(const CrowdDenyPreloadDataTest&) = delete;
~CrowdDenyPreloadDataTest() override = default;
protected:
void SetUp() override { ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); }
base::test::TaskEnvironment* task_environment() { return &task_environment_; }
CrowdDenyPreloadData* preload_data() { return &preload_data_; }
base::FilePath GetPathInTempDir(
base::FilePath::StringViewType filename) const {
return scoped_temp_dir_.GetPath().Append(filename);
}
void SerializeTestRawData(std::string_view raw_data, base::FilePath path) {
ASSERT_TRUE(base::WriteFile(path, raw_data));
}
void SerializeTestPreloadData(
chrome_browser_crowd_deny::PreloadData preload_data,
base::FilePath path) {
std::string binary_preload_data;
ASSERT_TRUE(preload_data.SerializeToString(&binary_preload_data));
ASSERT_NO_FATAL_FAILURE(SerializeTestRawData(binary_preload_data, path));
}
void LoadTestDataAndWait(base::FilePath path) {
preload_data()->LoadFromDisk(path, base::Version());
task_environment()->RunUntilIdle();
}
void SerializeAndLoadTestData(
chrome_browser_crowd_deny::PreloadData test_data) {
const base::FilePath temp_path =
GetPathInTempDir(FILE_PATH_LITERAL("Preload Data"));
ASSERT_NO_FATAL_FAILURE(
SerializeTestPreloadData(std::move(test_data), temp_path));
LoadTestDataAndWait(temp_path);
}
void SerializeAndLoadCannedTestData() {
chrome_browser_crowd_deny::PreloadData test_data;
auto* alpha_site_reputation = test_data.add_site_reputations();
alpha_site_reputation->set_domain(kTestDomainAlpha);
alpha_site_reputation->set_notification_ux_quality(SiteReputation::UNKNOWN);
auto* beta_site_reputation = test_data.add_site_reputations();
beta_site_reputation->set_domain(kTestDomainBeta);
beta_site_reputation->set_include_subdomains(false);
beta_site_reputation->set_notification_ux_quality(
SiteReputation::ACCEPTABLE);
auto* gamma_site_reputation = test_data.add_site_reputations();
gamma_site_reputation->set_domain(kTestDomainGamma);
gamma_site_reputation->set_warning_only(false);
gamma_site_reputation->set_notification_ux_quality(
SiteReputation::UNSOLICITED_PROMPTS);
auto* delta_site_reputation = test_data.add_site_reputations();
delta_site_reputation->set_domain(kTestDomainDelta);
delta_site_reputation->set_include_subdomains(true);
delta_site_reputation->set_warning_only(true);
delta_site_reputation->set_notification_ux_quality(
SiteReputation::ABUSIVE_PROMPTS);
auto* epsilon_site_reputation = test_data.add_site_reputations();
epsilon_site_reputation->set_domain(kTestDomainEpsilon);
// No |notification_ux_quality| field.
// No |include_subdomains| field.
// No |warning_only| field.
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadTestData(std::move(test_data)));
}
void ExpectEmptyPreloadData() {
for (const char* origin_string : kAllTestingOrigins) {
SCOPED_TRACE(origin_string);
EXPECT_FALSE(preload_data()->GetReputationDataForSite(
url::Origin::Create(GURL(origin_string))));
}
}
const SiteReputation* GetReputationDataForSite(const url::Origin& origin) {
return preload_data()->GetReputationDataForSite(origin);
}
private:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir scoped_temp_dir_;
CrowdDenyPreloadData preload_data_;
};
TEST_F(CrowdDenyPreloadDataTest, NoData) {
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, MissingFile) {
LoadTestDataAndWait(GetPathInTempDir(FILE_PATH_LITERAL("NonExistentFile")));
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, EmptyData) {
const base::FilePath empty_file_path =
GetPathInTempDir(FILE_PATH_LITERAL("EmptyFile"));
SerializeTestRawData(std::string_view(), empty_file_path);
LoadTestDataAndWait(empty_file_path);
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, BadData) {
const base::FilePath bad_data_path =
GetPathInTempDir(FILE_PATH_LITERAL("BadFile"));
SerializeTestRawData("This is not a proto.", bad_data_path);
LoadTestDataAndWait(bad_data_path);
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, DataIntegrityAndDefaults) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
const auto* data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginAlpha)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainAlpha, data->domain());
EXPECT_EQ(SiteReputation::UNKNOWN, data->notification_ux_quality());
EXPECT_FALSE(data->include_subdomains());
EXPECT_FALSE(data->warning_only());
data = GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginBeta)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainBeta, data->domain());
EXPECT_EQ(SiteReputation::ACCEPTABLE, data->notification_ux_quality());
EXPECT_FALSE(data->include_subdomains());
EXPECT_FALSE(data->warning_only());
data = GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginGamma)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainGamma, data->domain());
EXPECT_EQ(SiteReputation::UNSOLICITED_PROMPTS,
data->notification_ux_quality());
EXPECT_FALSE(data->include_subdomains());
EXPECT_FALSE(data->warning_only());
data = GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginDelta)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
EXPECT_EQ(SiteReputation::ABUSIVE_PROMPTS, data->notification_ux_quality());
EXPECT_TRUE(data->include_subdomains());
EXPECT_TRUE(data->warning_only());
data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginEpsilon)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainEpsilon, data->domain());
EXPECT_EQ(SiteReputation::UNKNOWN, data->notification_ux_quality());
EXPECT_FALSE(data->include_subdomains());
EXPECT_FALSE(data->warning_only());
data = GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginZeta)));
EXPECT_FALSE(data);
}
TEST_F(CrowdDenyPreloadDataTest, GetReputationReturnsNullForNonHttpsOrigins) {
const char* kNonHttpsOrigins[] = {
"http://alpha.com",
"wss://alpha.com",
"ftp://alpha.com",
"file:///alpha.com",
};
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
EXPECT_TRUE(
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginAlpha))));
for (const char* non_https_origin : kNonHttpsOrigins) {
SCOPED_TRACE(non_https_origin);
EXPECT_FALSE(
GetReputationDataForSite(url::Origin::Create(GURL(non_https_origin))));
}
}
TEST_F(CrowdDenyPreloadDataTest, GetReputationIgnoresPort) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
EXPECT_TRUE(GetReputationDataForSite(
url::Origin::Create(GURL("https://alpha.com:443"))));
EXPECT_TRUE(GetReputationDataForSite(
url::Origin::Create(GURL("https://alpha.com:1234"))));
}
TEST_F(CrowdDenyPreloadDataTest, GetReputationWithSubdomainMatching) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
const auto* data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginDelta)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
ASSERT_TRUE(data->include_subdomains());
data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginSubdomainOfDelta1)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginSubdomainOfDelta2)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginNotSubdomainOfDelta)));
EXPECT_FALSE(data);
data = GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginAlpha)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainAlpha, data->domain());
ASSERT_FALSE(data->include_subdomains());
// Should not return `alpha.com` because |include_subdomains| is not set.
data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginSubdomainOfAlpha)));
EXPECT_FALSE(data);
}
TEST_F(CrowdDenyPreloadDataTest, SubdomainSpecificOverride) {
chrome_browser_crowd_deny::PreloadData test_data;
auto* delta_site_reputation = test_data.add_site_reputations();
delta_site_reputation->set_domain(kTestDomainDelta);
delta_site_reputation->set_include_subdomains(true);
delta_site_reputation->set_notification_ux_quality(
SiteReputation::UNSOLICITED_PROMPTS);
auto* subdomain_site_reputation = test_data.add_site_reputations();
subdomain_site_reputation->set_domain(kTestSubdomainOfDelta1);
subdomain_site_reputation->set_include_subdomains(true);
subdomain_site_reputation->set_notification_ux_quality(
SiteReputation::ACCEPTABLE);
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadTestData(std::move(test_data)));
const auto* data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginSubdomainOfDelta1)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestSubdomainOfDelta1, data->domain());
EXPECT_EQ(SiteReputation::ACCEPTABLE, data->notification_ux_quality());
data = GetReputationDataForSite(
url::Origin::Create(GURL(kTestOriginSubdomainOfDelta2)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
EXPECT_EQ(SiteReputation::UNSOLICITED_PROMPTS,
data->notification_ux_quality());
}
TEST_F(CrowdDenyPreloadDataTest, Update) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
// Prepare and load an updated payload version, which updates the entry for
// `delta.com`, adds an entry for `epsilon.com`, and removes all others.
chrome_browser_crowd_deny::PreloadData test_data_v2;
auto* delta_site_reputation = test_data_v2.add_site_reputations();
delta_site_reputation->set_domain(kTestDomainDelta);
delta_site_reputation->set_notification_ux_quality(
SiteReputation::UNSOLICITED_PROMPTS);
auto* epsilon_site_reputation = test_data_v2.add_site_reputations();
epsilon_site_reputation->set_domain(kTestDomainEpsilon);
epsilon_site_reputation->set_notification_ux_quality(
SiteReputation::ACCEPTABLE);
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadTestData(std::move(test_data_v2)));
// Check that the updated preload data is visible.
EXPECT_FALSE(
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginAlpha))));
EXPECT_FALSE(
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginBeta))));
EXPECT_FALSE(
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginGamma))));
const auto* data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginDelta)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainDelta, data->domain());
EXPECT_EQ(SiteReputation::UNSOLICITED_PROMPTS,
data->notification_ux_quality());
data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginEpsilon)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainEpsilon, data->domain());
EXPECT_EQ(SiteReputation::ACCEPTABLE, data->notification_ux_quality());
}
TEST_F(CrowdDenyPreloadDataTest, UpdateToMissingFileWipesInMemoryState) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
LoadTestDataAndWait(GetPathInTempDir(FILE_PATH_LITERAL("NonExistentFile")));
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, UpdateToEmptyFileWipesInMemoryState) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
const base::FilePath empty_file_path =
GetPathInTempDir(FILE_PATH_LITERAL("EmptyFile"));
SerializeTestRawData(std::string_view(), empty_file_path);
LoadTestDataAndWait(empty_file_path);
ExpectEmptyPreloadData();
}
TEST_F(CrowdDenyPreloadDataTest, UpdateToBadFileWipesInMemoryState) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
const base::FilePath bad_data_path =
GetPathInTempDir(FILE_PATH_LITERAL("BadFile"));
SerializeTestRawData("This is not a proto.", bad_data_path);
LoadTestDataAndWait(bad_data_path);
ExpectEmptyPreloadData();
}
// During start-up congestion, it is possible that a new version of the
// component becomes available while the old version is pending being loaded.
// Ensure that when things settle down, the last version loaded will prevail,
// and nothing explodes on the way.
TEST_F(CrowdDenyPreloadDataTest, LastOneSurvivesFromUpdatesInQuickSuccession) {
ASSERT_NO_FATAL_FAILURE(SerializeAndLoadCannedTestData());
// Prepare and load two updated versions, each twice, in a quick success.
chrome_browser_crowd_deny::PreloadData test_data_v2;
auto* delta_site_reputation = test_data_v2.add_site_reputations();
delta_site_reputation->set_domain(kTestDomainDelta);
chrome_browser_crowd_deny::PreloadData test_data_v3;
auto* epsilon_site_reputation = test_data_v3.add_site_reputations();
epsilon_site_reputation->set_domain(kTestDomainEpsilon);
epsilon_site_reputation->set_notification_ux_quality(
SiteReputation::ACCEPTABLE);
const base::FilePath data_path_v2 =
GetPathInTempDir(FILE_PATH_LITERAL("DataV2"));
const base::FilePath data_path_v3 =
GetPathInTempDir(FILE_PATH_LITERAL("DataV3"));
SerializeTestPreloadData(std::move(test_data_v2), data_path_v2);
SerializeTestPreloadData(std::move(test_data_v3), data_path_v3);
// Trigger three loads without pumping the message loop.
//
// TODO(crbug.com/40109238): Think about making this test stronger. Even if
// the ordering were random, given the generous retry policy in continuous
// build, the test would still pass most of the time.
preload_data()->LoadFromDisk(data_path_v2, base::Version());
preload_data()->LoadFromDisk(data_path_v3, base::Version());
task_environment()->RunUntilIdle();
// Expect the new version to have become visible.
const auto* data =
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginEpsilon)));
ASSERT_TRUE(data);
EXPECT_EQ(kTestDomainEpsilon, data->domain());
EXPECT_EQ(SiteReputation::ACCEPTABLE, data->notification_ux_quality());
EXPECT_FALSE(
GetReputationDataForSite(url::Origin::Create(GURL(kTestOriginDelta))));
}