Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(469)

Unified Diff: components/password_manager/core/browser/password_syncable_service.cc

Issue 2981293003: Save Android Autofill credentials in the right format. (Closed)
Patch Set: test comment Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/password_manager/core/browser/password_syncable_service.cc
diff --git a/components/password_manager/core/browser/password_syncable_service.cc b/components/password_manager/core/browser/password_syncable_service.cc
index 75d20b2aeb5c91aacc9f1317575af85918021162..6584b88d194131ed25df970e8cd1b3a0065c1edd 100644
--- a/components/password_manager/core/browser/password_syncable_service.cc
+++ b/components/password_manager/core/browser/password_syncable_service.cc
@@ -12,8 +12,12 @@
#include "base/location.h"
#include "base/memory/ptr_util.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/time/default_clock.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_store_sync.h"
#include "components/sync/model/sync_error_factory.h"
@@ -36,20 +40,12 @@ std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
namespace {
// Returns true iff |password_specifics| and |password_specifics| are equal
-// memberwise.
-bool AreLocalAndSyncPasswordsEqual(
+// in the fields which don't comprise the sync tag.
+bool AreLocalAndSyncPasswordsNonSyncTagEqual(
const sync_pb::PasswordSpecificsData& password_specifics,
const autofill::PasswordForm& password_form) {
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() &&
- 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.preferred == password_specifics.preferred() &&
@@ -66,6 +62,38 @@ bool AreLocalAndSyncPasswordsEqual(
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(
PasswordStoreChange::Type type) {
switch (type) {
@@ -91,6 +119,190 @@ void AppendPasswordFromSpecifics(
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
struct PasswordSyncableService::SyncEntries {
@@ -124,8 +336,9 @@ struct PasswordSyncableService::SyncEntries {
PasswordSyncableService::PasswordSyncableService(
PasswordStoreSync* password_store)
- : password_store_(password_store), is_processing_sync_changes_(false) {
-}
+ : password_store_(password_store),
+ clock_(new base::DefaultClock),
+ is_processing_sync_changes_(false) {}
PasswordSyncableService::~PasswordSyncableService() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -163,17 +376,12 @@ syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
}
merge_result.set_num_items_before_association(new_local_entries.size());
+ // Changes from Sync to be applied locally.
SyncEntries sync_entries;
// Changes from password db that need to be propagated to sync.
syncer::SyncChangeList updated_db_entries;
- for (syncer::SyncDataList::const_iterator sync_iter =
- initial_sync_data.begin();
- sync_iter != initial_sync_data.end(); ++sync_iter) {
- CreateOrUpdateEntry(*sync_iter,
- &new_local_entries,
- &sync_entries,
- &updated_db_entries);
- }
+ MergeSyncDataWithLocalData(initial_sync_data, &new_local_entries,
+ &sync_entries, &updated_db_entries);
for (PasswordEntryMap::iterator it = new_local_entries.begin();
it != new_local_entries.end(); ++it) {
@@ -237,7 +445,7 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
SyncEntries sync_entries;
- base::Time time_now = base::Time::Now();
+ base::Time time_now = clock_->Now();
for (syncer::SyncChangeList::const_iterator it = change_list.begin();
it != change_list.end(); ++it) {
@@ -250,6 +458,13 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
}
AppendPasswordFromSpecifics(
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);
@@ -339,21 +554,132 @@ void PasswordSyncableService::WriteToPasswordStore(const SyncEntries& entries) {
password_store_->NotifyLoginsChanged(changes);
}
-// static
+void PasswordSyncableService::MergeSyncDataWithLocalData(
+ 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(base::MakeUnique<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(base::MakeUnique<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(
- const syncer::SyncData& data,
+ const sync_pb::PasswordSpecificsData& password_specifics,
PasswordEntryMap* unmatched_data_from_password_db,
SyncEntries* sync_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);
// Check whether the data from sync is already in the password store.
PasswordEntryMap::iterator existing_local_entry_iter =
unmatched_data_from_password_db->find(tag);
- base::Time time_now = base::Time::Now();
+ base::Time time_now = clock_->Now();
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 password store. Add the entry to the new_entries list.
@@ -401,31 +727,7 @@ syncer::SyncData SyncDataFromPassword(
sync_pb::EntitySpecifics password_data;
sync_pb::PasswordSpecificsData* password_specifics =
password_data.mutable_password()->mutable_client_only_encrypted_data();
-#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
+ PasswordSpecificsFromPassword(password_form, password_specifics);
std::string tag = MakePasswordSyncTag(*password_specifics);
return syncer::SyncData::CreateLocalData(tag, tag, password_data);

Powered by Google App Engine
This is Rietveld 408576698