Commit 131a0fdc authored by Igor Tukh's avatar Igor Tukh Committed by Commit Bot

[Permissions Auditing] Add PermissionAuditingDatabase.

PermissionAuditingDatabase is a SQL-based storage for keeping
information about the permissions usage. The information is represented
in source code with PermissionUsageRecord structure.


R=engedy@chromium.org

Bug: 169305053
Change-Id: I65da241073a82baac4c02d240da36f6c379b7c56
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435307
Commit-Queue: Balazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827261}
parent aa38a52d
......@@ -20,6 +20,8 @@ source_set("permissions") {
"features.h",
"notification_permission_ui_selector.cc",
"notification_permission_ui_selector.h",
"permission_auditing_database.cc",
"permission_auditing_database.h",
"permission_context_base.cc",
"permission_context_base.h",
"permission_decision_auto_blocker.cc",
......@@ -39,6 +41,8 @@ source_set("permissions") {
"permission_result.h",
"permission_uma_util.cc",
"permission_uma_util.h",
"permission_usage_session.cc",
"permission_usage_session.h",
"permission_util.cc",
"permission_util.h",
"permissions_client.cc",
......@@ -64,7 +68,9 @@ source_set("permissions") {
"//components/vector_icons",
"//content/public/browser",
"//services/metrics/public/cpp:ukm_builders",
"//sql",
"//third_party/blink/public/common",
"//third_party/sqlite",
"//ui/base",
"//url",
]
......@@ -133,6 +139,7 @@ source_set("unit_tests") {
sources = [
"chooser_context_base_unittest.cc",
"contexts/geolocation_permission_context_unittest.cc",
"permission_auditing_database_unittest.cc",
"permission_context_base_unittest.cc",
"permission_decision_auto_blocker_unittest.cc",
"permission_manager_unittest.cc",
......@@ -153,7 +160,11 @@ source_set("unit_tests") {
"//components/ukm/content",
"//components/variations",
"//content/test:test_support",
"//sql",
"//sql:test_support",
"//testing/gtest",
"//third_party/sqlite",
"//url",
]
if (is_android) {
deps += [
......
......@@ -15,9 +15,11 @@ include_rules = [
"+content/public/test",
"+media/base/android/media_drm_bridge.h",
"+services/metrics/public/cpp",
"+sql",
"+third_party/blink/public/common/loader/network_utils.h",
"+third_party/blink/public/mojom/feature_policy",
"+third_party/blink/public/mojom/quota",
"+third_party/sqlite",
"+ui/base",
"+ui/gfx",
]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/permission_auditing_database.h"
#include <algorithm>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/optional.h"
#include "base/strings/stringprintf.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "url/gurl.h"
namespace permissions {
namespace {
int64_t TimeToInt64(base::Time time) {
return time.ToDeltaSinceWindowsEpoch().InMicroseconds();
}
base::Time Int64ToTime(const int64_t& time) {
return base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(time));
}
// For this database, schema migration is supported for at least 1 year.
// This means we can deprecate old versions that landed more than a year ago.
//
// Version 1: https://crrev.com/c/2435307 - 2020-11-04
// The current number of the database schema.
constexpr int kVersion = 1;
// The lowest version of the database schema such that a legacy code can still
// read/write the current database.
constexpr int kCompatibleVersion = 1;
} // namespace
PermissionAuditingDatabase::PermissionAuditingDatabase() = default;
PermissionAuditingDatabase::~PermissionAuditingDatabase() = default;
bool PermissionAuditingDatabase::Init(const base::FilePath& path) {
if (!db_.Open(path)) {
return false;
}
sql::MetaTable metatable;
if (!metatable.Init(&db_, kVersion, kCompatibleVersion)) {
db_.Poison();
return false;
}
if (metatable.GetCompatibleVersionNumber() > kVersion) {
db_.Poison();
return false;
}
if (!db_.DoesTableExist("uses")) {
if (!CreateSchema()) {
db_.Poison();
return false;
}
}
// TODO(anyone): perform migration if metatable.GetVersionNumber() < kVersion
return true;
}
bool PermissionAuditingDatabase::CreateSchema() {
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
return false;
}
if (!db_.Execute("CREATE TABLE uses("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" origin TEXT NOT NULL,"
" content_setting_type INTEGER NOT NULL,"
" usage_start_time INTEGER NOT NULL,"
" usage_end_time INTEGER NOT NULL,"
" had_user_activation INTEGER NOT NULL,"
" was_foreground INTEGER NOT NULL,"
" had_focus INTEGER NOT NULL"
")")) {
return false;
}
if (!db_.Execute("CREATE UNIQUE INDEX setting_origin_start_time ON "
"uses(origin, content_setting_type,"
"usage_start_time)")) {
return false;
}
if (!db_.Execute("CREATE INDEX setting_origin_end_time ON "
"uses(origin, content_setting_type,"
"usage_end_time)")) {
return false;
}
if (!db_.Execute("CREATE INDEX start_time ON "
"uses(usage_start_time)")) {
return false;
}
if (!db_.Execute("CREATE INDEX end_time ON "
"uses(usage_end_time)")) {
return false;
}
return transaction.Commit();
}
bool PermissionAuditingDatabase::StorePermissionUsage(
const PermissionUsageSession& session) {
DCHECK(session.IsValid());
sql::Statement statement(
db_.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO uses(origin, content_setting_type,"
"usage_start_time, usage_end_time,"
"had_user_activation, was_foreground, had_focus)"
"VALUES (?, ?, ?, ?, ?, ?, ?)"));
statement.BindString(0, session.origin.Serialize());
statement.BindInt(1, static_cast<int32_t>(session.type));
statement.BindInt64(2, TimeToInt64(session.usage_start));
statement.BindInt64(3, TimeToInt64(session.usage_end));
statement.BindBool(4, session.had_user_activation);
statement.BindBool(5, session.was_foreground);
statement.BindBool(6, session.had_focus);
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
return false;
}
if (!statement.Run()) {
return false;
}
return transaction.Commit();
}
std::vector<PermissionUsageSession>
PermissionAuditingDatabase::GetPermissionUsageHistory(ContentSettingsType type,
const url::Origin& origin,
base::Time start_time) {
DCHECK(!origin.opaque());
std::vector<PermissionUsageSession> sessions;
sql::Statement statement(db_.GetCachedStatement(
SQL_FROM_HERE,
"SELECT usage_start_time, usage_end_time, had_user_activation,"
"was_foreground, had_focus "
"FROM uses "
"WHERE origin = ? AND content_setting_type = ? "
"AND usage_end_time >= ?"));
statement.BindString(0, origin.Serialize());
statement.BindInt(1, static_cast<int32_t>(type));
statement.BindInt64(2, start_time.is_null()
? std::numeric_limits<int64_t>::min()
: TimeToInt64(start_time));
while (statement.Step()) {
sessions.push_back({.origin = origin,
.type = type,
.usage_start = Int64ToTime(statement.ColumnInt64(0)),
.usage_end = Int64ToTime(statement.ColumnInt64(1)),
.had_user_activation = statement.ColumnBool(2),
.was_foreground = statement.ColumnBool(3),
.had_focus = statement.ColumnBool(4)});
}
return sessions;
}
base::Optional<base::Time>
PermissionAuditingDatabase::GetLastPermissionUsageTime(
ContentSettingsType type,
const url::Origin& origin) {
DCHECK(!origin.opaque());
sql::Statement statement(
db_.GetCachedStatement(SQL_FROM_HERE,
"SELECT usage_end_time "
"FROM uses "
"WHERE origin = ? AND content_setting_type = ? "
"ORDER BY usage_end_time DESC "
"LIMIT 1"));
statement.BindString(0, origin.Serialize());
statement.BindInt(1, static_cast<int32_t>(type));
base::Optional<base::Time> last_usage;
if (statement.Step()) {
last_usage = Int64ToTime(statement.ColumnInt64(0));
}
return last_usage;
}
bool PermissionAuditingDatabase::UpdateEndTime(ContentSettingsType type,
const url::Origin& origin,
base::Time start_time,
base::Time new_end_time) {
DCHECK(!origin.opaque());
DCHECK(!start_time.is_null());
DCHECK(!new_end_time.is_null());
DCHECK_LE(start_time, new_end_time);
sql::Statement statement(
db_.GetCachedStatement(SQL_FROM_HERE,
"UPDATE uses "
"SET usage_end_time = ? "
"WHERE origin = ? AND content_setting_type = ? "
"AND usage_start_time = ?"));
statement.BindInt64(0, TimeToInt64(new_end_time));
statement.BindString(1, origin.Serialize());
statement.BindInt(2, static_cast<int32_t>(type));
statement.BindInt64(3, TimeToInt64(start_time));
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
return false;
}
if (!statement.Run()) {
return false;
}
return transaction.Commit();
}
bool PermissionAuditingDatabase::DeleteSessionsBetween(base::Time start_time,
base::Time end_time) {
std::vector<int> ids;
sql::Statement statement(
db_.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM uses "
"WHERE usage_start_time BETWEEN ? AND ? "
"OR usage_end_time BETWEEN ? AND ?"));
auto start = start_time.is_null() ? std::numeric_limits<int64_t>::min()
: TimeToInt64(start_time);
auto end = end_time.is_null() ? std::numeric_limits<int64_t>::max()
: TimeToInt64(end_time);
statement.BindInt64(0, start);
statement.BindInt64(1, end);
statement.BindInt64(2, start);
statement.BindInt64(3, end);
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
return false;
}
if (!statement.Run()) {
return false;
}
return transaction.Commit();
}
} // namespace permissions
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PERMISSIONS_PERMISSION_AUDITING_DATABASE_H_
#define COMPONENTS_PERMISSIONS_PERMISSION_AUDITING_DATABASE_H_
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_usage_session.h"
#include "sql/database.h"
#include "url/origin.h"
namespace base {
class FilePath;
} // namespace base
namespace permissions {
// Stores permission usage sessions for specific url origin and
// ContentSettingType in an SQLite database. Additionally, handles the queries
// about the last permission usage time for a specific origin.
// Threading constraints:
// 1) This class is not thread-safe, so each instance must be used on the same
// sequence;
// 2) Instances must be used on a sequence that can execute blocking tasks.
class PermissionAuditingDatabase {
public:
PermissionAuditingDatabase();
~PermissionAuditingDatabase();
PermissionAuditingDatabase(const PermissionAuditingDatabase&) = delete;
PermissionAuditingDatabase& operator=(const PermissionAuditingDatabase&) =
delete;
PermissionAuditingDatabase(PermissionAuditingDatabase&&) = delete;
PermissionAuditingDatabase& operator=(const PermissionAuditingDatabase&&) =
delete;
// Opens an existing database at `path` or creates a new one if none exists,
// and returns true on success.
bool Init(const base::FilePath& path);
// Appends a new permission usage `session` of the given permission `type` on
// a given `origin`. The `session` must be valid according to IsValid().
// Operation will fail in case if a session with the same primary key, that
// is, origin, type, and usage start time, already exists in the database.
// Returns if the operation was successful.
bool StorePermissionUsage(const PermissionUsageSession& session);
// Returns the detailed history stored for the permission `type` on a given
// `origin` from the specified `start_time`. The `origin` must not be opaque.
std::vector<PermissionUsageSession> GetPermissionUsageHistory(
ContentSettingsType type,
const url::Origin& origin,
base::Time start_time);
// Returns when the given permission `type` was last used on a given `origin`.
// Returns nullopt if no permission usages match the given constraints. The
// `origin` must not be opaque.
base::Optional<base::Time> GetLastPermissionUsageTime(
ContentSettingsType type,
const url::Origin& origin);
// Updates the usage end time for a specific usage session. The session is
// identified by the primary key {`type`, `origin`, `start_time`}, and must
// already exist. `start_time` must be less than or equal to `new_end_time`.
// Operation will fail if `start_time` or `new_end_time` is null. Returns if
// the operation was successful.
bool UpdateEndTime(ContentSettingsType type,
const url::Origin& origin,
base::Time start_time,
base::Time new_end_time);
// Deletes permission usage sessions, which started or ended in the given
// time range. A null `start_time` or `end_time` time is treated as -inf and
// +inf, respectively. Returns if the operation was successful.
bool DeleteSessionsBetween(base::Time start_time, base::Time end_time);
private:
bool CreateSchema();
// The SQL connection to database.
sql::Database db_;
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PERMISSION_AUDITING_DATABASE_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/permission_usage_session.h"
#include <tuple>
namespace permissions {
bool PermissionUsageSession::operator==(
const PermissionUsageSession& other) const {
return std::tie(origin, type, usage_start, usage_end, had_user_activation,
was_foreground, had_focus) ==
std::tie(other.origin, other.type, other.usage_start, other.usage_end,
other.had_user_activation, other.was_foreground,
other.had_focus);
}
bool PermissionUsageSession::operator!=(
const PermissionUsageSession& other) const {
return !(*this == other);
}
bool PermissionUsageSession::IsValid() const {
return !(origin.opaque() || usage_start.is_null() || usage_end.is_null() ||
usage_end < usage_start);
}
} // namespace permissions
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PERMISSIONS_PERMISSION_USAGE_SESSION_H_
#define COMPONENTS_PERMISSIONS_PERMISSION_USAGE_SESSION_H_
#include "base/time/time.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "url/origin.h"
namespace permissions {
// Stores information about a permission usage session, which is a continuous
// time interval during which some permission was used by some site. For
// instance, a usage session could be a time interval during which a site
// accessed the camera. {type, origin, usage_start} forms the primary of a
// session.
struct PermissionUsageSession {
// The `origin` accessing the capability. Must not be opaque.
url::Origin origin;
ContentSettingsType type;
// The time interval in which the capability was accessed, such that
// `usage_start` <= `usage_end`, and neither is null.
base::Time usage_start;
base::Time usage_end;
// Specifies if the permission usage started with the browsing context having
// transient user activation.
bool had_user_activation;
// Specifies if the permission usage started in the foreground.
bool was_foreground;
// Specifies if the requesting frame had focus at the time the permission.
// usage started.
bool had_focus;
bool operator==(const PermissionUsageSession& other) const;
bool operator!=(const PermissionUsageSession& other) const;
// Checks if the session satisfies the following constraints:
// 1) `origin` is not opaque;
// 2) `usage_start` and `usage_end` are not null;
// 3) `usage_start` <= `usage_end`.
bool IsValid() const;
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PERMISSION_USAGE_SESSION_H_
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