[epiphany/wip/sync: 4/4] [wip] sync: Implement saved passwords sync



commit 9503634b9a95191b6653976fd57f221c9931e7ae
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Sun Apr 30 22:11:15 2017 +0300

    [wip] sync: Implement saved passwords sync

 data/org.gnome.epiphany.gschema.xml          |   15 +
 embed/meson.build                            |    1 +
 embed/web-extension/ephy-embed-form-auth.c   |   14 +
 embed/web-extension/ephy-embed-form-auth.h   |   23 +-
 embed/web-extension/ephy-web-extension.c     |   38 +-
 embed/web-extension/meson.build              |    1 +
 lib/ephy-password-manager.c                  |  480 ---------------------
 lib/ephy-password-manager.h                  |   77 ----
 lib/ephy-password-record.c                   |  236 -----------
 lib/ephy-prefs.h                             |    3 +
 lib/meson.build                              |    2 -
 lib/sync/ephy-password-manager.c             |  580 ++++++++++++++++++++++++++
 lib/sync/ephy-password-manager.h             |   81 ++++
 lib/sync/ephy-password-record.c              |  379 +++++++++++++++++
 lib/{ => sync}/ephy-password-record.h        |   18 +-
 lib/sync/meson.build                         |    2 +
 meson.build                                  |    2 +-
 src/ephy-shell.c                             |    9 +-
 src/passwords-dialog.c                       |    8 +-
 src/prefs-dialog.c                           |   29 +-
 src/profile-migrator/ephy-profile-migrator.c |   13 +-
 src/resources/gtk/prefs-dialog.ui            |    7 +
 22 files changed, 1165 insertions(+), 853 deletions(-)
---
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 2264bbd..ddc61dc 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -302,6 +302,21 @@
                        <summary>Initial sync or normal sync</summary>
                        <description>TRUE if bookmarks collection needs to be synced for the first time, 
FALSE otherwise.</description>
                </key>
+               <key type="b" name="sync-passwords-enabled">
+                       <default>false</default>
+                       <summary>Enable passwords sync</summary>
+                       <description>TRUE if passwords collection should be synced, FALSE 
otherwise.</description>
+               </key>
+               <key type="d" name="sync-passwords-time">
+                       <default>0</default>
+                       <summary>Passwords sync timestamp</summary>
+                       <description>The timestamp at which last passwords sync was made.</description>
+               </key>
+               <key type="b" name="sync-passwords-initial">
+                       <default>true</default>
+                       <summary>Initial sync or normal sync</summary>
+                       <description>TRUE if passwords collection needs to be synced for the first time, 
FALSE otherwise.</description>
+               </key>
        </schema>
        <enum id="org.gnome.Epiphany.Permission">
                <value nick="undecided" value="-1"/>
diff --git a/embed/meson.build b/embed/meson.build
index 99812c5..f6d2297 100644
--- a/embed/meson.build
+++ b/embed/meson.build
@@ -54,6 +54,7 @@ libephyembed_includes = include_directories(
   '../lib/contrib',
   '../lib/contrib/gvdb',
   '../lib/history',
+  '../lib/sync',
   '../lib/widgets',
   '../lib/widgets/contrib'
 )
diff --git a/embed/web-extension/ephy-embed-form-auth.c b/embed/web-extension/ephy-embed-form-auth.c
index 930cb3a..ae36027 100644
--- a/embed/web-extension/ephy-embed-form-auth.c
+++ b/embed/web-extension/ephy-embed-form-auth.c
@@ -29,6 +29,7 @@ struct _EphyEmbedFormAuth {
   WebKitDOMNode *username_node;
   WebKitDOMNode *password_node;
   char *username;
+  gboolean password_updated;
 };
 
 G_DEFINE_TYPE (EphyEmbedFormAuth, ephy_embed_form_auth, G_TYPE_OBJECT)
@@ -110,6 +111,19 @@ ephy_embed_form_auth_get_username (EphyEmbedFormAuth *form_auth)
   return form_auth->username;
 }
 
+gboolean
+ephy_embed_form_auth_get_password_updated (EphyEmbedFormAuth *form_auth)
+{
+  return form_auth->password_updated;
+}
+
+void
+ephy_embed_form_auth_set_password_updated (EphyEmbedFormAuth *form_auth,
+                                           gboolean           password_updated)
+{
+  form_auth->password_updated = password_updated;
+}
+
 WebKitDOMDocument *
 ephy_embed_form_auth_get_owner_document (EphyEmbedFormAuth *form_auth)
 {
diff --git a/embed/web-extension/ephy-embed-form-auth.h b/embed/web-extension/ephy-embed-form-auth.h
index cb78e06..3266771 100644
--- a/embed/web-extension/ephy-embed-form-auth.h
+++ b/embed/web-extension/ephy-embed-form-auth.h
@@ -30,15 +30,18 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyEmbedFormAuth, ephy_embed_form_auth, EPHY, EMBED_FORM_AUTH, GObject)
 
-EphyEmbedFormAuth *ephy_embed_form_auth_new               (WebKitWebPage     *web_page,
-                                                           WebKitDOMNode     *username_node,
-                                                           WebKitDOMNode     *password_node,
-                                                           const char        *username);
-WebKitDOMNode     *ephy_embed_form_auth_get_username_node (EphyEmbedFormAuth *form_auth);
-WebKitDOMNode     *ephy_embed_form_auth_get_password_node (EphyEmbedFormAuth *form_auth);
-SoupURI           *ephy_embed_form_auth_get_uri           (EphyEmbedFormAuth *form_auth);
-guint64            ephy_embed_form_auth_get_page_id       (EphyEmbedFormAuth *form_auth);
-const char        *ephy_embed_form_auth_get_username      (EphyEmbedFormAuth *form_auth);
-WebKitDOMDocument *ephy_embed_form_auth_get_owner_document(EphyEmbedFormAuth *form_auth);
+EphyEmbedFormAuth *ephy_embed_form_auth_new                   (WebKitWebPage     *web_page,
+                                                               WebKitDOMNode     *username_node,
+                                                               WebKitDOMNode     *password_node,
+                                                               const char        *username);
+WebKitDOMNode     *ephy_embed_form_auth_get_username_node     (EphyEmbedFormAuth *form_auth);
+WebKitDOMNode     *ephy_embed_form_auth_get_password_node     (EphyEmbedFormAuth *form_auth);
+SoupURI           *ephy_embed_form_auth_get_uri               (EphyEmbedFormAuth *form_auth);
+guint64            ephy_embed_form_auth_get_page_id           (EphyEmbedFormAuth *form_auth);
+const char        *ephy_embed_form_auth_get_username          (EphyEmbedFormAuth *form_auth);
+gboolean           ephy_embed_form_auth_get_password_updated  (EphyEmbedFormAuth *form_auth);
+void               ephy_embed_form_auth_set_password_updated  (EphyEmbedFormAuth *form_auth,
+                                                               gboolean           password_updated);
+WebKitDOMDocument *ephy_embed_form_auth_get_owner_document    (EphyEmbedFormAuth *form_auth);
 
 G_END_DECLS
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index b628358..588cd94 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -267,6 +267,7 @@ store_password (EphyEmbedFormAuth *form_auth)
   char *username_field_value = NULL;
   char *password_field_name = NULL;
   char *password_field_value = NULL;
