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

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

Issue 1066543004: Update affiliated web credentials when Android credentials are updated. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix the correct MessageLoopProxy occurrence this time. Created 5 years, 8 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_store.cc
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index f4cd06586a4c6b13c1ca9fa86376e01b9c62dc5a..5b13a3cb86e2f294dc3ac95555d326abdf738cae 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -65,6 +65,7 @@ PasswordStore::PasswordStore(
: main_thread_runner_(main_thread_runner),
db_thread_runner_(db_thread_runner),
observers_(new ObserverListThreadSafe<Observer>()),
+ is_propagating_password_changes_to_web_credentials_enabled_(false),
shutdown_called_(false) {
}
@@ -218,6 +219,36 @@ void PasswordStore::LogStatsForBulkDeletionDuringRollback(int num_deletions) {
num_deletions);
}
+PasswordStoreChangeList PasswordStore::AddLoginSync(const PasswordForm& form) {
+ // There is no good way to check if the password is actually up-to-date, or
+ // at least to check if it was actually changed. Assume it is.
+ if (AffiliatedMatchHelper::IsValidAndroidCredential(form))
+ ScheduleFindAndUpdateAffiliatedWebLogins(form);
+ return AddLoginImpl(form);
+}
+
+PasswordStoreChangeList PasswordStore::UpdateLoginSync(
+ const PasswordForm& form) {
+ if (AffiliatedMatchHelper::IsValidAndroidCredential(form)) {
+ // Ideally, a |form| would not be updated in any way unless it was ensured
+ // that it, as a whole, can be used for a successful login. This, sadly, can
+ // not be guaranteed. It might be that |form| just contains updates to some
+ // meta-attribute, while it still has an out-of-date password. If such a
+ // password were to be propagated to affiliated credentials in that case, it
+ // may very well overwrite the actual, up-to-date password. Try to mitigate
+ // this risk by ignoring updates unless they actually update the password.
+ scoped_ptr<PasswordForm> old_form(GetLoginImpl(form));
+ if (old_form && form.password_value != old_form->password_value)
+ ScheduleFindAndUpdateAffiliatedWebLogins(form);
+ }
+ return UpdateLoginImpl(form);
+}
+
+PasswordStoreChangeList PasswordStore::RemoveLoginSync(
+ const PasswordForm& form) {
+ return RemoveLoginImpl(form);
+}
+
void PasswordStore::NotifyLoginsChanged(
const PasswordStoreChangeList& changes) {
DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
@@ -301,6 +332,134 @@ void PasswordStore::ScheduleGetLoginsWithAffiliations(
additional_android_realms));
}
+scoped_ptr<PasswordForm> PasswordStore::GetLoginImpl(
+ const PasswordForm& primary_key) {
+ DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
+ ScopedVector<PasswordForm> candidates(
+ FillMatchingLogins(primary_key, DISALLOW_PROMPT));
+ for (PasswordForm*& candidate : candidates) {
+ if (candidate->signon_realm == primary_key.signon_realm &&
+ candidate->username_element == primary_key.username_element &&
+ candidate->username_value == primary_key.username_value &&
+ candidate->password_element == primary_key.password_element &&
+ candidate->origin == primary_key.origin &&
+ !candidate->IsPublicSuffixMatch()) {
+ scoped_ptr<PasswordForm> result(candidate);
+ candidate = nullptr;
+ return result.Pass();
+ }
+ }
+ return make_scoped_ptr<PasswordForm>(nullptr);
+}
+
+void PasswordStore::FindAndUpdateAffiliatedWebLogins(
+ const PasswordForm& added_or_updated_android_form) {
+ if (!affiliated_match_helper_ ||
+ !is_propagating_password_changes_to_web_credentials_enabled_) {
+ return;
+ }
+ affiliated_match_helper_->GetAffiliatedWebRealms(
+ added_or_updated_android_form,
+ base::Bind(&PasswordStore::ScheduleUpdateAffiliatedWebLoginsImpl, this,
+ added_or_updated_android_form));
+}
+
+void PasswordStore::ScheduleFindAndUpdateAffiliatedWebLogins(
+ const PasswordForm& added_or_updated_android_form) {
+ main_thread_runner_->PostTask(
+ FROM_HERE, base::Bind(&PasswordStore::FindAndUpdateAffiliatedWebLogins,
+ this, added_or_updated_android_form));
+}
+
+void PasswordStore::UpdateAffiliatedWebLoginsImpl(
+ const PasswordForm& updated_android_form,
+ const std::vector<std::string>& affiliated_web_realms) {
+ DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
+ PasswordStoreChangeList all_changes;
+ for (const std::string& affiliated_web_realm : affiliated_web_realms) {
+ PasswordForm web_form_template;
+ web_form_template.scheme = PasswordForm::SCHEME_HTML;
+ web_form_template.signon_realm = affiliated_web_realm;
+ ScopedVector<PasswordForm> web_logins(
+ FillMatchingLogins(web_form_template, DISALLOW_PROMPT));
+ for (PasswordForm* web_login : web_logins) {
+ // Do not update HTTP logins, logins saved under insecure conditions, and
+ // non-HTML login forms; PSL matches; logins with a different username;
+ // and logins with the same password (to avoid generating no-op updates).
+ if (!AffiliatedMatchHelper::IsValidWebCredential(*web_login) ||
+ web_login->IsPublicSuffixMatch() ||
+ web_login->username_value != updated_android_form.username_value ||
+ web_login->password_value == updated_android_form.password_value)
+ continue;
+
+ // If the |web_login| was updated in the same or a later chunk of Sync
+ // changes, assume that it is more recent and do not update it. Note that
+ // this check is far from perfect conflict resolution and mostly prevents
+ // long-dormant Sync clients doing damage when they wake up in the face
+ // of the following list of changes:
+ //
+ // Time Source Change
+ // ==== ====== ======
+ // #1 Android android_login.password_value = "A"
+ // #2 Client A web_login.password_value = "A" (propagation)
+ // #3 Client A web_login.password_value = "B" (manual overwrite)
+ //
+ // When long-dormant Sync client B wakes up, it will only get a distilled
+ // subset of not-yet-obsoleted changes {1, 3}. In this case, client B must
+ // not propagate password "A" to |web_login|. This is prevented as change
+ // #3 will arrive either in the same/later chunk of sync changes, so the
+ // |date_synced| of |web_login| value will be greater or equal.
+ //
+ // Note that this solution has several shortcomings:
+ //
+ // (1) It will not prevent local changes to |web_login| from being
+ // overwritten if they were made shortly after start-up, before
+ // Sync changes are applied. This should be tolerable.
+ //
+ // (2) It assumes that all Sync clients are fully capable of propagating
+ // changes to web credentials on their own. If client C runs an
+ // older version of Chrome and updates the password for |web_login|
+ // around the time when the |android_login| is updated, the updated
+ // password will not be propagated by client B to |web_login| when
+ // it wakes up, regardless of the temporal order of the original
+ // changes, as client B will see both credentials having the same
+ // |data_synced|.
+ //
+ // (2a) Above could be mitigated by looking not only at |data_synced|,
+ // but also at the actual order of Sync changes.
+ //
+ // (2b) However, (2a) is still not workable, as a Sync change is made
+ // when any attribute of the credential is updated, not only the
+ // password. Hence it is not possible for client B to distinguish
+ // between two following two event orders:
+ //
+ // #1 Android android_login.password_value = "A"
+ // #2 Client C web_login.password_value = "B" (manual overwrite)
+ // #3 Android android_login.random_attribute = "..."
+ //
+ // #1 Client C web_login.password_value = "B" (manual overwrite)
+ // #2 Android android_login.password_value = "A"
+ //
+ // And so it must assume that it is unsafe to update |web_login|.
+ if (web_login->date_synced >= updated_android_form.date_synced)
+ continue;
+
+ web_login->password_value = updated_android_form.password_value;
+
+ PasswordStoreChangeList changes = UpdateLoginImpl(*web_login);
+ all_changes.insert(all_changes.end(), changes.begin(), changes.end());
+ }
+ }
+ NotifyLoginsChanged(all_changes);
+}
+
+void PasswordStore::ScheduleUpdateAffiliatedWebLoginsImpl(
+ const PasswordForm& updated_android_form,
+ const std::vector<std::string>& affiliated_web_realms) {
+ ScheduleTask(base::Bind(&PasswordStore::UpdateAffiliatedWebLoginsImpl, this,
+ updated_android_form, affiliated_web_realms));
+}
+
void PasswordStore::InitSyncableService(
const syncer::SyncableService::StartSyncFlare& flare) {
DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());

Powered by Google App Engine
This is Rietveld 408576698