Commit e2e467d1 authored by Vasilii Sukhanov's avatar Vasilii Sukhanov Committed by Commit Bot

Revert special handling of Android credentials in Chrome Sync.

It was introduced in r488421 to handle the credentials without trailing '/' in signon_realm and origin.
Now however, the signon_realm is created correctly by GmsCore. The origin has no '/'. As a result
- r488421 is useless with a lot of complexity.
- r488421 is harmful because it prevent the credential from being deleted via passwords.google.com. See b/69245513

R=battre@chromium.org

Bug: 739101,801918
Change-Id: I20e64481b8dd972e3b43f18a00b0b3078f58c4fe
Reviewed-on: https://chromium-review.googlesource.com/897610Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Commit-Queue: Vasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534073}
parent e3486063
...@@ -12,12 +12,8 @@ ...@@ -12,12 +12,8 @@
#include "base/auto_reset.h" #include "base/auto_reset.h"
#include "base/location.h" #include "base/location.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/time/default_clock.h"
#include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_store_sync.h" #include "components/password_manager/core/browser/password_store_sync.h"
#include "components/sync/model/sync_error_factory.h" #include "components/sync/model/sync_error_factory.h"
...@@ -40,12 +36,20 @@ std::string MakePasswordSyncTag(const autofill::PasswordForm& password); ...@@ -40,12 +36,20 @@ std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
namespace { namespace {
// Returns true iff |password_specifics| and |password_specifics| are equal // Returns true iff |password_specifics| and |password_specifics| are equal
// in the fields which don't comprise the sync tag. // memberwise.
bool AreLocalAndSyncPasswordsNonSyncTagEqual( bool AreLocalAndSyncPasswordsEqual(
const sync_pb::PasswordSpecificsData& password_specifics, const sync_pb::PasswordSpecificsData& password_specifics,
const autofill::PasswordForm& password_form) { const autofill::PasswordForm& password_form) {
return (password_form.scheme == password_specifics.scheme() && return (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() && 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) == base::UTF16ToUTF8(password_form.password_value) ==
password_specifics.password_value() && password_specifics.password_value() &&
password_form.preferred == password_specifics.preferred() && password_form.preferred == password_specifics.preferred() &&
...@@ -62,38 +66,6 @@ bool AreLocalAndSyncPasswordsNonSyncTagEqual( ...@@ -62,38 +66,6 @@ bool AreLocalAndSyncPasswordsNonSyncTagEqual(
.Serialize() == password_form.federation_origin.Serialize()); .Serialize() == password_form.federation_origin.Serialize());
} }
// Returns true iff |password_specifics| and |password_specifics| are equal
// memberwise.
bool AreLocalAndSyncPasswordsEqual(
const sync_pb::PasswordSpecificsData& password_specifics,
const autofill::PasswordForm& password_form) {
return (password_form.signon_realm == password_specifics.signon_realm() &&
password_form.origin.spec() == password_specifics.origin() &&
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() &&
AreLocalAndSyncPasswordsNonSyncTagEqual(password_specifics,
password_form));
}
// Compares the fields which are not part of the sync tag.
bool AreNonSyncTagFieldsEqual(const sync_pb::PasswordSpecificsData& left,
const sync_pb::PasswordSpecificsData& right) {
return (left.scheme() == right.scheme() && left.action() == right.action() &&
left.password_value() == right.password_value() &&
left.preferred() == right.preferred() &&
left.date_created() == right.date_created() &&
left.blacklisted() == right.blacklisted() &&
left.type() == right.type() &&
left.times_used() == right.times_used() &&
left.display_name() == right.display_name() &&
left.avatar_url() == right.avatar_url() &&
left.federation_url() == right.federation_url());
}
syncer::SyncChange::SyncChangeType GetSyncChangeType( syncer::SyncChange::SyncChangeType GetSyncChangeType(
PasswordStoreChange::Type type) { PasswordStoreChange::Type type) {
switch (type) { switch (type) {
...@@ -119,190 +91,6 @@ void AppendPasswordFromSpecifics( ...@@ -119,190 +91,6 @@ void AppendPasswordFromSpecifics(
entries->back()->date_synced = sync_time; entries->back()->date_synced = sync_time;
} }
// Android autofill saves credential in a different format without trailing '/'.
std::string GetIncorrectAndroidSignonRealm(std::string android_autofill_realm) {
if (base::EndsWith(android_autofill_realm, "/", base::CompareCase::SENSITIVE))
android_autofill_realm.erase(android_autofill_realm.size() - 1);
return android_autofill_realm;
}
// The correct Android signon_realm should have a trailing '/'.
std::string GetCorrectAndroidSignonRealm(std::string android_realm) {
if (!base::EndsWith(android_realm, "/", base::CompareCase::SENSITIVE))
android_realm += '/';
return android_realm;
}
// Android autofill saves credentials in a different format without trailing
// '/'. Return a sync tag for the style used by Android Autofill in GMS Core
// v12.
std::string AndroidAutofillSyncTag(
const sync_pb::PasswordSpecificsData& password) {
// realm has the same value as the origin.
std::string origin = GetIncorrectAndroidSignonRealm(password.origin());
std::string signon_realm =
GetIncorrectAndroidSignonRealm(password.signon_realm());
return (net::EscapePath(origin) + "|" +
net::EscapePath(password.username_element()) + "|" +
net::EscapePath(password.username_value()) + "|" +
net::EscapePath(password.password_element()) + "|" +
net::EscapePath(signon_realm));
}
// Return a sync tag for the correct format used by Android.
std::string AndroidCorrectSyncTag(
const sync_pb::PasswordSpecificsData& password) {
// realm has the same value as the origin.
std::string origin = GetCorrectAndroidSignonRealm(password.origin());
std::string signon_realm =
GetCorrectAndroidSignonRealm(password.signon_realm());
return (net::EscapePath(origin) + "|" +
net::EscapePath(password.username_element()) + "|" +
net::EscapePath(password.username_value()) + "|" +
net::EscapePath(password.password_element()) + "|" +
net::EscapePath(signon_realm));
}
void PasswordSpecificsFromPassword(
const autofill::PasswordForm& password_form,
sync_pb::PasswordSpecificsData* password_specifics) {
#define CopyField(field) password_specifics->set_##field(password_form.field)
#define CopyStringField(field) \
password_specifics->set_##field(base::UTF16ToUTF8(password_form.field))
CopyField(scheme);
CopyField(signon_realm);
password_specifics->set_origin(password_form.origin.spec());
password_specifics->set_action(password_form.action.spec());
CopyStringField(username_element);
CopyStringField(password_element);
CopyStringField(username_value);
CopyStringField(password_value);
CopyField(preferred);
password_specifics->set_date_created(
password_form.date_created.ToInternalValue());
password_specifics->set_blacklisted(password_form.blacklisted_by_user);
CopyField(type);
CopyField(times_used);
CopyStringField(display_name);
password_specifics->set_avatar_url(password_form.icon_url.spec());
password_specifics->set_federation_url(
password_form.federation_origin.unique()
? std::string()
: password_form.federation_origin.Serialize());
#undef CopyStringField
#undef CopyField
}
struct AndroidMergeResult {
// New value for Android entry in the correct format.
base::Optional<syncer::SyncData> new_android_correct;
// New value for Android autofill entry.
base::Optional<syncer::SyncData> new_android_incorrect;
// New value for local entry in the correct format.
base::Optional<autofill::PasswordForm> new_local_correct;
// New value for local entry in the Android autofill format.
base::Optional<autofill::PasswordForm> new_local_incorrect;
};
// Perform deduplication of Android credentials saved in the wrong format. As
// the result all the four entries should be created and have the same value.
AndroidMergeResult Perform4WayMergeAndroidCredentials(
const sync_pb::PasswordSpecificsData* correct_android,
const sync_pb::PasswordSpecificsData* incorrect_android,
const autofill::PasswordForm* correct_local,
const autofill::PasswordForm* incorrect_local) {
AndroidMergeResult result;
base::Optional<sync_pb::PasswordSpecificsData> local_correct_ps;
if (correct_local) {
local_correct_ps = sync_pb::PasswordSpecificsData();
PasswordSpecificsFromPassword(*correct_local, &local_correct_ps.value());
}
base::Optional<sync_pb::PasswordSpecificsData> local_incorrect_ps;
if (incorrect_local) {
local_incorrect_ps = sync_pb::PasswordSpecificsData();
PasswordSpecificsFromPassword(*incorrect_local,
&local_incorrect_ps.value());
}
const sync_pb::PasswordSpecificsData* all_data[4] = {
correct_android, incorrect_android,
local_correct_ps ? &local_correct_ps.value() : nullptr,
local_incorrect_ps ? &local_incorrect_ps.value() : nullptr};
// |newest_data| will point to the newest entry out of all 4.
const sync_pb::PasswordSpecificsData* newest_data = nullptr;
for (int i = 0; i < 4; ++i) {
if (newest_data && all_data[i]) {
if (all_data[i]->date_created() > newest_data->date_created())
newest_data = all_data[i];
} else if (all_data[i]) {
newest_data = all_data[i];
}
}
DCHECK(newest_data);
const std::string correct_tag = AndroidCorrectSyncTag(*newest_data);
const std::string incorrect_tag = AndroidAutofillSyncTag(*newest_data);
const std::string correct_signon_realm =
GetCorrectAndroidSignonRealm(newest_data->signon_realm());
const std::string incorrect_signon_realm =
GetIncorrectAndroidSignonRealm(newest_data->signon_realm());
const std::string correct_origin =
GetCorrectAndroidSignonRealm(newest_data->origin());
const std::string incorrect_origin =
GetIncorrectAndroidSignonRealm(newest_data->origin());
DCHECK_EQ(GURL(incorrect_origin).spec(), incorrect_origin);
// Set the correct Sync entry if needed.
if (newest_data != correct_android &&
(!correct_android ||
!AreNonSyncTagFieldsEqual(*correct_android, *newest_data))) {
sync_pb::EntitySpecifics password_data;
sync_pb::PasswordSpecificsData* password_specifics =
password_data.mutable_password()->mutable_client_only_encrypted_data();
*password_specifics = *newest_data;
password_specifics->set_origin(correct_origin);
password_specifics->set_signon_realm(correct_signon_realm);
result.new_android_correct = syncer::SyncData::CreateLocalData(
correct_tag, correct_tag, password_data);
}
// Set the Andoroid Autofill Sync entry if needed.
if (newest_data != incorrect_android &&
(!incorrect_android ||
!AreNonSyncTagFieldsEqual(*incorrect_android, *newest_data))) {
sync_pb::EntitySpecifics password_data;
sync_pb::PasswordSpecificsData* password_specifics =
password_data.mutable_password()->mutable_client_only_encrypted_data();
*password_specifics = *newest_data;
password_specifics->set_origin(incorrect_origin);
password_specifics->set_signon_realm(incorrect_signon_realm);
result.new_android_incorrect = syncer::SyncData::CreateLocalData(
incorrect_tag, incorrect_tag, password_data);
}
// Set the correct local entry if needed.
if (!local_correct_ps ||
(newest_data != &local_correct_ps.value() &&
!AreNonSyncTagFieldsEqual(local_correct_ps.value(), *newest_data))) {
result.new_local_correct = PasswordFromSpecifics(*newest_data);
result.new_local_correct.value().origin = GURL(correct_origin);
result.new_local_correct.value().signon_realm = correct_signon_realm;
}
// Set the incorrect local entry if needed.
if (!local_incorrect_ps ||
(newest_data != &local_incorrect_ps.value() &&
!AreNonSyncTagFieldsEqual(local_incorrect_ps.value(), *newest_data))) {
result.new_local_incorrect = PasswordFromSpecifics(*newest_data);
result.new_local_incorrect.value().origin = GURL(incorrect_origin);
result.new_local_incorrect.value().signon_realm = incorrect_signon_realm;
}
return result;
}
} // namespace } // namespace
struct PasswordSyncableService::SyncEntries { struct PasswordSyncableService::SyncEntries {
...@@ -336,9 +124,8 @@ struct PasswordSyncableService::SyncEntries { ...@@ -336,9 +124,8 @@ struct PasswordSyncableService::SyncEntries {
PasswordSyncableService::PasswordSyncableService( PasswordSyncableService::PasswordSyncableService(
PasswordStoreSync* password_store) PasswordStoreSync* password_store)
: password_store_(password_store), : password_store_(password_store), is_processing_sync_changes_(false) {
clock_(new base::DefaultClock), }
is_processing_sync_changes_(false) {}
PasswordSyncableService::~PasswordSyncableService() = default; PasswordSyncableService::~PasswordSyncableService() = default;
...@@ -374,12 +161,17 @@ syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing( ...@@ -374,12 +161,17 @@ syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
} }
merge_result.set_num_items_before_association(new_local_entries.size()); merge_result.set_num_items_before_association(new_local_entries.size());
// Changes from Sync to be applied locally.
SyncEntries sync_entries; SyncEntries sync_entries;
// Changes from password db that need to be propagated to sync. // Changes from password db that need to be propagated to sync.
syncer::SyncChangeList updated_db_entries; syncer::SyncChangeList updated_db_entries;
MergeSyncDataWithLocalData(initial_sync_data, &new_local_entries, for (syncer::SyncDataList::const_iterator sync_iter =
&sync_entries, &updated_db_entries); initial_sync_data.begin();
sync_iter != initial_sync_data.end(); ++sync_iter) {
CreateOrUpdateEntry(*sync_iter,
&new_local_entries,
&sync_entries,
&updated_db_entries);
}
for (PasswordEntryMap::iterator it = new_local_entries.begin(); for (PasswordEntryMap::iterator it = new_local_entries.begin();
it != new_local_entries.end(); ++it) { it != new_local_entries.end(); ++it) {
...@@ -443,7 +235,7 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges( ...@@ -443,7 +235,7 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true); base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
SyncEntries sync_entries; SyncEntries sync_entries;
base::Time time_now = clock_->Now(); base::Time time_now = base::Time::Now();
for (syncer::SyncChangeList::const_iterator it = change_list.begin(); for (syncer::SyncChangeList::const_iterator it = change_list.begin();
it != change_list.end(); ++it) { it != change_list.end(); ++it) {
...@@ -456,13 +248,6 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges( ...@@ -456,13 +248,6 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
} }
AppendPasswordFromSpecifics( AppendPasswordFromSpecifics(
specifics.password().client_only_encrypted_data(), time_now, entries); specifics.password().client_only_encrypted_data(), time_now, entries);
if (IsValidAndroidFacetURI(entries->back()->signon_realm)) {
// Fix the Android Autofill credentials if needed.
entries->back()->signon_realm =
GetCorrectAndroidSignonRealm(entries->back()->signon_realm);
entries->back()->origin =
GURL(GetCorrectAndroidSignonRealm(entries->back()->origin.spec()));
}
} }
WriteToPasswordStore(sync_entries); WriteToPasswordStore(sync_entries);
...@@ -553,132 +338,21 @@ void PasswordSyncableService::WriteToPasswordStore(const SyncEntries& entries) { ...@@ -553,132 +338,21 @@ void PasswordSyncableService::WriteToPasswordStore(const SyncEntries& entries) {
password_store_->NotifyLoginsChanged(changes); password_store_->NotifyLoginsChanged(changes);
} }
void PasswordSyncableService::MergeSyncDataWithLocalData( // static
const syncer::SyncDataList& sync_data,
PasswordEntryMap* unmatched_data_from_password_db,
SyncEntries* sync_entries,
syncer::SyncChangeList* updated_db_entries) {
std::map<std::string, const sync_pb::PasswordSpecificsData*> sync_data_map;
for (const auto& data : sync_data) {
const sync_pb::EntitySpecifics& specifics = data.GetSpecifics();
const sync_pb::PasswordSpecificsData* password_specifics =
&specifics.password().client_only_encrypted_data();
sync_data_map[MakePasswordSyncTag(*password_specifics)] =
password_specifics;
}
DCHECK_EQ(sync_data_map.size(), sync_data.size());
for (auto it = sync_data_map.begin(); it != sync_data_map.end();) {
if (IsValidAndroidFacetURI(it->second->signon_realm())) {
// Perform deduplication of Android credentials saved in the wrong format.
// For each incorrect entry, a duplicate of it is created in the correct
// format, so Chrome can make use of it. The incorrect sync entries are
// not deleted for now.
std::string incorrect_tag = AndroidAutofillSyncTag(*it->second);
std::string correct_tag = AndroidCorrectSyncTag(*it->second);
auto it_sync_incorrect = sync_data_map.find(incorrect_tag);
auto it_sync_correct = sync_data_map.find(correct_tag);
auto it_local_data_correct =
unmatched_data_from_password_db->find(correct_tag);
auto it_local_data_incorrect =
unmatched_data_from_password_db->find(incorrect_tag);
if ((it != it_sync_incorrect && it != it_sync_correct) ||
(it_sync_incorrect == sync_data_map.end() &&
it_local_data_incorrect == unmatched_data_from_password_db->end())) {
// The current credential is in an unexpected format or incorrect
// credential don't exist. Just do what Sync would normally do.
CreateOrUpdateEntry(*it->second, unmatched_data_from_password_db,
sync_entries, updated_db_entries);
++it;
} else {
AndroidMergeResult result = Perform4WayMergeAndroidCredentials(
it_sync_correct == sync_data_map.end() ? nullptr
: it_sync_correct->second,
it_sync_incorrect == sync_data_map.end()
? nullptr
: it_sync_incorrect->second,
it_local_data_correct == unmatched_data_from_password_db->end()
? nullptr
: it_local_data_correct->second,
it_local_data_incorrect == unmatched_data_from_password_db->end()
? nullptr
: it_local_data_incorrect->second);
// Add or update the correct local entry.
if (result.new_local_correct) {
auto* local_changes = sync_entries->EntriesForChangeType(
it_local_data_correct == unmatched_data_from_password_db->end()
? syncer::SyncChange::ACTION_ADD
: syncer::SyncChange::ACTION_UPDATE);
local_changes->push_back(std::make_unique<autofill::PasswordForm>(
result.new_local_correct.value()));
local_changes->back()->date_synced = clock_->Now();
}
// Add or update the incorrect local entry.
if (result.new_local_incorrect) {
auto* local_changes = sync_entries->EntriesForChangeType(
it_local_data_incorrect == unmatched_data_from_password_db->end()
? syncer::SyncChange::ACTION_ADD
: syncer::SyncChange::ACTION_UPDATE);
local_changes->push_back(std::make_unique<autofill::PasswordForm>(
result.new_local_incorrect.value()));
local_changes->back()->date_synced = clock_->Now();
}
if (it_local_data_correct != unmatched_data_from_password_db->end())
unmatched_data_from_password_db->erase(it_local_data_correct);
if (it_local_data_incorrect != unmatched_data_from_password_db->end())
unmatched_data_from_password_db->erase(it_local_data_incorrect);
// Add or update the correct sync entry.
if (result.new_android_correct) {
updated_db_entries->push_back(
syncer::SyncChange(FROM_HERE,
it_sync_correct == sync_data_map.end()
? syncer::SyncChange::ACTION_ADD
: syncer::SyncChange::ACTION_UPDATE,
result.new_android_correct.value()));
}
// Add or update the Android Autofill sync entry.
if (result.new_android_incorrect) {
updated_db_entries->push_back(
syncer::SyncChange(FROM_HERE,
it_sync_incorrect == sync_data_map.end()
? syncer::SyncChange::ACTION_ADD
: syncer::SyncChange::ACTION_UPDATE,
result.new_android_incorrect.value()));
}
bool increment = true;
for (auto sync_data_it : {it_sync_incorrect, it_sync_correct}) {
if (sync_data_it != sync_data_map.end()) {
if (sync_data_it == it) {
it = sync_data_map.erase(it);
increment = false;
} else {
sync_data_map.erase(sync_data_it);
}
}
}
if (increment)
++it;
}
} else {
// Not Android.
CreateOrUpdateEntry(*it->second, unmatched_data_from_password_db,
sync_entries, updated_db_entries);
++it;
}
}
}
void PasswordSyncableService::CreateOrUpdateEntry( void PasswordSyncableService::CreateOrUpdateEntry(
const sync_pb::PasswordSpecificsData& password_specifics, const syncer::SyncData& data,
PasswordEntryMap* unmatched_data_from_password_db, PasswordEntryMap* unmatched_data_from_password_db,
SyncEntries* sync_entries, SyncEntries* sync_entries,
syncer::SyncChangeList* updated_db_entries) { syncer::SyncChangeList* updated_db_entries) {
const sync_pb::EntitySpecifics& specifics = data.GetSpecifics();
const sync_pb::PasswordSpecificsData& password_specifics(
specifics.password().client_only_encrypted_data());
std::string tag = MakePasswordSyncTag(password_specifics); std::string tag = MakePasswordSyncTag(password_specifics);
// Check whether the data from sync is already in the password store. // Check whether the data from sync is already in the password store.
PasswordEntryMap::iterator existing_local_entry_iter = PasswordEntryMap::iterator existing_local_entry_iter =
unmatched_data_from_password_db->find(tag); unmatched_data_from_password_db->find(tag);
base::Time time_now = clock_->Now(); base::Time time_now = base::Time::Now();
if (existing_local_entry_iter == unmatched_data_from_password_db->end()) { if (existing_local_entry_iter == unmatched_data_from_password_db->end()) {
// The sync data is not in the password store, so we need to create it in // The sync data is not in the password store, so we need to create it in
// the password store. Add the entry to the new_entries list. // the password store. Add the entry to the new_entries list.
...@@ -726,7 +400,31 @@ syncer::SyncData SyncDataFromPassword( ...@@ -726,7 +400,31 @@ syncer::SyncData SyncDataFromPassword(
sync_pb::EntitySpecifics password_data; sync_pb::EntitySpecifics password_data;
sync_pb::PasswordSpecificsData* password_specifics = sync_pb::PasswordSpecificsData* password_specifics =
password_data.mutable_password()->mutable_client_only_encrypted_data(); password_data.mutable_password()->mutable_client_only_encrypted_data();
PasswordSpecificsFromPassword(password_form, password_specifics); #define CopyField(field) password_specifics->set_##field(password_form.field)
#define CopyStringField(field) \
password_specifics->set_##field(base::UTF16ToUTF8(password_form.field))
CopyField(scheme);
CopyField(signon_realm);
password_specifics->set_origin(password_form.origin.spec());
password_specifics->set_action(password_form.action.spec());
CopyStringField(username_element);
CopyStringField(password_element);
CopyStringField(username_value);
CopyStringField(password_value);
CopyField(preferred);
password_specifics->set_date_created(
password_form.date_created.ToInternalValue());
password_specifics->set_blacklisted(password_form.blacklisted_by_user);
CopyField(type);
CopyField(times_used);
CopyStringField(display_name);
password_specifics->set_avatar_url(password_form.icon_url.spec());
password_specifics->set_federation_url(
password_form.federation_origin.unique()
? std::string()
: password_form.federation_origin.Serialize());
#undef CopyStringField
#undef CopyField
std::string tag = MakePasswordSyncTag(*password_specifics); std::string tag = MakePasswordSyncTag(*password_specifics);
return syncer::SyncData::CreateLocalData(tag, tag, password_data); return syncer::SyncData::CreateLocalData(tag, tag, password_data);
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "base/time/clock.h"
#include "components/password_manager/core/browser/password_store_change.h" #include "components/password_manager/core/browser/password_store_change.h"
#include "components/sync/model/sync_change.h" #include "components/sync/model/sync_change.h"
#include "components/sync/model/sync_data.h" #include "components/sync/model/sync_data.h"
...@@ -61,12 +60,6 @@ class PasswordSyncableService : public syncer::SyncableService { ...@@ -61,12 +60,6 @@ class PasswordSyncableService : public syncer::SyncableService {
void InjectStartSyncFlare( void InjectStartSyncFlare(
const syncer::SyncableService::StartSyncFlare& flare); const syncer::SyncableService::StartSyncFlare& flare);
#if defined(UNIT_TEST)
void set_clock(std::unique_ptr<base::Clock> clock) {
clock_ = std::move(clock);
}
#endif
private: private:
// Map from password sync tag to password form. // Map from password sync tag to password form.
typedef std::map<std::string, autofill::PasswordForm*> PasswordEntryMap; typedef std::map<std::string, autofill::PasswordForm*> PasswordEntryMap;
...@@ -87,23 +80,14 @@ class PasswordSyncableService : public syncer::SyncableService { ...@@ -87,23 +80,14 @@ class PasswordSyncableService : public syncer::SyncableService {
// Uses the |PasswordStore| APIs to change entries. // Uses the |PasswordStore| APIs to change entries.
void WriteToPasswordStore(const SyncEntries& entries); void WriteToPasswordStore(const SyncEntries& entries);
// Goes through |sync_data| and for each entry merges the data with
// |unmatched_data_from_password_db|. The result of merge is recorded in
// |sync_entries| or |updated_db_entries|. Successfully merged elements are
// removed from |unmatched_data_from_password_db|.
void MergeSyncDataWithLocalData(
const syncer::SyncDataList& sync_data,
PasswordEntryMap* unmatched_data_from_password_db,
SyncEntries* sync_entries,
syncer::SyncChangeList* updated_db_entries);
// Examines |data|, an entry in sync db, and updates |sync_entries| or // Examines |data|, an entry in sync db, and updates |sync_entries| or
// |updated_db_entries| accordingly. An element is removed from // |updated_db_entries| accordingly. An element is removed from
// |unmatched_data_from_password_db| if its tag is identical to |data|'s. // |unmatched_data_from_password_db| if its tag is identical to |data|'s.
void CreateOrUpdateEntry(const sync_pb::PasswordSpecificsData& data, static void CreateOrUpdateEntry(
PasswordEntryMap* unmatched_data_from_password_db, const syncer::SyncData& data,
SyncEntries* sync_entries, PasswordEntryMap* unmatched_data_from_password_db,
syncer::SyncChangeList* updated_db_entries); SyncEntries* sync_entries,
syncer::SyncChangeList* updated_db_entries);
// Calls |operation| for each element in |entries| and appends the changes to // Calls |operation| for each element in |entries| and appends the changes to
// |all_changes|. // |all_changes|.
...@@ -125,9 +109,6 @@ class PasswordSyncableService : public syncer::SyncableService { ...@@ -125,9 +109,6 @@ class PasswordSyncableService : public syncer::SyncableService {
// A signal activated by this class to start sync as soon as possible. // A signal activated by this class to start sync as soon as possible.
syncer::SyncableService::StartSyncFlare flare_; syncer::SyncableService::StartSyncFlare flare_;
// Clock for date_synced updates.
std::unique_ptr<base::Clock> clock_;
// True if processing sync changes is in progress. // True if processing sync changes is in progress.
bool is_processing_sync_changes_; bool is_processing_sync_changes_;
......
...@@ -15,9 +15,7 @@ ...@@ -15,9 +15,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_clock.h"
#include "components/password_manager/core/browser/mock_password_store.h" #include "components/password_manager/core/browser/mock_password_store.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/sync/model/sync_change_processor.h" #include "components/sync/model/sync_change_processor.h"
#include "components/sync/model/sync_error.h" #include "components/sync/model/sync_error.h"
#include "components/sync/model/sync_error_factory_mock.h" #include "components/sync/model/sync_error_factory_mock.h"
...@@ -54,8 +52,6 @@ namespace { ...@@ -54,8 +52,6 @@ namespace {
// PasswordForm values for tests. // PasswordForm values for tests.
constexpr autofill::PasswordForm::Type kArbitraryType = constexpr autofill::PasswordForm::Type kArbitraryType =
autofill::PasswordForm::TYPE_GENERATED; autofill::PasswordForm::TYPE_GENERATED;
constexpr char kAndroidAutofillRealm[] = "android://hash@com.magisto";
constexpr char kAndroidCorrectRealm[] = "android://hash@com.magisto/";
constexpr char kIconUrl[] = "https://fb.com/Icon"; constexpr char kIconUrl[] = "https://fb.com/Icon";
constexpr char kDisplayName[] = "Agent Smith"; constexpr char kDisplayName[] = "Agent Smith";
constexpr char kFederationUrl[] = "https://fb.com/"; constexpr char kFederationUrl[] = "https://fb.com/";
...@@ -73,8 +69,8 @@ const sync_pb::PasswordSpecificsData& GetPasswordSpecifics( ...@@ -73,8 +69,8 @@ const sync_pb::PasswordSpecificsData& GetPasswordSpecifics(
return sync_data.GetSpecifics().password().client_only_encrypted_data(); return sync_data.GetSpecifics().password().client_only_encrypted_data();
} }
MATCHER_P(HasDateSynced, time, "") { MATCHER(HasDateSynced, "") {
return arg.date_synced == time; return !arg.date_synced.is_null() && !arg.date_synced.is_max();
} }
MATCHER_P(PasswordIs, form, "") { MATCHER_P(PasswordIs, form, "") {
...@@ -127,14 +123,6 @@ ACTION_P(AppendForm, form) { ...@@ -127,14 +123,6 @@ ACTION_P(AppendForm, form) {
return true; return true;
} }
// The argument is std::vector<autofill::PasswordForm*>*. The caller is
// responsible for the lifetime of all the password forms.
ACTION_P(AppendForms, forms) {
for (const autofill::PasswordForm& form : forms)
arg0->push_back(std::make_unique<autofill::PasswordForm>(form));
return true;
}
// Creates a sync data consisting of password specifics. The sign on realm is // Creates a sync data consisting of password specifics. The sign on realm is
// set to |signon_realm|. // set to |signon_realm|.
SyncData CreateSyncData(const std::string& signon_realm) { SyncData CreateSyncData(const std::string& signon_realm) {
...@@ -185,14 +173,12 @@ class PasswordSyncableServiceWrapper { ...@@ -185,14 +173,12 @@ class PasswordSyncableServiceWrapper {
password_store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr); password_store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
service_.reset( service_.reset(
new PasswordSyncableService(password_store_->GetSyncInterface())); new PasswordSyncableService(password_store_->GetSyncInterface()));
auto clock = std::make_unique<base::SimpleTestClock>();
clock->SetNow(time()); ON_CALL(*password_store_, AddLoginImpl(HasDateSynced()))
service_->set_clock(std::move(clock));
ON_CALL(*password_store_, AddLoginImpl(HasDateSynced(time())))
.WillByDefault(Return(PasswordStoreChangeList())); .WillByDefault(Return(PasswordStoreChangeList()));
ON_CALL(*password_store_, RemoveLoginImpl(_)) ON_CALL(*password_store_, RemoveLoginImpl(_))
.WillByDefault(Return(PasswordStoreChangeList())); .WillByDefault(Return(PasswordStoreChangeList()));
ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced(time()))) ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced()))
.WillByDefault(Return(PasswordStoreChangeList())); .WillByDefault(Return(PasswordStoreChangeList()));
EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber()); EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber());
} }
...@@ -203,8 +189,6 @@ class PasswordSyncableServiceWrapper { ...@@ -203,8 +189,6 @@ class PasswordSyncableServiceWrapper {
PasswordSyncableService* service() { return service_.get(); } PasswordSyncableService* service() { return service_.get(); }
static base::Time time() { return base::Time::FromInternalValue(100000); }
// Returnes the scoped_ptr to |service_| thus NULLing out it. // Returnes the scoped_ptr to |service_| thus NULLing out it.
std::unique_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() { std::unique_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() {
return std::move(service_); return std::move(service_);
...@@ -233,6 +217,7 @@ class PasswordSyncableServiceTest : public testing::Test { ...@@ -233,6 +217,7 @@ class PasswordSyncableServiceTest : public testing::Test {
std::unique_ptr<MockSyncChangeProcessor> processor_; std::unique_ptr<MockSyncChangeProcessor> processor_;
private: private:
// Used by the password store.
base::test::ScopedTaskEnvironment scoped_task_environment_; base::test::ScopedTaskEnvironment scoped_task_environment_;
PasswordSyncableServiceWrapper wrapper_; PasswordSyncableServiceWrapper wrapper_;
}; };
...@@ -677,546 +662,6 @@ TEST_F(PasswordSyncableServiceTest, SerializeNonEmptyPasswordForm) { ...@@ -677,546 +662,6 @@ TEST_F(PasswordSyncableServiceTest, SerializeNonEmptyPasswordForm) {
EXPECT_EQ("https://google.com", specifics.federation_url()); EXPECT_EQ("https://google.com", specifics.federation_url());
} }
// Tests for Android Autofill credentials. Those are saved in the wrong format
// without trailing '/'. Nevertheless, password store should always contain the
// correct values.
class PasswordSyncableServiceAndroidAutofillTest : public testing::Test {
public:
PasswordSyncableServiceAndroidAutofillTest() = default;
static PasswordFormData android_incorrect(double creation_time) {
PasswordFormData data = {autofill::PasswordForm::SCHEME_HTML,
kAndroidAutofillRealm,
kAndroidAutofillRealm,
"",
L"",
L"",
L"",
L"username_value_1",
L"11111",
true,
creation_time};
return data;
}
static PasswordFormData android_correct(double creation_time) {
PasswordFormData data = {autofill::PasswordForm::SCHEME_HTML,
kAndroidCorrectRealm,
kAndroidCorrectRealm,
"",
L"",
L"",
L"",
L"username_value_1",
L"22222",
true,
creation_time};
return data;
}
static PasswordFormData android_incorrect2(double creation_time) {
PasswordFormData data = {autofill::PasswordForm::SCHEME_HTML,
kAndroidAutofillRealm,
kAndroidAutofillRealm,
"",
L"",
L"",
L"",
L"username_value_1",
L"33333",
false,
creation_time};
return data;
}
static PasswordFormData android_correct2(double creation_time) {
PasswordFormData data = {autofill::PasswordForm::SCHEME_HTML,
kAndroidCorrectRealm,
kAndroidCorrectRealm,
"",
L"",
L"",
L"",
L"username_value_1",
L"444444",
false,
creation_time};
return data;
}
static autofill::PasswordForm FormWithCorrectTag(PasswordFormData data) {
autofill::PasswordForm form = *FillPasswordFormWithData(data);
form.signon_realm = kAndroidCorrectRealm;
form.origin = GURL(kAndroidCorrectRealm);
form.date_synced = PasswordSyncableServiceWrapper::time();
return form;
}
static autofill::PasswordForm FormWithAndroidAutofillTag(
PasswordFormData data) {
autofill::PasswordForm form = *FillPasswordFormWithData(data);
form.signon_realm = kAndroidAutofillRealm;
form.origin = GURL(kAndroidAutofillRealm);
form.date_synced = PasswordSyncableServiceWrapper::time();
return form;
}
// Transforms |val| into |count| numbers from 1 to |count| inclusive.
static std::vector<unsigned> ExtractTimestamps(unsigned val, unsigned count) {
std::vector<unsigned> result;
for (unsigned i = 0; i < count; ++i) {
result.push_back(val % count + 1);
val /= count;
}
return result;
}
static testing::Message FormDataMessage(const std::string& prefix,
const PasswordFormData* data) {
testing::Message message;
message << prefix;
if (data)
message << *FillPasswordFormWithData(*data);
else
message << "NULL";
return message;
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
TEST_F(PasswordSyncableServiceAndroidAutofillTest, FourWayMerge) {
for (unsigned val = 0; val < 4 * 4 * 4 * 4; ++val) {
// Generate 4 creation timestamps for all the entries.
std::vector<unsigned> dates = ExtractTimestamps(val, 4);
ASSERT_EQ(4u, dates.size());
const unsigned latest = *std::max_element(dates.begin(), dates.end());
// Sync correct, Sync Android autofill, local correct, local incorrect.
const PasswordFormData data[4] = {
android_correct(dates[0]), android_incorrect(dates[1]),
android_correct2(dates[2]), android_incorrect2(dates[3])};
const PasswordFormData* latest_data =
std::find_if(data, data + 4, [latest](const PasswordFormData& data) {
return data.creation_time == latest;
});
ASSERT_TRUE(latest_data);
std::vector<autofill::PasswordForm> expected_sync_updates;
if (latest_data != &data[0])
expected_sync_updates.push_back(FormWithCorrectTag(*latest_data));
if (latest_data != &data[1])
expected_sync_updates.push_back(FormWithAndroidAutofillTag(*latest_data));
autofill::PasswordForm local_correct = *FillPasswordFormWithData(data[2]);
autofill::PasswordForm local_incorrect = *FillPasswordFormWithData(data[3]);
syncer::SyncData sync_correct =
SyncDataFromPassword(*FillPasswordFormWithData(data[0]));
syncer::SyncData sync_incorrect =
SyncDataFromPassword(*FillPasswordFormWithData(data[1]));
SCOPED_TRACE(*FillPasswordFormWithData(data[0]));
SCOPED_TRACE(*FillPasswordFormWithData(data[1]));
SCOPED_TRACE(*FillPasswordFormWithData(data[2]));
SCOPED_TRACE(*FillPasswordFormWithData(data[3]));
for (bool correct_sync_first : {true, false}) {
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
std::vector<autofill::PasswordForm> stored_forms = {local_correct,
local_incorrect};
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForms(stored_forms));
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
if (latest_data != &data[2]) {
EXPECT_CALL(*wrapper->password_store(),
UpdateLoginImpl(FormWithCorrectTag(*latest_data)));
}
if (latest_data != &data[3]) {
EXPECT_CALL(*wrapper->password_store(),
UpdateLoginImpl(FormWithAndroidAutofillTag(*latest_data)));
}
if (expected_sync_updates.size() == 1) {
EXPECT_CALL(*processor,
ProcessSyncChanges(_, ElementsAre(SyncChangeIs(
SyncChange::ACTION_UPDATE,
expected_sync_updates[0]))));
} else {
EXPECT_CALL(
*processor,
ProcessSyncChanges(_, UnorderedElementsAre(
SyncChangeIs(SyncChange::ACTION_UPDATE,
expected_sync_updates[0]),
SyncChangeIs(SyncChange::ACTION_UPDATE,
expected_sync_updates[1]))));
}
SyncDataList sync_list = {sync_correct, sync_incorrect};
if (!correct_sync_first)
std::swap(sync_list[0], sync_list[1]);
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, sync_list, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations on
// it are checked.
scoped_task_environment_.RunUntilIdle();
}
}
}
TEST_F(PasswordSyncableServiceAndroidAutofillTest, ThreeWayMerge) {
for (int j = 0; j < 4; ++j) {
// Whether the entry exists: Sync correct, Sync Android autofill,
// local correct, local incorrect.
bool entry_present[4] = {true, true, true, true};
entry_present[j] = false;
for (unsigned val = 0; val < 3 * 3 * 3; ++val) {
// Generate 3 creation timestamps for all the entries.
std::vector<unsigned> dates = ExtractTimestamps(val, 3);
ASSERT_EQ(3u, dates.size());
const unsigned latest = *std::max_element(dates.begin(), dates.end());
// Sync correct, Sync Android autofill, local correct, local incorrect.
std::vector<std::unique_ptr<PasswordFormData>> data;
int date_index = 0;
data.push_back(entry_present[0]
? std::make_unique<PasswordFormData>(
android_correct(dates[date_index++]))
: nullptr);
data.push_back(entry_present[1]
? std::make_unique<PasswordFormData>(
android_incorrect(dates[date_index++]))
: nullptr);
data.push_back(entry_present[2]
? std::make_unique<PasswordFormData>(
android_correct2(dates[date_index++]))
: nullptr);
data.push_back(entry_present[3]
? std::make_unique<PasswordFormData>(
android_incorrect2(dates[date_index++]))
: nullptr);
SCOPED_TRACE(val);
SCOPED_TRACE(j);
SCOPED_TRACE(FormDataMessage("data[0]=", data[0].get()));
SCOPED_TRACE(FormDataMessage("data[1]=", data[1].get()));
SCOPED_TRACE(FormDataMessage("data[2]=", data[2].get()));
SCOPED_TRACE(FormDataMessage("data[3]=", data[3].get()));
const PasswordFormData* latest_data =
std::find_if(data.begin(), data.end(),
[latest](const std::unique_ptr<PasswordFormData>& data) {
return data && data->creation_time == latest;
})
->get();
ASSERT_TRUE(latest_data);
std::vector<std::pair<SyncChange::SyncChangeType, autofill::PasswordForm>>
expected_sync_updates;
for (int i = 0; i < 2; ++i) {
if (latest_data != data[i].get()) {
expected_sync_updates.push_back(std::make_pair(
data[i] ? SyncChange::ACTION_UPDATE : SyncChange::ACTION_ADD,
i == 0 ? FormWithCorrectTag(*latest_data)
: FormWithAndroidAutofillTag(*latest_data)));
}
}
std::vector<autofill::PasswordForm> stored_forms;
for (int i = 2; i < 4; ++i) {
if (data[i])
stored_forms.push_back(*FillPasswordFormWithData(*data[i]));
}
SyncDataList sync_list;
for (int i = 0; i < 2; ++i) {
if (data[i]) {
sync_list.push_back(
SyncDataFromPassword(*FillPasswordFormWithData(*data[i])));
}
}
for (bool swap_sync_list : {false, true}) {
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForms(stored_forms));
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
for (int i = 2; i < 4; ++i) {
if (latest_data != data[i].get()) {
autofill::PasswordForm latest_form =
i == 2 ? FormWithCorrectTag(*latest_data)
: FormWithAndroidAutofillTag(*latest_data);
if (data[i]) {
EXPECT_CALL(*wrapper->password_store(),
UpdateLoginImpl(latest_form));
} else {
EXPECT_CALL(*wrapper->password_store(),
AddLoginImpl(latest_form));
}
}
}
if (expected_sync_updates.size() == 0) {
EXPECT_CALL(*processor, ProcessSyncChanges(_, IsEmpty()));
} else if (expected_sync_updates.size() == 1) {
EXPECT_CALL(
*processor,
ProcessSyncChanges(_, ElementsAre(SyncChangeIs(
expected_sync_updates[0].first,
expected_sync_updates[0].second))));
} else if (expected_sync_updates.size() == 2) {
EXPECT_CALL(
*processor,
ProcessSyncChanges(
_, UnorderedElementsAre(
SyncChangeIs(expected_sync_updates[0].first,
expected_sync_updates[0].second),
SyncChangeIs(expected_sync_updates[1].first,
expected_sync_updates[1].second))));
}
if (swap_sync_list && sync_list.size() == 2)
std::swap(sync_list[0], sync_list[1]);
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, sync_list, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations
// on it are checked.
scoped_task_environment_.RunUntilIdle();
}
}
}
}
TEST_F(PasswordSyncableServiceAndroidAutofillTest, TwoWayServerAndLocalMerge) {
for (unsigned i = 0; i < 2 * 2; ++i) {
// Generate 4 different combinations for local/server entries.
std::vector<unsigned> combination = ExtractTimestamps(i, 2);
ASSERT_EQ(2u, combination.size());
const bool sync_data_correct = !!combination[0];
const bool local_data_correct = !!combination[1];
for (unsigned val = 0; val < 2 * 2; ++val) {
std::vector<unsigned> dates = ExtractTimestamps(val, 2);
ASSERT_EQ(2u, dates.size());
const PasswordFormData sync_data = sync_data_correct
? android_correct(dates[0])
: android_incorrect(dates[0]);
const PasswordFormData local_data = local_data_correct
? android_correct2(dates[1])
: android_incorrect2(dates[1]);
const PasswordFormData* latest_data =
dates[1] > dates[0] ? &local_data : &sync_data;
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(*FillPasswordFormWithData(local_data)));
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
if (!local_data_correct || latest_data == &sync_data) {
if (local_data_correct) {
EXPECT_CALL(*wrapper->password_store(),
UpdateLoginImpl(FormWithCorrectTag(*latest_data)));
} else {
EXPECT_CALL(*wrapper->password_store(),
AddLoginImpl(FormWithCorrectTag(*latest_data)));
}
}
if (!local_data_correct && latest_data == &sync_data) {
EXPECT_CALL(*wrapper->password_store(),
UpdateLoginImpl(FormWithAndroidAutofillTag(*latest_data)));
} else if (local_data_correct && !sync_data_correct) {
EXPECT_CALL(*wrapper->password_store(),
AddLoginImpl(FormWithAndroidAutofillTag(*latest_data)));
}
std::vector<std::pair<SyncChange::SyncChangeType, autofill::PasswordForm>>
expected_sync_updates;
// Deal with the correct sync entry and incorrect one.
if (sync_data_correct) {
if (latest_data != &sync_data) {
expected_sync_updates.push_back(std::make_pair(
SyncChange::ACTION_UPDATE, FormWithCorrectTag(*latest_data)));
}
if (!local_data_correct) {
expected_sync_updates.push_back(
std::make_pair(SyncChange::ACTION_ADD,
FormWithAndroidAutofillTag(*latest_data)));
}
} else {
expected_sync_updates.push_back(std::make_pair(
SyncChange::ACTION_ADD, FormWithCorrectTag(*latest_data)));
if (latest_data != &sync_data) {
expected_sync_updates.push_back(
std::make_pair(SyncChange::ACTION_UPDATE,
FormWithAndroidAutofillTag(*latest_data)));
}
}
// Set expectation on |processor|.
if (expected_sync_updates.size() == 0) {
EXPECT_CALL(*processor, ProcessSyncChanges(_, IsEmpty()));
} else if (expected_sync_updates.size() == 1) {
EXPECT_CALL(
*processor,
ProcessSyncChanges(
_, ElementsAre(SyncChangeIs(expected_sync_updates[0].first,
expected_sync_updates[0].second))));
} else if (expected_sync_updates.size() == 2) {
EXPECT_CALL(*processor,
ProcessSyncChanges(
_, UnorderedElementsAre(
SyncChangeIs(expected_sync_updates[0].first,
expected_sync_updates[0].second),
SyncChangeIs(expected_sync_updates[1].first,
expected_sync_updates[1].second))));
}
SyncDataList sync_list = {
SyncDataFromPassword(*FillPasswordFormWithData(sync_data))};
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, sync_list, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations on
// it are checked.
scoped_task_environment_.RunUntilIdle();
}
}
}
TEST_F(PasswordSyncableServiceAndroidAutofillTest, OneEntryOnly) {
for (int i = 0; i < 3; ++i) {
// The case when only local incorrect entry exists is excluded. It's very
// exotic because a local incorrect entry can come only from the server.
// In such a case a copy will be uploaded to the server and next
// MergeDataAndStartSyncing will do a proper migration.
SCOPED_TRACE(i);
// Whether the entry exists: Sync correct, Sync Android autofill,
// local correct, local incorrect.
const bool entry_is_correct = i == 0 || i == 2;
const bool entry_is_local = i >= 2;
PasswordFormData data =
entry_is_correct ? android_correct(100) : android_incorrect(100);
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
if (entry_is_local) {
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(*FillPasswordFormWithData(data)));
} else {
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(Return(true));
}
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
if (!entry_is_local && !entry_is_correct) {
EXPECT_CALL(*wrapper->password_store(),
AddLoginImpl(FormWithAndroidAutofillTag(data)));
}
if (!entry_is_local) {
EXPECT_CALL(*wrapper->password_store(),
AddLoginImpl(FormWithCorrectTag(data)));
}
if (entry_is_correct && !entry_is_local) {
EXPECT_CALL(*processor, ProcessSyncChanges(_, IsEmpty()));
} else {
EXPECT_CALL(*processor,
ProcessSyncChanges(
_, ElementsAre(SyncChangeIs(SyncChange::ACTION_ADD,
FormWithCorrectTag(data)))));
}
SyncDataList sync_list;
if (!entry_is_local) {
sync_list.push_back(
SyncDataFromPassword(*FillPasswordFormWithData(data)));
}
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, sync_list, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations on
// it are checked.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_F(PasswordSyncableServiceAndroidAutofillTest, FourEqualEntries) {
// Sync correct, Sync Android autofill, local correct, local incorrect with
// the same content. Nothing should happen.
const PasswordFormData data = android_correct(100);
autofill::PasswordForm local_correct = FormWithCorrectTag(data);
autofill::PasswordForm local_incorrect = FormWithAndroidAutofillTag(data);
syncer::SyncData sync_correct = SyncDataFromPassword(local_correct);
syncer::SyncData sync_incorrect = SyncDataFromPassword(local_incorrect);
for (bool correct_sync_first : {true, false}) {
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
std::vector<autofill::PasswordForm> stored_forms = {local_correct,
local_incorrect};
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForms(stored_forms));
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
EXPECT_CALL(*processor, ProcessSyncChanges(_, IsEmpty()));
SyncDataList sync_list = {sync_correct, sync_incorrect};
if (!correct_sync_first)
std::swap(sync_list[0], sync_list[1]);
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, sync_list, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations on
// it are checked.
scoped_task_environment_.RunUntilIdle();
}
}
TEST_F(PasswordSyncableServiceAndroidAutofillTest, AndroidCorrectEqualEntries) {
// Sync correct, local correct with the same content. Nothing should happen.
const PasswordFormData data = android_correct(100);
autofill::PasswordForm local_correct = FormWithCorrectTag(data);
syncer::SyncData sync_correct = SyncDataFromPassword(local_correct);
auto wrapper = std::make_unique<PasswordSyncableServiceWrapper>();
auto processor =
std::make_unique<testing::StrictMock<MockSyncChangeProcessor>>();
EXPECT_CALL(*wrapper->password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(local_correct));
EXPECT_CALL(*wrapper->password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
EXPECT_CALL(*processor, ProcessSyncChanges(_, IsEmpty()));
wrapper->service()->MergeDataAndStartSyncing(
syncer::PASSWORDS, {sync_correct}, std::move(processor),
std::unique_ptr<syncer::SyncErrorFactory>());
wrapper.reset();
// Wait til PasswordStore is destroy end therefore all the expectations on
// it are checked.
scoped_task_environment_.RunUntilIdle();
}
} // namespace } // namespace
} // namespace password_manager } // namespace password_manager
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment