Commit 40c5adcc authored by sauski's avatar sauski Committed by Commit Bot

Access Context Auditing: Respect time range history deletions

Extends Access Context service and database to delete access records
with timestamps that fall within an observed history deletion time
range.

Bug: 1101675
Change-Id: I49e0fdfc4d638c6d065d63a188da57ae5ab9ffca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2312699
Commit-Queue: Theodore Olsauskas-Warren <sauski@google.com>
Reviewed-by: default avatarMartin Šrámek <msramek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791736}
parent f1c47d8d
...@@ -306,6 +306,38 @@ void AccessContextAuditDatabase::RemoveAllRecords() { ...@@ -306,6 +306,38 @@ void AccessContextAuditDatabase::RemoveAllRecords() {
transaction.Commit(); transaction.Commit();
} }
void AccessContextAuditDatabase::RemoveAllRecordsForTimeRange(base::Time begin,
base::Time end) {
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
std::string remove = "DELETE FROM ";
remove.append(kCookieTableName);
remove.append(" WHERE access_utc BETWEEN ? AND ?");
sql::Statement remove_cookies(
db_.GetCachedStatement(SQL_FROM_HERE, remove.c_str()));
remove_cookies.BindInt64(0,
begin.ToDeltaSinceWindowsEpoch().InMicroseconds());
remove_cookies.BindInt64(1, end.ToDeltaSinceWindowsEpoch().InMicroseconds());
if (!remove_cookies.Run())
return;
remove = "DELETE FROM ";
remove.append(kStorageAPITableName);
remove.append(" WHERE access_utc BETWEEN ? AND ?");
sql::Statement remove_storage_apis(
db_.GetCachedStatement(SQL_FROM_HERE, remove.c_str()));
remove_storage_apis.BindInt64(
0, begin.ToDeltaSinceWindowsEpoch().InMicroseconds());
remove_storage_apis.BindInt64(
1, end.ToDeltaSinceWindowsEpoch().InMicroseconds());
if (!remove_storage_apis.Run())
return;
transaction.Commit();
}
void AccessContextAuditDatabase::RemoveSessionOnlyRecords( void AccessContextAuditDatabase::RemoveSessionOnlyRecords(
scoped_refptr<content_settings::CookieSettings> cookie_settings, scoped_refptr<content_settings::CookieSettings> cookie_settings,
const ContentSettingsForOneType& content_settings) { const ContentSettingsForOneType& content_settings) {
......
...@@ -88,6 +88,9 @@ class AccessContextAuditDatabase ...@@ -88,6 +88,9 @@ class AccessContextAuditDatabase
// Removes all records from the the database. // Removes all records from the the database.
void RemoveAllRecords(); void RemoveAllRecords();
// Removes all records where |begin| <= record.last_access_time <= |end|.
void RemoveAllRecordsForTimeRange(base::Time begin, base::Time end);
// Removes all records that match the provided cookie details. // Removes all records that match the provided cookie details.
void RemoveAllRecordsForCookie(const std::string& name, void RemoveAllRecordsForCookie(const std::string& name,
const std::string& domain, const std::string& domain,
......
...@@ -320,6 +320,32 @@ TEST_F(AccessContextAuditDatabaseTest, RemoveAllRecords) { ...@@ -320,6 +320,32 @@ TEST_F(AccessContextAuditDatabaseTest, RemoveAllRecords) {
EXPECT_EQ(0u, storage_api_rows); EXPECT_EQ(0u, storage_api_rows);
} }
TEST_F(AccessContextAuditDatabaseTest, RemoveAllRecordsForTimeRange) {
// Check that records within the specified time range are removed.
auto test_records = GetTestRecords();
OpenDatabase();
database()->AddRecords(test_records);
ValidateDatabaseRecords(database(), test_records);
auto begin_time =
base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromHours(4));
auto end_time =
base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromHours(6));
database()->RemoveAllRecordsForTimeRange(begin_time, end_time);
test_records.erase(
std::remove_if(
test_records.begin(), test_records.end(),
[=](const AccessContextAuditDatabase::AccessRecord& record) {
return record.last_access_time >= begin_time &&
record.last_access_time <= end_time;
}),
test_records.end());
ValidateDatabaseRecords(database(), test_records);
}
TEST_F(AccessContextAuditDatabaseTest, RemoveAllCookieRecords) { TEST_F(AccessContextAuditDatabaseTest, RemoveAllCookieRecords) {
// Check that all matching cookie records are removed from the database. // Check that all matching cookie records are removed from the database.
auto test_records = GetTestRecords(); auto test_records = GetTestRecords();
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
#include "base/time/default_clock.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/browsing_data/access_context_audit_database.h" #include "chrome/browser/browsing_data/access_context_audit_database.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/content_settings/cookie_settings_factory.h"
...@@ -16,7 +17,7 @@ ...@@ -16,7 +17,7 @@
#include "content/public/browser/storage_partition.h" #include "content/public/browser/storage_partition.h"
AccessContextAuditService::AccessContextAuditService(Profile* profile) AccessContextAuditService::AccessContextAuditService(Profile* profile)
: profile_(profile) {} : clock_(base::DefaultClock::GetInstance()), profile_(profile) {}
AccessContextAuditService::~AccessContextAuditService() = default; AccessContextAuditService::~AccessContextAuditService() = default;
bool AccessContextAuditService::Init( bool AccessContextAuditService::Init(
...@@ -49,7 +50,7 @@ bool AccessContextAuditService::Init( ...@@ -49,7 +50,7 @@ bool AccessContextAuditService::Init(
void AccessContextAuditService::RecordCookieAccess( void AccessContextAuditService::RecordCookieAccess(
const net::CookieList& accessed_cookies, const net::CookieList& accessed_cookies,
const url::Origin& top_frame_origin) { const url::Origin& top_frame_origin) {
auto now = base::Time::Now(); auto now = clock_->Now();
std::vector<AccessContextAuditDatabase::AccessRecord> access_records; std::vector<AccessContextAuditDatabase::AccessRecord> access_records;
for (const auto& cookie : accessed_cookies) { for (const auto& cookie : accessed_cookies) {
// Do not record accesses to already expired cookies. This service is // Do not record accesses to already expired cookies. This service is
...@@ -71,8 +72,8 @@ void AccessContextAuditService::RecordStorageAPIAccess( ...@@ -71,8 +72,8 @@ void AccessContextAuditService::RecordStorageAPIAccess(
AccessContextAuditDatabase::StorageAPIType type, AccessContextAuditDatabase::StorageAPIType type,
const url::Origin& top_frame_origin) { const url::Origin& top_frame_origin) {
std::vector<AccessContextAuditDatabase::AccessRecord> access_record = { std::vector<AccessContextAuditDatabase::AccessRecord> access_record = {
AccessContextAuditDatabase::AccessRecord( AccessContextAuditDatabase::AccessRecord(top_frame_origin, type,
top_frame_origin, type, storage_origin, base::Time::Now())}; storage_origin, clock_->Now())};
database_task_runner_->PostTask( database_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AccessContextAuditDatabase::AddRecords, FROM_HERE, base::BindOnce(&AccessContextAuditDatabase::AddRecords,
database_, std::move(access_record))); database_, std::move(access_record)));
...@@ -122,6 +123,20 @@ void AccessContextAuditService::OnURLsDeleted( ...@@ -122,6 +123,20 @@ void AccessContextAuditService::OnURLsDeleted(
return; return;
} }
if (deletion_info.time_range().IsValid()) {
// If a time range is specified, a time based deletion is performed as a
// first pass before origins without history entries are removed. A second
// pass based on origins is required as access record timestamps are not
// directly comparable to history timestamps. Only deleting based on
// timestamp may persist origins on disk for which no other trace exists.
database_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&AccessContextAuditDatabase::RemoveAllRecordsForTimeRange,
database_, deletion_info.time_range().begin(),
deletion_info.time_range().end()));
}
std::vector<url::Origin> deleted_origins; std::vector<url::Origin> deleted_origins;
// Map is of type {Origin -> {Count, LastVisitTime}}. // Map is of type {Origin -> {Count, LastVisitTime}}.
for (const auto& origin_urls_remaining : for (const auto& origin_urls_remaining :
...@@ -141,6 +156,10 @@ void AccessContextAuditService::OnURLsDeleted( ...@@ -141,6 +156,10 @@ void AccessContextAuditService::OnURLsDeleted(
} }
} }
void AccessContextAuditService::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}
void AccessContextAuditService::SetTaskRunnerForTesting( void AccessContextAuditService::SetTaskRunnerForTesting(
scoped_refptr<base::SequencedTaskRunner> task_runner) { scoped_refptr<base::SequencedTaskRunner> task_runner) {
DCHECK(!database_task_runner_); DCHECK(!database_task_runner_);
......
...@@ -56,6 +56,10 @@ class AccessContextAuditService : public KeyedService, ...@@ -56,6 +56,10 @@ class AccessContextAuditService : public KeyedService,
void OnURLsDeleted(history::HistoryService* history_service, void OnURLsDeleted(history::HistoryService* history_service,
const history::DeletionInfo& deletion_info) override; const history::DeletionInfo& deletion_info) override;
// Override the internal clock used to record storage API access timestamps
// and check for expired cookies.
void SetClockForTesting(base::Clock* clock);
// Override internal task runner with provided task runner. Must be called // Override internal task runner with provided task runner. Must be called
// before Init(). // before Init().
void SetTaskRunnerForTesting( void SetTaskRunnerForTesting(
...@@ -71,6 +75,7 @@ class AccessContextAuditService : public KeyedService, ...@@ -71,6 +75,7 @@ class AccessContextAuditService : public KeyedService,
scoped_refptr<AccessContextAuditDatabase> database_; scoped_refptr<AccessContextAuditDatabase> database_;
scoped_refptr<base::SequencedTaskRunner> database_task_runner_; scoped_refptr<base::SequencedTaskRunner> database_task_runner_;
base::Clock* clock_;
Profile* profile_; Profile* profile_;
mojo::Receiver<network::mojom::CookieChangeListener> mojo::Receiver<network::mojom::CookieChangeListener>
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/i18n/time_formatting.h" #include "base/i18n/time_formatting.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_simple_task_runner.h" #include "base/test/test_simple_task_runner.h"
#include "chrome/browser/browsing_data/access_context_audit_database.h" #include "chrome/browser/browsing_data/access_context_audit_database.h"
#include "chrome/browser/browsing_data/access_context_audit_service_factory.h" #include "chrome/browser/browsing_data/access_context_audit_service_factory.h"
...@@ -146,6 +147,7 @@ class AccessContextAuditServiceTest : public testing::Test { ...@@ -146,6 +147,7 @@ class AccessContextAuditServiceTest : public testing::Test {
void ClearReturnedRecords() { records_.clear(); } void ClearReturnedRecords() { records_.clear(); }
TestCookieManager* cookie_manager() { return &cookie_manager_; } TestCookieManager* cookie_manager() { return &cookie_manager_; }
base::SimpleTestClock* clock() { return &clock_; }
TestingProfile* profile() { return profile_.get(); } TestingProfile* profile() { return profile_.get(); }
history::HistoryService* history_service() { return history_service_; } history::HistoryService* history_service() { return history_service_; }
AccessContextAuditService* service() { AccessContextAuditService* service() {
...@@ -155,6 +157,7 @@ class AccessContextAuditServiceTest : public testing::Test { ...@@ -155,6 +157,7 @@ class AccessContextAuditServiceTest : public testing::Test {
protected: protected:
content::BrowserTaskEnvironment browser_task_environment_; content::BrowserTaskEnvironment browser_task_environment_;
std::unique_ptr<TestingProfile> profile_; std::unique_ptr<TestingProfile> profile_;
base::SimpleTestClock clock_;
base::ScopedTempDir temp_directory_; base::ScopedTempDir temp_directory_;
TestCookieManager cookie_manager_; TestCookieManager cookie_manager_;
history::HistoryService* history_service_; history::HistoryService* history_service_;
...@@ -394,6 +397,93 @@ TEST_F(AccessContextAuditServiceTest, AllHistoryDeletion) { ...@@ -394,6 +397,93 @@ TEST_F(AccessContextAuditServiceTest, AllHistoryDeletion) {
EXPECT_EQ(0u, GetReturnedRecords().size()); EXPECT_EQ(0u, GetReturnedRecords().size());
} }
TEST_F(AccessContextAuditServiceTest, TimeRangeHistoryDeletion) {
// Test that deleting a time range of history records correctly removes
// records within the time range, as well as records for which no history
// entry for the top frame origin remains.
// Create a situation where origin https://foo.com has history entries and
// access records with timestamps both inside and outside the deleted range.
// Additionally create a single history entry for origin https://bar.com
// inside the deleted range, and multiple access records outside the range.
// After deletion, the access records for https://foo.com outside the deletion
// range should still be present, while all access records https://bar.com
// should have been removed.
const GURL kURL1 = GURL("https://foo.com/example.html");
const GURL kURL2 = GURL("https://bar.com/another.html");
const url::Origin kOrigin1 = url::Origin::Create(kURL1);
const url::Origin kOrigin2 = url::Origin::Create(kURL2);
const GURL kTestCookieURL = GURL("https://test.com");
const auto kTestStorageType1 =
AccessContextAuditDatabase::StorageAPIType::kWebDatabase;
const auto kTestStorageType2 =
AccessContextAuditDatabase::StorageAPIType::kAppCache;
clock()->SetNow(base::Time::Now());
service()->SetClockForTesting(clock());
const base::Time kInsideTimeRange =
clock()->Now() + base::TimeDelta::FromHours(1);
const base::Time kOutsideTimeRange =
clock()->Now() + base::TimeDelta::FromHours(3);
history_service()->AddPageWithDetails(kURL1, base::ASCIIToUTF16("Test1"), 1,
1, kInsideTimeRange, false,
history::SOURCE_BROWSED);
history_service()->AddPageWithDetails(kURL2, base::ASCIIToUTF16("Test2"), 1,
1, kInsideTimeRange, false,
history::SOURCE_BROWSED);
history_service()->AddPageWithDetails(kURL1, base::ASCIIToUTF16("Test3"), 1,
1, kOutsideTimeRange, false,
history::SOURCE_BROWSED);
// Record accesses to cookies both inside and outside the deletion range.
auto cookie_accessed_in_range = net::CanonicalCookie::Create(
kTestCookieURL, "inside=1; max-age=3600", kInsideTimeRange,
base::nullopt /* server_time */);
auto cookie_accessed_outside_range = net::CanonicalCookie::Create(
kTestCookieURL, "outside=1; max-age=3600", kOutsideTimeRange,
base::nullopt /* server_time */);
service()->RecordCookieAccess({*cookie_accessed_in_range}, kOrigin1);
service()->RecordCookieAccess({*cookie_accessed_outside_range}, kOrigin1);
service()->RecordCookieAccess({*cookie_accessed_outside_range}, kOrigin2);
// Record accesses to storage APIs both inside and outside the deletion range.
clock()->SetNow(kInsideTimeRange);
service()->RecordStorageAPIAccess(kOrigin1, kTestStorageType1, kOrigin1);
clock()->SetNow(kOutsideTimeRange);
service()->RecordStorageAPIAccess(kOrigin1, kTestStorageType2, kOrigin1);
service()->RecordStorageAPIAccess(kOrigin2, kTestStorageType1, kOrigin2);
// Ensure all records have been initially recorded.
service()->GetAllAccessRecords(
base::BindOnce(&AccessContextAuditServiceTest::AccessRecordCallback,
base::Unretained(this)));
browser_task_environment_.RunUntilIdle();
EXPECT_EQ(6u, GetReturnedRecords().size());
// Expire history in target time range.
base::RunLoop run_loop;
base::CancelableTaskTracker task_tracker;
history_service()->ExpireHistoryBetween(
std::set<GURL>(), kInsideTimeRange - base::TimeDelta::FromMinutes(10),
kInsideTimeRange + base::TimeDelta::FromMinutes(10),
/*user_initiated*/ true, run_loop.QuitClosure(), &task_tracker);
run_loop.Run();
// Ensure records have been removed as expected.
service()->GetAllAccessRecords(
base::BindOnce(&AccessContextAuditServiceTest::AccessRecordCallback,
base::Unretained(this)));
browser_task_environment_.RunUntilIdle();
EXPECT_EQ(2u, GetReturnedRecords().size());
CheckContainsCookieRecord(cookie_accessed_outside_range.get(), kOrigin1,
GetReturnedRecords());
CheckContainsStorageAPIRecord(kOrigin1, kTestStorageType2, kOrigin1,
GetReturnedRecords());
}
TEST_F(AccessContextAuditServiceTest, SessionOnlyRecords) { TEST_F(AccessContextAuditServiceTest, SessionOnlyRecords) {
// Check that data for cookie domains and storage origins are cleared on // Check that data for cookie domains and storage origins are cleared on
// service shutdown when the associated content settings indicate they should. // service shutdown when the associated content settings indicate they should.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment