blob: 97065065a01f917cfb124c359f0f2edf51252247 [file] [log] [blame]
// Copyright 2018 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/password_manager/core/browser/sync/password_sync_bridge.h"
#include <unordered_set>
#include "base/auto_reset.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/metadata_change_list.h"
#include "components/sync/model/model_type_change_processor.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/model_impl/in_memory_metadata_change_list.h"
#include "components/sync/model_impl/sync_metadata_store_change_list.h"
#include "net/base/escape.h"
#include "url/gurl.h"
namespace password_manager {
namespace {
std::string ComputeClientTag(
const sync_pb::PasswordSpecificsData& password_data) {
return net::EscapePath(GURL(password_data.origin()).spec()) + "|" +
net::EscapePath(password_data.username_element()) + "|" +
net::EscapePath(password_data.username_value()) + "|" +
net::EscapePath(password_data.password_element()) + "|" +
net::EscapePath(password_data.signon_realm());
}
sync_pb::PasswordSpecifics SpecificsFromPassword(
const autofill::PasswordForm& password_form) {
sync_pb::PasswordSpecifics specifics;
sync_pb::PasswordSpecificsData* password_data =
specifics.mutable_client_only_encrypted_data();
password_data->set_scheme(static_cast<int>(password_form.scheme));
password_data->set_signon_realm(password_form.signon_realm);
password_data->set_origin(password_form.origin.spec());
password_data->set_action(password_form.action.spec());
password_data->set_username_element(
base::UTF16ToUTF8(password_form.username_element));
password_data->set_password_element(
base::UTF16ToUTF8(password_form.password_element));
password_data->set_username_value(
base::UTF16ToUTF8(password_form.username_value));
password_data->set_password_value(
base::UTF16ToUTF8(password_form.password_value));
password_data->set_date_last_used(
password_form.date_last_used.ToDeltaSinceWindowsEpoch().InMicroseconds());
password_data->set_date_created(
password_form.date_created.ToDeltaSinceWindowsEpoch().InMicroseconds());
password_data->set_blacklisted(password_form.blacklisted_by_user);
password_data->set_type(static_cast<int>(password_form.type));
password_data->set_times_used(password_form.times_used);
password_data->set_display_name(
base::UTF16ToUTF8(password_form.display_name));
password_data->set_avatar_url(password_form.icon_url.spec());
password_data->set_federation_url(
password_form.federation_origin.opaque()
? std::string()
: password_form.federation_origin.Serialize());
return specifics;
}
autofill::PasswordForm PasswordFromEntityChange(
const syncer::EntityChange& entity_change,
base::Time sync_time) {
DCHECK(entity_change.data().specifics.has_password());
const sync_pb::PasswordSpecificsData& password_data =
entity_change.data().specifics.password().client_only_encrypted_data();
autofill::PasswordForm password;
password.scheme =
static_cast<autofill::PasswordForm::Scheme>(password_data.scheme());
password.signon_realm = password_data.signon_realm();
password.origin = GURL(password_data.origin());
password.action = GURL(password_data.action());
password.username_element =
base::UTF8ToUTF16(password_data.username_element());
password.password_element =
base::UTF8ToUTF16(password_data.password_element());
password.username_value = base::UTF8ToUTF16(password_data.username_value());
password.password_value = base::UTF8ToUTF16(password_data.password_value());
if (password_data.has_date_last_used()) {
password.date_last_used = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(password_data.date_last_used()));
} else if (password_data.preferred()) {
// For legacy passwords that don't have the |date_last_used| field set, we
// should it similar to the logic in login database migration.
password.date_last_used =
base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromDays(1));
}
password.date_created = base::Time::FromDeltaSinceWindowsEpoch(
// Use FromDeltaSinceWindowsEpoch because create_time_us has
// always used the Windows epoch.
base::TimeDelta::FromMicroseconds(password_data.date_created()));
password.blacklisted_by_user = password_data.blacklisted();
password.type =
static_cast<autofill::PasswordForm::Type>(password_data.type());
password.times_used = password_data.times_used();
password.display_name = base::UTF8ToUTF16(password_data.display_name());
password.icon_url = GURL(password_data.avatar_url());
password.federation_origin =
url::Origin::Create(GURL(password_data.federation_url()));
password.date_synced = sync_time;
return password;
}
std::unique_ptr<syncer::EntityData> CreateEntityData(
const autofill::PasswordForm& form) {
auto entity_data = std::make_unique<syncer::EntityData>();
*entity_data->specifics.mutable_password() = SpecificsFromPassword(form);
entity_data->name = form.signon_realm;
return entity_data;
}
int ParsePrimaryKey(const std::string& storage_key) {
int primary_key = 0;
bool success = base::StringToInt(storage_key, &primary_key);
DCHECK(success)
<< "Invalid storage key. Failed to convert the storage key to "
"an integer";
return primary_key;
}
// Returns true iff |password_specifics| and |password_form| are equal
// memberwise.
bool AreLocalAndRemotePasswordsEqual(
const sync_pb::PasswordSpecificsData& password_specifics,
const autofill::PasswordForm& password_form) {
return (
static_cast<int>(password_form.scheme) == password_specifics.scheme() &&
password_form.signon_realm == password_specifics.signon_realm() &&
password_form.origin.spec() == password_specifics.origin() &&
password_form.action.spec() == password_specifics.action() &&
base::UTF16ToUTF8(password_form.username_element) ==
password_specifics.username_element() &&
base::UTF16ToUTF8(password_form.password_element) ==
password_specifics.password_element() &&
base::UTF16ToUTF8(password_form.username_value) ==
password_specifics.username_value() &&
base::UTF16ToUTF8(password_form.password_value) ==
password_specifics.password_value() &&
password_form.date_last_used ==
base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(
password_specifics.date_last_used())) &&
password_form.date_created ==
base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(
password_specifics.date_created())) &&
password_form.blacklisted_by_user == password_specifics.blacklisted() &&
static_cast<int>(password_form.type) == password_specifics.type() &&
password_form.times_used == password_specifics.times_used() &&
base::UTF16ToUTF8(password_form.display_name) ==
password_specifics.display_name() &&
password_form.icon_url.spec() == password_specifics.avatar_url() &&
url::Origin::Create(GURL(password_specifics.federation_url()))
.Serialize() == password_form.federation_origin.Serialize());
}
bool ShouldRecoverPasswordsDuringMerge() {
return !base::FeatureList::IsEnabled(features::kDeleteCorruptedPasswords);
}
// A simple class for scoping a password store sync transaction. If the
// transaction hasn't been committed, it will be rolled back when it goes out of
// scope.
class ScopedStoreTransaction {
public:
explicit ScopedStoreTransaction(PasswordStoreSync* store) : store_(store) {
store_->BeginTransaction();
committed_ = false;
}
void Commit() {
if (!committed_) {
store_->CommitTransaction();
committed_ = true;
}
}
~ScopedStoreTransaction() {
if (!committed_) {
store_->RollbackTransaction();
}
}
private:
PasswordStoreSync* store_;
bool committed_;
DISALLOW_COPY_AND_ASSIGN(ScopedStoreTransaction);
};
} // namespace
PasswordSyncBridge::PasswordSyncBridge(
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
PasswordStoreSync* password_store_sync,
const base::RepeatingClosure& sync_enabled_or_disabled_cb)
: ModelTypeSyncBridge(std::move(change_processor)),
password_store_sync_(password_store_sync),
sync_enabled_or_disabled_cb_(sync_enabled_or_disabled_cb) {
DCHECK(password_store_sync_);
DCHECK(sync_enabled_or_disabled_cb_);
// The metadata store could be null if the login database initialization
// fails.
if (!password_store_sync_->GetMetadataStore()) {
this->change_processor()->ReportError(
{FROM_HERE, "Password metadata store isn't available."});
return;
}
std::unique_ptr<syncer::MetadataBatch> batch =
password_store_sync_->GetMetadataStore()->GetAllSyncMetadata();
if (!batch) {
this->change_processor()->ReportError(
{FROM_HERE, "Failed reading passwords metadata from password store."});
return;
}
this->change_processor()->ModelReadyToSync(std::move(batch));
}
PasswordSyncBridge::~PasswordSyncBridge() = default;
void PasswordSyncBridge::ActOnPasswordStoreChanges(
const PasswordStoreChangeList& local_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// It's the responsibility of the callers to call this method within the same
// transaction as the data changes to fulfill atomic writes of data and
// metadata constraint.
// TODO(mamir):ActOnPasswordStoreChanges() DCHECK we are inside a
// transaction!;
if (!change_processor()->IsTrackingMetadata()) {
return; // Sync processor not yet ready, don't sync.
}
// ActOnPasswordStoreChanges() can be called from ApplySyncChanges(). Do
// nothing in this case.
if (is_processing_remote_sync_changes_) {
return;
}
syncer::SyncMetadataStoreChangeList metadata_change_list(
password_store_sync_->GetMetadataStore(), syncer::PASSWORDS);
for (const PasswordStoreChange& change : local_changes) {
const std::string storage_key = base::NumberToString(change.primary_key());
switch (change.type()) {
case PasswordStoreChange::ADD:
case PasswordStoreChange::UPDATE: {
change_processor()->Put(storage_key, CreateEntityData(change.form()),
&metadata_change_list);
break;
}
case PasswordStoreChange::REMOVE: {
change_processor()->Delete(storage_key, &metadata_change_list);
break;
}
}
}
}
std::unique_ptr<syncer::MetadataChangeList>
PasswordSyncBridge::CreateMetadataChangeList() {
return std::make_unique<syncer::InMemoryMetadataChangeList>();
}
base::Optional<syncer::ModelError> PasswordSyncBridge::MergeSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
base::Optional<syncer::ModelError> error = MergeSyncDataInternal(
std::move(metadata_change_list), std::move(entity_data));
if (error) {
base::UmaHistogramCounts10000(
"Sync.DownloadedPasswordsCountWhenInitialMergeFails",
entity_data.size());
} else {
sync_enabled_or_disabled_cb_.Run();
}
return error;
}
base::Optional<syncer::ModelError> PasswordSyncBridge::MergeSyncDataInternal(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
// This method merges the local and remote passwords based on their client
// tags. For a form |F|, there are three cases to handle:
// 1. |F| exists only in the local model --> |F| should be Put() in the change
// processor.
// 2. |F| exists only in the remote model --> |F| should be AddLoginSync() to
// the local password store.
// 3. |F| exists in both the local and the remote models --> both versions
// should be merged by accepting the most recently created one, and update
// local and remote models accordingly.
base::AutoReset<bool> processing_changes(&is_processing_remote_sync_changes_,
true);
// Read all local passwords.
PrimaryKeyToFormMap key_to_local_form_map;
FormRetrievalResult read_result =
password_store_sync_->ReadAllLogins(&key_to_local_form_map);
if (read_result == FormRetrievalResult::kDbError) {
metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
return syncer::ModelError(FROM_HERE,
"Failed to load entries from password store.");
}
if (read_result == FormRetrievalResult::kEncrytionServiceFailure) {
if (!ShouldRecoverPasswordsDuringMerge()) {
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_DECRYPTION);
return syncer::ModelError(FROM_HERE,
"Failed to load entries from password store. "
"Encryption service failure.");
}
base::Optional<syncer::ModelError> cleanup_result_error =
CleanupPasswordStore();
if (cleanup_result_error) {
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_CLEANUP);
return cleanup_result_error;
}
// Clean up done successfully, try to read again.
read_result = password_store_sync_->ReadAllLogins(&key_to_local_form_map);
if (read_result != FormRetrievalResult::kSuccess) {
metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
return syncer::ModelError(
FROM_HERE,
"Failed to load entries from password store after cleanup.");
}
}
DCHECK_EQ(read_result, FormRetrievalResult::kSuccess);
// Collect the client tags of remote passwords and the corresponding
// EntityChange. Note that |entity_data| only contains client tag *hashes*.
std::map<std::string, const syncer::EntityChange*>
client_tag_to_remote_entity_change_map;
for (const std::unique_ptr<syncer::EntityChange>& entity_change :
entity_data) {
client_tag_to_remote_entity_change_map[GetClientTag(
entity_change->data())] = entity_change.get();
}
// This is used to keep track of all the changes applied to the password
// store to notify other observers of the password store.
PasswordStoreChangeList password_store_changes;
{
ScopedStoreTransaction transaction(password_store_sync_);
const base::Time time_now = base::Time::Now();
// For any local password that doesn't exist in the remote passwords, issue
// a change_processor()->Put(). For any local password that exists in the
// remote passwords, both should be merged by picking the most recently
// created version. Password comparison is done by comparing the client
// tags. In addition, collect the client tags of local passwords.
std::unordered_set<std::string> client_tags_of_local_passwords;
for (const auto& pair : key_to_local_form_map) {
const int primary_key = pair.first;
const autofill::PasswordForm& local_password_form = *pair.second;
std::unique_ptr<syncer::EntityData> local_form_entity_data =
CreateEntityData(local_password_form);
const std::string client_tag_of_local_password =
GetClientTag(*local_form_entity_data);
client_tags_of_local_passwords.insert(client_tag_of_local_password);
if (client_tag_to_remote_entity_change_map.count(
client_tag_of_local_password) == 0) {
// Local password doesn't exist in the remote model, Put() it in the
// processor.
change_processor()->Put(
/*storage_key=*/base::NumberToString(primary_key),
std::move(local_form_entity_data), metadata_change_list.get());
continue;
}
// Local password exists in the remote model as well. A merge is required.
const syncer::EntityChange& remote_entity_change =
*client_tag_to_remote_entity_change_map[client_tag_of_local_password];
const sync_pb::PasswordSpecificsData& remote_password_specifics =
remote_entity_change.data()
.specifics.password()
.client_only_encrypted_data();
// First, we need to inform the processor about the storage key anyway.
change_processor()->UpdateStorageKey(remote_entity_change.data(),
/*storage_key=*/
base::NumberToString(primary_key),
metadata_change_list.get());
if (AreLocalAndRemotePasswordsEqual(remote_password_specifics,
local_password_form)) {
// Passwords are identical, nothing else to do.
continue;
}
// Passwords aren't identical, pick the most recently created one.
if (base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(
remote_password_specifics.date_created())) <
local_password_form.date_created) {
// The local password is more recent, update the processor.
change_processor()->Put(
/*storage_key=*/base::NumberToString(primary_key),
std::move(local_form_entity_data), metadata_change_list.get());
} else {
// The remote password is more recent, update the local model.
UpdateLoginError update_login_error;
PasswordStoreChangeList changes = password_store_sync_->UpdateLoginSync(
PasswordFromEntityChange(remote_entity_change,
/*sync_time=*/time_now),
&update_login_error);
DCHECK_LE(changes.size(), 1U);
base::UmaHistogramEnumeration(
"PasswordManager.MergeSyncData.UpdateLoginSyncError",
update_login_error);
if (changes.empty()) {
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_UPDATE);
return syncer::ModelError(
FROM_HERE, "Failed to update an entry in the password store.");
}
DCHECK(changes[0].primary_key() == primary_key);
password_store_changes.push_back(changes[0]);
}
}
// At this point, we have processed all local passwords. In addition, we
// also have processed all remote passwords that exist in the local model.
// What's remaining is to process remote passwords that don't exist in the
// local model.
// For any remote password that doesn't exist in the local passwords, issue
// a password_store_sync_->AddLoginSync() and invoke the
// change_processor()->UpdateStorageKey(). Password comparison is done by
// comparing the client tags.
for (const std::unique_ptr<syncer::EntityChange>& entity_change :
entity_data) {
const std::string client_tag_of_remote_password =
GetClientTag(entity_change->data());
if (client_tags_of_local_passwords.count(client_tag_of_remote_password) !=
0) {
// Passwords in both local and remote models have been processed
// already.
continue;
}
AddLoginError add_login_error;
PasswordStoreChangeList changes = password_store_sync_->AddLoginSync(
PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
&add_login_error);
base::UmaHistogramEnumeration(
"PasswordManager.MergeSyncData.AddLoginSyncError", add_login_error);
// TODO(crbug.com/939302): It's not yet clear if the DCHECK_LE below is
// legit. However, recent crashes suggest that 2 changes are returned
// when trying to AddLoginSync (details are in the bug). Once this is
// resolved, we should update the call the UpdateStorageKey() if
// necessary and remove unnecessary DCHECKs below.
// DCHECK_LE(changes.size(), 1U);
DCHECK_LE(changes.size(), 2U);
if (changes.empty()) {
DCHECK_NE(add_login_error, AddLoginError::kNone);
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_ADD);
// If the remote update is invalid, direct the processor to ignore and
// move on.
if (add_login_error == AddLoginError::kConstraintViolation) {
change_processor()->UntrackEntityForClientTagHash(
entity_change->data().client_tag_hash);
continue;
}
// For all other types of error, we should stop syncing.
return syncer::ModelError(
FROM_HERE, "Failed to add an entry in the password store.");
}
if (changes.size() == 1) {
DCHECK_EQ(changes[0].type(), PasswordStoreChange::ADD);
} else {
// There must be 2 changes.
DCHECK_EQ(changes[0].type(), PasswordStoreChange::REMOVE);
DCHECK_EQ(changes[1].type(), PasswordStoreChange::ADD);
}
change_processor()->UpdateStorageKey(
entity_change->data(),
/*storage_key=*/
base::NumberToString(changes.back().primary_key()),
metadata_change_list.get());
password_store_changes.insert(password_store_changes.end(),
changes.begin(), changes.end());
}
// Persist the metadata changes.
// TODO(mamir): add some test coverage for the metadata persistence.
syncer::SyncMetadataStoreChangeList sync_metadata_store_change_list(
password_store_sync_->GetMetadataStore(), syncer::PASSWORDS);
// |metadata_change_list| must have been created via
// CreateMetadataChangeList() so downcasting is safe.
static_cast<syncer::InMemoryMetadataChangeList*>(metadata_change_list.get())
->TransferChangesTo(&sync_metadata_store_change_list);
base::Optional<syncer::ModelError> error =
sync_metadata_store_change_list.TakeError();
if (error) {
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_METADATA_PERSISTENCE);
return error;
}
transaction.Commit();
} // End of scoped transaction.
if (!password_store_changes.empty()) {
// It could be the case that there are no remote passwords. In such case,
// there would be no changes to the password store other than the sync
// metadata changes, and no need to notify observers since they aren't
// interested in changes to sync metadata.
password_store_sync_->NotifyLoginsChanged(password_store_changes);
}
metrics_util::LogPasswordSyncState(metrics_util::SYNCING_OK);
return base::nullopt;
}
base::Optional<syncer::ModelError> PasswordSyncBridge::ApplySyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_changes) {
base::AutoReset<bool> processing_changes(&is_processing_remote_sync_changes_,
true);
const base::Time time_now = base::Time::Now();
// This is used to keep track of all the changes applied to the password store
// to notify other observers of the password store.
PasswordStoreChangeList password_store_changes;
{
ScopedStoreTransaction transaction(password_store_sync_);
for (const std::unique_ptr<syncer::EntityChange>& entity_change :
entity_changes) {
PasswordStoreChangeList changes;
switch (entity_change->type()) {
case syncer::EntityChange::ACTION_ADD:
AddLoginError add_login_error;
changes = password_store_sync_->AddLoginSync(
PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
&add_login_error);
base::UmaHistogramEnumeration(
"PasswordManager.ApplySyncChanges.AddLoginSyncError",
add_login_error);
// If the addition has been successful, inform the processor about the
// assigned storage key. AddLoginSync() might return multiple changes
// and the last one should be the one representing the actual addition
// in the DB.
if (changes.empty()) {
DCHECK_NE(add_login_error, AddLoginError::kNone);
metrics_util::LogApplySyncChangesState(
metrics_util::ApplySyncChangesState::kApplyAddFailed);
// If the remote update is invalid, direct the processor to ignore
// and move on.
if (add_login_error == AddLoginError::kConstraintViolation) {
change_processor()->UntrackEntityForClientTagHash(
entity_change->data().client_tag_hash);
continue;
}
// For all other types of error, we should stop syncing.
return syncer::ModelError(
FROM_HERE, "Failed to add an entry to the password store.");
}
// TODO(crbug.com/939302): It's not yet clear if the DCHECK_LE below
// is legit. However, recent crashes suggest that 2 changes are
// returned when trying to AddLoginSync (details are in the bug). Once
// this is resolved, we should update the call the UpdateStorageKey()
// if necessary and remove unnecessary DCHECKs below.
// DCHECK_EQ(1U, changes.size());
DCHECK_LE(changes.size(), 2U);
if (changes.size() == 1) {
DCHECK_EQ(changes[0].type(), PasswordStoreChange::ADD);
} else {
// There must be 2 changes.
DCHECK_EQ(changes[0].type(), PasswordStoreChange::REMOVE);
DCHECK_EQ(changes[1].type(), PasswordStoreChange::ADD);
}
change_processor()->UpdateStorageKey(
entity_change->data(),
/*storage_key=*/
base::NumberToString(changes.back().primary_key()),
metadata_change_list.get());
break;
case syncer::EntityChange::ACTION_UPDATE:
// TODO(mamir): This had been added to mitigate some potential issues
// in the login database. Once the underlying cause is verified, we
// should remove this check.
if (entity_change->storage_key().empty()) {
continue;
}
UpdateLoginError update_login_error;
changes = password_store_sync_->UpdateLoginSync(
PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
&update_login_error);
base::UmaHistogramEnumeration(
"PasswordManager.ApplySyncChanges.UpdateLoginSyncError",
update_login_error);
// If there are no entries to update, direct the processor to ignore
// and move on.
if (update_login_error == UpdateLoginError::kNoUpdatedRecords) {
change_processor()->UntrackEntityForClientTagHash(
entity_change->data().client_tag_hash);
continue;
}
if (changes.empty()) {
metrics_util::LogApplySyncChangesState(
metrics_util::ApplySyncChangesState::kApplyUpdateFailed);
return syncer::ModelError(
FROM_HERE, "Failed to update an entry in the password store.");
}
DCHECK_EQ(1U, changes.size());
DCHECK(changes[0].primary_key() ==
ParsePrimaryKey(entity_change->storage_key()));
break;
case syncer::EntityChange::ACTION_DELETE: {
// TODO(mamir): This had been added to mitigate some potential issues
// in the login database. Once the underlying cause is verified, we
// should remove this check.
if (entity_change->storage_key().empty()) {
continue;
}
int primary_key = ParsePrimaryKey(entity_change->storage_key());
changes =
password_store_sync_->RemoveLoginByPrimaryKeySync(primary_key);
if (changes.empty()) {
metrics_util::LogApplySyncChangesState(
metrics_util::ApplySyncChangesState::kApplyDeleteFailed);
// TODO(mamir): Revisit this after checking UMA to decide if this
// relaxation is crucial or not.
continue;
}
DCHECK_EQ(1U, changes.size());
DCHECK_EQ(changes[0].primary_key(), primary_key);
break;
}
}
password_store_changes.insert(password_store_changes.end(),
changes.begin(), changes.end());
}
// Persist the metadata changes.
// TODO(mamir): add some test coverage for the metadata persistence.
syncer::SyncMetadataStoreChangeList sync_metadata_store_change_list(
password_store_sync_->GetMetadataStore(), syncer::PASSWORDS);
// |metadata_change_list| must have been created via
// CreateMetadataChangeList() so downcasting is safe.
static_cast<syncer::InMemoryMetadataChangeList*>(metadata_change_list.get())
->TransferChangesTo(&sync_metadata_store_change_list);
base::Optional<syncer::ModelError> error =
sync_metadata_store_change_list.TakeError();
if (error) {
metrics_util::LogApplySyncChangesState(
metrics_util::ApplySyncChangesState::kApplyMetadataChangesFailed);
return error;
}
transaction.Commit();
} // End of scoped transaction.
if (!password_store_changes.empty()) {
// It could be the case that there are no password store changes, and all
// changes are only metadata changes. In such case, no need to notify
// observers since they aren't interested in changes to sync metadata.
password_store_sync_->NotifyLoginsChanged(password_store_changes);
}
metrics_util::LogApplySyncChangesState(
metrics_util::ApplySyncChangesState::kApplyOK);
return base::nullopt;
}
void PasswordSyncBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
// This method is called only when there are uncommitted changes on startup.
// There are more efficient implementations, but since this method is rarely
// called, simplicity is preferred over efficiency.
PrimaryKeyToFormMap key_to_form_map;
if (password_store_sync_->ReadAllLogins(&key_to_form_map) !=
FormRetrievalResult::kSuccess) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load entries from the password store."});
return;
}
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const std::string& storage_key : storage_keys) {
int primary_key = ParsePrimaryKey(storage_key);
if (key_to_form_map.count(primary_key) != 0) {
batch->Put(storage_key, CreateEntityData(*key_to_form_map[primary_key]));
}
}
std::move(callback).Run(std::move(batch));
}
void PasswordSyncBridge::GetAllDataForDebugging(DataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PrimaryKeyToFormMap key_to_form_map;
if (password_store_sync_->ReadAllLogins(&key_to_form_map) !=
FormRetrievalResult::kSuccess) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load entries from the password store."});
return;
}
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& pair : key_to_form_map) {
autofill::PasswordForm form = *pair.second;
form.password_value = base::UTF8ToUTF16("hidden");
batch->Put(base::NumberToString(pair.first), CreateEntityData(form));
}
std::move(callback).Run(std::move(batch));
}
std::string PasswordSyncBridge::GetClientTag(
const syncer::EntityData& entity_data) {
DCHECK(entity_data.specifics.has_password())
<< "EntityData does not have password specifics.";
return ComputeClientTag(
entity_data.specifics.password().client_only_encrypted_data());
}
std::string PasswordSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) {
NOTREACHED() << "PasswordSyncBridge does not support GetStorageKey.";
return std::string();
}
bool PasswordSyncBridge::SupportsGetStorageKey() const {
return false;
}
void PasswordSyncBridge::ApplyStopSyncChanges(
std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
if (delete_metadata_change_list) {
password_store_sync_->GetMetadataStore()->DeleteAllSyncMetadata();
// If this is the account store, also delete the actual data.
if (password_store_sync_->IsAccountStore()) {
base::AutoReset<bool> processing_changes(
&is_processing_remote_sync_changes_, true);
PasswordStoreChangeList password_store_changes;
PrimaryKeyToFormMap logins;
FormRetrievalResult result = password_store_sync_->ReadAllLogins(&logins);
if (result == FormRetrievalResult::kSuccess) {
for (const auto& primary_key_and_form : logins) {
password_store_changes.emplace_back(PasswordStoreChange::REMOVE,
*primary_key_and_form.second,
primary_key_and_form.first);
}
}
password_store_sync_->DeleteAndRecreateDatabaseFile();
password_store_sync_->NotifyLoginsChanged(password_store_changes);
sync_enabled_or_disabled_cb_.Run();
}
}
}
// static
std::string PasswordSyncBridge::ComputeClientTagForTesting(
const sync_pb::PasswordSpecificsData& password_data) {
return ComputeClientTag(password_data);
}
base::Optional<syncer::ModelError> PasswordSyncBridge::CleanupPasswordStore() {
DatabaseCleanupResult cleanup_result =
password_store_sync_->DeleteUndecryptableLogins();
switch (cleanup_result) {
case DatabaseCleanupResult::kSuccess:
break;
case DatabaseCleanupResult::kEncryptionUnavailable:
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_DECRYPTION);
return syncer::ModelError(
FROM_HERE, "Failed to get encryption key during database cleanup.");
case DatabaseCleanupResult::kItemFailure:
case DatabaseCleanupResult::kDatabaseUnavailable:
metrics_util::LogPasswordSyncState(
metrics_util::NOT_SYNCING_FAILED_CLEANUP);
return syncer::ModelError(FROM_HERE, "Failed to cleanup database.");
}
return base::nullopt;
}
} // namespace password_manager