+  gboolean password_updated;
   WebKitDOMNode *username_node;
   EphyWebExtension *extension = ephy_web_extension_get ();
 
@@ -283,12 +284,14 @@ store_password (EphyEmbedFormAuth *form_auth)
 
   uri = ephy_embed_form_auth_get_uri (form_auth);
   uri_str = soup_uri_to_string (uri, FALSE);
+  password_updated = ephy_embed_form_auth_get_password_updated (form_auth);
   ephy_password_manager_save (extension->password_manager,
                               uri_str,
                               username_field_name,
                               password_field_name,
                               username_field_value,
-                              password_field_value);
+                              password_field_value,
+                              !password_updated);
 
   g_free (uri_str);
   g_free (username_field_name);
@@ -417,15 +420,18 @@ should_store_cb (GSList   *records,
       LOG ("User/password already stored. Not asking about storing.");
     } else if (permission == EPHY_PERMISSION_PERMIT) {
       LOG ("User/password not yet stored. Storing.");
+      ephy_embed_form_auth_set_password_updated (form_auth, TRUE);
       store_password (form_auth);
     } else {
       LOG ("User/password not yet stored. Asking about storing.");
+      ephy_embed_form_auth_set_password_updated (form_auth, TRUE);
       request_decision_on_storing (g_object_ref (form_auth));
     }
 
     g_free (username_field_value);
   } else {
     LOG ("No result on query; asking whether we should store.");
+    ephy_embed_form_auth_set_password_updated (form_auth, FALSE);
     request_decision_on_storing (g_object_ref (form_auth));
   }
 
@@ -730,8 +736,7 @@ show_user_choices (WebKitDOMDocument *document,
   WebKitDOMNode *body;
   WebKitDOMElement *main_div;
   WebKitDOMElement *ul;
-  GSList *iter;
-  GSList *password_records_list;
+  GSList *cached_users;
   gboolean username_node_ever_edited;
   double x, y;
   double input_width;
@@ -777,32 +782,27 @@ show_user_choices (WebKitDOMDocument *document,
                                     "padding: 0;",
                                     NULL);
 
-  password_records_list = (GSList *)g_object_get_data (G_OBJECT (username_node),
-                                                       "ephy-password-records-list");
+  cached_users = (GSList *)g_object_get_data (G_OBJECT (username_node), "ephy-cached-users");
 
   username_node_ever_edited =
     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (username_node),
                                         "ephy-user-ever-edited"));
 
-  for (iter = password_records_list; iter; iter = iter->next) {
-    EphyPasswordRecord *record;
+  for (GSList *l = cached_users; l && l->data; l = l->next) {
+    const char *user = l->data;
     WebKitDOMElement *li;
     WebKitDOMElement *anchor;
     char *child_style;
-    const char *record_username;
     gboolean is_selected;
 
-    record = EPHY_PASSWORD_RECORD (iter->data);
-    record_username = ephy_password_record_get_username (record);
-
     /* Filter out the available names that do not match, but show all options in
      * case we have been triggered by something other than the user editing the
      * input.
      */
-    if (username_node_ever_edited && !g_str_has_prefix (record_username, username))
+    if (username_node_ever_edited && !g_str_has_prefix (user, username))
       continue;
 
-    is_selected = !g_strcmp0 (username, record_username);
+    is_selected = !g_strcmp0 (username, user);
 
     li = webkit_dom_document_create_element (document, "li", NULL);
     webkit_dom_element_set_attribute (li, "tabindex", "-1", NULL);
@@ -834,7 +834,7 @@ show_user_choices (WebKitDOMDocument *document,
                                                 username_node);
 
     webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (anchor),
-                                      record_username,
+                                      user,
                                       NULL);
   }
 
