blob: ddb884b701406e38c687af3e8e9238926b5bf8c2 [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 "content/browser/btm/btm_storage.h"
#include <memory>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "content/browser/btm/btm_utils.h"
#include "content/public/common/btm_utils.h"
#include "content/public/common/content_features.h"
#include "services/network/public/mojom/clear_data_filter.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "url/gurl.h"
namespace content {
BtmStorage::BtmStorage(const std::optional<base::FilePath>& path)
: db_(std::make_unique<BtmDatabase>(path)) {
base::AssertLongCPUWorkAllowed();
}
BtmStorage::~BtmStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
// BtmDatabase interaction functions ------------------------------------------
BtmState BtmStorage::Read(const GURL& url) {
return ReadSite(GetSiteForBtm(url));
}
BtmState BtmStorage::ReadSite(std::string site) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
std::optional<StateValue> state = db_->Read(site);
if (state.has_value()) {
// We should not have entries in the DB without any timestamps.
DCHECK(state->user_activation_times.has_value() ||
state->bounce_times.has_value() ||
state->web_authn_assertion_times.has_value());
return BtmState(this, std::move(site), state.value());
}
return BtmState(this, std::move(site));
}
void BtmStorage::Write(const BtmState& state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
db_->Write(state.site(), state.user_activation_times(), state.bounce_times(),
state.web_authn_assertion_times());
}
std::optional<PopupsStateValue> BtmStorage::ReadPopup(
const std::string& first_party_site,
const std::string& tracking_site) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
return db_->ReadPopup(first_party_site, tracking_site);
}
std::vector<PopupWithTime> BtmStorage::ReadRecentPopupsWithInteraction(
const base::TimeDelta& lookback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
return db_->ReadRecentPopupsWithInteraction(lookback);
}
bool BtmStorage::WritePopup(const std::string& first_party_site,
const std::string& tracking_site,
const uint64_t access_id,
const base::Time& popup_time,
bool is_current_interaction,
bool is_authentication_interaction) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
return db_->WritePopup(first_party_site, tracking_site, access_id, popup_time,
is_current_interaction, is_authentication_interaction);
}
void BtmStorage::RemoveEvents(base::Time delete_begin,
base::Time delete_end,
network::mojom::ClearDataFilterPtr filter,
const BtmEventRemovalType type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
DCHECK(delete_end.is_null() || delete_begin <= delete_end);
if (delete_end.is_null()) {
delete_end = base::Time::Max();
}
if (delete_begin.is_null()) {
delete_begin = base::Time::Min();
}
if (filter.is_null()) {
db_->RemoveEventsByTime(delete_begin, delete_end, type);
} else if (type == BtmEventRemovalType::kStorage && filter->origins.empty()) {
// Site-filtered deletion is only supported for cookie-related
// DIPS events, since only cookie deletion allows domains but not hosts.
//
// TODO(jdh): Assess the use of cookie deletions with both a time range and
// a list of domains to determine whether supporting time ranges here is
// necessary.
// Time ranges aren't currently supported for site-filtered
// deletion of DIPS Events.
if (delete_begin != base::Time::Min() || delete_end != base::Time::Max()) {
// TODO (kaklilu@): Add a UMA metric to record if this happens.
return;
}
bool preserve =
(filter->type == network::mojom::ClearDataFilter::Type::KEEP_MATCHES);
std::vector<std::string> sites = std::move(filter->domains);
db_->RemoveEventsBySite(preserve, sites, type);
}
}
void BtmStorage::RemoveRows(const std::vector<std::string>& sites) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
db_->RemoveRows(BtmDatabaseTable::kBounces, sites);
}
void BtmStorage::RemoveRowsWithoutProtectiveEvent(
const std::set<std::string>& sites) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
std::set<std::string> filtered_sites =
FilterSitesWithoutProtectiveEvent(sites);
RemoveRows(
std::vector<std::string>(filtered_sites.begin(), filtered_sites.end()));
}
// BtmTabHelper Function Impls ------------------------------------------------
void BtmStorage::RecordUserActivation(const GURL& url, base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
BtmState state = Read(url);
state.update_user_activation_time(time);
}
void BtmStorage::RecordWebAuthnAssertion(const GURL& url, base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
BtmState state = Read(url);
state.update_web_authn_assertion_time(time);
}
void BtmStorage::RecordBounce(const GURL& url, base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
BtmState state = Read(url);
state.update_bounce_time(time);
}
std::pair<std::set<std::string>, std::set<std::string>>
BtmStorage::FilterSitesWithProtectiveEvent(
const std::set<std::string>& sites) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
return {
db_->FilterSites(sites, BtmDatabase::BounceFilterType::kUserActivation),
db_->FilterSites(sites,
BtmDatabase::BounceFilterType::kWebAuthnAssertion)};
}
std::set<std::string> BtmStorage::FilterSitesWithoutProtectiveEvent(
std::set<std::string> sites) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
std::set<std::string> interacted_sites =
db_->FilterSites(sites, BtmDatabase::BounceFilterType::kProtectiveEvent);
for (const auto& site : interacted_sites) {
if (sites.count(site)) {
sites.erase(site);
}
}
return sites;
}
std::vector<std::string> BtmStorage::GetSitesThatBounced(
base::TimeDelta grace_period) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(db_);
return db_->GetSitesThatBounced(grace_period);
}
std::vector<std::string> BtmStorage::GetSitesToClear(
std::optional<base::TimeDelta> custom_period) const {
std::vector<std::string> sites_to_clear;
base::TimeDelta grace_period =
custom_period.value_or(features::kBtmGracePeriod.Get());
switch (features::kBtmTriggeringAction.Get()) {
case BtmTriggeringAction::kNone: {
return {};
}
case BtmTriggeringAction::kBounce: {
sites_to_clear = GetSitesThatBounced(grace_period);
break;
}
}
return sites_to_clear;
}
bool BtmStorage::DidSiteHaveUserActivationSince(const GURL& url,
base::Time bound) {
auto last_user_activation_time = LastUserActivationTime(url);
return last_user_activation_time.has_value() &&
last_user_activation_time >= bound;
}
std::optional<base::Time> BtmStorage::LastUserActivationTime(const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const BtmState state = Read(url);
if (!state.user_activation_times().has_value()) {
return std::nullopt;
}
return state.user_activation_times()->second;
}
std::optional<base::Time> BtmStorage::LastWebAuthnAssertionTime(
const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const BtmState state = Read(url);
if (!state.web_authn_assertion_times().has_value()) {
return std::nullopt;
}
return state.web_authn_assertion_times()->second;
}
std::optional<base::Time> BtmStorage::LastUserActivationOrAuthnAssertionTime(
const GURL& url) {
return LastInteractionTimeAndType(url).first;
}
std::pair<std::optional<base::Time>, BtmInteractionType>
BtmStorage::LastInteractionTimeAndType(const GURL& url) {
base::Time last_user_activation_time =
LastUserActivationTime(url).value_or(base::Time::Min());
base::Time last_web_authn_assertion_time =
LastWebAuthnAssertionTime(url).value_or(base::Time::Min());
if ((last_user_activation_time == last_web_authn_assertion_time) &&
(last_user_activation_time == base::Time::Min())) {
return std::make_pair(std::nullopt, BtmInteractionType::NoInteraction);
}
if (last_user_activation_time >= last_web_authn_assertion_time) {
return std::make_pair(last_user_activation_time,
BtmInteractionType::UserActivation);
}
return std::make_pair(last_web_authn_assertion_time,
BtmInteractionType::Authentication);
}
/* static */
void BtmStorage::DeleteDatabaseFiles(base::FilePath path,
base::OnceClosure on_complete) {
// TODO (jdh): Decide how to handle the case of failing to delete db files.
base::ThreadPool::PostTaskAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(IgnoreResult(&sql::Database::Delete), std::move(path)),
std::move(on_complete));
}
std::optional<base::Time> BtmStorage::GetTimerLastFired() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return db_->GetTimerLastFired();
}
bool BtmStorage::SetTimerLastFired(base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return db_->SetTimerLastFired(time);
}
} // namespace content