blob: 91cda0743db3c45dfa79368dda7d2086a7bd2b08 [file] [log] [blame]
// Copyright 2014 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/password_manager/core/browser/login_database.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/pickle.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/insecure_credentials_table.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_notes_table.h"
#include "components/password_manager/core/browser/password_store_change.h"
#include "components/password_manager/core/browser/psl_matching_helper.h"
#include "components/password_manager/core/browser/sql_table_builder.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/sync/base/features.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/re2/src/re2/re2.h"
#include "url/origin.h"
#include "url/url_constants.h"
using autofill::GaiaIdHash;
namespace password_manager {
// The current version number of the login database schema.
constexpr int kCurrentVersionNumber = 34;
// The oldest version of the schema such that a legacy Chrome client using that
// version can still read/write the current database.
constexpr int kCompatibleVersionNumber = 33;
base::Pickle SerializeAlternativeElementVector(
const AlternativeElementVector& vector) {
base::Pickle p;
for (const auto& element : vector) {
p.WriteString16(element.value);
p.WriteString16(element.name);
}
return p;
}
AlternativeElementVector DeserializeAlternativeElementVector(
const base::Pickle& p) {
AlternativeElementVector ret;
std::u16string value;
std::u16string field_name;
base::PickleIterator iterator(p);
while (iterator.ReadString16(&value)) {
bool name_success = iterator.ReadString16(&field_name);
DCHECK(name_success);
// TODO(crbug.com/1260336): migrate field identifier from name to
// field_signature + field_rank. Field names are not unique and have
// collisions on some forms. We don't store field_renderer_id in the
// storage as it has no guarantees to be stable across page reloads.
ret.emplace_back(AlternativeElement::Value(value),
autofill::FieldRendererId(),
AlternativeElement::Name(field_name));
}
return ret;
}
base::Pickle SerializeGaiaIdHashVector(const std::vector<GaiaIdHash>& hashes) {
base::Pickle p;
for (const auto& hash : hashes) {
p.WriteString(hash.ToBinary());
}
return p;
}
std::vector<GaiaIdHash> DeserializeGaiaIdHashVector(const base::Pickle& p) {
std::vector<GaiaIdHash> hashes;
std::string hash;
base::PickleIterator iterator(p);
while (iterator.ReadString(&hash)) {
hashes.push_back(GaiaIdHash::FromBinary(hash));
}
return hashes;
}
namespace {
// Common prefix for all histograms.
constexpr char kPasswordManager[] = "PasswordManager";
// A simple class for scoping a login database transaction. This does not
// support rollback since the login database doesn't either.
class ScopedTransaction {
public:
explicit ScopedTransaction(LoginDatabase* db) : db_(db) {
db_->BeginTransaction();
}
ScopedTransaction(const ScopedTransaction&) = delete;
ScopedTransaction& operator=(const ScopedTransaction&) = delete;
~ScopedTransaction() { db_->CommitTransaction(); }
private:
raw_ptr<LoginDatabase> db_;
};
// Convenience enum for interacting with SQL queries that use all the columns.
enum LoginDatabaseTableColumns {
COLUMN_ORIGIN_URL = 0,
COLUMN_ACTION_URL,
COLUMN_USERNAME_ELEMENT,
COLUMN_USERNAME_VALUE,
COLUMN_PASSWORD_ELEMENT,
COLUMN_PASSWORD_VALUE,
COLUMN_SUBMIT_ELEMENT,
COLUMN_SIGNON_REALM,
COLUMN_DATE_CREATED,
COLUMN_BLOCKLISTED_BY_USER,
COLUMN_SCHEME,
COLUMN_PASSWORD_TYPE,
COLUMN_TIMES_USED,
COLUMN_FORM_DATA,
COLUMN_DISPLAY_NAME,
COLUMN_ICON_URL,
COLUMN_FEDERATION_URL,
COLUMN_SKIP_ZERO_CLICK,
COLUMN_GENERATION_UPLOAD_STATUS,
COLUMN_POSSIBLE_USERNAME_PAIRS,
COLUMN_ID,
COLUMN_DATE_LAST_USED,
COLUMN_MOVING_BLOCKED_FOR,
COLUMN_DATE_PASSWORD_MODIFIED,
COLUMN_NUM // Keep this last.
};
enum class HistogramSize { SMALL, LARGE };
// An enum for UMA reporting. Add values to the end only.
enum DatabaseInitError {
INIT_OK = 0,
OPEN_FILE_ERROR = 1,
START_TRANSACTION_ERROR = 2,
META_TABLE_INIT_ERROR = 3,
INCOMPATIBLE_VERSION = 4,
INIT_LOGINS_ERROR = 5,
INIT_STATS_ERROR = 6,
MIGRATION_ERROR = 7,
COMMIT_TRANSACTION_ERROR = 8,
INIT_COMPROMISED_CREDENTIALS_ERROR = 9,
INIT_FIELD_INFO_ERROR = 10,
FOREIGN_KEY_ERROR = 11,
INIT_PASSWORD_NOTES_ERROR = 12,
DATABASE_INIT_ERROR_COUNT,
};
// Represents the encryption issues of the login database. Entries should
// not be renumbered and numeric values should never be reused. Always keep this
// enum in sync with the corresponding LoginDatabaseEncryptionStatus in
// enums.xml.
enum class LoginDatabaseEncryptionStatus {
kNoIssues = 0,
kInvalidEntriesInDatabase = 1,
kEncryptionUnavailable = 2,
kMaxValue = kEncryptionUnavailable,
};
// Struct to hold table builder for "logins", "insecure_credentials",
// "sync_entities_metadata", and "sync_model_metadata" tables.
struct SQLTableBuilders {
raw_ptr<SQLTableBuilder> logins;
raw_ptr<SQLTableBuilder> insecure_credentials;
raw_ptr<SQLTableBuilder> password_notes;
raw_ptr<SQLTableBuilder> sync_entities_metadata;
raw_ptr<SQLTableBuilder> sync_model_metadata;
};
base::span<const uint8_t> PickleToSpan(const base::Pickle& pickle) {
return base::make_span(reinterpret_cast<const uint8_t*>(pickle.data()),
pickle.size());
}
base::Pickle PickleFromSpan(base::span<const uint8_t> data) {
return base::Pickle(reinterpret_cast<const char*>(data.data()), data.size());
}
void BindAddStatement(const PasswordForm& form, sql::Statement* s) {
s->BindString(COLUMN_ORIGIN_URL, form.url.spec());
s->BindString(COLUMN_ACTION_URL, form.action.spec());
s->BindString16(COLUMN_USERNAME_ELEMENT, form.username_element);
s->BindString16(COLUMN_USERNAME_VALUE, form.username_value);
s->BindString16(COLUMN_PASSWORD_ELEMENT, form.password_element);
s->BindBlob(COLUMN_PASSWORD_VALUE, form.encrypted_password);
s->BindString16(COLUMN_SUBMIT_ELEMENT, form.submit_element);
s->BindString(COLUMN_SIGNON_REALM, form.signon_realm);
s->BindTime(COLUMN_DATE_CREATED, form.date_created);
s->BindInt(COLUMN_BLOCKLISTED_BY_USER, form.blocked_by_user);
s->BindInt(COLUMN_SCHEME, static_cast<int>(form.scheme));
s->BindInt(COLUMN_PASSWORD_TYPE, static_cast<int>(form.type));
s->BindInt(COLUMN_TIMES_USED, form.times_used_in_html_form);
base::Pickle form_data_pickle;
autofill::SerializeFormData(form.form_data, &form_data_pickle);
s->BindBlob(COLUMN_FORM_DATA, PickleToSpan(form_data_pickle));
s->BindString16(COLUMN_DISPLAY_NAME, form.display_name);
s->BindString(COLUMN_ICON_URL, form.icon_url.spec());
// An empty Origin serializes as "null" which would be strange to store here.
s->BindString(COLUMN_FEDERATION_URL,
form.federation_origin.opaque()
? std::string()
: form.federation_origin.Serialize());
s->BindInt(COLUMN_SKIP_ZERO_CLICK, form.skip_zero_click);
s->BindInt(COLUMN_GENERATION_UPLOAD_STATUS,
static_cast<int>(form.generation_upload_status));
base::Pickle usernames_pickle =
SerializeAlternativeElementVector(form.all_alternative_usernames);
s->BindBlob(COLUMN_POSSIBLE_USERNAME_PAIRS, PickleToSpan(usernames_pickle));
s->BindTime(COLUMN_DATE_LAST_USED, form.date_last_used);
base::Pickle moving_blocked_for_pickle =
SerializeGaiaIdHashVector(form.moving_blocked_for_list);
s->BindBlob(COLUMN_MOVING_BLOCKED_FOR,
PickleToSpan(moving_blocked_for_pickle));
s->BindTime(COLUMN_DATE_PASSWORD_MODIFIED, form.date_password_modified);
}
// Output parameter is the first one because of binding order.
void AddCallback(int* output_err, int err, sql::Statement* /*stmt*/) {
DCHECK(output_err);
*output_err = err;
if (err == 19 /*SQLITE_CONSTRAINT*/) {
DLOG(WARNING) << "LoginDatabase::AddLogin updated an existing form";
}
}
class ScopedDbErrorHandler {
public:
explicit ScopedDbErrorHandler(sql::Database* db) : db_(db) {
db_->set_error_callback(
base::BindRepeating(AddCallback, &sqlite_error_code_));
}
ScopedDbErrorHandler(const ScopedDbErrorHandler&) = delete;
ScopedDbErrorHandler& operator=(const ScopedDbErrorHandler&) = delete;
~ScopedDbErrorHandler() { db_->reset_error_callback(); }
void reset_error_code() { sqlite_error_code_ = 0; }
int get_error_code() const { return sqlite_error_code_; }
private:
raw_ptr<sql::Database> db_;
int sqlite_error_code_{0};
};
bool DoesMatchConstraints(const PasswordForm& form) {
if (!IsValidAndroidFacetURI(form.signon_realm) && form.url.is_empty()) {
DLOG(ERROR) << "Constraint violation: form.origin is empty";
return false;
}
if (form.signon_realm.empty()) {
DLOG(ERROR) << "Constraint violation: form.signon_realm is empty";
return false;
}
return true;
}
void LogDatabaseInitError(DatabaseInitError error) {
UMA_HISTOGRAM_ENUMERATION("PasswordManager.LoginDatabaseInit2", error,
DATABASE_INIT_ERROR_COUNT);
}
bool ClearAllSyncMetadata(sql::Database* db) {
sql::Statement s1(
db->GetCachedStatement(SQL_FROM_HERE, "DELETE FROM sync_model_metadata"));
sql::Statement s2(db->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM sync_entities_metadata"));
return s1.Run() && s2.Run();
}
// Seals the version of the given builders. This is method should be always used
// to seal versions of all builder to make sure all builders are at the same
// version.
void SealVersion(SQLTableBuilders builders, unsigned expected_version) {
unsigned logins_version = builders.logins->SealVersion();
DCHECK_EQ(expected_version, logins_version);
unsigned insecure_credentials_version =
builders.insecure_credentials->SealVersion();
DCHECK_EQ(expected_version, insecure_credentials_version);
unsigned notes_version = builders.password_notes->SealVersion();
DCHECK_EQ(expected_version, notes_version);
unsigned sync_entities_metadata_version =
builders.sync_entities_metadata->SealVersion();
DCHECK_EQ(expected_version, sync_entities_metadata_version);
unsigned sync_model_metadata_version =
builders.sync_model_metadata->SealVersion();
DCHECK_EQ(expected_version, sync_model_metadata_version);
}
// Teaches |builders| about the different DB schemes in different versions.
void InitializeBuilders(SQLTableBuilders builders) {
// Versions 0 and 1, which are the same.
builders.logins->AddColumnToUniqueKey("origin_url", "VARCHAR NOT NULL");
builders.logins->AddColumn("action_url", "VARCHAR");
builders.logins->AddColumnToUniqueKey("username_element", "VARCHAR");
builders.logins->AddColumnToUniqueKey("username_value", "VARCHAR");
builders.logins->AddColumnToUniqueKey("password_element", "VARCHAR");
builders.logins->AddColumn("password_value", "BLOB");
builders.logins->AddColumn("submit_element", "VARCHAR");
builders.logins->AddColumnToUniqueKey("signon_realm", "VARCHAR NOT NULL");
builders.logins->AddColumn("ssl_valid", "INTEGER NOT NULL");
builders.logins->AddColumn("preferred", "INTEGER NOT NULL");
builders.logins->AddColumn("date_created", "INTEGER NOT NULL");
builders.logins->AddColumn("blacklisted_by_user", "INTEGER NOT NULL");
builders.logins->AddColumn("scheme", "INTEGER NOT NULL");
builders.logins->AddIndex("logins_signon", {"signon_realm"});
SealVersion(builders, /*expected_version=*/0u);
SealVersion(builders, /*expected_version=*/1u);
// Version 2.
builders.logins->AddColumn("password_type", "INTEGER");
builders.logins->AddColumn("possible_usernames", "BLOB");
SealVersion(builders, /*expected_version=*/2u);
// Version 3.
builders.logins->AddColumn("times_used", "INTEGER");
SealVersion(builders, /*expected_version=*/3u);
// Version 4.
builders.logins->AddColumn("form_data", "BLOB");
SealVersion(builders, /*expected_version=*/4u);
// Version 5.
builders.logins->AddColumn("use_additional_auth", "INTEGER");
SealVersion(builders, /*expected_version=*/5u);
// Version 6.
builders.logins->AddColumn("date_synced", "INTEGER");
SealVersion(builders, /*expected_version=*/6u);
// Version 7.
builders.logins->AddColumn("display_name", "VARCHAR");
builders.logins->AddColumn("avatar_url", "VARCHAR");
builders.logins->AddColumn("federation_url", "VARCHAR");
builders.logins->AddColumn("is_zero_click", "INTEGER");
SealVersion(builders, /*expected_version=*/7u);
// Version 8.
SealVersion(builders, /*expected_version=*/8u);
// Version 9.
SealVersion(builders, /*expected_version=*/9u);
// Version 10.
builders.logins->DropColumn("use_additional_auth");
SealVersion(builders, /*expected_version=*/10u);
// Version 11.
builders.logins->RenameColumn("is_zero_click", "skip_zero_click");
SealVersion(builders, /*expected_version=*/11u);
// Version 12.
builders.logins->AddColumn("generation_upload_status", "INTEGER");
SealVersion(builders, /*expected_version=*/12u);
// Version 13.
SealVersion(builders, /*expected_version=*/13u);
// Version 14.
builders.logins->RenameColumn("avatar_url", "icon_url");
SealVersion(builders, /*expected_version=*/14u);
// Version 15.
SealVersion(builders, /*expected_version=*/15u);
// Version 16.
SealVersion(builders, /*expected_version=*/16u);
// Version 17.
SealVersion(builders, /*expected_version=*/17u);
// Version 18.
builders.logins->DropColumn("ssl_valid");
SealVersion(builders, /*expected_version=*/18u);
// Version 19.
builders.logins->DropColumn("possible_usernames");
builders.logins->AddColumn("possible_username_pairs", "BLOB");
SealVersion(builders, /*expected_version=*/19u);
// Version 20.
builders.logins->AddPrimaryKeyColumn("id");
SealVersion(builders, /*expected_version=*/20u);
// Version 21.
builders.sync_entities_metadata->AddPrimaryKeyColumn("storage_key");
builders.sync_entities_metadata->AddColumn("metadata", "VARCHAR NOT NULL");
builders.sync_model_metadata->AddPrimaryKeyColumn("id");
builders.sync_model_metadata->AddColumn("model_metadata", "VARCHAR NOT NULL");
SealVersion(builders, /*expected_version=*/21u);
// Version 22. Changes in Sync metadata encryption.
SealVersion(builders, /*expected_version=*/22u);
// Version 23. Version 22 could have some corruption in Sync metadata and
// hence we are migrating users on it by clearing their metadata to make Sync
// start clean from scratch.
SealVersion(builders, /*expected_version=*/23u);
// Version 24. Version 23 could have some corruption in Sync metadata and
// hence we are migrating users on it by clearing their metadata to make Sync
// start clean from scratch.
SealVersion(builders, /*expected_version=*/24u);
// Version 25. Introduce date_last_used column to replace the preferred
// column. MigrateDatabase() will take care of migrating the data.
builders.logins->AddColumn("date_last_used", "INTEGER NOT NULL DEFAULT 0");
SealVersion(builders, /*expected_version=*/25u);
// Version 26 is the first version where the id is AUTOINCREMENT.
SealVersion(builders, /*expected_version=*/26u);
// Version 27. Add the moving_blocked_for column to contain serialized list of
// gaia id hashes for users that prefer not to move this credential to their
// account store.
builders.logins->AddColumn("moving_blocked_for", "BLOB");
SealVersion(builders, /*expected_version=*/27u);
// Version 28.
builders.logins->DropColumn("preferred");
SealVersion(builders, /*expected_version=*/28u);
// Version 29.
// Migrate the compromised credentials from "compromised_credentials" to the
// new table "insecure credentials" with a foreign key to the logins table.
builders.insecure_credentials->AddColumnToUniqueKey(
"parent_id", "INTEGER", "logins", "foreign_key_index");
builders.insecure_credentials->AddColumnToUniqueKey("insecurity_type",
"INTEGER NOT NULL");
builders.insecure_credentials->AddColumn("create_time", "INTEGER NOT NULL");
builders.insecure_credentials->AddColumn("is_muted",
"INTEGER NOT NULL DEFAULT 0");
SealVersion(builders, /*expected_version=*/29u);
// Version 30. Introduce 'date_password_modified' column.
builders.logins->AddColumn("date_password_modified",
"INTEGER NOT NULL DEFAULT 0");
SealVersion(builders, /*expected_version=*/30u);
// Version 31. Dropped 'date_synced' column.
builders.logins->DropColumn("date_synced");
SealVersion(builders, /*expected_version=*/31u);
// Version 32. Set timestamps of uninitialized timestamps in
// 'insecure_credentials' table.
SealVersion(builders, /*expected_version=*/32u);
// Version 33. Introduce password notes table.
builders.password_notes->AddPrimaryKeyColumn("id");
builders.password_notes->AddColumnToUniqueKey(
"parent_id", "INTEGER NOT NULL", "logins", "foreign_key_index_notes");
builders.password_notes->AddColumnToUniqueKey("key", "VARCHAR NOT NULL");
builders.password_notes->AddColumn("value", "BLOB");
builders.password_notes->AddColumn("date_created", "INTEGER NOT NULL");
builders.password_notes->AddColumn("confidential", "INTEGER");
SealVersion(builders, /*expected_version=*/33u);
// Version 34. Add `trigger_notification_from_backend` column to the
// `insecure_credentials_table`.
builders.insecure_credentials->AddColumn("trigger_notification_from_backend",
"INTEGER NOT NULL DEFAULT 0");
SealVersion(builders, /*expected_version=*/34u);
DCHECK_EQ(static_cast<size_t>(COLUMN_NUM), builders.logins->NumberOfColumns())
<< "Adjust LoginDatabaseTableColumns if you change column definitions "
"here.";
}
// Callback called upon each migration step of the logins table. It's used to
// inject custom schema migration logic not covered by the generic
// SQLTableBuilder migration. |new_version| indicates how far
// SQLTableBuilder is in the migration process.
bool LoginsTablePostMigrationStepCallback(sql::Database* db,
unsigned new_version) {
// In version 26, the primary key of the logins table became an
// AUTOINCREMENT field. Since SQLite doesn't allow changing the column type,
// the only way is to actually create a temp table with the primary key
// properly set as an AUTOINCREMENT field, and move the data there. The code
// has been adjusted such that newly created tables have the primary key
// properly set as AUTOINCREMENT.
if (new_version == 26) {
// This statement creates the logins database similar to version 26 with
// the primary key column set to AUTOINCREMENT.
const char temp_table_create_statement_version_26[] =
"CREATE TABLE logins_temp (origin_url VARCHAR NOT NULL,action_url "
"VARCHAR,username_element VARCHAR,username_value "
"VARCHAR,password_element VARCHAR,password_value BLOB,submit_element "
"VARCHAR,signon_realm VARCHAR NOT NULL,preferred INTEGER NOT "
"NULL,date_created INTEGER NOT NULL,blacklisted_by_user INTEGER NOT "
"NULL,scheme INTEGER NOT NULL,password_type INTEGER,times_used "
"INTEGER,form_data BLOB,date_synced INTEGER,display_name "
"VARCHAR,icon_url VARCHAR,federation_url VARCHAR,skip_zero_click "
"INTEGER,generation_upload_status INTEGER,possible_username_pairs "
"BLOB,id INTEGER PRIMARY KEY AUTOINCREMENT,date_last_used "
"INTEGER,UNIQUE (origin_url, username_element, username_value, "
"password_element, signon_realm))";
const char move_data_statement[] =
"INSERT INTO logins_temp SELECT * from logins";
const char drop_table_statement[] = "DROP TABLE logins";
const char rename_table_statement[] =
"ALTER TABLE logins_temp RENAME TO logins";
sql::Transaction transaction(db);
if (!(transaction.Begin() &&
db->Execute(temp_table_create_statement_version_26) &&
db->Execute(move_data_statement) &&
db->Execute(drop_table_statement) &&
db->Execute(rename_table_statement) && transaction.Commit())) {
return false;
}
}
return true;
}
bool InsecureCredentialsPostMigrationStepCallback(
SQLTableBuilder* insecure_credentials_builder,
sql::Database* db,
unsigned new_version) {
if (new_version == 29) {
std::string create_table_statement =
"CREATE TABLE insecure_credentials ("
"parent_id INTEGER REFERENCES logins ON UPDATE CASCADE ON DELETE "
"CASCADE DEFERRABLE INITIALLY DEFERRED, "
"insecurity_type INTEGER NOT NULL, "
"create_time INTEGER NOT NULL, "
"is_muted INTEGER NOT NULL DEFAULT 0, "
"UNIQUE (parent_id, insecurity_type))";
std::string create_index_statement =
"CREATE INDEX foreign_key_index ON insecure_credentials "
"(parent_id)";
sql::Transaction creation_transaction(db);
bool table_creation_success = creation_transaction.Begin() &&
db->Execute(create_table_statement.c_str()) &&
db->Execute(create_index_statement.c_str()) &&
creation_transaction.Commit();
if (!table_creation_success) {
LOG(ERROR) << "Failed to create the 'insecure_credentials' table";
LogDatabaseInitError(INIT_COMPROMISED_CREDENTIALS_ERROR);
return false;
}
if (!db->DoesTableExist("compromised_credentials")) {
return true;
}
// The 'compromised_credentials' table must be migrated to
// 'insecure_credentials'.
constexpr char select_compromised[] =
"SELECT "
"id, create_time, compromise_type FROM compromised_credentials "
"INNER JOIN logins ON "
"compromised_credentials.url = logins.signon_realm AND "
"compromised_credentials.username = logins.username_value";
const std::string insert_statement = base::StringPrintf(
"INSERT OR REPLACE INTO %s "
"(parent_id, create_time, insecurity_type) %s",
InsecureCredentialsTable::kTableName, select_compromised);
constexpr char drop_table_statement[] =
"DROP TABLE compromised_credentials";
sql::Transaction transaction(db);
if (!(transaction.Begin() && db->Execute(insert_statement.c_str()) &&
db->Execute(drop_table_statement) && transaction.Commit())) {
return false;
}
}
return true;
}
bool PasswordNotesPostMigrationStepCallback(
SQLTableBuilder* password_notes_builder,
sql::Database* db,
unsigned new_version) {
if (new_version == 33) {
std::string create_table_statement =
"CREATE TABLE password_notes ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"parent_id INTEGER NOT NULL REFERENCES logins ON UPDATE CASCADE ON "
"DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "
"key VARCHAR NOT NULL, "
"value BLOB, "
"date_created INTEGER NOT NULL, "
"confidential INTEGER, "
"UNIQUE (parent_id, key))";
std::string create_index_statement =
"CREATE INDEX foreign_key_index_notes ON password_notes (parent_id)";
sql::Transaction transaction(db);
bool table_creation_success =
transaction.Begin() && db->Execute(create_table_statement.c_str()) &&
db->Execute(create_index_statement.c_str()) && transaction.Commit();
if (!table_creation_success) {
LOG(ERROR) << "Failed to create the 'password_notes' table";
LogDatabaseInitError(INIT_PASSWORD_NOTES_ERROR);
return false;
}
}
return true;
}
// Call this after having called InitializeBuilders(), to migrate the database
// from the current version to kCurrentVersionNumber.
bool MigrateDatabase(unsigned current_version,
SQLTableBuilders builders,
sql::Database* db) {
if (!builders.logins->MigrateFrom(
current_version, db,
base::BindRepeating(&LoginsTablePostMigrationStepCallback))) {
return false;
}
if (!builders.insecure_credentials->MigrateFrom(
current_version, db,
base::BindRepeating(&InsecureCredentialsPostMigrationStepCallback,
builders.insecure_credentials))) {
return false;
}
if (!builders.password_notes->MigrateFrom(
current_version, db,
base::BindRepeating(&PasswordNotesPostMigrationStepCallback,
builders.password_notes))) {
return false;
}
if (!builders.sync_entities_metadata->MigrateFrom(current_version, db)) {
return false;
}
if (!builders.sync_model_metadata->MigrateFrom(current_version, db)) {
return false;
}
// Data changes, not covered by the schema migration above.
if (current_version <= 8) {
sql::Statement fix_time_format;
fix_time_format.Assign(db->GetUniqueStatement(
"UPDATE logins SET date_created = (date_created * ?) + ?"));
fix_time_format.BindInt64(0, base::Time::kMicrosecondsPerSecond);
fix_time_format.BindInt64(1, base::Time::kTimeTToMicrosecondsOffset);
if (!fix_time_format.Run()) {
return false;
}
}
if (current_version <= 16) {
sql::Statement reset_zero_click;
reset_zero_click.Assign(
db->GetUniqueStatement("UPDATE logins SET skip_zero_click = 1"));
if (!reset_zero_click.Run()) {
return false;
}
}
// Sync Metadata tables have been introduced in version 21. It is enough to
// drop all data because Sync would populate the tables properly at startup.
if (current_version >= 21 && current_version < 26) {
if (!ClearAllSyncMetadata(db)) {
return false;
}
}
// Set the default value for 'date_password_modified'.
if (current_version < 30) {
sql::Statement set_date_password_modified;
set_date_password_modified.Assign(db->GetUniqueStatement(
"UPDATE logins SET date_password_modified = date_created"));
if (!set_date_password_modified.Run()) {
return false;
}
}
// Set the create_time value when uninitialized for 'insecure_credentials'.
if (current_version >= 29 && current_version < 32) {
sql::Statement set_timestamp;
set_timestamp.Assign(
db->GetUniqueStatement("UPDATE insecure_credentials SET create_time = "
"? WHERE create_time = 0"));
set_timestamp.BindTime(0, base::Time::Now());
if (!set_timestamp.Run()) {
return false;
}
}
return true;
}
// Because of https://crbug.com/295851, some early version numbers might be
// wrong. This function detects that and fixes the version.
bool FixVersionIfNeeded(sql::Database* db, int* current_version) {
if (*current_version == 1) {
int extra_columns = 0;
if (db->DoesColumnExist("logins", "password_type")) {
++extra_columns;
}
if (db->DoesColumnExist("logins", "possible_usernames")) {
++extra_columns;
}
if (extra_columns == 2) {
*current_version = 2;
} else if (extra_columns == 1) {
// If this is https://crbug.com/295851 then either both columns exist
// or none.
return false;
}
}
if (*current_version == 2) {
if (db->DoesColumnExist("logins", "times_used")) {
*current_version = 3;
}
}
if (*current_version == 3) {
if (db->DoesColumnExist("logins", "form_data")) {
*current_version = 4;
}
}
// "date_last_used" columns has been introduced in version 25. if it exists,
// the version should be at least 25. This has been added to address this bug
// (crbug.com/1020320).
if (*current_version < 25) {
if (db->DoesColumnExist("logins", "date_last_used")) {
*current_version = 25;
}
}
return true;
}
// Generates the string "(?,?,...,?)" with |count| repetitions of "?".
std::string GeneratePlaceholders(size_t count) {
std::string result(2 * count + 1, ',');
result.front() = '(';
result.back() = ')';
for (size_t i = 1; i < 2 * count + 1; i += 2) {
result[i] = '?';
}
return result;
}
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
// Fills |form| with necessary data required to be removed from the database
// and returns it.
PasswordForm GetFormForRemoval(sql::Statement& statement) {
PasswordForm form;
form.url = GURL(statement.ColumnString(COLUMN_ORIGIN_URL));
form.username_element = statement.ColumnString16(COLUMN_USERNAME_ELEMENT);
form.username_value = statement.ColumnString16(COLUMN_USERNAME_VALUE);
form.password_element = statement.ColumnString16(COLUMN_PASSWORD_ELEMENT);
form.signon_realm = statement.ColumnString(COLUMN_SIGNON_REALM);
return form;
}
#endif
// Whether we should try to return the decryptable passwords while the
// encryption service fails for some passwords.
bool ShouldReturnPartialPasswords() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
return base::FeatureList::IsEnabled(features::kSkipUndecryptablePasswords);
#else
return false;
#endif
}
} // namespace
struct LoginDatabase::PrimaryKeyAndPassword {
int primary_key;
std::string encrypted_password;
std::u16string decrypted_password;
};
LoginDatabase::LoginDatabase(const base::FilePath& db_path,
IsAccountStore is_account_store)
: db_path_(db_path),
is_account_store_(is_account_store),
// Set options for a small, private database (based on WebDatabase).
db_({.exclusive_locking = true, .page_size = 2048, .cache_size = 32}) {}
LoginDatabase::~LoginDatabase() = default;
bool LoginDatabase::Init() {
TRACE_EVENT0("passwords", "LoginDatabase::Init");
db_.set_histogram_tag("Passwords");
if (!db_.Open(db_path_)) {
LogDatabaseInitError(OPEN_FILE_ERROR);
LOG(ERROR) << "Unable to open the password store database.";
return false;
}
if (!db_.Execute("PRAGMA foreign_keys = ON")) {
LogDatabaseInitError(FOREIGN_KEY_ERROR);
LOG(ERROR) << "Unable to activate foreign keys.";
return false;
}
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
LogDatabaseInitError(START_TRANSACTION_ERROR);
LOG(ERROR) << "Unable to start a transaction.";
db_.Close();
return false;
}
// Check the database version.
if (!meta_table_.Init(&db_, kCurrentVersionNumber,
kCompatibleVersionNumber)) {
LogDatabaseInitError(META_TABLE_INIT_ERROR);
LOG(ERROR) << "Unable to create the meta table.";
transaction.Rollback();
db_.Close();
return false;
}
if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
LogDatabaseInitError(INCOMPATIBLE_VERSION);
LOG(ERROR) << "Password store database is too new, kCurrentVersionNumber="
<< kCurrentVersionNumber << ", GetCompatibleVersionNumber="
<< meta_table_.GetCompatibleVersionNumber();
transaction.Rollback();
db_.Close();
return false;
}
SQLTableBuilder logins_builder("logins");
SQLTableBuilder insecure_credentials_builder(
InsecureCredentialsTable::kTableName);
SQLTableBuilder password_notes_builder(PasswordNotesTable::kTableName);
SQLTableBuilder sync_entities_metadata_builder("sync_entities_metadata");
SQLTableBuilder sync_model_metadata_builder("sync_model_metadata");
SQLTableBuilders builders = {
&logins_builder, &insecure_credentials_builder, &password_notes_builder,
&sync_entities_metadata_builder, &sync_model_metadata_builder};
InitializeBuilders(builders);
InitializeStatementStrings(logins_builder);
if (!logins_builder.CreateTable(&db_)) {
LOG(ERROR) << "Failed to create the 'logins' table";
transaction.Rollback();
db_.Close();
return false;
}
if (!sync_entities_metadata_builder.CreateTable(&db_)) {
LOG(ERROR) << "Failed to create the 'sync_entities_metadata' table";
transaction.Rollback();
db_.Close();
return false;
}
if (!sync_model_metadata_builder.CreateTable(&db_)) {
LOG(ERROR) << "Failed to create the 'sync_model_metadata' table";
transaction.Rollback();
db_.Close();
return false;
}
stats_table_.Init(&db_);
insecure_credentials_table_.Init(&db_);
password_notes_table_.Init(&db_);
field_info_table_.Init(&db_);
int current_version = meta_table_.GetVersionNumber();
bool migration_success = FixVersionIfNeeded(&db_, &current_version);
// If the file on disk is an older database version, bring it up to date.
if (migration_success && current_version < kCurrentVersionNumber) {
migration_success = MigrateDatabase(
base::checked_cast<unsigned>(current_version), builders, &db_);
}
// Enforce that 'insecure_credentials' is created only after the 'logins'
// table was created and migrated to the latest version. This guarantees the
// existence of the `id` column in the `logins` table which was introduced
// only in version 20 and is referenced by `insecure_credentials` table. The
// table will be created here for a new profile. For an old profile it's
// created in MigrateDatabase above.
if (migration_success && !insecure_credentials_builder.CreateTable(&db_)) {
LOG(ERROR) << "Failed to create the 'insecure_credentials' table";
LogDatabaseInitError(INIT_COMPROMISED_CREDENTIALS_ERROR);
transaction.Rollback();
db_.Close();
return false;
}
// Enforce that 'password_notes' is created only after the 'logins' table was
// created and migrated to the latest version. This guarantees the existence
// of the `id` column in the `logins` table which was introduced only in
// version 20 and is referenced by 'password_notes' table. The table will be
// created here for a new profile. For an old profile it's created in
// MigrateDatabase above.
if (migration_success && !password_notes_builder.CreateTable(&db_)) {
LOG(ERROR) << "Failed to create the 'password_notes' table";
LogDatabaseInitError(INIT_PASSWORD_NOTES_ERROR);
transaction.Rollback();
db_.Close();
return false;
}
if (migration_success && current_version <= 15) {
migration_success = stats_table_.MigrateToVersion(16);
}
if (migration_success && current_version < kCurrentVersionNumber) {
// |migration_success| could be true when no logins have been actually
// migrated. We should protect against downgrading the database version in
// such case. Update the database version only if a migration took place.
migration_success =
meta_table_.SetCompatibleVersionNumber(kCompatibleVersionNumber) &&
meta_table_.SetVersionNumber(kCurrentVersionNumber);
}
if (!migration_success) {
LogDatabaseInitError(MIGRATION_ERROR);
LOG(ERROR) << "Unable to migrate database from "
<< meta_table_.GetVersionNumber() << " to "
<< kCurrentVersionNumber;
transaction.Rollback();
db_.Close();
return false;
}
if (!stats_table_.CreateTableIfNecessary()) {
LogDatabaseInitError(INIT_STATS_ERROR);
LOG(ERROR) << "Unable to create the stats table.";
transaction.Rollback();
db_.Close();
return false;
}
// The table "leaked_credentials" was previously created without a flag.
// The table is now renamed to "compromised_credentials" and also includes
// a new column so the old table needs to be deleted.
if (db_.DoesTableExist("leaked_credentials")) {
if (!db_.Execute("DROP TABLE leaked_credentials")) {
LOG(ERROR) << "Unable to create the stats table.";
transaction.Rollback();
db_.Close();
return false;
}
}
if (!field_info_table_.CreateTableIfNecessary()) {
LogDatabaseInitError(INIT_FIELD_INFO_ERROR);
LOG(ERROR) << "Unable to create the field info table.";
transaction.Rollback();
db_.Close();
return false;
}
if (!transaction.Commit()) {
LogDatabaseInitError(COMMIT_TRANSACTION_ERROR);
LOG(ERROR) << "Unable to commit a transaction.";
db_.Close();
return false;
}
LogDatabaseInitError(INIT_OK);
return true;
}
void LoginDatabase::ReportBubbleSuppressionMetrics() {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
base::UmaHistogramCustomCounts(
"PasswordManager.BubbleSuppression.AccountsInStatisticsTable2",
stats_table_.GetNumAccounts(), 0, 1000, 100);
#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
}
void LoginDatabase::ReportInaccessiblePasswordsMetrics() {
sql::Statement get_passwords_statement(
db_.GetUniqueStatement("SELECT password_value "
"FROM logins WHERE blacklisted_by_user = 0"));
size_t failed_encryption = 0;
while (get_passwords_statement.Step()) {
std::u16string decrypted_password;
if (DecryptedString(get_passwords_statement.ColumnString(0),
&decrypted_password) != ENCRYPTION_RESULT_SUCCESS) {
++failed_encryption;
}
}
base::StringPiece suffix_for_store =
is_account_store_.value() ? ".AccountStore" : ".ProfileStore";
base::UmaHistogramCounts100(base::StrCat({kPasswordManager, suffix_for_store,
".InaccessiblePasswords3"}),
failed_encryption);
LoginDatabaseEncryptionStatus encryption_status =
LoginDatabaseEncryptionStatus::kNoIssues;
if (!OSCrypt::IsEncryptionAvailable()) {
encryption_status = LoginDatabaseEncryptionStatus::kEncryptionUnavailable;
} else if (failed_encryption > 0) {
encryption_status =
LoginDatabaseEncryptionStatus::kInvalidEntriesInDatabase;
}
base::UmaHistogramEnumeration(
base::StrCat({kPasswordManager, suffix_for_store,
".LoginDatabaseEncryptionStatus"}),
encryption_status);
}
void LoginDatabase::ReportMetrics() {
TRACE_EVENT0("passwords", "LoginDatabase::ReportMetrics");
ReportInaccessiblePasswordsMetrics();
// BubbleSuppression fields aren't used in the account store.
if (is_account_store_.value()) {
return;
}
ReportBubbleSuppressionMetrics();
}
PasswordStoreChangeList LoginDatabase::AddLogin(const PasswordForm& form,
AddCredentialError* error) {
TRACE_EVENT0("passwords", "LoginDatabase::AddLogin");
if (error) {
*error = AddCredentialError::kNone;
}
if (!DoesMatchConstraints(form)) {
if (error) {
*error = AddCredentialError::kConstraintViolation;
}
return PasswordStoreChangeList();
}
// [iOS] Passwords created in Credential Provider Extension (CPE) are already
// encrypted in the keychain and there is no need to do the process again.
// However, the password needs to be decryped instead so the actual password
// syncs correctly.
bool has_encrypted_password =
!form.encrypted_password.empty() && form.password_value.empty();
PasswordForm form_with_encrypted_password = form;
if (has_encrypted_password) {
std::u16string decrypted_password;
if (DecryptedString(form.encrypted_password, &decrypted_password) !=
ENCRYPTION_RESULT_SUCCESS) {
if (error) {
*error = AddCredentialError::kEncryptionServiceFailure;
}
return PasswordStoreChangeList();
}
form_with_encrypted_password.password_value = decrypted_password;
} else {
std::string encrypted_password;
if (EncryptedString(form.password_value, &encrypted_password) !=
ENCRYPTION_RESULT_SUCCESS) {
if (error) {
*error = AddCredentialError::kEncryptionServiceFailure;
}
return PasswordStoreChangeList();
}
form_with_encrypted_password.encrypted_password = encrypted_password;
}
PasswordStoreChangeList list;
DCHECK(!add_statement_.empty());
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, add_statement_.c_str()));
BindAddStatement(form_with_encrypted_password, &s);
ScopedDbErrorHandler db_error_handler(&db_);
const bool success = s.Run();
if (success) {
// If success, the row never existed so password was not changed.
FillFormInStore(&form_with_encrypted_password);
FormPrimaryKey primary_key = FormPrimaryKey(db_.GetLastInsertRowId());
form_with_encrypted_password.primary_key = primary_key;
if (!form_with_encrypted_password.password_issues.empty()) {
UpdateInsecureCredentials(primary_key,
form_with_encrypted_password.password_issues);
}
UpdatePasswordNotes(primary_key, form_with_encrypted_password.notes);
list.emplace_back(PasswordStoreChange::ADD,
std::move(form_with_encrypted_password),
/*password_changed=*/false);
return list;
}
// Repeat the same statement but with REPLACE semantic.
db_error_handler.reset_error_code();
DCHECK(!add_replace_statement_.empty());
PrimaryKeyAndPassword old_primary_key_password =
GetPrimaryKeyAndPassword(form);
bool password_changed =
form.password_value != old_primary_key_password.decrypted_password;
s.Assign(
db_.GetCachedStatement(SQL_FROM_HERE, add_replace_statement_.c_str()));
BindAddStatement(form_with_encrypted_password, &s);
if (s.Run()) {
PasswordForm removed_form = form;
FillFormInStore(&removed_form);
removed_form.primary_key =
FormPrimaryKey(old_primary_key_password.primary_key);
list.emplace_back(PasswordStoreChange::REMOVE, removed_form);
FillFormInStore(&form_with_encrypted_password);
FormPrimaryKey primary_key = FormPrimaryKey(db_.GetLastInsertRowId());
form_with_encrypted_password.primary_key = primary_key;
InsecureCredentialsChanged insecure_changed(false);
if (!form_with_encrypted_password.password_issues.empty()) {
insecure_changed = UpdateInsecureCredentials(
primary_key, form_with_encrypted_password.password_issues);
}
UpdatePasswordNotes(primary_key, form_with_encrypted_password.notes);
list.emplace_back(PasswordStoreChange::ADD,
std::move(form_with_encrypted_password), password_changed,
insecure_changed);
} else if (error) {
if (db_error_handler.get_error_code() == 19 /*SQLITE_CONSTRAINT*/) {
*error = AddCredentialError::kConstraintViolation;
} else {
*error = AddCredentialError::kDbError;
}
}
return list;
}
PasswordStoreChangeList LoginDatabase::UpdateLogin(
const PasswordForm& form,
UpdateCredentialError* error) {
TRACE_EVENT0("passwords", "LoginDatabase::UpdateLogin");
if (error) {
*error = UpdateCredentialError::kNone;
}
std::string encrypted_password;
if (EncryptedString(form.password_value, &encrypted_password) !=
ENCRYPTION_RESULT_SUCCESS) {
if (error) {
*error = UpdateCredentialError::kEncryptionServiceFailure;
}
return PasswordStoreChangeList();
}
const PrimaryKeyAndPassword old_primary_key_password =
GetPrimaryKeyAndPassword(form);
#if BUILDFLAG(IS_IOS)
DeleteEncryptedPasswordFromKeychain(
old_primary_key_password.encrypted_password);
#endif
DCHECK(!update_statement_.empty());
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, update_statement_.c_str()));
int next_param = 0;
s.BindString(next_param++, form.action.spec());
s.BindBlob(next_param++, encrypted_password);
s.BindString16(next_param++, form.submit_element);
s.BindTime(next_param++, form.date_created);
s.BindInt(next_param++, form.blocked_by_user);
s.BindInt(next_param++, static_cast<int>(form.scheme));
s.BindInt(next_param++, static_cast<int>(form.type));
s.BindInt(next_param++, form.times_used_in_html_form);
base::Pickle form_data_pickle;
autofill::SerializeFormData(form.form_data, &form_data_pickle);
s.BindBlob(next_param++, PickleToSpan(form_data_pickle));
s.BindString16(next_param++, form.display_name);
s.BindString(next_param++, form.icon_url.spec());
// An empty Origin serializes as "null" which would be strange to store here.
s.BindString(next_param++, form.federation_origin.opaque()
? std::string()
: form.federation_origin.Serialize());
s.BindInt(next_param++, form.skip_zero_click);
s.BindInt(next_param++, static_cast<int>(form.generation_upload_status));
base::Pickle username_pickle =
SerializeAlternativeElementVector(form.all_alternative_usernames);
s.BindBlob(next_param++, PickleToSpan(username_pickle));
s.BindTime(next_param++, form.date_last_used);
base::Pickle moving_blocked_for_pickle =
SerializeGaiaIdHashVector(form.moving_blocked_for_list);
s.BindBlob(next_param++, PickleToSpan(moving_blocked_for_pickle));
s.BindTime(next_param++, form.date_password_modified);
// NOTE: Add new fields here unless the field is a part of the unique key.
// If so, add new field below.
// WHERE starts here.
s.BindString(next_param++, form.url.spec());
s.BindString16(next_param++, form.username_element);
s.BindString16(next_param++, form.username_value);
s.BindString16(next_param++, form.password_element);
s.BindString(next_param++, form.signon_realm);
// NOTE: Add new fields here only if the field is a part of the unique key.
// Otherwise, add the field above "WHERE starts here" comment.
if (!s.Run()) {
if (error) {
*error = UpdateCredentialError::kDbError;
}
return PasswordStoreChangeList();
}
// If no rows changed due to this command, it means that there was no row to
// update, so there is no point trying to update insecure credentials data or
// the notes table.
if (db_.GetLastChangeCount() == 0) {
if (error) {
*error = UpdateCredentialError::kNoUpdatedRecords;
}
return PasswordStoreChangeList();
}
bool password_changed =
form.password_value != old_primary_key_password.decrypted_password;
PasswordForm form_with_encrypted_password = form;
form_with_encrypted_password.encrypted_password = encrypted_password;
// TODO(crbug.com/1223022): It should be the responsibility of the caller to
// set `password_issues` to empty.
// Remove this once all `UpdateLogin` calls have been checked.
if (password_changed) {
form_with_encrypted_password.password_issues =
base::flat_map<InsecureType, InsecurityMetadata>();
}
InsecureCredentialsChanged insecure_changed = UpdateInsecureCredentials(
FormPrimaryKey(old_primary_key_password.primary_key),
form_with_encrypted_password.password_issues);
UpdatePasswordNotes(FormPrimaryKey(old_primary_key_password.primary_key),
form.notes);
PasswordStoreChangeList list;
FillFormInStore(&form_with_encrypted_password);
form_with_encrypted_password.primary_key =
FormPrimaryKey(old_primary_key_password.primary_key);
list.emplace_back(PasswordStoreChange::UPDATE,
std::move(form_with_encrypted_password), password_changed,
insecure_changed);
return list;
}
bool LoginDatabase::RemoveLogin(const PasswordForm& form,
PasswordStoreChangeList* changes) {
TRACE_EVENT0("passwords", "LoginDatabase::RemoveLogin");
if (changes) {
changes->clear();
}
const PrimaryKeyAndPassword old_primary_key_password =
GetPrimaryKeyAndPassword(form);
#if BUILDFLAG(IS_IOS)
DeleteEncryptedPasswordFromKeychain(
old_primary_key_password.encrypted_password);
#endif
// Remove a login by UNIQUE-constrained fields.
DCHECK(!delete_statement_.empty());
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, delete_statement_.c_str()));
s.BindString(0, form.url.spec());
s.BindString16(1, form.username_element);
s.BindString16(2, form.username_value);
s.BindString16(3, form.password_element);
s.BindString(4, form.signon_realm);
if (!s.Run() || db_.GetLastChangeCount() == 0) {
return false;
}
if (changes) {
PasswordForm removed_form = form;
FillFormInStore(&removed_form);
removed_form.primary_key =
FormPrimaryKey(old_primary_key_password.primary_key);
changes->emplace_back(PasswordStoreChange::REMOVE, removed_form,
/*password_changed=*/true);
}
return true;
}
bool LoginDatabase::RemoveLoginByPrimaryKey(FormPrimaryKey primary_key,
PasswordStoreChangeList* changes) {
TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginByPrimaryKey");
PasswordForm form;
if (changes) {
changes->clear();
sql::Statement s1(db_.GetCachedStatement(
SQL_FROM_HERE, "SELECT * FROM logins WHERE id = ?"));
s1.BindInt(0, primary_key.value());
if (!s1.Step()) {
return false;
}
EncryptionResult result = InitPasswordFormFromStatement(
s1, /*decrypt_and_fill_password_value=*/false, &form);
DCHECK_EQ(result, ENCRYPTION_RESULT_SUCCESS);
DCHECK_EQ(form.primary_key.value(), primary_key);
}
#if BUILDFLAG(IS_IOS)
DeleteEncryptedPasswordById(primary_key.value());
#endif
DCHECK(!delete_by_id_statement_.empty());
sql::Statement s2(
db_.GetCachedStatement(SQL_FROM_HERE, delete_by_id_statement_.c_str()));
s2.BindInt(0, primary_key.value());
if (!s2.Run() || db_.GetLastChangeCount() == 0) {
return false;
}
if (changes) {
FillFormInStore(&form);
changes->emplace_back(PasswordStoreChange::REMOVE, std::move(form),
/*password_changed=*/true);
}
return true;
}
bool LoginDatabase::RemoveLoginsCreatedBetween(
base::Time delete_begin,
base::Time delete_end,
PasswordStoreChangeList* changes) {
TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginsCreatedBetween");
if (changes) {
changes->clear();
}
std::vector<std::unique_ptr<PasswordForm>> forms;
ScopedTransaction transaction(this);
if (!GetLoginsCreatedBetween(delete_begin, delete_end, &forms)) {
return false;
}
#if BUILDFLAG(IS_IOS)
for (const auto& form : forms) {
DeleteEncryptedPasswordById(form->primary_key.value().value());
}
#endif
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM logins WHERE "
"date_created >= ? AND date_created < ?"));
s.BindTime(0, delete_begin);
s.BindTime(1, delete_end.is_null() ? base::Time::Max() : delete_end);
if (!s.Run()) {
return false;
}
if (changes) {
for (auto& form : forms) {
changes->emplace_back(PasswordStoreChange::REMOVE, *form,
/*password_changed=*/true);
}
}
return true;
}
bool LoginDatabase::GetAutoSignInLogins(
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetAutoSignInLogins");
DCHECK(forms);
DCHECK(!autosignin_statement_.empty());
forms->clear();
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, autosignin_statement_.c_str()));
FormRetrievalResult result = StatementToForms(&s, nullptr, forms);
return (result == FormRetrievalResult::kSuccess ||
result ==
FormRetrievalResult::kEncryptionServiceFailureWithPartialData);
}
bool LoginDatabase::DisableAutoSignInForOrigin(const GURL& origin) {
sql::Statement s(db_.GetCachedStatement(
SQL_FROM_HERE,
"UPDATE logins SET skip_zero_click = 1 WHERE origin_url = ?;"));
s.BindString(0, origin.spec());
return s.Run();
}
LoginDatabase::EncryptionResult LoginDatabase::InitPasswordFormFromStatement(
sql::Statement& s,
bool decrypt_and_fill_password_value,
PasswordForm* form) const {
std::string encrypted_password;
s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password);
std::u16string decrypted_password;
if (decrypt_and_fill_password_value) {
EncryptionResult encryption_result =
DecryptedString(encrypted_password, &decrypted_password);
if (encryption_result != ENCRYPTION_RESULT_SUCCESS) {
VLOG(0) << "Password decryption failed, encryption_result is "
<< encryption_result;
return encryption_result;
}
}
form->primary_key = FormPrimaryKey(s.ColumnInt(COLUMN_ID));
std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL);
form->url = GURL(tmp);
tmp = s.ColumnString(COLUMN_ACTION_URL);
form->action = GURL(tmp);
form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT);
form->username_value = s.ColumnString16(COLUMN_USERNAME_VALUE);
form->password_element = s.ColumnString16(COLUMN_PASSWORD_ELEMENT);
form->password_value = decrypted_password;
form->encrypted_password = encrypted_password;
form->submit_element = s.ColumnString16(COLUMN_SUBMIT_ELEMENT);
tmp = s.ColumnString(COLUMN_SIGNON_REALM);
form->signon_realm = tmp;
form->date_created = s.ColumnTime(COLUMN_DATE_CREATED);
form->blocked_by_user = (s.ColumnInt(COLUMN_BLOCKLISTED_BY_USER) > 0);
// TODO(crbug.com/1151214): Add metrics to capture how often these values fall
// out of the valid enum range.
form->scheme = static_cast<PasswordForm::Scheme>(s.ColumnInt(COLUMN_SCHEME));
form->type =
static_cast<PasswordForm::Type>(s.ColumnInt(COLUMN_PASSWORD_TYPE));
base::span<const uint8_t> possible_username_pairs_blob =
s.ColumnBlob(COLUMN_POSSIBLE_USERNAME_PAIRS);
if (!possible_username_pairs_blob.empty()) {
base::Pickle pickle = PickleFromSpan(possible_username_pairs_blob);
form->all_alternative_usernames =
DeserializeAlternativeElementVector(pickle);
}
form->times_used_in_html_form = s.ColumnInt(COLUMN_TIMES_USED);
base::span<const uint8_t> form_data_blob = s.ColumnBlob(COLUMN_FORM_DATA);
if (!form_data_blob.empty()) {
base::Pickle form_data_pickle = PickleFromSpan(form_data_blob);
base::PickleIterator form_data_iter(form_data_pickle);
bool success =
autofill::DeserializeFormData(&form_data_iter, &form->form_data);
metrics_util::FormDeserializationStatus status =
success ? metrics_util::LOGIN_DATABASE_SUCCESS
: metrics_util::LOGIN_DATABASE_FAILURE;
metrics_util::LogFormDataDeserializationStatus(status);
}
form->display_name = s.ColumnString16(COLUMN_DISPLAY_NAME);
form->icon_url = GURL(s.ColumnString(COLUMN_ICON_URL));
form->federation_origin =
url::Origin::Create(GURL(s.ColumnString(COLUMN_FEDERATION_URL)));
form->skip_zero_click = (s.ColumnInt(COLUMN_SKIP_ZERO_CLICK) > 0);
form->generation_upload_status =
static_cast<PasswordForm::GenerationUploadStatus>(
s.ColumnInt(COLUMN_GENERATION_UPLOAD_STATUS));
form->date_last_used = s.ColumnTime(COLUMN_DATE_LAST_USED);
base::span<const uint8_t> moving_blocked_for_blob =
s.ColumnBlob(COLUMN_MOVING_BLOCKED_FOR);
if (!moving_blocked_for_blob.empty()) {
base::Pickle pickle = PickleFromSpan(moving_blocked_for_blob);
form->moving_blocked_for_list = DeserializeGaiaIdHashVector(pickle);
}
form->date_password_modified = s.ColumnTime(COLUMN_DATE_PASSWORD_MODIFIED);
PopulateFormWithPasswordIssues(form);
PopulateFormWithNotes(form);
return ENCRYPTION_RESULT_SUCCESS;
}
bool LoginDatabase::GetLogins(
const PasswordFormDigest& form,
bool should_PSL_matching_apply,
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetLogins");
DCHECK(forms);
forms->clear();
const bool should_federated_apply =
form.scheme == PasswordForm::Scheme::kHtml;
DCHECK(!get_statement_.empty());
DCHECK(!get_statement_psl_.empty());
DCHECK(!get_statement_federated_.empty());
DCHECK(!get_statement_psl_federated_.empty());
const std::string* sql_query = &get_statement_;
if (should_PSL_matching_apply && should_federated_apply) {
sql_query = &get_statement_psl_federated_;
} else if (should_PSL_matching_apply) {
sql_query = &get_statement_psl_;
} else if (should_federated_apply) {
sql_query = &get_statement_federated_;
}
// TODO(nyquist) Consider usage of GetCachedStatement when
// http://crbug.com/248608 is fixed.
sql::Statement s(db_.GetUniqueStatement(sql_query->c_str()));
s.BindString(0, form.signon_realm);
int placeholder = 1;
// PSL matching only applies to HTML forms.
if (should_PSL_matching_apply) {
s.BindString(placeholder++, GetRegexForPSLMatching(form.signon_realm));
if (should_federated_apply) {
// This regex matches any subdomain of |registered_domain|, in particular
// it matches the empty subdomain. Hence exact domain matches are also
// retrieved.
s.BindString(placeholder++,
GetRegexForPSLFederatedMatching(form.signon_realm));
}
} else if (should_federated_apply) {
s.BindString(placeholder++,
GetExpressionForFederatedMatching(form.url) + "%");
}
FormRetrievalResult result = StatementToForms(
&s, should_PSL_matching_apply || should_federated_apply ? &form : nullptr,
forms);
if (result != FormRetrievalResult::kSuccess &&
result != FormRetrievalResult::kEncryptionServiceFailureWithPartialData) {
forms->clear();
return false;
}
return true;
}
bool LoginDatabase::GetLoginsCreatedBetween(
const base::Time begin,
const base::Time end,
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetLoginsCreatedBetween");
DCHECK(forms);
DCHECK(!created_statement_.empty());
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, created_statement_.c_str()));
s.BindTime(0, begin);
s.BindTime(1, end.is_null() ? base::Time::Max() : end);
return StatementToForms(&s, nullptr, forms) == FormRetrievalResult::kSuccess;
}
FormRetrievalResult LoginDatabase::GetAllLogins(
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetAllLogins");
DCHECK(forms);
forms->clear();
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, "SELECT * FROM logins"));
return StatementToForms(&s, nullptr, forms);
}
FormRetrievalResult LoginDatabase::GetLoginsBySignonRealmAndUsername(
const std::string& signon_realm,
const std::u16string& username,
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetLoginsBySignonRealmAndUsername");
forms->clear();
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, get_statement_username_.c_str()));
s.BindString(0, signon_realm);
s.BindString16(1, username);
return StatementToForms(&s, nullptr, forms);
}
bool LoginDatabase::GetAutofillableLogins(
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetAutofillableLogins");
return GetAllLoginsWithBlocklistSetting(false, forms);
}
bool LoginDatabase::GetBlocklistLogins(
std::vector<std::unique_ptr<PasswordForm>>* forms) {
TRACE_EVENT0("passwords", "LoginDatabase::GetBlocklistLogins");
return GetAllLoginsWithBlocklistSetting(true, forms);
}
bool LoginDatabase::GetAllLoginsWithBlocklistSetting(
bool blocklisted,
std::vector<std::unique_ptr<PasswordForm>>* forms) {
DCHECK(forms);
DCHECK(!blocklisted_statement_.empty());
forms->clear();
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, blocklisted_statement_.c_str()));
s.BindInt(0, blocklisted ? 1 : 0);
FormRetrievalResult result = StatementToForms(&s, nullptr, forms);
if (result != FormRetrievalResult::kSuccess &&
result != FormRetrievalResult::kEncryptionServiceFailureWithPartialData) {
forms->clear();
return false;
}
return true;
}
bool LoginDatabase::IsEmpty() {
sql::Statement s(
db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM logins"));
return s.Step() && s.ColumnInt(0) == 0;
}
bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
TRACE_EVENT0("passwords", "LoginDatabase::DeleteAndRecreateDatabaseFile");
DCHECK(db_.is_open());
meta_table_.Reset();
db_.Close();
sql::Database::Delete(db_path_);
return Init();
}
DatabaseCleanupResult LoginDatabase::DeleteUndecryptableLogins() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
TRACE_EVENT0("passwords", "LoginDatabase::DeleteUndecryptableLogins");
// If the Keychain in MacOS or the real secret key in Linux is unavailable,
// don't delete any logins.
if (!OSCrypt::IsEncryptionAvailable()) {
metrics_util::LogDeleteUndecryptableLoginsReturnValue(
metrics_util::DeleteCorruptedPasswordsResult::kEncryptionUnavailable);
return DatabaseCleanupResult::kEncryptionUnavailable;
}
DCHECK(db_.is_open());
sql::Statement s(db_.GetUniqueStatement("SELECT * FROM logins"));
std::vector<PasswordForm> forms_to_be_deleted;
while (s.Step()) {
std::string encrypted_password;
s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password);
std::u16string decrypted_password;
if (DecryptedString(encrypted_password, &decrypted_password) ==
ENCRYPTION_RESULT_SUCCESS) {
continue;
}
// If it was not possible to decrypt the password, remove it from the
// database.
forms_to_be_deleted.push_back(GetFormForRemoval(s));
}
for (const auto& form : forms_to_be_deleted) {
if (!RemoveLogin(form, nullptr)) {
metrics_util::LogDeleteUndecryptableLoginsReturnValue(
metrics_util::DeleteCorruptedPasswordsResult::kItemFailure);
return DatabaseCleanupResult::kItemFailure;
}
}
if (forms_to_be_deleted.empty()) {
metrics_util::LogDeleteUndecryptableLoginsReturnValue(
metrics_util::DeleteCorruptedPasswordsResult::kSuccessNoDeletions);
} else {
metrics_util::LogDeleteUndecryptableLoginsReturnValue(
metrics_util::DeleteCorruptedPasswordsResult::kSuccessPasswordsDeleted);
}
#endif
return DatabaseCleanupResult::kSuccess;
}
bool LoginDatabase::BeginTransaction() {
TRACE_EVENT0("passwords", "LoginDatabase::BeginTransaction");
return db_.BeginTransaction();
}
void LoginDatabase::RollbackTransaction() {
TRACE_EVENT0("passwords", "LoginDatabase::RollbackTransaction");
db_.RollbackTransaction();
}
bool LoginDatabase::CommitTransaction() {
TRACE_EVENT0("passwords", "LoginDatabase::CommitTransaction");
return db_.CommitTransaction();
}
LoginDatabase::SyncMetadataStore::SyncMetadataStore(sql::Database* db)
: db_(db) {
CHECK(db);
}
LoginDatabase::SyncMetadataStore::~SyncMetadataStore() = default;
std::unique_ptr<syncer::MetadataBatch>
LoginDatabase::SyncMetadataStore::GetAllSyncEntityMetadata() {
auto metadata_batch = std::make_unique<syncer::MetadataBatch>();
sql::Statement s(db_->GetCachedStatement(SQL_FROM_HERE,
"SELECT storage_key, metadata FROM "
"sync_entities_metadata"));
while (s.Step()) {
int storage_key_int = s.ColumnInt(0);
std::string storage_key = base::NumberToString(storage_key_int);
std::string encrypted_serialized_metadata = s.ColumnString(1);
std::string decrypted_serialized_metadata;
if (!OSCrypt::DecryptString(encrypted_serialized_metadata,
&decrypted_serialized_metadata)) {
DLOG(WARNING) << "Failed to decrypt PASSWORD model type "
"sync_pb::EntityMetadata.";
return nullptr;
}
auto entity_metadata = std::make_unique<sync_pb::EntityMetadata>();
if (entity_metadata->ParseFromString(decrypted_serialized_metadata)) {
metadata_batch->AddMetadata(storage_key, std::move(entity_metadata));
} else {
DLOG(WARNING) << "Failed to deserialize PASSWORD model type "
"sync_pb::EntityMetadata.";
return nullptr;
}
}
if (!s.Succeeded()) {
return nullptr;
}
return metadata_batch;
}
std::unique_ptr<sync_pb::ModelTypeState>
LoginDatabase::SyncMetadataStore::GetModelTypeState() {
auto state = std::make_unique<sync_pb::ModelTypeState>();
sql::Statement s(db_->GetCachedStatement(
SQL_FROM_HERE,
"SELECT model_metadata FROM sync_model_metadata WHERE id=1"));
if (!s.Step()) {
if (s.Succeeded()) {
return state;
} else {
return nullptr;
}
}
std::string serialized_state = s.ColumnString(0);
if (state->ParseFromString(serialized_state)) {
return state;
}
return nullptr;
}
std::unique_ptr<syncer::MetadataBatch>
LoginDatabase::SyncMetadataStore::GetAllSyncMetadata() {
TRACE_EVENT0("passwords", "SyncMetadataStore::GetAllSyncMetadata");
std::unique_ptr<syncer::MetadataBatch> metadata_batch =
GetAllSyncEntityMetadata();
if (metadata_batch == nullptr) {
return nullptr;
}
std::unique_ptr<sync_pb::ModelTypeState> model_type_state =
GetModelTypeState();
if (model_type_state == nullptr) {
return nullptr;
}
metadata_batch->SetModelTypeState(*model_type_state);
return metadata_batch;
}
void LoginDatabase::SyncMetadataStore::DeleteAllSyncMetadata() {
TRACE_EVENT0("passwords", "SyncMetadataStore::DeleteAllSyncMetadata");
bool had_unsynced_deletions = HasUnsyncedDeletions();
ClearAllSyncMetadata(db_);
if (had_unsynced_deletions && deletions_have_synced_callback_) {
// Note: At this point we can't be fully sure whether the deletions actually
// reached the server yet. We might have sent a commit, but haven't received
// the commit confirmation. Let's be conservative and assume they haven't
// been successfully deleted.
deletions_have_synced_callback_.Run(/*success=*/false);
}
}
bool LoginDatabase::SyncMetadataStore::UpdateEntityMetadata(
syncer::ModelType model_type,
const std::string& storage_key,
const sync_pb::EntityMetadata& metadata) {
TRACE_EVENT0("passwords", "SyncMetadataStore::UpdateSyncMetadata");
DCHECK_EQ(model_type, syncer::PASSWORDS);
int storage_key_int = 0;
if (!base::StringToInt(storage_key, &storage_key_int)) {
DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
"an integer.";
return false;
}
std::string encrypted_metadata;
if (!OSCrypt::EncryptString(metadata.SerializeAsString(),
&encrypted_metadata)) {
DLOG(ERROR) << "Cannot encrypt the sync metadata";
return false;
}
sql::Statement s(
db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT OR REPLACE INTO sync_entities_metadata "
"(storage_key, metadata) VALUES(?, ?)"));
s.BindInt(0, storage_key_int);
s.BindString(1, encrypted_metadata);
bool had_unsynced_deletions = HasUnsyncedDeletions();
bool result = s.Run();
if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
deletions_have_synced_callback_) {
deletions_have_synced_callback_.Run(/*success=*/true);
}
return result;
}
bool LoginDatabase::SyncMetadataStore::ClearEntityMetadata(
syncer::ModelType model_type,
const std::string& storage_key) {
TRACE_EVENT0("passwords", "SyncMetadataStore::ClearSyncMetadata");
DCHECK_EQ(model_type, syncer::PASSWORDS);
int storage_key_int = 0;
if (!base::StringToInt(storage_key, &storage_key_int)) {
DLOG(ERROR) << "Invalid storage key. Failed to convert the storage key to "
"an integer.";
return false;
}
sql::Statement s(
db_->GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM sync_entities_metadata WHERE "
"storage_key=?"));
s.BindInt(0, storage_key_int);
bool had_unsynced_deletions = HasUnsyncedDeletions();
bool result = s.Run();
if (result && had_unsynced_deletions && !HasUnsyncedDeletions() &&
deletions_have_synced_callback_) {
deletions_have_synced_callback_.Run(/*success=*/true);
}
return result;
}
bool LoginDatabase::SyncMetadataStore::UpdateModelTypeState(
syncer::ModelType model_type,
const sync_pb::ModelTypeState& model_type_state) {
TRACE_EVENT0("passwords", "SyncMetadataStore::UpdateModelTypeState");
DCHECK_EQ(model_type, syncer::PASSWORDS);
// Make sure only one row is left by storing it in the entry with id=1
// every time.
sql::Statement s(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE INTO sync_model_metadata (id, model_metadata) "
"VALUES(1, ?)"));
s.BindString(0, model_type_state.SerializeAsString());
return s.Run();
}
bool LoginDatabase::SyncMetadataStore::ClearModelTypeState(
syncer::ModelType model_type) {
TRACE_EVENT0("passwords", "SyncMetadataStore::ClearModelTypeState");
DCHECK_EQ(model_type, syncer::PASSWORDS);
sql::Statement s(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM sync_model_metadata WHERE id=1"));
return s.Run();
}
void LoginDatabase::SyncMetadataStore::SetDeletionsHaveSyncedCallback(
base::RepeatingCallback<void(bool)> callback) {
deletions_have_synced_callback_ = std::move(callback);
}
bool LoginDatabase::SyncMetadataStore::HasUnsyncedDeletions() {
TRACE_EVENT0("passwords", "SyncMetadataStore::HasUnsyncedDeletions");
std::unique_ptr<syncer::MetadataBatch> batch = GetAllSyncEntityMetadata();
if (!batch) {
return false;
}
for (const auto& metadata_entry : batch->GetAllMetadata()) {
// Note: No need for an explicit "is unsynced" check: Once the deletion is
// committed, the metadata entry is removed.
if (metadata_entry.second->is_deleted()) {
return true;
}
}
return false;
}
LoginDatabase::PrimaryKeyAndPassword LoginDatabase::GetPrimaryKeyAndPassword(
const PasswordForm& form) const {
DCHECK(!id_and_password_statement_.empty());
sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
id_and_password_statement_.c_str()));
s.BindString(0, form.url.spec());
s.BindString16(1, form.username_element);
s.BindString16(2, form.username_value);
s.BindString16(3, form.password_element);
s.BindString(4, form.signon_realm);
if (s.Step()) {
PrimaryKeyAndPassword result = {s.ColumnInt(0)};
s.ColumnBlobAsString(1, &result.encrypted_password);
if (DecryptedString(result.encrypted_password,
&result.decrypted_password) !=
ENCRYPTION_RESULT_SUCCESS) {
result.decrypted_password.clear();
}
return result;
}
return {-1, std::string(), std::u16string()};
}
FormRetrievalResult LoginDatabase::StatementToForms(
sql::Statement* statement,
const PasswordFormDigest* matched_form,
std::vector<std::unique_ptr<PasswordForm>>* forms) {
DCHECK(forms);
forms->clear();
bool has_service_failure = false;
while (statement->Step()) {
auto new_form = std::make_unique<PasswordForm>();
FillFormInStore(new_form.get());
EncryptionResult result = InitPasswordFormFromStatement(
*statement, /*decrypt_and_fill_password_value=*/true, new_form.get());
if (result == ENCRYPTION_RESULT_SERVICE_FAILURE) {
has_service_failure = true;
continue;
}
if (result == ENCRYPTION_RESULT_ITEM_FAILURE) {
continue;
}
DCHECK_EQ(ENCRYPTION_RESULT_SUCCESS, result);
if (matched_form) {
switch (GetMatchResult(*new_form, *matched_form)) {
case MatchResult::NO_MATCH:
continue;
case MatchResult::EXACT_MATCH:
case MatchResult::FEDERATED_MATCH:
break;
case MatchResult::PSL_MATCH:
case MatchResult::FEDERATED_PSL_MATCH:
new_form->is_public_suffix_match = true;
break;
}
}
forms->emplace_back(std::move(new_form));
}
if (!statement->Succeeded()) {
return FormRetrievalResult::kDbError;
}
if (has_service_failure &&
(forms->empty() || !ShouldReturnPartialPasswords())) {
return FormRetrievalResult::kEncryptionServiceFailure;
}
if (has_service_failure) {
return FormRetrievalResult::kEncryptionServiceFailureWithPartialData;
}
return FormRetrievalResult::kSuccess;
}
void LoginDatabase::InitializeStatementStrings(const SQLTableBuilder& builder) {
// This method may be called multiple times, if Chrome switches backends and
// LoginDatabase::DeleteAndRecreateDatabaseFile ends up being called. In those
// case do not recompute the SQL statements, because they would end up the
// same.
if (!add_statement_.empty()) {
return;
}
// Initialize the cached strings.
std::string all_column_names = builder.ListAllColumnNames();
std::string right_amount_of_placeholders =
GeneratePlaceholders(builder.NumberOfColumns());
std::string all_unique_key_column_names = builder.ListAllUniqueKeyNames();
std::string all_nonunique_key_column_names =
builder.ListAllNonuniqueKeyNames();
add_statement_ = "INSERT INTO logins (" + all_column_names + ") VALUES " +
right_amount_of_placeholders;
DCHECK(add_replace_statement_.empty());
add_replace_statement_ = "INSERT OR REPLACE INTO logins (" +
all_column_names + ") VALUES " +
right_amount_of_placeholders;
DCHECK(update_statement_.empty());
update_statement_ = "UPDATE logins SET " + all_nonunique_key_column_names +
" WHERE " + all_unique_key_column_names;
DCHECK(delete_statement_.empty());
delete_statement_ = "DELETE FROM logins WHERE " + all_unique_key_column_names;
DCHECK(delete_by_id_statement_.empty());
delete_by_id_statement_ = "DELETE FROM logins WHERE id=?";
DCHECK(autosignin_statement_.empty());
autosignin_statement_ = "SELECT " + all_column_names +
" FROM logins "
"WHERE skip_zero_click = 0 ORDER BY origin_url";
DCHECK(get_statement_.empty());
get_statement_ = "SELECT " + all_column_names +
" FROM logins "
"WHERE signon_realm == ?";
std::string psl_statement = "OR signon_realm REGEXP ? ";
std::string federated_statement =
"OR (signon_realm LIKE ? AND password_type == 2) ";
std::string psl_federated_statement =
"OR (signon_realm REGEXP ? AND password_type == 2) ";
DCHECK(get_statement_psl_.empty());
get_statement_psl_ = get_statement_ + psl_statement;
DCHECK(get_statement_federated_.empty());
get_statement_federated_ = get_statement_ + federated_statement;
DCHECK(get_statement_psl_federated_.empty());
get_statement_psl_federated_ =
get_statement_ + psl_statement + psl_federated_statement;
DCHECK(get_statement_username_.empty());
get_statement_username_ = get_statement_ + " AND username_value == ?";
DCHECK(created_statement_.empty());
created_statement_ =
"SELECT " + all_column_names +
" FROM logins WHERE date_created >= ? AND date_created < "
"? ORDER BY origin_url";
DCHECK(blocklisted_statement_.empty());
blocklisted_statement_ =
"SELECT " + all_column_names +
" FROM logins WHERE blacklisted_by_user == ? ORDER BY origin_url";
DCHECK(encrypted_password_statement_by_id_.empty());
encrypted_password_statement_by_id_ =
"SELECT password_value FROM logins WHERE id=?";
DCHECK(id_and_password_statement_.empty());
id_and_password_statement_ = "SELECT id, password_value FROM logins WHERE " +
all_unique_key_column_names;
}
void LoginDatabase::FillFormInStore(PasswordForm* form) const {
form->in_store = is_account_store() ? PasswordForm::Store::kAccountStore
: PasswordForm::Store::kProfileStore;
}
void LoginDatabase::PopulateFormWithPasswordIssues(PasswordForm* form) const {
DCHECK(form->primary_key.has_value());
std::vector<InsecureCredential> insecure_credentials =
insecure_credentials_table_.GetRows(form->primary_key.value());
base::flat_map<InsecureType, InsecurityMetadata> issues;
for (const auto& insecure_credential : insecure_credentials) {
issues[insecure_credential.insecure_type] = InsecurityMetadata(
insecure_credential.create_time, insecure_credential.is_muted,
insecure_credential.trigger_notification_from_backend);
}
form->password_issues = std::move(issues);
}
InsecureCredentialsChanged LoginDatabase::UpdateInsecureCredentials(
FormPrimaryKey primary_key,
const base::flat_map<InsecureType, InsecurityMetadata>& password_issues) {
bool changed = false;
for (const auto& password_issue : password_issues) {
changed = insecure_credentials_table_.InsertOrReplace(
primary_key, password_issue.first, password_issue.second) ||
changed;
}
// If an insecure type has been removed from the form it has to be removed
// from the database. This can currently happen for phished entries.
for (auto insecure_type : {InsecureType::kLeaked, InsecureType::kPhished,
InsecureType::kWeak, InsecureType::kReused}) {
if (password_issues.find(insecure_type) == password_issues.end()) {
changed =
insecure_credentials_table_.RemoveRow(primary_key, insecure_type) ||
changed;
}
}
return InsecureCredentialsChanged(changed);
}
void LoginDatabase::PopulateFormWithNotes(PasswordForm* form) const {
DCHECK(form->primary_key.has_value());
if (!base::FeatureList::IsEnabled(syncer::kPasswordNotesWithBackup)) {
return;
}
form->notes =
password_notes_table_.GetPasswordNotes(form->primary_key.value());
}
void LoginDatabase::UpdatePasswordNotes(
FormPrimaryKey primary_key,
const std::vector<PasswordNote>& notes) {
if (!base::FeatureList::IsEnabled(syncer::kPasswordNotesWithBackup)) {
return;
}
password_notes_table_.RemovePasswordNotes(primary_key);
for (const PasswordNote& note : notes) {
password_notes_table_.InsertOrReplace(primary_key, note);
}
}
} // namespace password_manager