blob: 1cdda697a5e70b969caf1006730c551bd49b5f70 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/browsing_data/content/browsing_data_model.h"
#include "base/barrier_closure.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "components/browsing_data/content/browsing_data_model_test_util.h"
#include "components/browsing_data/content/test_browsing_data_model_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_storage_partition.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/test/test_network_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using browsing_data_model_test_util::BrowsingDataEntry;
using browsing_data_model_test_util::ValidateBrowsingDataEntries;
namespace {
class MockNetworkContext : public network::TestNetworkContext {
public:
explicit MockNetworkContext(
mojo::PendingReceiver<network::mojom::NetworkContext> receiver)
: receiver_(this, std::move(receiver)) {}
MOCK_METHOD(void,
GetStoredTrustTokenCounts,
(GetStoredTrustTokenCountsCallback),
(override));
MOCK_METHOD(void,
DeleteStoredTrustTokens,
(const url::Origin&, DeleteStoredTrustTokensCallback),
(override));
private:
mojo::Receiver<network::mojom::NetworkContext> receiver_;
};
} // namespace
class BrowsingDataModelTest : public testing::Test {
public:
BrowsingDataModelTest() {
mojo::PendingRemote<network::mojom::NetworkContext> network_context_remote;
mock_network_context_ = std::make_unique<MockNetworkContext>(
network_context_remote.InitWithNewPipeAndPassReceiver());
storage_partition()->SetNetworkContextForTesting(
std::move(network_context_remote));
model_ = BrowsingDataModel::BuildEmpty(
storage_partition(),
std::make_unique<browsing_data::TestBrowsingDataModelDelegate>());
}
~BrowsingDataModelTest() override = default;
void TearDown() override { mock_network_context_.reset(); }
protected:
void BuildModel(base::OnceClosure completed) {
model()->PopulateFromDisk(std::move(completed));
}
void DeleteModel() { model_.reset(); }
content::BrowserContext* browser_context() { return &browser_context_; }
BrowsingDataModel* model() { return model_.get(); }
content::StoragePartition* storage_partition() {
return browser_context()->GetDefaultStoragePartition();
}
MockNetworkContext* mock_network_context() {
return mock_network_context_.get();
}
const url::Origin kSubdomainOrigin =
url::Origin::Create(GURL("https://subsite.example.com"));
const std::string kSubdomainOriginHost = "subsite.example.com";
const std::string kSubdomainOriginSite = "example.com";
const url::Origin kSiteOrigin =
url::Origin::Create(GURL("https://example.com"));
const std::string kSiteOriginHost = "example.com";
const url::Origin kAnotherSiteOrigin =
url::Origin::Create(GURL("https://another-example.com"));
const std::string kAnotherSiteOriginHost = "another-example.com";
const url::Origin kTestOrigin = url::Origin::Create(GURL("https://a.test"));
const std::string kTestOriginHost = "a.test";
private:
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext browser_context_;
std::unique_ptr<BrowsingDataModel> model_;
content::TestStoragePartition storage_partition_;
std::unique_ptr<MockNetworkContext> mock_network_context_;
};
TEST_F(BrowsingDataModelTest, PrimaryHostMapping) {
model()->AddBrowsingData(kSubdomainOrigin,
BrowsingDataModel::StorageType::kTrustTokens, 0, 1);
model()->AddBrowsingData(
blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 123, 0);
model()->AddBrowsingData(
blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage, 456, 0);
ValidateBrowsingDataEntries(
model(),
{{kSubdomainOriginHost,
kSubdomainOrigin,
{BrowsingDataModel::StorageType::kTrustTokens, 0, 1}},
{kSubdomainOriginSite,
blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
{BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 123, 0}},
{kSubdomainOriginHost,
blink::StorageKey::CreateFirstParty(kSubdomainOrigin),
{BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage, 456, 0}}});
}
TEST_F(BrowsingDataModelTest, EntryCoalescense) {
// Check that multiple entries are correctly coalesced.
// Browsing data with the same owner + data_key pair should update the
// same entry's details.
model()->AddBrowsingData(
blink::StorageKey::CreateFirstParty(kSiteOrigin),
BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 123, 0);
model()->AddBrowsingData(
blink::StorageKey::CreateFirstParty(kSiteOrigin),
BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage, 234, 5);
auto expected_entries = std::vector<BrowsingDataEntry>(
{{kSiteOriginHost,
blink::StorageKey::CreateFirstParty(kSiteOrigin),
{{BrowsingDataModel::StorageType::kPartitionedQuotaStorage,
BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage},
123 + 234,
5}}});
ValidateBrowsingDataEntries(model(), expected_entries);
// Entries related to the same owner, but different data_keys, should
// create a new entry.
model()->AddBrowsingData(
blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin),
BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 345, 0);
model()->AddBrowsingData(
kAnotherSiteOrigin, BrowsingDataModel::StorageType::kTrustTokens, 456, 6);
expected_entries.push_back(
{kAnotherSiteOriginHost,
blink::StorageKey::CreateFirstParty(kAnotherSiteOrigin),
{BrowsingDataModel::StorageType::kPartitionedQuotaStorage, 345}});
expected_entries.push_back(
{kAnotherSiteOriginHost,
kAnotherSiteOrigin,
{BrowsingDataModel::StorageType::kTrustTokens, 456, 6}});
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
}
TEST_F(BrowsingDataModelTest, ConcurrentDeletions) {
// Check that the model is able to support multiple deletion operations in
// flight at the same time, even if the backends finish out-of-order.
std::vector<::network::mojom::StoredTrustTokensForIssuerPtr> tokens;
tokens.emplace_back(absl::in_place, kSubdomainOrigin, 10);
tokens.emplace_back(absl::in_place, kAnotherSiteOrigin, 20);
EXPECT_CALL(*mock_network_context(), GetStoredTrustTokenCounts(testing::_))
.WillOnce(
[&](network::TestNetworkContext::GetStoredTrustTokenCountsCallback
callback) { std::move(callback).Run(std::move(tokens)); });
base::RunLoop run_loop;
BuildModel(run_loop.QuitWhenIdleClosure());
run_loop.Run();
// The size of trust token storage is aliased to a small amount of data, 100B.
auto expected_entries = std::vector<BrowsingDataEntry>{
{kSubdomainOriginHost,
kSubdomainOrigin,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
{kAnotherSiteOriginHost,
kAnotherSiteOrigin,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
{kTestOriginHost,
kTestOrigin,
{static_cast<BrowsingDataModel::StorageType>(
browsing_data::TestBrowsingDataModelDelegate::StorageType::
kTestDelegateType),
0, 0}}};
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
// Save the deletion callbacks provided to the mock network context, so we
// can later fulfil them out-of-order. The mock lives on the other side of a
// mojo interface, so the callbacks can only be populated async.
base::RunLoop delete_run_loop;
base::RepeatingClosure delete_barrier = base::BarrierClosure(
2, base::BindLambdaForTesting([&]() { delete_run_loop.QuitWhenIdle(); }));
network::TestNetworkContext::DeleteStoredTrustTokensCallback
delete_tokens_complete_1;
network::TestNetworkContext::DeleteStoredTrustTokensCallback
delete_tokens_complete_2;
{
testing::InSequence sequence;
EXPECT_CALL(*mock_network_context(),
DeleteStoredTrustTokens(kSubdomainOrigin, testing::_))
.WillOnce(
[&](const url::Origin& origin,
network::TestNetworkContext::DeleteStoredTrustTokensCallback
callback) {
delete_tokens_complete_1 = std::move(callback);
delete_barrier.Run();
});
EXPECT_CALL(*mock_network_context(),
DeleteStoredTrustTokens(kAnotherSiteOrigin, testing::_))
.WillOnce(
[&](const url::Origin& origin,
network::TestNetworkContext::DeleteStoredTrustTokensCallback
callback) {
delete_tokens_complete_2 = std::move(callback);
delete_barrier.Run();
});
}
// As running the saved callbacks is also async, we need more run loops to
// confirm that the model receives the deletion completed callback.
base::RunLoop delete_callback_1_runloop;
base::RunLoop delete_callback_2_runloop;
model()->RemoveBrowsingData(kSubdomainOriginHost,
delete_callback_1_runloop.QuitWhenIdleClosure());
// Removal from the model should be synchronous.
expected_entries.erase(expected_entries.begin());
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
model()->RemoveBrowsingData(kAnotherSiteOriginHost,
delete_callback_2_runloop.QuitWhenIdleClosure());
expected_entries.erase(expected_entries.begin());
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
delete_run_loop.Run();
// We're now holding two unfulfilled backend deletion callbacks. Delete the
// model (for extra test coverage) and then fire them in the opposite order
// they were called. The callbacks provided with RemoveBrowsingData should
// still be fired in matching order.
DeleteModel();
std::move(delete_tokens_complete_2)
.Run(
network::mojom::DeleteStoredTrustTokensStatus::kSuccessTokensDeleted);
delete_callback_2_runloop.Run();
EXPECT_FALSE(delete_callback_1_runloop.AnyQuitCalled());
EXPECT_TRUE(delete_callback_2_runloop.AnyQuitCalled());
std::move(delete_tokens_complete_1)
.Run(
network::mojom::DeleteStoredTrustTokensStatus::kSuccessTokensDeleted);
delete_callback_1_runloop.Run();
// The test not timing out is sufficient coverage, but this is easier to grok.
EXPECT_TRUE(delete_callback_1_runloop.AnyQuitCalled());
EXPECT_TRUE(delete_callback_2_runloop.AnyQuitCalled());
}
TEST_F(BrowsingDataModelTest, DelegateDataDeleted) {
// Needed to when building model from disk, returning an empty list as it's
// not needed for this test.
EXPECT_CALL(*mock_network_context(), GetStoredTrustTokenCounts(testing::_))
.WillOnce(
[&](network::TestNetworkContext::GetStoredTrustTokenCountsCallback
callback) { std::move(callback).Run({}); });
base::RunLoop run_loop;
BuildModel(run_loop.QuitWhenIdleClosure());
run_loop.Run();
auto expected_entries = std::vector<BrowsingDataEntry>{
{kTestOriginHost,
kTestOrigin,
{static_cast<BrowsingDataModel::StorageType>(
browsing_data::TestBrowsingDataModelDelegate::StorageType::
kTestDelegateType),
0, 0}}};
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
expected_entries.erase(expected_entries.begin());
EXPECT_TRUE(expected_entries.empty());
model()->RemoveBrowsingData(kTestOriginHost, base::DoNothing());
// Model should be empty after deleting delegated data.
browsing_data_model_test_util::ValidateBrowsingDataEntries(model(),
expected_entries);
}
// A BrowsingDataModel::Delegate that marks all Origin-keyed data belonging
// to a given host as being owned by its origin rather than its host.
class OriginOwnershipDelegate : public BrowsingDataModel::Delegate {
public:
explicit OriginOwnershipDelegate(const std::string& origin_owned_host)
: origin_owned_host_(origin_owned_host) {}
// BrowsingDataModel::Delegate:
void GetAllDataKeys(
base::OnceCallback<void(std::vector<DelegateEntry>)> callback) override {
std::move(callback).Run({});
}
void RemoveDataKey(BrowsingDataModel::DataKey data_key,
BrowsingDataModel::StorageTypeSet storage_types,
base::OnceClosure callback) override {
std::move(callback).Run();
}
absl::optional<BrowsingDataModel::DataOwner> GetDataOwner(
BrowsingDataModel::DataKey data_key,
BrowsingDataModel::StorageType storage_type) const override {
url::Origin* origin = absl::get_if<url::Origin>(&data_key);
if (origin && origin->host() == origin_owned_host_) {
return *origin;
}
return absl::nullopt;
}
private:
std::string origin_owned_host_;
};
TEST_F(BrowsingDataModelTest, DelegateDataCanBeOriginOwned) {
std::string origin_owned_host = "origin.owned.com";
std::unique_ptr<BrowsingDataModel> model = BrowsingDataModel::BuildEmpty(
storage_partition(),
std::make_unique<OriginOwnershipDelegate>(origin_owned_host));
auto httpOriginOwned = url::Origin::Create(GURL("http://origin.owned.com"));
model->AddBrowsingData(httpOriginOwned,
BrowsingDataModel::StorageType::kTrustTokens, 100);
auto httpsOriginOwned = url::Origin::Create(GURL("https://origin.owned.com"));
model->AddBrowsingData(httpsOriginOwned,
BrowsingDataModel::StorageType::kTrustTokens, 100);
auto httpHostOwned = url::Origin::Create(GURL("http://host.owned.com"));
model->AddBrowsingData(httpHostOwned,
BrowsingDataModel::StorageType::kTrustTokens, 100);
auto httpsHostOwned = url::Origin::Create(GURL("https://host.owned.com"));
model->AddBrowsingData(httpsHostOwned,
BrowsingDataModel::StorageType::kTrustTokens, 100);
auto expected_entries = std::vector<BrowsingDataEntry>{
{httpOriginOwned,
httpOriginOwned,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
{httpsOriginOwned,
httpsOriginOwned,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
{"host.owned.com",
httpHostOwned,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
{"host.owned.com",
httpsHostOwned,
{BrowsingDataModel::StorageType::kTrustTokens, 100, 0}},
};
browsing_data_model_test_util::ValidateBrowsingDataEntries(model.get(),
expected_entries);
}