[epiphany/wip/sync: 2/5] sync: Implement saved passwords sync
- From: Gabriel Ivașcu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/sync: 2/5] sync: Implement saved passwords sync
- Date: Wed, 3 May 2017 22:57:17 +0000 (UTC)
commit 0d4d2d58b095c01bfa9f669a3a77ea511331b647
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date: Sun Apr 30 22:11:15 2017 +0300
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 | 53 +-
embed/web-extension/meson.build | 1 +
lib/ephy-password-manager.c | 483 ------------
lib/ephy-password-manager.h | 77 --
lib/ephy-password-record.c | 236 ------
lib/ephy-password-record.h | 45 --
lib/ephy-prefs.h | 3 +
lib/meson.build | 2 -
lib/sync/ephy-password-manager.c | 1057 ++++++++++++++++++++++++++
lib/sync/ephy-password-manager.h | 82 ++
lib/sync/ephy-password-record.c | 370 +++++++++
lib/sync/ephy-password-record.h | 50 ++
lib/sync/ephy-sync-service.c | 160 +++-
lib/sync/ephy-sync-service.h | 2 +-
lib/sync/ephy-synchronizable-manager.c | 27 +-
lib/sync/ephy-synchronizable-manager.h | 22 +-
lib/sync/meson.build | 2 +
meson.build | 2 +-
src/bookmarks/ephy-bookmarks-manager.c | 24 +-
src/ephy-shell.c | 11 +-
src/passwords-dialog.c | 26 +-
src/prefs-dialog.c | 34 +-
src/profile-migrator/ephy-profile-migrator.c | 24 +-
src/resources/gtk/prefs-dialog.ui | 7 +
28 files changed, 1874 insertions(+), 979 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..bcc3446 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -30,6 +30,7 @@
#include "ephy-permissions-manager.h"
#include "ephy-prefs.h"
#include "ephy-settings.h"
+#include "ephy-sync-service.h"
#include "ephy-uri-helpers.h"
#include "ephy-uri-tester.h"
#include "ephy-web-dom-utils.h"
@@ -51,6 +52,7 @@ struct _EphyWebExtension {
GDBusConnection *dbus_connection;
GArray *page_created_signals_pending;
+ EphySyncService *sync_service;
EphyPasswordManager *password_manager;
GHashTable *form_auth_data_save_requests;
EphyWebOverviewModel *overview_model;
@@ -267,6 +269,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 +286,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_value,
+ password_field_value,
username_field_name,
password_field_name,
- username_field_value,
- password_field_value);
+ !password_updated);
g_free (uri_str);
g_free (username_field_name);
@@ -417,15 +422,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));
}
@@ -475,9 +483,9 @@ form_submitted_cb (WebKitDOMHTMLFormElement *dom_form,
ephy_password_manager_query (extension->password_manager,
uri_str,
+ username_field_value,
username_field_name,
password_field_name,
- username_field_value,
should_store_cb,
form_auth);
@@ -555,9 +563,9 @@ pre_fill_form (EphyEmbedFormAuth *form_auth)
ephy_password_manager_query (extension->password_manager,
uri_str,
+ username,
username_field_name,
password_field_name,
- username,
fill_form_cb,
form_auth);
@@ -730,8 +738,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 +784,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 +836,7 @@ show_user_choices (WebKitDOMDocument *document,
username_node);
webkit_dom_node_set_text_content (WEBKIT_DOM_NODE (anchor),
- record_username,
+ user,
NULL);
}
@@ -1108,7 +1110,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 +1128,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 +1151,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);
@@ -1453,6 +1455,7 @@ ephy_web_extension_dispose (GObject *object)
g_clear_object (&extension->overview_model);
g_clear_object (&extension->permissions_manager);
g_clear_object (&extension->password_manager);
+ g_clear_object (&extension->sync_service);
if (extension->form_auth_data_save_requests) {
g_hash_table_destroy (extension->form_auth_data_save_requests);
@@ -1562,8 +1565,12 @@ ephy_web_extension_initialize (EphyWebExtension *extension,
extension->initialized = TRUE;
extension->extension = g_object_ref (wk_extension);
- if (!is_private_profile)
+ if (!is_private_profile) {
+ extension->sync_service = ephy_sync_service_new (FALSE);
extension->password_manager = ephy_password_manager_new ();
+ ephy_sync_service_register_manager (extension->sync_service,
+ EPHY_SYNCHRONIZABLE_MANAGER (extension->password_manager));
+ }
extension->permissions_manager = ephy_permissions_manager_new ();
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..0c0b219
--- /dev/null
+++ b/lib/sync/ephy-password-manager.c
@@ -0,0 +1,1057 @@
+/* -*- 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 },
+ { HOSTNAME_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;
+
+typedef struct {
+ EphyPasswordManager *manager;
+ char *password;
+} UpdatePasswordAsyncData;
+
+typedef struct {
+ EphyPasswordManager *manager;
+ EphyPasswordRecord *record;
+} ReplaceRecordAsyncData;
+
+typedef struct {
+ EphyPasswordManager *manager;
+ gboolean is_initial;
+ GSList *remotes_deleted;
+ GSList *remotes_updated;
+ EphySynchronizableManagerMergeCallback callback;
+ gpointer user_data;
+} MergeAsyncData;
+
+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_assert (data);
+
+ g_slice_free (QueryAsyncData, data);
+}
+
+static UpdatePasswordAsyncData *
+update_password_async_data_new (EphyPasswordManager *manager,
+ const char *password)
+{
+ UpdatePasswordAsyncData *data;
+
+ data = g_slice_new (UpdatePasswordAsyncData);
+ data->manager = g_object_ref (manager);
+ data->password = g_strdup (password);
+
+ return data;
+}
+
+static void
+update_password_async_data_free (UpdatePasswordAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->manager);
+ g_free (data->password);
+ g_slice_free (UpdatePasswordAsyncData, data);
+}
+
+static MergeAsyncData *
+merge_async_data_new (EphyPasswordManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
+{
+ MergeAsyncData *data;
+
+ data = g_slice_new (MergeAsyncData);
+ data->manager = g_object_ref (manager);
+ data->is_initial = is_initial;
+ data->remotes_deleted = remotes_deleted;
+ data->remotes_updated = remotes_updated;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ return data;
+}
+
+static void
+merge_async_data_free (MergeAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->manager);
+ g_slice_free (MergeAsyncData, data);
+}
+
+static ReplaceRecordAsyncData *
+replace_record_async_data_new (EphyPasswordManager *manager,
+ EphyPasswordRecord *record)
+{
+ ReplaceRecordAsyncData *data;
+
+ data = g_slice_new (ReplaceRecordAsyncData);
+ data->manager = g_object_ref (manager);
+ data->record = g_object_ref (record);
+
+ return data;
+}
+
+static void
+replace_record_async_data_free (ReplaceRecordAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->manager);
+ g_object_unref (data->record);
+ g_slice_free (ReplaceRecordAsyncData, data);
+}
+
+static GHashTable *
+get_attributes_table (const char *id,
+ const char *uri,
+ const char *username,
+ const char *username_field,
+ const char *password_field,
+ double server_time_modified)
+{
+ 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 (HOSTNAME_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));
+ if (server_time_modified >= 0)
+ g_hash_table_insert (attributes,
+ g_strdup (SERVER_TIME_MODIFIED_KEY),
+ g_strdup_printf ("%.2lf", server_time_modified));
+
+ return attributes;
+}
+
+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 = EPHY_PASSWORD_MANAGER (user_data);
+
+ 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;
+}
+
+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);
+}
+
+static void
+store_internal (const char *password,
+ GHashTable *attributes,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SecretValue *value;
+ GTask *task;
+ const char *hostname;
+ const char *username;
+ char *label;
+
+ g_assert (password);
+ g_assert (attributes);
+
+ task = g_task_new (NULL, NULL, callback, user_data);
+ value = secret_value_new (password, -1, "text/plain");
+ hostname = g_hash_table_lookup (attributes, HOSTNAME_KEY);
+ username = g_hash_table_lookup (attributes, USERNAME_KEY);
+
+ 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)",
+ hostname, username,
+ (char *)g_hash_table_lookup (attributes, USERNAME_FIELD_KEY),
+ (char *)g_hash_table_lookup (attributes, PASSWORD_FIELD_KEY));
+
+ 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);
+ secret_value_unref (value);
+ g_object_unref (task);
+}
+
+static void
+ephy_password_manger_store_record (EphyPasswordManager *self,
+ EphyPasswordRecord *record)
+{
+ GHashTable *attributes;
+
+ g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+ g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+ attributes = get_attributes_table (ephy_password_record_get_id (record),
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record),
+ ephy_password_record_get_username_field (record),
+ ephy_password_record_get_password_field (record),
+ ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE
(record)));
+ store_internal (ephy_password_record_get_password (record), attributes, NULL, NULL);
+ ephy_password_manager_cache_add (self,
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record));
+
+ g_hash_table_unref (attributes);
+}
+
+static void
+update_password_cb (GSList *records,
+ gpointer user_data)
+{
+ UpdatePasswordAsyncData *data = (UpdatePasswordAsyncData *)user_data;
+ EphyPasswordRecord *record;
+
+ /* We only expect one matching record here. */
+ g_assert (g_slist_length (records) == 1);
+
+ record = EPHY_PASSWORD_RECORD (records->data);
+ ephy_password_record_set_password (record, data->password);
+ ephy_password_manger_store_record (data->manager, record);
+ g_signal_emit_by_name (data->manager, "synchronizable-modified", record);
+
+ g_slist_free_full (records, g_object_unref);
+ update_password_async_data_free (data);
+}
+
+void
+ephy_password_manager_save (EphyPasswordManager *self,
+ const char *uri,
+ const char *username,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ gboolean is_new)
+{
+ EphyPasswordRecord *record;
+ char *hostname;
+ char *uuid;
+ char *id;
+ gint64 timestamp;
+
+ 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) {
+ LOG ("Updating password for (%s, %s, %s, %s)",
+ uri, username, username_field, password_field);
+ ephy_password_manager_query (self, uri, username,
+ username_field, password_field,
+ update_password_cb,
+ update_password_async_data_new (self, password));
+ return;
+ }
+
+ uuid = g_uuid_string_random ();
+ id = g_strdup_printf ("{%s}", uuid);
+ timestamp = g_get_real_time () / 1000;
+ hostname = ephy_uri_to_security_origin (uri);
+ record = ephy_password_record_new (id, hostname,
+ username, password,
+ username_field, password_field,
+ timestamp, timestamp);
+ ephy_password_manger_store_record (self, record);
+ g_signal_emit_by_name (self, "synchronizable-modified", record);
+
+ g_free (hostname);
+ g_free (uuid);
+ g_free (id);
+ g_object_unref (record);
+}
+
+static void
+secret_service_search_cb (SecretService *service,
+ GAsyncResult *result,
+ QueryAsyncData *data)
+{
+ GList *matches = NULL;
+ 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, HOSTNAME_KEY);
+ const char *username = g_hash_table_lookup (attributes, USERNAME_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 *timestamp = g_hash_table_lookup (attributes, SERVER_TIME_MODIFIED_KEY);
+ const char *password = secret_value_get (value, NULL);
+ double server_time_modified;
+ EphyPasswordRecord *record;
+
+ LOG ("Found password record for (%s, %s, %s, %s)",
+ hostname, username, username_field, password_field);
+
+ record = ephy_password_record_new (id, hostname,
+ username, password,
+ username_field, password_field,
+ secret_item_get_created (item) * 1000,
+ secret_item_get_modified (item) * 1000);
+ 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);
+ g_list_free_full (matches, g_object_unref);
+}
+
+void
+ephy_password_manager_query (EphyPasswordManager *self,
+ const char *uri,
+ const char *username,
+ const char *username_field,
+ const char *password_field,
+ 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, username_field, password_field);
+
+ attributes = get_attributes_table (NULL, uri, username,
+ username_field, password_field, -1);
+ 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);
+}
+
+void
+ephy_password_manager_store_raw (const char *uri,
+ const char *username,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GHashTable *attributes;
+
+ g_return_if_fail (uri);
+ g_return_if_fail (password);
+ g_return_if_fail (!username_field || username);
+ g_return_if_fail (!password_field || password);
+
+ attributes = get_attributes_table (NULL, uri, username,
+ username_field, password_field, -1);
+ store_internal (password, attributes, callback, user_data);
+
+ g_hash_table_unref (attributes);
+}
+
+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);
+ }
+}
+
+static void
+ephy_password_manager_forget_record (EphyPasswordManager *self,
+ EphyPasswordRecord *record)
+{
+ GHashTable *attributes;
+
+ g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+ g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+ attributes = get_attributes_table (ephy_password_record_get_id (record),
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record),
+ ephy_password_record_get_username_field (record),
+ ephy_password_record_get_password_field (record),
+ ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE
(record)));
+ secret_service_clear (NULL, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
+ (GAsyncReadyCallback)secret_service_clear_cb, NULL);
+
+ ephy_password_manager_cache_remove (self,
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record));
+ g_hash_table_unref (attributes);
+}
+
+static void
+forget_cb (GSList *records,
+ gpointer user_data)
+{
+ EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
+ EphyPasswordRecord *record;
+
+ /* We only expect one matching record here. */
+ g_assert (g_slist_length (records) == 1);
+
+ record = EPHY_PASSWORD_RECORD (records->data);
+ g_signal_emit_by_name (self, "synchronizable-deleted", record);
+ ephy_password_manager_forget_record (self, record);
+
+ g_slist_free_full (records, g_object_unref);
+}
+
+void
+ephy_password_manager_forget (EphyPasswordManager *self,
+ const char *hostname,
+ const char *username,
+ const char *username_field,
+ const char *password_field)
+{
+ 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);
+
+ /* synchronizable-deleted signal needs an EphySynchronizable object,
+ * therefore we need to obtain the password record first and then emit
+ * the signal before clearing the password from the secret schema. */
+ ephy_password_manager_query (self, hostname, username,
+ username_field, password_field,
+ forget_cb, self);
+}
+
+static void
+forget_all_cb (GSList *records,
+ gpointer user_data)
+{
+ EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
+ GHashTable *attributes;
+
+ 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);
+
+ for (GSList *l = records; l && l->data; l = l->next)
+ g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+
+ ephy_password_manager_cache_clear (self);
+
+ g_hash_table_unref (attributes);
+ g_slist_free_full (records, g_object_unref);
+}
+
+void
+ephy_password_manager_forget_all (EphyPasswordManager *self)
+{
+ g_return_if_fail (EPHY_IS_PASSWORD_MANAGER (self));
+
+ /* synchronizable-deleted signal needs an EphySynchronizable object, therefore
+ * we need to obtain the password records first and emit the signal for each
+ * one before clearing the secret schema. */
+ ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+ forget_all_cb, self);
+}
+
+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)
+{
+ EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+ EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
+
+ ephy_password_manger_store_record (self, record);
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+ EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
+
+ ephy_password_manager_forget_record (self, record);
+}
+
+static EphyPasswordRecord *
+get_record_by_id (GSList *records,
+ const char *id)
+{
+ g_assert (id);
+
+ for (GSList *l = records; l && l->data; l = l->next) {
+ if (!g_strcmp0 (ephy_password_record_get_id (l->data), id))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static EphyPasswordRecord *
+get_record_by_parameters (GSList *records,
+ const char *hostname,
+ const char *username,
+ const char *username_field,
+ const char *password_field)
+{
+ for (GSList *l = records; l && l->data; l = l->next) {
+ if (!g_strcmp0 (ephy_password_record_get_username (l->data), username) &&
+ !g_strcmp0 (ephy_password_record_get_hostname (l->data), hostname) &&
+ !g_strcmp0 (ephy_password_record_get_username_field (l->data), username_field) &&
+ !g_strcmp0 (ephy_password_record_get_password_field (l->data), password_field))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static void
+replace_record_cb (GSList *records,
+ gpointer user_data)
+{
+ ReplaceRecordAsyncData *data = (ReplaceRecordAsyncData *)user_data;
+
+ /* We only expect one matching record here. */
+ g_assert (g_slist_length (records) == 1);
+
+ ephy_password_manager_forget_record (data->manager, records->data);
+ ephy_password_manger_store_record (data->manager, data->record);
+
+ replace_record_async_data_free (data);
+}
+
+static void
+ephy_password_manager_replace_record (EphyPasswordManager *self,
+ EphyPasswordRecord *record)
+{
+ g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+ g_assert (EPHY_IS_PASSWORD_RECORD (record));
+
+ ephy_password_manager_query (self,
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record),
+ ephy_password_record_get_username_field (record),
+ ephy_password_record_get_password_field (record),
+ replace_record_cb,
+ replace_record_async_data_new (self, record));
+}
+
+static GSList *
+ephy_password_manager_handle_initial_merge (EphyPasswordManager *self,
+ GSList *local_records,
+ GSList *remote_records)
+{
+ EphyPasswordRecord *record;
+ GHashTable *dont_upload;
+ GSList *to_upload = NULL;
+
+ g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+
+ /* A saved password record is uniquely identified by its id or by its tuple of
+ * (hostname, username, username field, password field). When it comes to
+ * importing passwords from server, we may encounter duplicates either by id
+ * or by mentioned tuple. Moreover, we assume that same id means same tuple
+ * but same tuple does not necessarily means same id. This is what our merge
+ * logic is based on. */
+
+ dont_upload = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (GSList *l = remote_records; l && l->data; l = l->next) {
+ const char *id = ephy_password_record_get_id (l->data);
+ const char *hostname = ephy_password_record_get_hostname (l->data);
+ const char *username = ephy_password_record_get_username (l->data);
+ const char *password = ephy_password_record_get_password (l->data);
+ const char *username_field = ephy_password_record_get_username_field (l->data);
+ const char *password_field = ephy_password_record_get_password_field (l->data);
+ guint64 timestamp = ephy_password_record_get_time_password_changed (l->data);
+ double server_time_modified = ephy_synchronizable_get_server_time_modified (l->data);
+
+ record = get_record_by_id (local_records, id);
+ if (record) {
+ if (!g_strcmp0 (ephy_password_record_get_password (record), password)) {
+ /* Same id, same password. Nothing to do. */
+ g_hash_table_add (dont_upload, (char *)id);
+ } else {
+ /* Same id, different password. Keep the most recent modified. */
+ if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+ /* Local record is newer. Keep it and upload it to server.
+ * Also, must keep the most recent server time modified. */
+ if (ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE (record)) <
server_time_modified) {
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record),
server_time_modified);
+ ephy_password_manager_replace_record (self, record);
+ }
+ } else {
+ /* Remote record is newer. Forget local record and store remote record. */
+ ephy_password_manager_forget_record (self, record);
+ ephy_password_manger_store_record (self, l->data);
+ g_hash_table_add (dont_upload, (char *)id);
+ }
+ }
+ } else {
+ record = get_record_by_parameters (local_records,
+ hostname, username,
+ username_field, password_field);
+ if (record) {
+ /* Different id, same tuple. Keep the most recent modified. */
+ if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+ /* Local record is newer. Keep it, upload it and delete remote record from server. */
+ g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+ } else {
+ /* Remote record is newer. Forget local record and store remote record. */
+ ephy_password_manager_forget_record (self, record);
+ ephy_password_manger_store_record (self, l->data);
+ g_hash_table_add (dont_upload, (char *)id);
+ }
+ } else {
+ /* Different id, different tuple. This is a new record, add it. */
+ ephy_password_manger_store_record (self, l->data);
+ g_hash_table_add (dont_upload, (char *)id);
+ }
+ }
+ }
+
+ /* Set the remaining local records to be uploaded to server. */
+ for (GSList *l = local_records; l && l->data; l = l->next) {
+ record = EPHY_PASSWORD_RECORD (l->data);
+ if (!g_hash_table_contains (dont_upload, ephy_password_record_get_id (record)))
+ to_upload = g_slist_prepend (to_upload, g_object_ref (record));
+ }
+
+ g_hash_table_unref (dont_upload);
+
+ return to_upload;
+}
+
+static GSList *
+ephy_password_manager_handle_regular_merge (EphyPasswordManager *self,
+ GSList *local_records,
+ GSList *deleted_records,
+ GSList *updated_records)
+{
+ EphyPasswordRecord *record;
+ GSList *to_upload = NULL;
+
+ g_assert (EPHY_IS_PASSWORD_MANAGER (self));
+
+ for (GSList *l = deleted_records; l && l->data; l = l->next) {
+ record = get_record_by_id (local_records, ephy_password_record_get_id (l->data));
+ if (record)
+ ephy_password_manager_forget_record (self, record);
+ }
+
+ /* See comment in ephy_password_manager_handle_initial_merge.
+ * Same logic applies here too. */
+ for (GSList *l = updated_records; l && l->data; l = l->next) {
+ const char *id = ephy_password_record_get_id (l->data);
+ const char *hostname = ephy_password_record_get_hostname (l->data);
+ const char *username = ephy_password_record_get_username (l->data);
+ const char *username_field = ephy_password_record_get_username_field (l->data);
+ const char *password_field = ephy_password_record_get_password_field (l->data);
+ guint64 timestamp = ephy_password_record_get_time_password_changed (l->data);
+
+ record = get_record_by_id (local_records, id);
+ if (record) {
+ /* Same id. Overwrite local record. */
+ ephy_password_manager_forget_record (self, record);
+ ephy_password_manger_store_record (self, l->data);
+ } else {
+ record = get_record_by_parameters (local_records,
+ hostname, username,
+ username_field, password_field);
+ if (record) {
+ /* Different id, same tuple. Keep the most recent modified. */
+ if (ephy_password_record_get_time_password_changed (record) > timestamp) {
+ /* Local record is newer. Keep it, upload it and delete remote record from server. */
+ to_upload = g_slist_prepend (to_upload, g_object_ref (record));
+ g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
+ } else {
+ /* Remote record is newer. Forget local record and store remote record. */
+ ephy_password_manager_forget_record (self, record);
+ ephy_password_manger_store_record (self, l->data);
+ }
+ } else {
+ /* Different id, different tuple. This is a new record, add it. */
+ ephy_password_manger_store_record (self, l->data);
+ }
+ }
+ }
+
+ return to_upload;
+}
+
+static void
+merge_cb (GSList *records,
+ gpointer user_data)
+{
+ MergeAsyncData *data = (MergeAsyncData *)user_data;
+ GSList *to_upload = NULL;
+
+ if (data->is_initial)
+ to_upload = ephy_password_manager_handle_initial_merge (data->manager,
+ records,
+ data->remotes_updated);
+ else
+ to_upload = ephy_password_manager_handle_regular_merge (data->manager,
+ records,
+ data->remotes_deleted,
+ data->remotes_updated);
+
+ data->callback (to_upload, data->user_data);
+
+ g_slist_free_full (records, g_object_unref);
+ merge_async_data_free (data);
+}
+
+static void
+synchronizable_manager_merge (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
+{
+ EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
+
+ ephy_password_manager_query (self, NULL, NULL, NULL, NULL,
+ merge_cb,
+ merge_async_data_new (self,
+ is_initial,
+ remotes_deleted,
+ remotes_updated,
+ callback,
+ user_data));
+}
+
+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 = synchronizable_manager_merge;
+}
diff --git a/lib/sync/ephy-password-manager.h b/lib/sync/ephy-password-manager.h
new file mode 100644
index 0000000..70c08cd
--- /dev/null
+++ b/lib/sync/ephy-password-manager.h
@@ -0,0 +1,82 @@
+/* -*- 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 HOSTNAME_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,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ gboolean is_new);
+void ephy_password_manager_query (EphyPasswordManager *self,
+ const char *uri,
+ const char
*username,
+ const char
*username_field,
+ const char
*password_field,
+ EphyPasswordManagerQueryCallback
callback,
+ gpointer
user_data);
+void ephy_password_manager_forget (EphyPasswordManager *self,
+ const char *hostname,
+ const char *username,
+ const char *username_field,
+ const char *password_field);
+void ephy_password_manager_forget_all (EphyPasswordManager *self);
+/* Note: Below functions are deprecated and should not be used in newly written code.
+ * The only reason they still exist is that the profile migrator expects them. */
+void ephy_password_manager_store_raw (const char *uri,
+ const char *username,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ephy_password_manager_store_finish (GAsyncResult *result,
+ GError **error);
+
+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..63ffc6b
--- /dev/null
+++ b/lib/sync/ephy-password-record.c
@@ -0,0 +1,370 @@
+/* -*- 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 *username;
+ char *password;
+ char *username_field;
+ char *password_field;
+ 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_USERNAME, /* Epiphany && Firefox Sync */
+ PROP_PASSWORD, /* Epiphany && Firefox Sync */
+ PROP_USERNAME_FIELD, /* Epiphany && Firefox Sync */
+ PROP_PASSWORD_FIELD, /* 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_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_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_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_USERNAME:
+ g_value_set_string (value, self->username);
+ break;
+ case PROP_PASSWORD:
+ g_value_set_string (value, self->password);
+ 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_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->username, g_free);
+ g_clear_pointer (&self->password, g_free);
+ g_clear_pointer (&self->username_field, g_free);
+ g_clear_pointer (&self->password_field, 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_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_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_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,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ guint64 time_created,
+ guint64 time_password_changed)
+{
+ return EPHY_PASSWORD_RECORD (g_object_new (EPHY_TYPE_PASSWORD_RECORD,
+ "id", id,
+ "hostname", hostname,
+ "formSubmitURL", hostname,
+ "username", username,
+ "password", password,
+ "usernameField", username_field,
+ "passwordField", password_field,
+ "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 (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);
+}
+
+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;
+}
+
+guint64
+ephy_password_record_get_time_password_changed (EphyPasswordRecord *self)
+{
+ g_return_val_if_fail (EPHY_IS_PASSWORD_RECORD (self), 0);
+
+ return self->time_password_changed;
+}
+
+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/sync/ephy-password-record.h b/lib/sync/ephy-password-record.h
new file mode 100644
index 0000000..e4d1106
--- /dev/null
+++ b/lib/sync/ephy-password-record.h
@@ -0,0 +1,50 @@
+/* -*- 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 <glib-object.h>
+#include <libsecret/secret.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_PASSWORD_RECORD (ephy_password_record_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyPasswordRecord, ephy_password_record, EPHY, PASSWORD_RECORD, GObject)
+
+EphyPasswordRecord *ephy_password_record_new (const char *id,
+ const char *hostname,
+ const char *username,
+ const char *password,
+ const char *username_field,
+ const char *password_field,
+ 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 (EphyPasswordRecord *self);
+const char *ephy_password_record_get_password (EphyPasswordRecord *self);
+void ephy_password_record_set_password (EphyPasswordRecord *self,
+ const char *password);
+const char *ephy_password_record_get_username_field (EphyPasswordRecord *self);
+const char *ephy_password_record_get_password_field (EphyPasswordRecord *self);
+guint64 ephy_password_record_get_time_password_changed (EphyPasswordRecord *self);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-sync-service.c b/lib/sync/ephy-sync-service.c
index 7d35fb7..657f1d9 100644
--- a/lib/sync/ephy-sync-service.c
+++ b/lib/sync/ephy-sync-service.c
@@ -75,6 +75,8 @@ struct _EphySyncService {
char *certificate;
SyncCryptoRSAKeyPair *rsa_key_pair;
+
+ gboolean sync_periodically;
};
G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
@@ -95,6 +97,14 @@ static const char * const secrets[LAST_SECRET] = {
};
enum {
+ PROP_0,
+ PROP_SYNC_PERIODICALLY,
+ LAST_PROP
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+enum {
STORE_FINISHED,
SIGN_IN_ERROR,
SYNC_FREQUENCY_CHANGED,
@@ -131,6 +141,8 @@ typedef struct {
EphySynchronizableManager *manager;
gboolean is_initial;
gboolean is_last;
+ GSList *remotes_deleted;
+ GSList *remotes_updated;
} SyncCollectionAsyncData;
typedef struct {
@@ -233,6 +245,8 @@ sync_collection_async_data_new (EphySyncService *service,
data->manager = g_object_ref (manager);
data->is_initial = is_initial;
data->is_last = is_last;
+ data->remotes_deleted = NULL;
+ data->remotes_updated = NULL;
return data;
}
@@ -244,6 +258,8 @@ sync_collection_async_data_free (SyncCollectionAsyncData *data)
g_object_unref (data->service);
g_object_unref (data->manager);
+ g_slist_free_full (data->remotes_deleted, g_object_unref);
+ g_slist_free_full (data->remotes_updated, g_object_unref);
g_slice_free (SyncCollectionAsyncData, data);
}
@@ -273,6 +289,40 @@ sync_async_data_free (SyncAsyncData *data)
g_slice_free (SyncAsyncData, data);
}
+static void
+ephy_sync_service_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphySyncService *self = EPHY_SYNC_SERVICE (object);
+
+ switch (prop_id) {
+ case PROP_SYNC_PERIODICALLY:
+ self->sync_periodically = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_sync_service_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphySyncService *self = EPHY_SYNC_SERVICE (object);
+
+ switch (prop_id) {
+ case PROP_SYNC_PERIODICALLY:
+ g_value_set_boolean (value, self->sync_periodically);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
static const char *
ephy_sync_service_get_secret (EphySyncService *self,
const char *name)
@@ -943,6 +993,7 @@ ephy_sync_service_delete_synchronizable (EphySyncService *self,
char *record;
char *payload;
char *body;
+ char *id_safe;
const char *collection;
const char *id;
@@ -951,9 +1002,12 @@ ephy_sync_service_delete_synchronizable (EphySyncService *self,
g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
g_assert (ephy_sync_service_is_signed_in (self));
- id = ephy_synchronizable_get_id (synchronizable);
collection = ephy_synchronizable_manager_get_collection_name (manager);
- endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+ id = ephy_synchronizable_get_id (synchronizable);
+ /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+ * Curly braces are unsafe characters in URLs so they must be encoded. */
+ id_safe = soup_uri_encode (id, NULL);
+ endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
node = json_node_new (JSON_NODE_OBJECT);
object = json_object_new ();
@@ -973,6 +1027,7 @@ ephy_sync_service_delete_synchronizable (EphySyncService *self,
SOUP_METHOD_PUT, body, -1, -1,
delete_synchronizable_cb, NULL);
+ g_free (id_safe);
g_free (endpoint);
g_free (record);
g_free (payload);
@@ -1042,6 +1097,7 @@ ephy_sync_service_download_synchronizable (EphySyncService *self,
{
SyncAsyncData *data;
char *endpoint;
+ char *id_safe;
const char *collection;
const char *id;
@@ -1052,7 +1108,10 @@ ephy_sync_service_download_synchronizable (EphySyncService *self,
id = ephy_synchronizable_get_id (synchronizable);
collection = ephy_synchronizable_manager_get_collection_name (manager);
- endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+ /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+ * Curly braces are unsafe characters in URLs so they must be encoded. */
+ id_safe = soup_uri_encode (id, NULL);
+ endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
data = sync_async_data_new (self, manager, synchronizable);
LOG ("Downloading object with id %s...", id);
@@ -1061,6 +1120,7 @@ ephy_sync_service_download_synchronizable (EphySyncService *self,
download_synchronizable_cb, data);
g_free (endpoint);
+ g_free (id_safe);
}
static void
@@ -1099,6 +1159,7 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
JsonNode *bso;
char *endpoint;
char *body;
+ char *id_safe;
const char *collection;
const char *id;
@@ -1111,7 +1172,10 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
bundle = ephy_sync_service_get_key_bundle (self, collection);
bso = ephy_synchronizable_to_bso (synchronizable, bundle);
id = ephy_synchronizable_get_id (synchronizable);
- endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+ /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
+ * Curly braces are unsafe characters in URLs so they must be encoded. */
+ id_safe = soup_uri_encode (id, NULL);
+ endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
data = sync_async_data_new (self, manager, synchronizable);
body = json_to_string (bso, FALSE);
@@ -1120,6 +1184,7 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
ephy_synchronizable_get_server_time_modified (synchronizable),
upload_synchronizable_cb, data);
+ g_free (id_safe);
g_free (body);
g_free (endpoint);
json_node_unref (bso);
@@ -1127,6 +1192,23 @@ ephy_sync_service_upload_synchronizable (EphySyncService *self,
}
static void
+merge_finished_cb (GSList *to_upload,
+ gpointer user_data)
+{
+ SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
+
+ for (GSList *l = to_upload; l && l->data; l = l->next)
+ ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+
+ if (data->is_last)
+ g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
+
+ if (to_upload)
+ g_slist_free_full (to_upload, g_object_unref);
+ sync_collection_async_data_free (data);
+}
+
+static void
sync_collection_cb (SoupSession *session,
SoupMessage *msg,
gpointer user_data)
@@ -1137,9 +1219,6 @@ sync_collection_cb (SoupSession *session,
JsonNode *node = NULL;
JsonArray *array = NULL;
GError *error = NULL;
- GSList *remotes_updated = NULL;
- GSList *remotes_deleted = NULL;
- GSList *to_upload = NULL;
GType type;
const char *collection;
const char *last_modified;
@@ -1150,21 +1229,19 @@ sync_collection_cb (SoupSession *session,
if (msg->status_code != 200) {
g_warning ("Failed to get records in collection %s. Status code: %u, response: %s",
collection, msg->status_code, msg->response_body->data);
- goto out;
+ goto out_error;
}
node = json_from_string (msg->response_body->data, &error);
if (error) {
g_warning ("Response is not a valid JSON: %s", error->message);
- goto out;
+ goto out_error;
}
array = json_node_get_array (node);
if (!array) {
g_warning ("JSON node does not hold an array");
- goto out;
+ goto out_error;
}
- LOG ("Found %u new remote objects...", json_array_get_length (array));
-
type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
bundle = ephy_sync_service_get_key_bundle (data->service, collection);
for (guint i = 0; i < json_array_get_length (array); i++) {
@@ -1175,36 +1252,35 @@ sync_collection_cb (SoupSession *session,
continue;
}
if (is_deleted)
- remotes_deleted = g_slist_prepend (remotes_deleted, remote);
+ data->remotes_deleted = g_slist_prepend (data->remotes_deleted, remote);
else
- remotes_updated = g_slist_prepend (remotes_updated, remote);
+ data->remotes_updated = g_slist_prepend (data->remotes_updated, remote);
}
- to_upload = ephy_synchronizable_manager_merge (data->manager, data->is_initial,
- remotes_deleted, remotes_updated);
- for (GSList *l = to_upload; l && l->data; l = l->next)
- ephy_sync_service_upload_synchronizable (data->service, data->manager, l->data);
+ LOG ("Found %u deleted objects and %u new/updated objects in %s collection",
+ g_slist_length (data->remotes_deleted),
+ g_slist_length (data->remotes_updated),
+ collection);
/* Update sync time. */
last_modified = soup_message_headers_get_one (msg->response_headers, "X-Last-Modified");
ephy_synchronizable_manager_set_sync_time (data->manager, g_ascii_strtod (last_modified, NULL));
ephy_synchronizable_manager_set_is_initial_sync (data->manager, FALSE);
-out:
+ ephy_synchronizable_manager_merge (data->manager, data->is_initial,
+ data->remotes_deleted, data->remotes_updated,
+ merge_finished_cb, data);
+ goto out_no_error;
+
+out_error:
if (data->is_last)
g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
-
- if (to_upload)
- g_slist_free_full (to_upload, g_object_unref);
- if (remotes_updated)
- g_slist_free_full (remotes_updated, g_object_unref);
- if (remotes_deleted)
- g_slist_free_full (remotes_deleted, g_object_unref);
+ sync_collection_async_data_free (data);
+out_no_error:
if (node)
json_node_unref (node);
if (error)
g_error_free (error);
- sync_collection_async_data_free (data);
}
static void
@@ -1447,7 +1523,8 @@ load_secrets_cb (SecretService *service,
ephy_sync_service_set_secret (self, l->data,
json_object_get_string_member (object, l->data));
- ephy_sync_service_start_periodical_sync (self);
+ if (self->sync_periodically)
+ ephy_sync_service_start_periodical_sync (self);
goto out_no_error;
out_error:
@@ -1572,8 +1649,19 @@ ephy_sync_service_class_init (EphySyncServiceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = ephy_sync_service_set_property;
+ object_class->get_property = ephy_sync_service_get_property;
object_class->dispose = ephy_sync_service_dispose;
+ obj_properties[PROP_SYNC_PERIODICALLY] =
+ g_param_spec_boolean ("sync-periodically",
+ "Sync periodically",
+ "Whether should periodically sync data",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+
signals[STORE_FINISHED] =
g_signal_new ("sync-secrets-store-finished",
EPHY_TYPE_SYNC_SERVICE,
@@ -1616,9 +1704,11 @@ ephy_sync_service_init (EphySyncService *self)
self->storage_queue = g_queue_new ();
self->secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
- settings = ephy_embed_prefs_get_settings ();
- user_agent = webkit_settings_get_user_agent (settings);
- g_object_set (self->session, "user-agent", user_agent, NULL);
+ if (self->sync_periodically) {
+ settings = ephy_embed_prefs_get_settings ();
+ user_agent = webkit_settings_get_user_agent (settings);
+ g_object_set (self->session, "user-agent", user_agent, NULL);
+ }
account = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
if (g_strcmp0 (account, "")) {
@@ -1634,9 +1724,11 @@ ephy_sync_service_init (EphySyncService *self)
}
EphySyncService *
-ephy_sync_service_new (void)
+ephy_sync_service_new (gboolean sync_periodically)
{
- return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+ return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE,
+ "sync-periodically", sync_periodically,
+ NULL));
}
gboolean
@@ -2182,6 +2274,7 @@ ephy_sync_service_do_sign_out (EphySyncService *self)
g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, "");
g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL, TRUE);
+ g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_INITIAL, TRUE);
}
void
@@ -2198,6 +2291,7 @@ ephy_sync_service_start_periodical_sync (EphySyncService *self)
{
g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
g_return_if_fail (ephy_sync_service_is_signed_in (self));
+ g_return_if_fail (self->sync_periodically);
ephy_sync_service_sync (self);
ephy_sync_service_schedule_periodical_sync (self);
diff --git a/lib/sync/ephy-sync-service.h b/lib/sync/ephy-sync-service.h
index b45892c..369b9a6 100644
--- a/lib/sync/ephy-sync-service.h
+++ b/lib/sync/ephy-sync-service.h
@@ -30,7 +30,7 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphySyncService, ephy_sync_service, EPHY, SYNC_SERVICE, GObject)
-EphySyncService *ephy_sync_service_new (void);
+EphySyncService *ephy_sync_service_new (gboolean sync_periodically);
gboolean ephy_sync_service_is_signed_in (EphySyncService *self);
const char *ephy_sync_service_get_sync_user (EphySyncService *self);
void ephy_sync_service_do_sign_in (EphySyncService *self,
diff --git a/lib/sync/ephy-synchronizable-manager.c b/lib/sync/ephy-synchronizable-manager.c
index c25a5cb..01c27f1 100644
--- a/lib/sync/ephy-synchronizable-manager.c
+++ b/lib/sync/ephy-synchronizable-manager.c
@@ -227,23 +227,28 @@ ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
* objects that were removed remotely from the server.
* @remotes_updated: (transfer none): a #GSList holding the #EphySynchronizable
* objects that were updated remotely on the server.
+ * @callback: an #EphySynchronizableManagerMergeCallback that will be called
+ * when the merge is complete.
+ * @user_data: user data to pass to the @callback.
*
* Merges a list of remote-deleted objects and a list of remote-updated objects
- * with the local objects in @manager's collection.
- *
- * Return value: (transfer full): a #GSList holding the #EphySynchronizable
- * objects that need to be re-uploaded to server.
+ * with the local objects in @manager's collection. When the merge is completed,
+ * @callback will be invoked with a list of objects that need to be (re)uploaded
+ * to server. Transfer full for the list of objects to be (re)uploaded.
**/
-GSList *
-ephy_synchronizable_manager_merge (EphySynchronizableManager *manager,
- gboolean is_initial,
- GSList *remotes_deleted,
- GSList *remotes_updated)
+void
+ephy_synchronizable_manager_merge (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
{
EphySynchronizableManagerInterface *iface;
- g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_return_if_fail (callback);
iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
- return iface->merge (manager, is_initial, remotes_deleted, remotes_updated);
+ iface->merge (manager, is_initial, remotes_deleted, remotes_updated, callback, user_data);
}
diff --git a/lib/sync/ephy-synchronizable-manager.h b/lib/sync/ephy-synchronizable-manager.h
index fe791a3..e848b94 100644
--- a/lib/sync/ephy-synchronizable-manager.h
+++ b/lib/sync/ephy-synchronizable-manager.h
@@ -30,6 +30,8 @@ G_BEGIN_DECLS
G_DECLARE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, EPHY, SYNCHRONIZABLE_MANAGER,
GObject)
+typedef void (*EphySynchronizableManagerMergeCallback) (GSList *to_upload, gpointer user_data);
+
struct _EphySynchronizableManagerInterface {
GTypeInterface parent_iface;
@@ -45,10 +47,12 @@ struct _EphySynchronizableManagerInterface {
EphySynchronizable *synchronizable);
void (*remove) (EphySynchronizableManager *manager,
EphySynchronizable *synchronizable);
- GSList * (*merge) (EphySynchronizableManager *manager,
- gboolean is_initial,
- GSList *remotes_deleted,
- GSList *remotes_updated);
+ void (*merge) (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data);
};
const char *ephy_synchronizable_manager_get_collection_name (EphySynchronizableManager *manager);
@@ -63,9 +67,11 @@ void ephy_synchronizable_manager_add (EphySyn
EphySynchronizable
*synchronizable);
void ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
EphySynchronizable
*synchronizable);
-GSList *ephy_synchronizable_manager_merge (EphySynchronizableManager *manager,
- gboolean
is_initial,
- GSList
*remotes_deleted,
- GSList
*remotes_updated);
+void ephy_synchronizable_manager_merge (EphySynchronizableManager
*manager,
+ gboolean
is_initial,
+ GSList
*remotes_deleted,
+ GSList
*remotes_updated,
+
EphySynchronizableManagerMergeCallback callback,
+ gpointer
user_data);
G_END_DECLS
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/bookmarks/ephy-bookmarks-manager.c b/src/bookmarks/ephy-bookmarks-manager.c
index 8f7619a..58c34a8 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -888,18 +888,26 @@ next:
return to_upload;
}
-static GSList *
-synchronizable_manager_merge (EphySynchronizableManager *manager,
- gboolean is_initial,
- GSList *remotes_deleted,
- GSList *remotes_updated)
+static void
+synchronizable_manager_merge (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated,
+ EphySynchronizableManagerMergeCallback callback,
+ gpointer user_data)
{
EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+ GSList *to_upload = NULL;
if (is_initial)
- return ephy_bookmarks_manager_handle_initial_merge (self, remotes_updated);
-
- return ephy_bookmarks_manager_handle_regular_merge (self, remotes_updated, remotes_deleted);
+ to_upload = ephy_bookmarks_manager_handle_initial_merge (self,
+ remotes_updated);
+ else
+ to_upload = ephy_bookmarks_manager_handle_regular_merge (self,
+ remotes_updated,
+ remotes_deleted);
+
+ callback (to_upload, user_data);
}
static void
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 6f917a5..0310705 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 ();
+ ephy_shell->sync_service = ephy_sync_service_new (TRUE);
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..3023dc2 100644
--- a/src/passwords-dialog.c
+++ b/src/passwords-dialog.c
@@ -62,16 +62,6 @@ struct _EphyPasswordsDialog {
G_DEFINE_TYPE (EphyPasswordsDialog, ephy_passwords_dialog, GTK_TYPE_DIALOG)
-static void populate_model (EphyPasswordsDialog *dialog);
-
-static void
-reload_model (EphyPasswordsDialog *dialog)
-{
- gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
- dialog->filled = FALSE;
- populate_model (dialog);
-}
-
static void
ephy_passwords_dialog_dispose (GObject *object)
{
@@ -140,10 +130,10 @@ 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_username (record));
+ ephy_password_record_get_hostname (record),
+ ephy_password_record_get_username (record),
+ ephy_password_record_get_username_field (record),
+ ephy_password_record_get_password_field (record));
dialog->records = g_slist_remove (dialog->records, record);
g_object_unref (record);
g_value_unset (&val);
@@ -351,10 +341,12 @@ forget_all (GSimpleAction *action,
EphyPasswordsDialog *dialog = EPHY_PASSWORDS_DIALOG (user_data);
ephy_password_manager_forget_all (dialog->manager);
+
+ gtk_list_store_clear (GTK_LIST_STORE (dialog->liststore));
+ dialog->filled = FALSE;
+
g_slist_free_full (dialog->records, g_object_unref);
dialog->records = NULL;
-
- reload_model (dialog);
}
static void
@@ -370,7 +362,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 a4f9457..72ee246 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 = NULL;
- 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
@@ -234,6 +236,7 @@ sync_secrets_store_finished_cb (EphySyncService *service,
PrefsDialog *dialog)
{
EphyBookmarksManager *bookmarks_manager;
+ EphyPasswordManager *password_manager;
g_assert (EPHY_IS_SYNC_SERVICE (service));
g_assert (EPHY_IS_PREFS_DIALOG (dialog));
@@ -264,6 +267,10 @@ sync_secrets_store_finished_cb (EphySyncService *service,
bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (bookmarks_manager));
}
+ if (g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED)) {
+ password_manager = ephy_shell_get_password_manager (ephy_shell_get_default ());
+ ephy_sync_service_register_manager (service, EPHY_SYNCHRONIZABLE_MANAGER (password_manager));
+ }
g_free (text);
g_free (user);
@@ -593,6 +600,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);
@@ -1647,7 +1655,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,
@@ -1660,6 +1671,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..42a65c3 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,20 +338,20 @@ 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,
- username,
- password,
- (GAsyncReadyCallback)store_form_auth_data_cb,
- g_hash_table_ref (attributes));
+ ephy_password_manager_store_raw (actual_server,
+ username,
+ password,
+ username_field,
+ password_field,
+ (GAsyncReadyCallback)store_form_auth_data_cb,
+ g_hash_table_ref (attributes));
g_free (actual_server);
secret_value_unref (secret);
g_hash_table_unref (t);
@@ -440,7 +440,7 @@ migrate_insecure_password (SecretItem *item)
const char *original_uri;
attributes = secret_item_get_attributes (item);
- original_uri = g_hash_table_lookup (attributes, URI_KEY);
+ original_uri = g_hash_table_lookup (attributes, HOSTNAME_KEY);
original_origin = webkit_security_origin_new_for_uri (original_uri);
if (original_origin == NULL) {
g_warning ("Failed to convert URI %s to a security origin, insecure password will not be migrated",
original_uri);
@@ -459,7 +459,7 @@ migrate_insecure_password (SecretItem *item)
new_uri = webkit_security_origin_to_string (new_origin);
webkit_security_origin_unref (new_origin);
- g_hash_table_replace (attributes, g_strdup (URI_KEY), new_uri);
+ g_hash_table_replace (attributes, g_strdup (HOSTNAME_KEY), new_uri);
secret_item_set_attributes_sync (item, EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL, &error);
if (error != NULL) {
g_warning ("Failed to convert URI %s to https://, insecure password will not be migrated: %s",
original_uri, error->message);
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]