blob: 716b99d8e1a167660bac941abdbf5ef0bd872ae3 [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.
#ifndef CONTENT_BROWSER_BTM_BTM_DATABASE_H_
#define CONTENT_BROWSER_BTM_BTM_DATABASE_H_
#include <stddef.h>
#include <string>
#include <vector>
#include "base/strings/cstring_view.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "content/browser/btm/btm_utils.h"
#include "content/common/content_export.h"
#include "content/public/common/content_features.h"
#include "sql/database.h"
#include "sql/init_status.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
namespace content {
// Encapsulates an SQL database that holds DIPS info.
class CONTENT_EXPORT BtmDatabase {
public:
enum class BounceFilterType {
// Filter for bounces with either user activations or WebAuthn assertions.
kProtectiveEvent,
// Filter for bounces with user activations.
kUserActivation,
// Filter for bounces with WebAuthn assertions.
kWebAuthnAssertion,
};
// Version number of the database schema.
// NOTE: When changing the version, add a new golden file for the new version
// at `//chrome/test/data/dips/v<N>.sql`.
static constexpr int kLatestSchemaVersion = 9;
// The minimum database schema version this Chrome code is compatible with.
static constexpr int kMinCompatibleSchemaVersion = 9;
static constexpr char kPrepopulatedKey[] = "prepopulated";
// The length of time that will be waited between emitting db health metrics.
static constexpr base::TimeDelta kMetricsInterval = base::Hours(24);
// How long DIPS maintains popups in storage (for recording Popup Heuristic
// storage accesses).
static constexpr base::TimeDelta kPopupTtl = base::Days(60);
// Passing in an std::nullopt `db_path` causes the db to be created in
// memory. Init() must be called before using the BtmDatabase to make sure it
// is initialized.
explicit BtmDatabase(const std::optional<base::FilePath>& db_path);
// This object must be destroyed on the thread where all accesses are
// happening to avoid thread-safety problems.
~BtmDatabase();
BtmDatabase(const BtmDatabase&) = delete;
BtmDatabase& operator=(const BtmDatabase&) = delete;
// DIPS Bounce table functions -----------------------------------------------
bool Write(const std::string& site,
const TimestampRange& storage_times,
const TimestampRange& user_activation_times,
const TimestampRange& stateful_bounce_times,
const TimestampRange& bounce_times,
const TimestampRange& web_authn_assertion_times);
bool WritePopup(const std::string& opener_site,
const std::string& popup_site,
const uint64_t access_id,
const base::Time& popup_time,
bool is_current_interaction,
bool is_authentication_interaction);
std::optional<StateValue> Read(const std::string& site);
std::optional<PopupsStateValue> ReadPopup(const std::string& opener_site,
const std::string& popup_site);
// Returns all entries from the `popups` table with a current interaction,
// where the last popup time was more recent than `lookback` ago.
std::vector<PopupWithTime> ReadRecentPopupsWithInteraction(
const base::TimeDelta& lookback);
// Note: this doesn't clear expired interactions from the database unlike
// the other database querying methods.
std::vector<std::string> GetAllSitesForTesting(const BtmDatabaseTable table);
// Returns the subset of sites in `sites` WITH some property recorded, as
// determined by the filtering condition.
//
// (For example, a protective event is a user activation or successful
// WebAuthn assertion.)
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
std::set<std::string> FilterSites(const std::set<std::string>& sites,
BounceFilterType filter);
// Returns all sites which bounced the user and aren't protected from DIPS.
//
// A site can be protected in several ways:
// - it's still in its grace period after the first bounce
// - it received user activation or WAA before the first bounce
// - it received user activation or WAA in the grace period after the first
// bounce.
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
std::vector<std::string> GetSitesThatBounced(base::TimeDelta grace_period);
// Deletes all rows in the database whose interactions have expired out.
//
// When an interaction happens before a DIPS-triggering action or during the
// following grace-period, it protects that site from its data being cleared
// by DIPS. Further interactions will prolong that protection until the last
// one reaches the `interaction_ttl`.
//
// Clearing expired interactions effectively restarts the DIPS procedure for
// determining if a site is a tracker for sites that are cleared.
//
// Returns the number of rows that are removed.
size_t ClearExpiredRows();
// Delete the row from the bounces table for `site`. Returns true if query
// executes successfully.
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
bool RemoveRow(const BtmDatabaseTable table, std::string_view site);
bool RemoveRows(const BtmDatabaseTable table,
const std::vector<std::string>& sites);
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
bool RemoveEventsByTime(const base::Time& delete_begin,
const base::Time& delete_end,
const BtmEventRemovalType type);
// Because |sites| is taken from a ClearDataFilter, this only removes
// storage and stateful bounce timestamps at the moment.
bool RemoveEventsBySite(bool preserve,
const std::vector<std::string>& sites,
const BtmEventRemovalType type);
// Returns the number of entries present in the database.
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
size_t GetEntryCount(const BtmDatabaseTable table);
// If the number of entries in the database is greater than
// |GetMaxEntries()|, garbage collect. Returns the number of entries deleted
// (useful for debugging).
size_t GarbageCollect();
// Removes the |purge_goal| entries with the oldest
// |MAX(last_user_activation_time,last_site_storage_time)| value. Returns the
// number of entries deleted.
//
// NOTE: The SQLITE sub-query in this method must match that of
// `GetGarbageCollectOldestSitesForTesting()`'s query.
size_t GarbageCollectOldest(const BtmDatabaseTable table, int purge_goal);
// Used for testing the intended behavior `GarbageCollectOldest()`.
//
// NOTE: The SQLITE query in this method must match that of
// `GarbageCollectOldest()`'s sub-query.
std::vector<std::string> GetGarbageCollectOldestSitesForTesting(
const BtmDatabaseTable table);
bool in_memory() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return db_path_.empty();
}
// Checks that the internal SQLite database is initialized.
bool CheckDBInit();
size_t GetMaxEntries() const { return max_entries_; }
size_t GetPurgeEntries() const { return purge_entries_; }
std::optional<base::Time> GetTimerLastFired();
bool SetTimerLastFired(base::Time time);
// Testing functions --------------------------------------------------
void SetMaxEntriesForTesting(size_t entries) { max_entries_ = entries; }
void SetPurgeEntriesForTesting(size_t entries) { purge_entries_ = entries; }
void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
bool ExecuteSqlForTesting(const base::cstring_view sql);
bool SetConfigValueForTesting(std::string_view name, int64_t value) {
return SetConfigValue(name, value);
}
std::optional<int64_t> GetConfigValueForTesting(std::string_view name) {
return GetConfigValue(name);
}
protected:
// Initialization functions --------------------------------------------------
sql::InitStatus Init();
sql::InitStatus InitImpl();
sql::InitStatus OpenDatabase();
// Creates the bounce table following the latest schema.
bool InitTables();
// Internal utility functions ------------------------------------------------
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
bool ClearTimestamps(const base::Time& delete_begin,
const base::Time& delete_end,
const BtmEventRemovalType type);
bool ClearTimestampsBySite(bool preserve,
const std::vector<std::string>& sites,
const BtmEventRemovalType type);
bool RemoveEmptyRows();
void LogDatabaseMetrics();
private:
// Callback for database errors.
void DatabaseErrorCallback(int extended_error, sql::Statement* stmt);
// Only ClearTimestamps() should call this method.
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
bool AdjustFirstTimestamps(const base::Time& delete_begin,
const base::Time& delete_end,
const BtmEventRemovalType type);
// Only ClearTimestamps() should call this method.
//
// NOTE: This method's main procedure is performed after calling
// `ClearExpiredRows()`.
bool AdjustLastTimestamps(const base::Time& delete_begin,
const base::Time& delete_end,
const BtmEventRemovalType type);
// Upsert the row for `key` in the config table to contain `value`.
bool SetConfigValue(std::string_view key, int64_t value);
// Get the value for `key` from the config table, or nullopt if absent.
std::optional<int64_t> GetConfigValue(std::string_view key);
// Checks if a given timestamp is missing or past its BTM interaction TTL.
bool IsNullOrExpired(std::optional<base::Time> time);
// When the number of entries in the database exceeds |max_entries_|, purge
// down to |max_entries_| - |purge_entries_|.
size_t max_entries_ = 3500;
size_t purge_entries_ = 300;
const base::FilePath db_path_ GUARDED_BY_CONTEXT(sequence_checker_);
std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_);
bool db_init_ = false;
raw_ptr<base::Clock> clock_ = base::DefaultClock::GetInstance();
sql::MetaTable meta_table_ GUARDED_BY_CONTEXT(sequence_checker_);
mutable base::Time last_health_metrics_time_
GUARDED_BY_CONTEXT(sequence_checker_) = base::Time::Min();
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace content
#endif // CONTENT_BROWSER_BTM_BTM_DATABASE_H_