@@ -1108,7 +1108,7 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
     /* We have a field that may be the user, and one for a password. */
     if (ephy_web_dom_utils_find_form_auth_elements (form, &username_node, &password_node)) {
       EphyEmbedFormAuth *form_auth;
-      GSList *password_records_list;
+      GSList *cached_users;
       const char *uri;
 
       LOG ("Hooking and pre-filling a form");
@@ -1126,11 +1126,11 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
 
       /* Plug in the user autocomplete */
       uri = webkit_web_page_get_uri (web_page);
-      password_records_list = ephy_password_manager_get_cached_by_uri (extension->password_manager, uri);
+      cached_users = ephy_password_manager_get_cached_users_for_uri (extension->password_manager, uri);
 
-      if (password_records_list && password_records_list->next && username_node) {
+      if (cached_users && cached_users->next && username_node) {
         LOG ("More than 1 password saved, hooking menu for choosing which on focus");
-        g_object_set_data (G_OBJECT (username_node), "ephy-password-records-list", password_records_list);
+        g_object_set_data (G_OBJECT (username_node), "ephy-cached-users", cached_users);
         g_object_set_data (G_OBJECT (username_node), "ephy-form-auth", form_auth);
         g_object_set_data (G_OBJECT (username_node), "ephy-document", document);
         webkit_dom_event_target_add_event_listener (WEBKIT_DOM_EVENT_TARGET (username_node), "input",
@@ -1149,7 +1149,7 @@ web_page_form_controls_associated (WebKitWebPage    *web_page,
                                                     G_CALLBACK (username_node_changed_cb), FALSE,
                                                     web_page);
       } else
-        LOG ("No items or a single item in password_records_list, not hooking menu for choosing.");
+        LOG ("No items or a single item in cached_users, not hooking menu for choosing.");
 
       pre_fill_form (form_auth);
 
diff --git a/embed/web-extension/meson.build b/embed/web-extension/meson.build
index de6be46..af53a64 100644
--- a/embed/web-extension/meson.build
+++ b/embed/web-extension/meson.build
@@ -10,6 +10,7 @@ web_extension_sources = [
 
 web_extension_deps = [
   ephymisc_dep,
+  ephysync_dep,
   webkit2gtk_web_extension_dep
 ]
 
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 3167d12..d91fdf2 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -157,6 +157,9 @@ static const char * const ephy_prefs_web_schema[] = {
 #define EPHY_PREFS_SYNC_BOOKMARKS_ENABLED "sync-bookmarks-enabled"
 #define EPHY_PREFS_SYNC_BOOKMARKS_TIME    "sync-bookmarks-time"
 #define EPHY_PREFS_SYNC_BOOKMARKS_INITIAL "sync-bookmarks-initial"
+#define EPHY_PREFS_SYNC_PASSWORDS_ENABLED "sync-passwords-enabled"
+#define EPHY_PREFS_SYNC_PASSWORDS_TIME    "sync-passwords-time"
+#define EPHY_PREFS_SYNC_PASSWORDS_INITIAL "sync-passwords-initial"
 
 static struct {
   const char *schema;
diff --git a/lib/meson.build b/lib/meson.build
index 6e88051..b81c721 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -22,8 +22,6 @@ libephymisc_sources = [
   'ephy-filters-manager.c',
   'ephy-gui.c',
   'ephy-langs.c',
-  'ephy-password-manager.c',
-  'ephy-password-record.c',
   'ephy-permissions-manager.c',
   'ephy-profile-utils.c',
   'ephy-search-engine-manager.c',
diff --git a/lib/sync/ephy-password-manager.c b/lib/sync/ephy-password-manager.c
new file mode 100644
index 0000000..ed44e4a
--- /dev/null
+++ b/lib/sync/ephy-password-manager.c
@@ -0,0 +1,580 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-password-manager.h"
+
+#include "ephy-debug.h"
+#include "ephy-settings.h"
+#include "ephy-synchronizable-manager.h"
+#include "ephy-uri-helpers.h"
+
+#include <glib/gi18n.h>
+#include <stdio.h>
+
+const SecretSchema *
+ephy_password_manager_get_password_schema (void)
+{
+  static const SecretSchema schema = {
+    "org.epiphany.FormPassword", SECRET_SCHEMA_NONE,
+    {
+      { ID_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { URI_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { USERNAME_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { PASSWORD_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { USERNAME_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { SERVER_TIME_MODIFIED_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING},
+      { "NULL", 0 },
+    }
+  };
+  return &schema;
+}
+
+struct _EphyPasswordManager {
+  GObject parent_instance;
+
+  GHashTable *cache;
+};
+
+static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyPasswordManager, ephy_password_manager, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+                                                ephy_synchronizable_manager_iface_init))
+
+typedef struct {
+  EphyPasswordManagerQueryCallback callback;
+  gpointer user_data;
+} QueryAsyncData;
+
+static QueryAsyncData *
+query_async_data_new (EphyPasswordManagerQueryCallback callback,
+                      gpointer                         user_data)
+{
+  QueryAsyncData *data;
+
+  data = g_slice_new (QueryAsyncData);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  return data;
+}
+
+static void
+query_async_data_free (QueryAsyncData *data)
+{
+  g_slice_free (QueryAsyncData, data);
+}
+
+static void
+ephy_password_manager_cache_clear (EphyPasswordManager *self)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+
+  g_hash_table_iter_init (&iter, self->cache);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_slist_free_full (value, g_free);
+  g_hash_table_remove_all (self->cache);
+}
+
+static void
+ephy_password_manager_cache_remove (EphyPasswordManager *self,
+                                    const char          *hostname,
+                                    const char          *username)
+{
+  GSList *usernames;
+  GSList *new_usernames = NULL;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+  g_assert (hostname);
+  g_assert (username);
+
+  usernames = g_hash_table_lookup (self->cache, hostname);
+  if (usernames) {
+    for (GSList *l = usernames; l && l->data; l = l->next) {
+      if (g_strcmp0 (username, l->data))
+        new_usernames = g_slist_prepend (new_usernames, g_strdup (l->data));
+    }
+    g_hash_table_replace (self->cache, g_strdup (hostname), new_usernames);
+    g_slist_free_full (usernames, g_free);
+  }
+}
+
+static void
+ephy_password_manager_cache_add (EphyPasswordManager *self,
+                                 const char          *hostname,
+                                 const char          *username)
+{
+  GSList *usernames;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+  g_assert (self->cache);
+  g_assert (hostname);
+  g_assert (username);
+
+  usernames = g_hash_table_lookup (self->cache, hostname);
+  for (GSList *l = usernames; l && l->data; l = l->next) {
+    if (!g_strcmp0 (username, l->data))
+      return;
+  }
+  usernames = g_slist_prepend (usernames, g_strdup (username));
+  g_hash_table_replace (self->cache, g_strdup (hostname), usernames);
+}
+
+static void
+populate_cache_cb (GSList   *records,
+                   gpointer  user_data)
+{
+  EphyPasswordManager *self;
+
+  g_assert (EPHY_IS_PASSWORD_MANAGER (user_data));
+  self = EPHY_PASSWORD_MANAGER (user_data);
+  g_assert (self->cache);
+
+  for (GSList *l = records; l && l->data; l = l->next) {
+    EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (l->data);
+    const char *hostname = ephy_password_record_get_hostname (record);
+    const char *username = ephy_password_record_get_username (record);
+
+    ephy_password_manager_cache_add (self, hostname, username);
+  }
+
+  g_slist_free_full (records, g_object_unref);
+}
+
+static void
+ephy_password_manager_dispose (GObject *object)
+{
+  EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (object);
+
+  if (self->cache) {
+    ephy_password_manager_cache_clear (self);
+    g_clear_pointer (&self->cache, g_hash_table_unref);
+  }
+
+  G_OBJECT_CLASS (ephy_password_manager_parent_class)->dispose (object);
+}
+
+static void
+ephy_password_manager_class_init (EphyPasswordManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ephy_password_manager_dispose;
+}
+
+static void
+ephy_password_manager_init (EphyPasswordManager *self)
+{
+  LOG ("Loading usernames into internal cache...");
+  self->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+                               populate_cache_cb, self);
+}
+
+EphyPasswordManager *
+ephy_password_manager_new (void)
+{
+  return EPHY_PASSWORD_MANAGER (g_object_new (EPHY_TYPE_PASSWORD_MANAGER, NULL));
+}
+
+GSList *
+ephy_password_manager_get_cached_users_for_uri (EphyPasswordManager *self,
+                                                const char          *uri)
+{
+  GSList *list;
+  char *hostname;
+
+  g_return_val_if_fail (EPHY_IS_PASSWORD_MANAGER (self), NULL);
+  g_return_val_if_fail (uri, NULL);
+
+  hostname = ephy_uri_to_security_origin (uri);
+  list = g_hash_table_lookup (self->cache, hostname);
+  g_free (hostname);
+
+  return list;
+}
+
+void
+ephy_password_manager_save (EphyPasswordManager *self,
+                            const char          *uri,
+                            const char          *username_field,
+                            const char          *password_field,
+                            const char          *username,
+                            const char          *password,
+                            gboolean             is_new)
+{
+  char *hostname;
+  char *id = NULL;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+  g_return_if_fail (uri);
+  g_return_if_fail (password);
+  g_return_if_fail (!username_field || username);
+  g_return_if_fail (!password_field || password);
+
+  if (is_new) {
+    char *uuid = g_uuid_string_random ();
+    id = g_strdup_printf ("{%s}", uuid);
+    g_free (uuid);
+  }
+
+  hostname = ephy_uri_to_security_origin (uri);
+  ephy_password_manager_cache_add (self, hostname, username);
+
+  /* Add/overwrite the password in the secret schema. */
+  ephy_password_manager_store (id, hostname,
+                               username_field, password_field,
+                               username, password,
+                               NULL, NULL);
+
+  g_free (hostname);
+  g_free (id);
+}
+
+static GHashTable *
+get_attributes_table (const char *id,
+                      const char *uri,
+                      const char *username_field,
+                      const char *password_field,
+                      const char *username)
+{
+  GHashTable *attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+
+  if (id)
+    g_hash_table_insert (attributes, g_strdup (ID_KEY), g_strdup (id));
+  if (uri)
+    g_hash_table_insert (attributes, g_strdup (URI_KEY), ephy_uri_to_security_origin (uri));
+  if (username)
+    g_hash_table_insert (attributes, g_strdup (USERNAME_KEY), g_strdup (username));
+  if (username_field)
+    g_hash_table_insert (attributes, g_strdup (USERNAME_FIELD_KEY), g_strdup (username_field));
+  if (password_field)
+    g_hash_table_insert (attributes, g_strdup (PASSWORD_FIELD_KEY), g_strdup (password_field));
+
+  return attributes;
+}
+
+static void
+secret_service_search_cb (SecretService  *service,
+                          GAsyncResult   *result,
+                          QueryAsyncData *data)
+{
+  GList *matches;
+  GSList *records = NULL;
+  GError *error = NULL;
+
+  matches = secret_service_search_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to search secrets in password schema: %s", error->message);
+    g_error_free (error);
+    goto out;
+  }
+
+  for (GList *l = matches; l && l->data; l = l->next) {
+    SecretItem *item = (SecretItem *)l->data;
+    GHashTable *attributes = secret_item_get_attributes (item);
+    SecretValue *value = secret_item_get_secret (item);
+    const char *id = g_hash_table_lookup (attributes, ID_KEY);
+    const char *hostname = g_hash_table_lookup (attributes, URI_KEY);
+    const char *username_field = g_hash_table_lookup (attributes, USERNAME_FIELD_KEY);
+    const char *password_field = g_hash_table_lookup (attributes, PASSWORD_FIELD_KEY);
+    const char *username = g_hash_table_lookup (attributes, USERNAME_KEY);
+    const char *timestamp = g_hash_table_lookup (attributes, SERVER_TIME_MODIFIED_KEY);
+    const char *password = secret_value_get (value, NULL);
+    EphyPasswordRecord *record;
+
+    LOG ("Found password record for (%s, %s, %s, %s)",
+         hostname, username_field, password_field, username);
+LOG ("id: %s, time created: %lu, time modified: %lu, server_time_modified: %s", id, secret_item_get_created 
(item), secret_item_get_modified (item), timestamp);
+
+    record = ephy_password_record_new (id, hostname,
+                                       username_field, password_field,
+                                       username, password,
+                                       secret_item_get_created (item) * 1000,
+                                       secret_item_get_modified (item) * 1000);
+    if (timestamp) {
+      double server_time_modified;
+      sscanf (timestamp, "%lf", &server_time_modified);
+      ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record),
+                                                    server_time_modified);
+    }
+    records = g_slist_prepend (records, record);
+
+    secret_value_unref (value);
+    g_hash_table_unref (attributes);
+  }
+
+out:
+  if (data->callback)
+    data->callback (records, data->user_data);
+  query_async_data_free (data);
+}
+
+void
+ephy_password_manager_query (EphyPasswordManager              *self,
+                             const char                       *uri,
+                             const char                       *username_field,
+                             const char                       *password_field,
+                             const char                       *username,
+                             EphyPasswordManagerQueryCallback  callback,
+                             gpointer                          user_data)
+{
+  QueryAsyncData *data;
+  GHashTable *attributes;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+
+  LOG ("Querying password records for (%s, %s, %s, %s)",
+       uri, username_field, password_field, username);
+
+  attributes = get_attributes_table (NULL, uri, username_field, password_field, username);
+  data = query_async_data_new (callback, user_data);
+
+  secret_service_search (NULL,
+                         EPHY_FORM_PASSWORD_SCHEMA,
+                         attributes,
+                         SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+                         NULL,
+                         (GAsyncReadyCallback)secret_service_search_cb,
+                         data);
+
+  g_hash_table_unref (attributes);
+}
+
+static void
+secret_service_store_cb (SecretService *service,
+                         GAsyncResult  *result,
+                         GTask         *task)
+{
+  GError *error = NULL;
+
+  secret_service_store_finish (service, result, &error);
+  if (error)
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+
+  g_object_unref (task);
+}
+
+void
+ephy_password_manager_store (const char          *id,
+                             const char          *uri,
+                             const char          *username_field,
+                             const char          *password_field,
+                             const char          *username,
+                             const char          *password,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
+{
+  SecretValue *value;
+  GHashTable *attributes;
+  GTask *task;
+  char *hostname;
+  char *label;
+
+  g_return_if_fail (uri);
+  g_return_if_fail (password);
+  g_return_if_fail (!username_field || username);
+  g_return_if_fail (!password_field || password);
+
+  task = g_task_new (NULL, NULL, callback, user_data);
+  value = secret_value_new (password, -1, "text/plain");
+  attributes = get_attributes_table (id, uri, username_field, password_field, username);
+  hostname = ephy_uri_to_security_origin (uri);
+
+  if (username) {
+    /* Translators: The first %s is the username and the second one is the
+     * security origin where this is happening. Example: gnome gmail com and
+     * https://mail.google.com. */
+    label = g_strdup_printf (_("Password for %s in a form in %s"), username, hostname);
+  } else {
+    /* Translators: The %s is the security origin where this is happening.
+     * Example: https://mail.google.com. */
+    label = g_strdup_printf (_("Password in a form in %s"), hostname);
+  }
+
+  LOG ("Storing password record for (%s, %s, %s, %s)",
+       uri, username_field, password_field, username);
+
+  secret_service_store (NULL, EPHY_FORM_PASSWORD_SCHEMA,
+                        attributes, NULL, label, value, NULL,
+                        (GAsyncReadyCallback)secret_service_store_cb,
+                        g_object_ref (task));
+
+  g_free (label);
+  g_free (hostname);
+  secret_value_unref (value);
+  g_hash_table_unref (attributes);
+  g_object_unref (task);
+}
+
+gboolean
+ephy_password_manager_store_finish (GAsyncResult  *result,
+                                    GError       **error)
+{
+  g_return_val_if_fail (!error || !(*error), FALSE);
+  g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+secret_service_clear_cb (SecretService *service,
+                         GAsyncResult  *result,
+                         gpointer       user_data)
+{
+  GError *error = NULL;
+
+  secret_service_clear_finish (service, result, &error);
+  if (error) {
+    g_warning ("Failed to clear secrets from password schema: %s", error->message);
+    g_error_free (error);
+  }
+}
+
+void
+ephy_password_manager_forget (EphyPasswordManager *self,
+                              const char          *hostname,
+                              const char          *username_field,
+                              const char          *password_field,
+                              const char          *username)
+{
+  GHashTable *attributes;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+  g_return_if_fail (hostname);
+  g_return_if_fail (password_field);
+  g_return_if_fail (!username_field || username);
+
+  attributes = get_attributes_table (NULL, hostname, username_field, password_field, username);
+  secret_service_clear (NULL, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
+                        (GAsyncReadyCallback)secret_service_clear_cb, NULL);
+
+  ephy_password_manager_cache_remove (self, hostname, username);
+}
+
+void
+ephy_password_manager_forget_all (EphyPasswordManager *self)
+{
+  GHashTable *attributes;
+
+  g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+
+  attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
+  secret_service_clear (NULL, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
+                        (GAsyncReadyCallback)secret_service_clear_cb, NULL);
+
+  ephy_password_manager_cache_clear (self);
+
+  g_hash_table_unref (attributes);
+}
+
+static const char *
+synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+  gboolean sync_with_firefox = g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                                       EPHY_PREFS_SYNC_WITH_FIREFOX);
+
+  return sync_with_firefox ? "passwords" : "ephy-passwords";
+}
+
+static GType
+synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+  return EPHY_TYPE_PASSWORD_RECORD;
+}
+
+static gboolean
+synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+  return g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+                                 EPHY_PREFS_SYNC_PASSWORDS_INITIAL);
+}
+
+static void
+synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+                                            gboolean                   is_initial)
+{
+  g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+                          EPHY_PREFS_SYNC_PASSWORDS_INITIAL,
+                          is_initial);
+}
+
+static double
+synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+  return g_settings_get_double (EPHY_SETTINGS_SYNC,
+                                EPHY_PREFS_SYNC_PASSWORDS_TIME);
+}
+
+static void
+synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+                                      double                     sync_time)
+{
+  g_settings_set_double (EPHY_SETTINGS_SYNC,
+                         EPHY_PREFS_SYNC_PASSWORDS_TIME,
+                         sync_time);
+}
+
+static void
+synchronizable_manager_add (EphySynchronizableManager *manager,
+                            EphySynchronizable        *synchronizable)
+{
+  /* TODO: Implement this. */
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+                               EphySynchronizable        *synchronizable)
+{
+  /* TODO: Implement this. */
+}
+
+static GSList *
+synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+                                      gboolean                   is_initial,
+                                      GSList                    *remotes_deleted,
+                                      GSList                    *remotes_updated)
+{
+  /* TODO: Implement this. */
+
+  return NULL;
+}
+
+static void
+ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface)
+{
+  iface->get_collection_name = synchronizable_manager_get_collection_name;
+  iface->get_synchronizable_type = synchronizable_manager_get_synchronizable_type;
+  iface->is_initial_sync = synchronizable_manager_is_initial_sync;
+  iface->set_is_initial_sync = synchronizable_manager_set_is_initial_sync;
+  iface->get_sync_time = synchronizable_manager_get_sync_time;
+  iface->set_sync_time = synchronizable_manager_set_sync_time;
+  iface->add = synchronizable_manager_add;
+  iface->remove = synchronizable_manager_remove;
+  iface->merge_remotes = synchronizable_manager_merge_remotes;
+}
diff --git a/lib/sync/ephy-password-manager.h b/lib/sync/ephy-password-manager.h
new file mode 100644
index 0000000..9ea6bbf
--- /dev/null
+++ b/lib/sync/ephy-password-manager.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-password-record.h"
+
+#include <glib-object.h>
+#include <libsecret/secret.h>
+
+G_BEGIN_DECLS
+
+const SecretSchema *ephy_password_manager_get_password_schema (void) G_GNUC_CONST;
+
+#define ID_KEY                    "id"
+#define URI_KEY                   "uri"
+#define USERNAME_FIELD_KEY        "form_username"
+#define PASSWORD_FIELD_KEY        "form_password"
+#define USERNAME_KEY              "username"
+#define SERVER_TIME_MODIFIED_KEY  "server_time_modified"
+
+#define EPHY_FORM_PASSWORD_SCHEMA ephy_password_manager_get_password_schema ()
+
+#define EPHY_TYPE_PASSWORD_MANAGER (ephy_password_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyPasswordManager, ephy_password_manager, EPHY, PASSWORD_MANAGER, GObject)
+
+typedef void (*EphyPasswordManagerQueryCallback) (GSList *records, gpointer user_data);
+
+EphyPasswordManager *ephy_password_manager_new                      (void);
+GSList              *ephy_password_manager_get_cached_users_for_uri (EphyPasswordManager *self,
+                                                                     const char          *uri);
+void                 ephy_password_manager_save                     (EphyPasswordManager *self,
+                                                                     const char          *uri,
+                                                                     const char          *username_field,
+                                                                     const char          *password_field,
+                                                                     const char          *username,
+                                                                     const char          *password,
+                                                                     gboolean             is_new);
+void                 ephy_password_manager_query                    (EphyPasswordManager              *self,
+                                                                     const char                       *uri,
+                                                                     const char                       
*username_field,
+                                                                     const char                       
*password_field,
+                                                                     const char                       
*username,
+                                                                     EphyPasswordManagerQueryCallback  
callback,
+                                                                     gpointer                          
user_data);
+void                 ephy_password_manager_store                     (const char          *id,
+                                                                      const char          *uri,
+                                                                      const char          *username_field,
+                                                                      const char          *password_field,
+                                                                      const char          *username,
+                                                                      const char          *password,
+                                                                      GAsyncReadyCallback  callback,
+                                                                      gpointer             user_data);
+gboolean             ephy_password_manager_store_finish              (GAsyncResult  *result,
+                                                                      GError       **error);
+void                 ephy_password_manager_forget                    (EphyPasswordManager *self,
+                                                                      const char          *hostname,
+                                                                      const char          *username_field,
+                                                                      const char          *password_field,
+                                                                      const char          *username);
+void                 ephy_password_manager_forget_all                (EphyPasswordManager *self);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-password-record.c b/lib/sync/ephy-password-record.c
new file mode 100644
index 0000000..bd6fa5b
--- /dev/null
+++ b/lib/sync/ephy-password-record.c
@@ -0,0 +1,379 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-password-record.h"
+
+#include "ephy-synchronizable.h"
+
+struct _EphyPasswordRecord {
+  GObject parent_instance;
+
+  char    *id;
+  char    *hostname;
+  char    *form_submit_url;
+  char    *http_realm;
+  char    *username_field;
+  char    *password_field;
+  char    *username;
+  char    *password;
+  guint64  time_created;
+  guint64  time_password_changed;
+
+  double   server_time_modified;
+};
+
+static void json_serializable_iface_init (JsonSerializableIface *iface);
+static void ephy_synchronizable_iface_init (EphySynchronizableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyPasswordRecord, ephy_password_record, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
+                                                json_serializable_iface_init)
+                         G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE,
+                                                ephy_synchronizable_iface_init))
+
+enum {
+  PROP_0,
+  PROP_ID,                    /* Firefox Sync */
+  PROP_HOSTNAME,              /* Epiphany && Firefox Sync */
+  PROP_FORM_SUBMIT_URL,       /* Firefox Sync */
+  PROP_HTTP_REALM,            /* Firefox Sync */
+  PROP_USERNAME_FIELD,        /* Epiphany && Firefox Sync */
+  PROP_PASSWORD_FIELD,        /* Epiphany && Firefox Sync */
+  PROP_USERNAME,              /* Epiphany && Firefox Sync */
+  PROP_PASSWORD,              /* Epiphany && Firefox Sync */
+  PROP_TIME_CREATED,          /* Firefox Sync */
+  PROP_TIME_PASSWORD_CHANGED, /* Firefox Sync */
+  LAST_PROP,
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+static void
+ephy_password_record_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  switch (prop_id) {
+    case PROP_ID:
+      g_free (self->id);
+      self->id = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_HOSTNAME:
+      g_free (self->hostname);
+      self->hostname = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_FORM_SUBMIT_URL:
+      g_free (self->form_submit_url);
+      self->form_submit_url = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_HTTP_REALM:
+      g_free (self->http_realm);
+      self->http_realm = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_USERNAME_FIELD:
+      g_free (self->username_field);
+      self->username_field = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PASSWORD_FIELD:
+      g_free (self->password_field);
+      self->password_field = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_USERNAME:
+      g_free (self->username);
+      self->username = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_PASSWORD:
+      g_free (self->password);
+      self->password = g_strdup (g_value_get_string (value));
+      break;
+    case PROP_TIME_CREATED:
+      self->time_created = g_value_get_uint64 (value);
+      break;
+    case PROP_TIME_PASSWORD_CHANGED:
+      self->time_password_changed = g_value_get_uint64 (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_password_record_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  switch (prop_id) {
+    case PROP_ID:
+      g_value_set_string (value, self->id);
+      break;
+    case PROP_HOSTNAME:
+      g_value_set_string (value, self->hostname);
+      break;
+    case PROP_FORM_SUBMIT_URL:
+      g_value_set_string (value, self->form_submit_url);
+      break;
+    case PROP_HTTP_REALM:
+      g_value_set_string (value, self->http_realm);
+      break;
+    case PROP_USERNAME_FIELD:
+      g_value_set_string (value, self->username_field);
+      break;
+    case PROP_PASSWORD_FIELD:
+      g_value_set_string (value, self->password_field);
+      break;
+    case PROP_USERNAME:
+      g_value_set_string (value, self->username);
+      break;
+    case PROP_PASSWORD:
+      g_value_set_string (value, self->password);
+      break;
+    case PROP_TIME_CREATED:
+      g_value_set_uint64 (value, self->time_created);
+      break;
+    case PROP_TIME_PASSWORD_CHANGED:
+      g_value_set_uint64 (value, self->time_password_changed);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_password_record_dispose (GObject *object)
+{
+  EphyPasswordRecord *self = EPHY_PASSWORD_RECORD (object);
+
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->hostname, g_free);
+  g_clear_pointer (&self->form_submit_url, g_free);
+  g_clear_pointer (&self->http_realm, g_free);
+  g_clear_pointer (&self->username_field, g_free);
+  g_clear_pointer (&self->password_field, g_free);
+  g_clear_pointer (&self->username, g_free);
+  g_clear_pointer (&self->password, g_free);
+
+  G_OBJECT_CLASS (ephy_password_record_parent_class)->dispose (object);
+}
+
+static void
+ephy_password_record_class_init (EphyPasswordRecordClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = ephy_password_record_set_property;
+  object_class->get_property = ephy_password_record_get_property;
+  object_class->dispose = ephy_password_record_dispose;
+
+  obj_properties[PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "Id of the password record",
+                         "Default id",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_HOSTNAME] =
+    g_param_spec_string ("hostname",
+                         "Hostname",
+                         "Hostname url that password is applicable at",
+                         "Default hostname",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_FORM_SUBMIT_URL] =
+    g_param_spec_string ("formSubmitURL",
+                         "Form submit URL",
+                         "Submission URL set by form",
+                         "Default form submit URL",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_HTTP_REALM] =
+    g_param_spec_string ("httpRealm",
+                         "HTTP realm",
+                         "HTTP realm for which the login is valid",
+                         "Default HTTP realm",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_USERNAME_FIELD] =
+    g_param_spec_string ("usernameField",
+                         "Username field",
+                         "HTML field name of the username",
+                         "Default username field",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_PASSWORD_FIELD] =
+    g_param_spec_string ("passwordField",
+                         "Password field",
+                         "HTML field name of the password",
+                         "Default password field",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_USERNAME] =
+    g_param_spec_string ("username",
+                         "Username",
+                         "Username to log in as",
+                         "Default username",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_PASSWORD] =
+    g_param_spec_string ("password",
+                         "Password",
+                         "Password for the username",
+                         "Default password",
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_TIME_CREATED] =
+    g_param_spec_uint64 ("timeCreated",
+                         "Time created",
+                         "Unix timestamp in milliseconds at which the password was created",
+                         0,
+                         G_MAXUINT64,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+  obj_properties[PROP_TIME_PASSWORD_CHANGED] =
+    g_param_spec_uint64 ("timePasswordChanged",
+                         "Time password changed",
+                         "Unix timestamp in milliseconds at which the password was changed",
+                         0,
+                         G_MAXUINT64,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_password_record_init (EphyPasswordRecord *self)
+{
+}
+
+EphyPasswordRecord *
+ephy_password_record_new (const char *id,
+                          const char *hostname,
+                          const char *username_field,
+                          const char *password_field,
+                          const char *username,
+                          const char *password,
+                          guint64     time_created,
+                          guint64     time_password_changed)
+{
+  return EPHY_PASSWORD_RECORD (g_object_new (EPHY_TYPE_PASSWORD_RECORD,
+                                             "id", id,
+                                             "hostname", hostname,
+                                             "formSubmitURL", hostname,
+                                             "httpRealm", hostname,
+                                             "usernameField", username_field,
+                                             "passwordField", password_field,
+                                             "username", username,
+                                             "password", password,
+                                             "timeCreated", time_created,
+                                             "timePasswordChanged", time_password_changed,
+                                             NULL));
+}
+
+const char *
+ephy_password_record_get_id (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->id;
+}
+
+const char *
+ephy_password_record_get_hostname (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->hostname;
+}
+
+const char *
+ephy_password_record_get_username_field (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->username_field;
+}
+
+const char *
+ephy_password_record_get_password_field (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->password_field;
+}
+
+const char *
+ephy_password_record_get_username (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->username;
+}
+
+const char *
+ephy_password_record_get_password (EphyPasswordRecord *self)
+{
+  g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), NULL);
+
+  return self->password;
+}
+
+void
+ephy_password_record_set_password (EphyPasswordRecord *self,
+                                   const char         *password)
+{
+  g_return_if_fail (EPHY_IS_PASSWORD_RECORD (self));
+
+  g_free (self->password);
+  self->password = g_strdup (password);
+}
+
+static void
+json_serializable_iface_init (JsonSerializableIface *iface)
+{
+  iface->serialize_property = json_serializable_default_serialize_property;
+  iface->deserialize_property = json_serializable_default_deserialize_property;
+}
+
+static const char *
+synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+  return ephy_password_record_get_id (EPHY_PASSWORD_RECORD (synchronizable));
+}
+
+static double
+synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+  return EPHY_PASSWORD_RECORD (synchronizable)->server_time_modified;
+}
+
+static void
+synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+                                         double              server_time_modified)
+{
+  EPHY_PASSWORD_RECORD (synchronizable)->server_time_modified = server_time_modified;
+}
+
+static void
+ephy_synchronizable_iface_init (EphySynchronizableInterface *iface)
+{
+  iface->get_id = synchronizable_get_id;
+  iface->get_server_time_modified = synchronizable_get_server_time_modified;
+  iface->set_server_time_modified = synchronizable_set_server_time_modified;
+  iface->to_bso = ephy_synchronizable_default_to_bso;
+}
diff --git a/lib/ephy-password-record.h b/lib/sync/ephy-password-record.h
similarity index 74%
rename from lib/ephy-password-record.h
rename to lib/sync/ephy-password-record.h
index 2dcd128..a0f316f 100644
--- a/lib/ephy-password-record.h
+++ b/lib/sync/ephy-password-record.h
@@ -29,14 +29,18 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyPasswordRecord, ephy_password_record, EPHY, PASSWORD_RECORD, GObject)
 
