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()); |