Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / password_manager / native_backend_gnome_x.cc
index f6af2cc..1035e95 100644 (file)
 #include <string>
 #include <vector>
 
+#include "base/basictypes.h"
 #include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/time/time.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/password_manager/core/browser/psl_matching_helper.h"
 #include "content/public/browser/browser_thread.h"
 
 using autofill::PasswordForm;
+using base::UTF8ToUTF16;
+using base::UTF16ToUTF8;
 using content::BrowserThread;
 
 #define GNOME_KEYRING_DEFINE_POINTER(name) \
@@ -94,7 +101,7 @@ const char kGnomeKeyringAppString[] = "chrome";
 // Convert the attributes of a given keyring entry into a new PasswordForm.
 // Note: does *not* get the actual password, as that is not a key attribute!
 // Returns NULL if the attributes are for the wrong application.
-PasswordForm* FormFromAttributes(GnomeKeyringAttributeList* attrs) {
+scoped_ptr<PasswordForm> FormFromAttributes(GnomeKeyringAttributeList* attrs) {
   // Read the string and int attributes into the appropriate map.
   std::map<std::string, std::string> string_attr_map;
   std::map<std::string, uint32_t> uint_attr_map;
@@ -108,9 +115,9 @@ PasswordForm* FormFromAttributes(GnomeKeyringAttributeList* attrs) {
   // Check to make sure this is a password we care about.
   const std::string& app_value = string_attr_map["application"];
   if (!base::StringPiece(app_value).starts_with(kGnomeKeyringAppString))
-    return NULL;
+    return scoped_ptr<PasswordForm>();
 
-  PasswordForm* form = new PasswordForm();
+  scoped_ptr<PasswordForm> form(new PasswordForm());
   form->origin = GURL(string_attr_map["origin_url"]);
   form->action = GURL(string_attr_map["action_url"]);
   form->username_element = UTF8ToUTF16(string_attr_map["username_element"]);
@@ -126,38 +133,83 @@ PasswordForm* FormFromAttributes(GnomeKeyringAttributeList* attrs) {
   DCHECK(date_ok);
   form->date_created = base::Time::FromTimeT(date_created);
   form->blacklisted_by_user = uint_attr_map["blacklisted_by_user"];
+  form->type = static_cast<PasswordForm::Type>(uint_attr_map["type"]);
+  form->times_used = uint_attr_map["times_used"];
   form->scheme = static_cast<PasswordForm::Scheme>(uint_attr_map["scheme"]);
-
-  return form;
+  int64 date_synced = 0;
+  base::StringToInt64(string_attr_map["date_synced"], &date_synced);
+  form->date_synced = base::Time::FromInternalValue(date_synced);
+  form->display_name = UTF8ToUTF16(string_attr_map["display_name"]);
+  form->avatar_url = GURL(string_attr_map["avatar_url"]);
+  form->federation_url = GURL(string_attr_map["federation_url"]);
+  form->is_zero_click = uint_attr_map["is_zero_click"];
+
+  return form.Pass();
 }
 
 // Parse all the results from the given GList into a PasswordFormList, and free
 // the GList. PasswordForms are allocated on the heap, and should be deleted by
-// the consumer.
+// the consumer. If not NULL, |lookup_form| is used to filter out results --
+// only credentials with signon realms passing the PSL matching against
+// |lookup_form->signon_realm| will be kept. PSL matched results get their
+// signon_realm, origin, and action rewritten to those of |lookup_form_|, with
+// the original signon_realm saved into the result's original_signon_realm data
+// member.
 void ConvertFormList(GList* found,
+                     const PasswordForm* lookup_form,
                      NativeBackendGnome::PasswordFormList* forms) {
-  GList* element = g_list_first(found);
-  while (element != NULL) {
+  password_manager::PSLDomainMatchMetric psl_domain_match_metric =
+      password_manager::PSL_DOMAIN_MATCH_NONE;
+  for (GList* element = g_list_first(found); element != NULL;
+       element = g_list_next(element)) {
     GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data);
     GnomeKeyringAttributeList* attrs = data->attributes;
 
-    PasswordForm* form = FormFromAttributes(attrs);
+    scoped_ptr<PasswordForm> form(FormFromAttributes(attrs));
     if (form) {
+      if (lookup_form && form->signon_realm != lookup_form->signon_realm) {
+        // This is not an exact match, we try PSL matching.
+        if (lookup_form->scheme != PasswordForm::SCHEME_HTML ||
+            form->scheme != PasswordForm::SCHEME_HTML ||
+            !(password_manager::IsPublicSuffixDomainMatch(
+                lookup_form->signon_realm, form->signon_realm))) {
+          continue;
+        }
+        psl_domain_match_metric = password_manager::PSL_DOMAIN_MATCH_FOUND;
+        form->original_signon_realm = form->signon_realm;
+        form->signon_realm = lookup_form->signon_realm;
+        form->origin = lookup_form->origin;
+        form->action = lookup_form->action;
+      }
       if (data->secret) {
         form->password_value = UTF8ToUTF16(data->secret);
       } else {
         LOG(WARNING) << "Unable to access password from list element!";
       }
-      forms->push_back(form);
+      forms->push_back(form.release());
     } else {
       LOG(WARNING) << "Could not initialize PasswordForm from attributes!";
     }
-
-    element = g_list_next(element);
+  }
+  if (lookup_form) {
+    const GURL signon_realm(lookup_form->signon_realm);
+    std::string registered_domain =
+        password_manager::GetRegistryControlledDomain(signon_realm);
+    UMA_HISTOGRAM_ENUMERATION(
+        "PasswordManager.PslDomainMatchTriggering",
+        password_manager::ShouldPSLDomainMatchingApply(registered_domain)
+            ? psl_domain_match_metric
+            : password_manager::PSL_DOMAIN_MATCH_NOT_USED,
+        password_manager::PSL_DOMAIN_MATCH_COUNT);
   }
 }
 
 // Schema is analagous to the fields in PasswordForm.
+// TODO(gcasto): Adding 'form_data' would be nice, but we would need to
+// serialize in a way that is guaranteed to not have any embedded NULLs. Pickle
+// doesn't make this guarantee, so we just don't serialize this field. Since
+// it's only used to crowd source data collection it doesn't matter that much
+// if it's not available on this platform.
 const GnomeKeyringPasswordSchema kGnomeSchema = {
   GNOME_KEYRING_ITEM_GENERIC_SECRET, {
     { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
@@ -172,6 +224,13 @@ const GnomeKeyringPasswordSchema kGnomeSchema = {
     { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
     { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
     { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
+    { "type", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
+    { "times_used", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
+    { "date_synced", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+    { "display_name", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+    { "avatar_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+    { "federation_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
+    { "is_zero_click", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
     // This field is always "chrome" so that we can search for it.
     { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
     { NULL }
@@ -218,6 +277,27 @@ class GKRMethod : public GnomeKeyringLoader {
   GnomeKeyringResult WaitResult(PasswordFormList* forms);
 
  private:
+  struct GnomeKeyringAttributeListFreeDeleter {
+    inline void operator()(void* list) const {
+      gnome_keyring_attribute_list_free(
+          static_cast<GnomeKeyringAttributeList*>(list));
+    }
+  };
+
+  typedef scoped_ptr<GnomeKeyringAttributeList,
+                     GnomeKeyringAttributeListFreeDeleter> ScopedAttributeList;
+
+  // Helper methods to abbreviate Gnome Keyring long API names.
+  static void AppendString(ScopedAttributeList* list,
+                           const char* name,
+                           const char* value);
+  static void AppendString(ScopedAttributeList* list,
+                           const char* name,
+                           const std::string& value);
+  static void AppendUint32(ScopedAttributeList* list,
+                           const char* name,
+                           guint32 value);
+
   // All these callbacks are called on UI thread.
   static void OnOperationDone(GnomeKeyringResult result, gpointer data);
 
@@ -227,6 +307,14 @@ class GKRMethod : public GnomeKeyringLoader {
   base::WaitableEvent event_;
   GnomeKeyringResult result_;
   NativeBackendGnome::PasswordFormList forms_;
+  // If the credential search is specified by a single form and needs to use PSL
+  // matching, then the specifying form is stored in |lookup_form_|. If PSL
+  // matching is used to find a result, then the results signon realm, origin
+  // and action are stored are replaced by those of |lookup_form_|.
+  // Additionally, |lookup_form_->signon_realm| is also used to narrow down the
+  // found logins to those which indeed PSL-match the look-up. And finally,
+  // |lookup_form_| set to NULL means that PSL matching is not required.
+  scoped_ptr<PasswordForm> lookup_form_;
 };
 
 void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
@@ -236,6 +324,7 @@ void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
   // We don't want to actually save passwords as though on January 1, 1970.
   if (!date_created)
     date_created = time(NULL);
+  int64 date_synced = form.date_synced.ToInternalValue();
   gnome_keyring_store_password(
       &kGnomeSchema,
       NULL,  // Default keyring.
@@ -255,7 +344,14 @@ void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
       "preferred", form.preferred,
       "date_created", base::Int64ToString(date_created).c_str(),
       "blacklisted_by_user", form.blacklisted_by_user,
+      "type", form.type,
+      "times_used", form.times_used,
       "scheme", form.scheme,
+      "date_synced", base::Int64ToString(date_synced).c_str(),
+      "display_name", UTF16ToUTF8(form.display_name).c_str(),
+      "avatar_url", form.avatar_url.spec().c_str(),
+      "federation_url", form.federation_url.spec().c_str(),
+      "is_zero_click", form.is_zero_click,
       "application", app_string,
       NULL);
 }
@@ -263,51 +359,40 @@ void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
 void GKRMethod::AddLoginSearch(const PasswordForm& form,
                                const char* app_string) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  lookup_form_.reset(NULL);
   // Search GNOME Keyring for matching passwords to update.
-  gnome_keyring_find_itemsv(
-      GNOME_KEYRING_ITEM_GENERIC_SECRET,
-      OnOperationGetList,
-      this,  // data
-      NULL,  // destroy_data
-      "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      form.origin.spec().c_str(),
-      "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.username_element).c_str(),
-      "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.username_value).c_str(),
-      "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.password_element).c_str(),
-      "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.submit_element).c_str(),
-      "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      form.signon_realm.c_str(),
-      "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      app_string,
-      NULL);
+  ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
+  AppendString(&attrs, "origin_url", form.origin.spec());
+  AppendString(&attrs, "username_element", UTF16ToUTF8(form.username_element));
+  AppendString(&attrs, "username_value", UTF16ToUTF8(form.username_value));
+  AppendString(&attrs, "password_element", UTF16ToUTF8(form.password_element));
+  AppendString(&attrs, "submit_element", UTF16ToUTF8(form.submit_element));
+  AppendString(&attrs, "signon_realm", form.signon_realm);
+  AppendString(&attrs, "application", app_string);
+  gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
+                           attrs.get(),
+                           OnOperationGetList,
+                           /*data=*/this,
+                           /*destroy_data=*/NULL);
 }
 
 void GKRMethod::UpdateLoginSearch(const PasswordForm& form,
                                   const char* app_string) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  lookup_form_.reset(NULL);
   // Search GNOME Keyring for matching passwords to update.
-  gnome_keyring_find_itemsv(
-      GNOME_KEYRING_ITEM_GENERIC_SECRET,
-      OnOperationGetList,
-      this,  // data
-      NULL,  // destroy_data
-      "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      form.origin.spec().c_str(),
-      "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.username_element).c_str(),
-      "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.username_value).c_str(),
-      "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      UTF16ToUTF8(form.password_element).c_str(),
-      "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      form.signon_realm.c_str(),
-      "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      app_string,
-      NULL);
+  ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
+  AppendString(&attrs, "origin_url", form.origin.spec());
+  AppendString(&attrs, "username_element", UTF16ToUTF8(form.username_element));
+  AppendString(&attrs, "username_value", UTF16ToUTF8(form.username_value));
+  AppendString(&attrs, "password_element", UTF16ToUTF8(form.password_element));
+  AppendString(&attrs, "signon_realm", form.signon_realm);
+  AppendString(&attrs, "application", app_string);
+  gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
+                           attrs.get(),
+                           OnOperationGetList,
+                           /*data=*/this,
+                           /*destroy_data=*/NULL);
 }
 
 void GKRMethod::RemoveLogin(const PasswordForm& form, const char* app_string) {
@@ -319,7 +404,6 @@ void GKRMethod::RemoveLogin(const PasswordForm& form, const char* app_string) {
       this,  // data
       NULL,  // destroy_data
       "origin_url", form.origin.spec().c_str(),
-      "action_url", form.action.spec().c_str(),
       "username_element", UTF16ToUTF8(form.username_element).c_str(),
       "username_value", UTF16ToUTF8(form.username_value).c_str(),
       "password_element", UTF16ToUTF8(form.password_element).c_str(),
@@ -331,47 +415,49 @@ void GKRMethod::RemoveLogin(const PasswordForm& form, const char* app_string) {
 
 void GKRMethod::GetLogins(const PasswordForm& form, const char* app_string) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  lookup_form_.reset(new PasswordForm(form));
   // Search GNOME Keyring for matching passwords.
-  gnome_keyring_find_itemsv(
-      GNOME_KEYRING_ITEM_GENERIC_SECRET,
-      OnOperationGetList,
-      this,  // data
-      NULL,  // destroy_data
-      "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      form.signon_realm.c_str(),
-      "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      app_string,
-      NULL);
+  ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
+  if (!password_manager::ShouldPSLDomainMatchingApply(
+          password_manager::GetRegistryControlledDomain(
+              GURL(form.signon_realm)))) {
+    AppendString(&attrs, "signon_realm", form.signon_realm);
+  }
+  AppendString(&attrs, "application", app_string);
+  gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
+                           attrs.get(),
+                           OnOperationGetList,
+                           /*data=*/this,
+                           /*destroy_data=*/NULL);
 }
 
 void GKRMethod::GetLoginsList(uint32_t blacklisted_by_user,
                               const char* app_string) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  lookup_form_.reset(NULL);
   // Search GNOME Keyring for matching passwords.
-  gnome_keyring_find_itemsv(
-      GNOME_KEYRING_ITEM_GENERIC_SECRET,
-      OnOperationGetList,
-      this,  // data
-      NULL,  // destroy_data
-      "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32,
-      blacklisted_by_user,
-      "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      app_string,
-      NULL);
+  ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
+  AppendUint32(&attrs, "blacklisted_by_user", blacklisted_by_user);
+  AppendString(&attrs, "application", app_string);
+  gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
+                           attrs.get(),
+                           OnOperationGetList,
+                           /*data=*/this,
+                           /*destroy_data=*/NULL);
 }
 
 void GKRMethod::GetAllLogins(const char* app_string) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  lookup_form_.reset(NULL);
   // We need to search for something, otherwise we get no results - so
   // we search for the fixed application string.
-  gnome_keyring_find_itemsv(
-      GNOME_KEYRING_ITEM_GENERIC_SECRET,
-      OnOperationGetList,
-      this,  // data
-      NULL,  // destroy_data
-      "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING,
-      app_string,
-      NULL);
+  ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
+  AppendString(&attrs, "application", app_string);
+  gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
+                           attrs.get(),
+                           OnOperationGetList,
+                           /*data=*/this,
+                           /*destroy_data=*/NULL);
 }
 
 GnomeKeyringResult GKRMethod::WaitResult() {
@@ -395,6 +481,27 @@ GnomeKeyringResult GKRMethod::WaitResult(PasswordFormList* forms) {
 }
 
 // static
+void GKRMethod::AppendString(GKRMethod::ScopedAttributeList* list,
+                             const char* name,
+                             const char* value) {
+  gnome_keyring_attribute_list_append_string(list->get(), name, value);
+}
+
+// static
+void GKRMethod::AppendString(GKRMethod::ScopedAttributeList* list,
+                             const char* name,
+                             const std::string& value) {
+  AppendString(list, name, value.c_str());
+}
+
+// static
+void GKRMethod::AppendUint32(GKRMethod::ScopedAttributeList* list,
+                             const char* name,
+                             guint32 value) {
+  gnome_keyring_attribute_list_append_uint32(list->get(), name, value);
+}
+
+// static
 void GKRMethod::OnOperationDone(GnomeKeyringResult result, gpointer data) {
   GKRMethod* method = static_cast<GKRMethod*>(data);
   method->result_ = result;
@@ -408,25 +515,16 @@ void GKRMethod::OnOperationGetList(GnomeKeyringResult result, GList* list,
   method->result_ = result;
   method->forms_.clear();
   // |list| will be freed after this callback returns, so convert it now.
-  ConvertFormList(list, &method->forms_);
+  ConvertFormList(list, method->lookup_form_.get(), &method->forms_);
+  method->lookup_form_.reset(NULL);
   method->event_.Signal();
 }
 
 }  // namespace
 
-NativeBackendGnome::NativeBackendGnome(LocalProfileId id, PrefService* prefs)
-    : profile_id_(id), prefs_(prefs) {
-  // TODO(mdm): after a few more releases, remove the code which is now dead due
-  // to the true || here, and simplify this code. We don't do it yet to make it
-  // easier to revert if necessary.
-  if (true || PasswordStoreX::PasswordsUseLocalProfileId(prefs)) {
-    app_string_ = GetProfileSpecificAppString();
-    // We already did the migration previously. Don't try again.
-    migrate_tried_ = true;
-  } else {
-    app_string_ = kGnomeKeyringAppString;
-    migrate_tried_ = false;
-  }
+NativeBackendGnome::NativeBackendGnome(LocalProfileId id)
+    : profile_id_(id) {
+  app_string_ = GetProfileSpecificAppString();
 }
 
 NativeBackendGnome::~NativeBackendGnome() {
@@ -449,13 +547,11 @@ bool NativeBackendGnome::RawAddLogin(const PasswordForm& form) {
                << gnome_keyring_result_to_message(result);
     return false;
   }
-  // Successful write. Try migration if necessary.
-  if (!migrate_tried_)
-    MigrateToProfileSpecificLogins();
   return true;
 }
 
-bool NativeBackendGnome::AddLogin(const PasswordForm& form) {
+password_manager::PasswordStoreChangeList NativeBackendGnome::AddLogin(
+    const PasswordForm& form) {
   // Based on LoginDatabase::AddLogin(), we search for an existing match based
   // on origin_url, username_element, username_value, password_element, submit
   // element, and signon_realm first, remove that, and then add the new entry.
@@ -467,83 +563,75 @@ bool NativeBackendGnome::AddLogin(const PasswordForm& form) {
                           base::Bind(&GKRMethod::AddLoginSearch,
                                      base::Unretained(&method),
                                      form, app_string_.c_str()));
-  PasswordFormList forms;
-  GnomeKeyringResult result = method.WaitResult(&forms);
+  ScopedVector<autofill::PasswordForm> forms;
+  GnomeKeyringResult result = method.WaitResult(&forms.get());
   if (result != GNOME_KEYRING_RESULT_OK &&
       result != GNOME_KEYRING_RESULT_NO_MATCH) {
     LOG(ERROR) << "Keyring find failed: "
                << gnome_keyring_result_to_message(result);
-    return false;
+    return password_manager::PasswordStoreChangeList();
   }
+  password_manager::PasswordStoreChangeList changes;
   if (forms.size() > 0) {
     if (forms.size() > 1) {
       LOG(WARNING) << "Adding login when there are " << forms.size()
                    << " matching logins already! Will replace only the first.";
     }
 
-    // We try migration before updating the existing logins, since otherwise
-    // we'd do it after making some but not all of the changes below.
-    if (forms.size() > 0 && !migrate_tried_)
-      MigrateToProfileSpecificLogins();
-
-    RemoveLogin(*forms[0]);
-    for (size_t i = 0; i < forms.size(); ++i)
-      delete forms[i];
+    if (RemoveLogin(*forms[0])) {
+      changes.push_back(password_manager::PasswordStoreChange(
+          password_manager::PasswordStoreChange::REMOVE, *forms[0]));
+    }
+  }
+  if (RawAddLogin(form)) {
+    changes.push_back(password_manager::PasswordStoreChange(
+        password_manager::PasswordStoreChange::ADD, form));
   }
-  return RawAddLogin(form);
+  return changes;
 }
 
-bool NativeBackendGnome::UpdateLogin(const PasswordForm& form) {
+bool NativeBackendGnome::UpdateLogin(
+    const PasswordForm& form,
+    password_manager::PasswordStoreChangeList* changes) {
   // Based on LoginDatabase::UpdateLogin(), we search for forms to update by
   // origin_url, username_element, username_value, password_element, and
   // signon_realm. We then compare the result to the updated form. If they
-  // differ in any of the action, password_value, ssl_valid, or preferred
-  // fields, then we remove the original, and then add the new entry. We'd add
-  // the new one first, and then delete the original, but then the delete might
-  // actually delete the newly-added entry!
+  // differ in any of the mutable fields, then we remove the original, and
+  // then add the new entry. We'd add the new one first, and then delete the
+  // original, but then the delete might actually delete the newly-added entry!
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+  DCHECK(changes);
+  changes->clear();
   GKRMethod method;
   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                           base::Bind(&GKRMethod::UpdateLoginSearch,
                                      base::Unretained(&method),
                                      form, app_string_.c_str()));
-  PasswordFormList forms;
-  GnomeKeyringResult result = method.WaitResult(&forms);
+  ScopedVector<autofill::PasswordForm> forms;
+  GnomeKeyringResult result = method.WaitResult(&forms.get());
   if (result != GNOME_KEYRING_RESULT_OK) {
     LOG(ERROR) << "Keyring find failed: "
                << gnome_keyring_result_to_message(result);
     return false;
   }
 
-  // We try migration before updating the existing logins, since otherwise
-  // we'd do it after making some but not all of the changes below.
-  if (forms.size() > 0 && !migrate_tried_)
-    MigrateToProfileSpecificLogins();
-
-  bool ok = true;
+  bool removed = false;
   for (size_t i = 0; i < forms.size(); ++i) {
-    if (forms[i]->action != form.action ||
-        forms[i]->password_value != form.password_value ||
-        forms[i]->ssl_valid != form.ssl_valid ||
-        forms[i]->preferred != form.preferred) {
+    if (*forms[i] != form) {
       RemoveLogin(*forms[i]);
+      removed = true;
     }
   }
-  for (size_t i = 0; i < forms.size(); ++i) {
-    if (forms[i]->action != form.action ||
-        forms[i]->password_value != form.password_value ||
-        forms[i]->ssl_valid != form.ssl_valid ||
-        forms[i]->preferred != form.preferred) {
-      forms[i]->action = form.action;
-      forms[i]->password_value = form.password_value;
-      forms[i]->ssl_valid = form.ssl_valid;
-      forms[i]->preferred = form.preferred;
-      if (!RawAddLogin(*forms[i]))
-        ok = false;
-    }
-    delete forms[i];
+  if (!removed)
+    return true;
+
+  if (RawAddLogin(form)) {
+    password_manager::PasswordStoreChange change(
+        password_manager::PasswordStoreChange::UPDATE, form);
+    changes->push_back(change);
+    return true;
   }
-  return ok;
+  return false;
 }
 
 bool NativeBackendGnome::RemoveLogin(const PasswordForm& form) {
@@ -561,35 +649,22 @@ bool NativeBackendGnome::RemoveLogin(const PasswordForm& form) {
                  << gnome_keyring_result_to_message(result);
     return false;
   }
-  // Successful write. Try migration if necessary. Note that presumably if we've
-  // been asked to delete a login, it's because we returned it previously; thus,
-  // this will probably never happen since we'd have already tried migration.
-  if (!migrate_tried_)
-    MigrateToProfileSpecificLogins();
   return true;
 }
 
 bool NativeBackendGnome::RemoveLoginsCreatedBetween(
-    const base::Time& delete_begin,
-    const base::Time& delete_end) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
-  bool ok = true;
-  // We could walk the list and delete items as we find them, but it is much
-  // easier to build the list and use RemoveLogin() to delete them.
-  PasswordFormList forms;
-  if (!GetAllLogins(&forms))
-    return false;
-  // No need to try migration here: GetAllLogins() does it.
+    base::Time delete_begin,
+    base::Time delete_end,
+    password_manager::PasswordStoreChangeList* changes) {
+  return RemoveLoginsBetween(
+      delete_begin, delete_end, CREATION_TIMESTAMP, changes);
+}
 
-  for (size_t i = 0; i < forms.size(); ++i) {
-    if (delete_begin <= forms[i]->date_created &&
-        (delete_end.is_null() || forms[i]->date_created < delete_end)) {
-      if (!RemoveLogin(*forms[i]))
-        ok = false;
-    }
-    delete forms[i];
-  }
-  return ok;
+bool NativeBackendGnome::RemoveLoginsSyncedBetween(
+    base::Time delete_begin,
+    base::Time delete_end,
+    password_manager::PasswordStoreChangeList* changes) {
+  return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
 }
 
 bool NativeBackendGnome::GetLogins(const PasswordForm& form,
@@ -608,33 +683,6 @@ bool NativeBackendGnome::GetLogins(const PasswordForm& form,
                << gnome_keyring_result_to_message(result);
     return false;
   }
-  // Successful read of actual data. Try migration if necessary.
-  if (!migrate_tried_)
-    MigrateToProfileSpecificLogins();
-  return true;
-}
-
-bool NativeBackendGnome::GetLoginsCreatedBetween(const base::Time& get_begin,
-                                                 const base::Time& get_end,
-                                                 PasswordFormList* forms) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
-  // We could walk the list and add items as we find them, but it is much
-  // easier to build the list and then filter the results.
-  PasswordFormList all_forms;
-  if (!GetAllLogins(&all_forms))
-    return false;
-  // No need to try migration here: GetAllLogins() does it.
-
-  forms->reserve(forms->size() + all_forms.size());
-  for (size_t i = 0; i < all_forms.size(); ++i) {
-    if (get_begin <= all_forms[i]->date_created &&
-        (get_end.is_null() || all_forms[i]->date_created < get_end)) {
-      forms->push_back(all_forms[i]);
-    } else {
-      delete all_forms[i];
-    }
-  }
-
   return true;
 }
 
@@ -665,9 +713,6 @@ bool NativeBackendGnome::GetLoginsList(PasswordFormList* forms,
                << gnome_keyring_result_to_message(result);
     return false;
   }
-  // Successful read of actual data. Try migration if necessary.
-  if (!migrate_tried_)
-    MigrateToProfileSpecificLogins();
   return true;
 }
 
@@ -685,56 +730,65 @@ bool NativeBackendGnome::GetAllLogins(PasswordFormList* forms) {
                << gnome_keyring_result_to_message(result);
     return false;
   }
-  // Successful read of actual data. Try migration if necessary.
-  if (!migrate_tried_)
-    MigrateToProfileSpecificLogins();
   return true;
 }
 
-std::string NativeBackendGnome::GetProfileSpecificAppString() const {
-  // Originally, the application string was always just "chrome" and used only
-  // so that we had *something* to search for since GNOME Keyring won't search
-  // for nothing. Now we use it to distinguish passwords for different profiles.
-  return base::StringPrintf("%s-%d", kGnomeKeyringAppString, profile_id_);
-}
-
-void NativeBackendGnome::MigrateToProfileSpecificLogins() {
+bool NativeBackendGnome::GetLoginsBetween(base::Time get_begin,
+                                          base::Time get_end,
+                                          TimestampToCompare date_to_compare,
+                                          PasswordFormList* forms) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+  // We could walk the list and add items as we find them, but it is much
+  // easier to build the list and then filter the results.
+  PasswordFormList all_forms;
+  if (!GetAllLogins(&all_forms))
+    return false;
 
-  DCHECK(!migrate_tried_);
-  DCHECK_EQ(app_string_, kGnomeKeyringAppString);
-
-  // Record the fact that we've attempted migration already right away, so that
-  // we don't get recursive calls back to MigrateToProfileSpecificLogins().
-  migrate_tried_ = true;
+  base::Time autofill::PasswordForm::*date_member =
+      date_to_compare == CREATION_TIMESTAMP
+          ? &autofill::PasswordForm::date_created
+          : &autofill::PasswordForm::date_synced;
+  for (size_t i = 0; i < all_forms.size(); ++i) {
+    if (get_begin <= all_forms[i]->*date_member &&
+        (get_end.is_null() || all_forms[i]->*date_member < get_end)) {
+      forms->push_back(all_forms[i]);
+    } else {
+      delete all_forms[i];
+    }
+  }
 
-  // First get all the logins, using the old app string.
-  PasswordFormList forms;
-  if (!GetAllLogins(&forms))
-    return;
+  return true;
+}
 
-  // Now switch to a profile-specific app string.
-  app_string_ = GetProfileSpecificAppString();
+bool NativeBackendGnome::RemoveLoginsBetween(
+    base::Time get_begin,
+    base::Time get_end,
+    TimestampToCompare date_to_compare,
+    password_manager::PasswordStoreChangeList* changes) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
+  DCHECK(changes);
+  changes->clear();
+  // We could walk the list and delete items as we find them, but it is much
+  // easier to build the list and use RemoveLogin() to delete them.
+  ScopedVector<autofill::PasswordForm> forms;
+  if (!GetLoginsBetween(get_begin, get_end, date_to_compare, &forms.get()))
+    return false;
 
-  // Try to add all the logins with the new app string.
   bool ok = true;
   for (size_t i = 0; i < forms.size(); ++i) {
-    if (!RawAddLogin(*forms[i]))
+    if (RemoveLogin(*forms[i])) {
+      changes->push_back(password_manager::PasswordStoreChange(
+          password_manager::PasswordStoreChange::REMOVE, *forms[i]));
+    } else {
       ok = false;
-    delete forms[i];
+    }
   }
+  return ok;
+}
 
-  if (ok) {
-    // All good! Keep the new app string and set a persistent pref.
-    // NOTE: We explicitly don't delete the old passwords yet. They are
-    // potentially shared with other profiles and other user data dirs!
-    // Each other profile must be able to migrate the shared data as well,
-    // so we must leave it alone. After a few releases, we'll add code to
-    // delete them, and eventually remove this migration code.
-    // TODO(mdm): follow through with the plan above.
-    PasswordStoreX::SetPasswordsUseLocalProfileId(prefs_);
-  } else {
-    // We failed to migrate for some reason. Use the old app string.
-    app_string_ = kGnomeKeyringAppString;
-  }
+std::string NativeBackendGnome::GetProfileSpecificAppString() const {
+  // Originally, the application string was always just "chrome" and used only
+  // so that we had *something* to search for since GNOME Keyring won't search
+  // for nothing. Now we use it to distinguish passwords for different profiles.
+  return base::StringPrintf("%s-%d", kGnomeKeyringAppString, profile_id_);
 }