-EphyPasswordRecord *ephy_password_record_new                (const char *origin,
-                                                             const char *form_username,
-                                                             const char *form_passwords,
+EphyPasswordRecord *ephy_password_record_new                (const char *id,
+                                                             const char *hostname,
+                                                             const char *username_field,
+                                                             const char *password_field,
                                                              const char *username,
-                                                             const char *password);
-const char         *ephy_password_record_get_origin         (EphyPasswordRecord *self);
-const char         *ephy_password_record_get_form_username  (EphyPasswordRecord *self);
-const char         *ephy_password_record_get_form_password  (EphyPasswordRecord *self);
+                                                             const char *password,
+                                                             guint64     time_created,
+                                                             guint64     time_password_changed);
+const char         *ephy_password_record_get_id             (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_hostname       (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_username_field (EphyPasswordRecord *self);
+const char         *ephy_password_record_get_password_field (EphyPasswordRecord *self);
 const char         *ephy_password_record_get_username       (EphyPasswordRecord *self);
 const char         *ephy_password_record_get_password       (EphyPasswordRecord *self);
 void                ephy_password_record_set_password       (EphyPasswordRecord *self,
diff --git a/lib/sync/meson.build b/lib/sync/meson.build
index b4e6a22..658c17c 100644
--- a/lib/sync/meson.build
+++ b/lib/sync/meson.build
@@ -1,4 +1,6 @@
 libephysync_sources = [
+  'ephy-password-manager.c',
+  'ephy-password-record.c',
   'ephy-sync-crypto.c',
   'ephy-sync-service.c',
   'ephy-synchronizable-manager.c',
diff --git a/meson.build b/meson.build
index 1119843..61e755f 100644
--- a/meson.build
+++ b/meson.build
@@ -41,7 +41,7 @@ configure_file(
   configuration: conf
 )
 
-glib_requirement = '>= 2.46.0'
+glib_requirement = '>= 2.52.0'
 gtk_requirement = '>= 3.22.0'
 nettle_requirement = '>= 3.2'
 webkitgtk_requirement = '>= 2.16.0'
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 6f917a5..4c36fd0 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -346,13 +346,15 @@ ephy_shell_startup (GApplication *application)
                               G_BINDING_SYNC_CREATE);
     }
 
-    ephy_shell->password_manager = ephy_password_manager_new ();
-
     /* Create the sync service and register synchronizable managers. */
     ephy_shell->sync_service = ephy_sync_service_new ();
     if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_ENABLED))
       ephy_sync_service_register_manager (ephy_shell->sync_service,
                                           EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager 
(ephy_shell)));
+    if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED))
+      ephy_sync_service_register_manager (ephy_shell->sync_service,
+                                          EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager 
(ephy_shell)));
+
     gtk_application_set_app_menu (GTK_APPLICATION (application),
                                   G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
   } else {
@@ -821,6 +823,9 @@ ephy_shell_get_password_manager (EphyShell *shell)
 {
   g_return_val_if_fail (EPHY_IS_SHELL (shell), NULL);
 
+  if (shell->password_manager == NULL)
+    shell->password_manager = ephy_password_manager_new ();
+
   return shell->password_manager;
 }
 
diff --git a/src/passwords-dialog.c b/src/passwords-dialog.c
index 2f0637f..ddb7377 100644
--- a/src/passwords-dialog.c
+++ b/src/passwords-dialog.c
@@ -140,9 +140,9 @@ forget (GSimpleAction *action,
     gtk_tree_model_get_value (model, &iter, COL_PASSWORDS_DATA, &val);
     record = g_value_get_object (&val);
     ephy_password_manager_forget (dialog->manager,
-                                  ephy_password_record_get_origin (record),
-                                  ephy_password_record_get_form_username (record),
-                                  ephy_password_record_get_form_password (record),
+                                  ephy_password_record_get_hostname (record),
+                                  ephy_password_record_get_username_field (record),
+                                  ephy_password_record_get_password_field (record),
                                   ephy_password_record_get_username (record));
     dialog->records = g_slist_remove (dialog->records, record);
     g_object_unref (record);
@@ -370,7 +370,7 @@ populate_model_cb (GSList   *records,
     gtk_list_store_insert_with_values (GTK_LIST_STORE (dialog->liststore),
                                        &iter,
                                        -1,
-                                       COL_PASSWORDS_ORIGIN, ephy_password_record_get_origin (record),
+                                       COL_PASSWORDS_ORIGIN, ephy_password_record_get_hostname (record),
                                        COL_PASSWORDS_USER, ephy_password_record_get_username (record),
                                        COL_PASSWORDS_PASSWORD, ephy_password_record_get_password (record),
                                        COL_PASSWORDS_INVISIBLE, "●●●●●●●●",
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index 46c4e73..e05d8c3 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -121,6 +121,7 @@ struct _PrefsDialog {
   GtkWidget *sync_options_box;
   GtkWidget *sync_with_firefox_checkbutton;
   GtkWidget *sync_bookmarks_checkbutton;
+  GtkWidget *sync_passwords_checkbutton;
   GtkWidget *sync_frequency_5_min_radiobutton;
   GtkWidget *sync_frequency_15_min_radiobutton;
   GtkWidget *sync_frequency_30_min_radiobutton;
@@ -175,19 +176,20 @@ prefs_dialog_finalize (GObject *object)
 }
 
 static void
-sync_bookmarks_toggled_cb (GtkToggleButton *button,
-                           PrefsDialog     *dialog)
+sync_collection_toggled_cb (GtkToggleButton *button,
+                            PrefsDialog     *dialog)
 {
-  EphyBookmarksManager *manager;
+  EphySynchronizableManager *manager;
 
-  manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+  if (GTK_WIDGET (button) == dialog->sync_bookmarks_checkbutton)
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+  else if (GTK_WIDGET (button) == dialog->sync_passwords_checkbutton)
+    manager = EPHY_SYNCHRONIZABLE_MANAGER (ephy_shell_get_password_manager (ephy_shell_get_default ()));
 
   if (gtk_toggle_button_get_active (button))
-    ephy_sync_service_register_manager (dialog->sync_service,
-                                        EPHY_SYNCHRONIZABLE_MANAGER (manager));
+    ephy_sync_service_register_manager (dialog->sync_service, manager);
   else
-    ephy_sync_service_unregister_manager (dialog->sync_service,
-                                          EPHY_SYNCHRONIZABLE_MANAGER (manager));
+    ephy_sync_service_unregister_manager (dialog->sync_service, manager);
 }
 
 static void
@@ -586,6 +588,7 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_options_box);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_with_firefox_checkbutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_bookmarks_checkbutton);
+  gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_passwords_checkbutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_5_min_radiobutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_15_min_radiobutton);
   gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_30_min_radiobutton);
@@ -1640,7 +1643,10 @@ setup_sync_page (PrefsDialog *dialog)
                            G_CALLBACK (sync_finished_cb),
                            dialog, 0);
   g_signal_connect_object (dialog->sync_bookmarks_checkbutton, "toggled",
-                           G_CALLBACK (sync_bookmarks_toggled_cb),
+                           G_CALLBACK (sync_collection_toggled_cb),
+                           dialog, 0);
+  g_signal_connect_object (dialog->sync_passwords_checkbutton, "toggled",
+                           G_CALLBACK (sync_collection_toggled_cb),
                            dialog, 0);
 
   g_settings_bind (sync_settings,
@@ -1653,6 +1659,11 @@ setup_sync_page (PrefsDialog *dialog)
                    dialog->sync_bookmarks_checkbutton,
                    "active",
                    G_SETTINGS_BIND_DEFAULT);
+  g_settings_bind (sync_settings,
+                   EPHY_PREFS_SYNC_PASSWORDS_ENABLED,
+                   dialog->sync_passwords_checkbutton,
+                   "active",
+                   G_SETTINGS_BIND_DEFAULT);
   g_settings_bind_with_mapping (sync_settings,
                                 EPHY_PREFS_SYNC_FREQUENCY,
                                 dialog->sync_frequency_5_min_radiobutton,
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index 1ce5335..e0bb528 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -309,7 +309,7 @@ load_collection_items_cb (SecretCollection *collection,
   SecretValue *secret;
   GList *l;
   GHashTable *attributes, *t;
-  const char *server, *username, *form_username, *form_password, *password;
+  const char *server, *username, *username_field, *password_field, *password;
   char *actual_server;
   SoupURI *uri;
   GError *error = NULL;
@@ -338,16 +338,17 @@ load_collection_items_cb (SecretCollection *collection,
       username = g_hash_table_lookup (attributes, "user");
       uri = soup_uri_new (server);
       t = soup_form_decode (uri->query);
-      form_username = g_hash_table_lookup (t, FORM_USERNAME_KEY);
-      form_password = g_hash_table_lookup (t, FORM_PASSWORD_KEY);
+      username_field = g_hash_table_lookup (t, USERNAME_FIELD_KEY);
+      password_field = g_hash_table_lookup (t, PASSWORD_FIELD_KEY);
       soup_uri_set_query (uri, NULL);
       actual_server = soup_uri_to_string (uri, FALSE);
       secret_item_load_secret_sync (item, NULL, NULL);
       secret = secret_item_get_secret (item);
       password = secret_value_get (secret, NULL);
-      ephy_password_manager_store (actual_server,
-                                   form_username,
-                                   form_password,
+      ephy_password_manager_store (NULL,
+                                   actual_server,
+                                   username_field,
+                                   password_field,
                                    username,
                                    password,
                                    (GAsyncReadyCallback)store_form_auth_data_cb,
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index 09c5ac9..ac5bc0f 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -915,6 +915,13 @@
                                 <property name="use-underline">True</property>
                               </object>
                             </child>
+                            <child>
+                              <object class="GtkCheckButton" id="sync_passwords_checkbutton">
+                                <property name="label" translatable="yes">_Passwords</property>
+                                <property name="visible">True</property>
+                                <property name="use-underline">True</property>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]