[epiphany/wip/sync: 1/3] sync: Replace old bookmarks sync code
- From: Gabriel Ivașcu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/sync: 1/3] sync: Replace old bookmarks sync code
- Date: Sat, 29 Apr 2017 09:42:48 +0000 (UTC)
commit 8a66bcd030ba3bbeb0f3fbe67c13d9bc3b3adb5d
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date: Tue Apr 25 16:14:10 2017 +0300
sync: Replace old bookmarks sync code
configure.ac | 2 +-
data/org.gnome.epiphany.gschema.xml | 48 +-
lib/ephy-prefs.h | 11 +-
lib/ephy-settings.h | 1 +
po/POTFILES.in | 1 -
src/Makefile.am | 8 +-
src/bookmarks/ephy-add-bookmark-popover.c | 17 +-
src/bookmarks/ephy-bookmark-properties-grid.c | 147 +-
src/bookmarks/ephy-bookmark.c | 443 ++--
src/bookmarks/ephy-bookmark.h | 80 +-
src/bookmarks/ephy-bookmarks-export.c | 4 +-
src/bookmarks/ephy-bookmarks-import.c | 23 +-
src/bookmarks/ephy-bookmarks-manager.c | 439 ++++-
src/ephy-shell.c | 30 +-
src/prefs-dialog.c | 655 ++++---
src/profile-migrator/Makefile.am | 1 +
src/profile-migrator/ephy-profile-migrator.c | 7 +-
src/resources/gtk/prefs-dialog.ui | 181 ++-
src/sync/ephy-sync-crypto.c | 1130 +++++++----
src/sync/ephy-sync-crypto.h | 138 +-
src/sync/ephy-sync-secret.c | 258 ---
src/sync/ephy-sync-secret.h | 49 -
src/sync/ephy-sync-service.c | 2725 ++++++++++++++++---------
src/sync/ephy-sync-service.h | 71 +-
src/sync/ephy-sync-utils.c | 195 --
src/sync/ephy-sync-utils.h | 52 -
src/sync/ephy-synchronizable-manager.c | 249 +++
src/sync/ephy-synchronizable-manager.h | 71 +
src/sync/ephy-synchronizable.c | 246 +++
src/sync/ephy-synchronizable.h | 59 +
30 files changed, 4542 insertions(+), 2799 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index cc4ffe1..92fd717 100644
--- a/configure.ac
+++ b/configure.ac
@@ -109,7 +109,7 @@ PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= $GTK_REQUIRED])
PKG_CHECK_MODULES([GTK_UNIX_PRINT], [gtk+-unix-print-3.0 >= $GTK_REQUIRED])
PKG_CHECK_MODULES([ICU_UC], [icu-uc >= 4.6])
PKG_CHECK_MODULES([HOGWEED], [hogweed >= 3.2])
-PKG_CHECK_MODULES([JSON_GLIB], [json-glib-1.0 >= 1.2.0])
+PKG_CHECK_MODULES([JSON_GLIB], [json-glib-1.0 >= 1.2.4])
PKG_CHECK_MODULES([LIBNOTIFY], [libnotify >= 0.5.1])
PKG_CHECK_MODULES([LIBSECRET], [libsecret-1 >= 0.14])
PKG_CHECK_MODULES([LIBSOUP], [libsoup-2.4 >= 2.48.0])
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index 6540572..2264bbd 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -6,6 +6,7 @@
<child schema="org.gnome.Epiphany.state" name="state"/>
<child schema="org.gnome.Epiphany.lockdown" name="lockdown"/>
<child schema="org.gnome.Epiphany.permissions" name="permissions"/>
+ <child schema="org.gnome.Epiphany.sync" name="sync"/>
<key type="b" name="enable-caret-browsing">
<default>false</default>
<summary>Browse with caret</summary>
@@ -99,16 +100,6 @@
<description>This option sets a limit to the number of web processes that will be
used at the same time for the “one-secondary-process-per-web-view” model. The default value is “0” and means
no limit.</description>
</key>
- <key type="s" name="sync-user">
- <default>''</default>
- <summary>The sync user currently logged in</summary>
- <description>The email linked to the Firefox Account used to sync data with Mozilla’s
servers.</description>
- </key>
- <key type="d" name="sync-time">
- <default>0</default>
- <summary>Sync timestamp</summary>
- <description>The timestamp at which last we had the last sync</description>
- </key>
<key type="as" name="adblock-filters">
<default>['https://easylist.to/easylist/easylist.txt',
'https://easylist.to/easylist/easyprivacy.txt']</default>
<summary>List of adblock filters</summary>
@@ -275,6 +266,43 @@
<default>false</default>
</key>
</schema>
+ <schema path="/org/gnome/Epiphany/sync/" id="org.gnome.Epiphany.sync">
+ <key type="s" name="sync-user">
+ <default>''</default>
+ <summary>Currently signed in sync user</summary>
+ <description>The email linked to the Firefox Account used to sync data with Mozilla’s
servers.</description>
+ </key>
+ <key type="s" name="sync-client-id">
+ <default>''</default>
+ <summary>Sync client ID</summary>
+ <description>The sync client ID of the current device.</description>
+ </key>
+ <key type="u" name="sync-frequency">
+ <default>30</default>
+ <summary>The sync frequency in minutes</summary>
+ <description>The number of minutes between two consecutive syncs.</description>
+ </key>
+ <key type="b" name="sync-with-firefox">
+ <default>false</default>
+ <summary>Sync data with Firefox</summary>
+ <description>TRUE if Ephy collections should be synced with Firefox collections,
FALSE otherwise.</description>
+ </key>
+ <key type="b" name="sync-bookmarks-enabled">
+ <default>false</default>
+ <summary>Enable bookmarks sync</summary>
+ <description>TRUE if bookmarks collection should be synced, FALSE
otherwise.</description>
+ </key>
+ <key type="d" name="sync-bookmarks-time">
+ <default>0</default>
+ <summary>Bookmarks sync timestamp</summary>
+ <description>The timestamp at which last bookmarks sync was made.</description>
+ </key>
+ <key type="b" name="sync-bookmarks-initial">
+ <default>true</default>
+ <summary>Initial sync or normal sync</summary>
+ <description>TRUE if bookmarks 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"/>
<value nick="deny" value="0"/>
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 919037e..3167d12 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -136,8 +136,6 @@ static const char * const ephy_prefs_web_schema[] = {
#define EPHY_PREFS_RESTORE_SESSION_DELAYING_LOADS "restore-session-delaying-loads"
#define EPHY_PREFS_PROCESS_MODEL "process-model"
#define EPHY_PREFS_MAX_PROCESSES "max-processes"
-#define EPHY_PREFS_SYNC_USER "sync-user"
-#define EPHY_PREFS_SYNC_TIME "sync-time"
#define EPHY_PREFS_ADBLOCK_FILTERS "adblock-filters"
#define EPHY_PREFS_SEARCH_ENGINES "search-engines"
#define EPHY_PREFS_DEFAULT_SEARCH_ENGINE "default-search-engine"
@@ -151,6 +149,15 @@ static const char * const ephy_prefs_web_schema[] = {
#define EPHY_PREFS_LOCKDOWN_PRINTING "disable-printing"
#define EPHY_PREFS_LOCKDOWN_QUIT "disable-quit"
+#define EPHY_PREFS_SYNC_SCHEMA "org.gnome.Epiphany.sync"
+#define EPHY_PREFS_SYNC_USER "sync-user"
+#define EPHY_PREFS_SYNC_CLIENT_ID "sync-client-id"
+#define EPHY_PREFS_SYNC_FREQUENCY "sync-frequency"
+#define EPHY_PREFS_SYNC_WITH_FIREFOX "sync-with-firefox"
+#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"
+
static struct {
const char *schema;
const char *path;
diff --git a/lib/ephy-settings.h b/lib/ephy-settings.h
index 23d80d3..ee186c4 100644
--- a/lib/ephy-settings.h
+++ b/lib/ephy-settings.h
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
#define EPHY_SETTINGS_WEB ephy_settings_get (EPHY_PREFS_WEB_SCHEMA)
#define EPHY_SETTINGS_LOCKDOWN ephy_settings_get (EPHY_PREFS_LOCKDOWN_SCHEMA)
#define EPHY_SETTINGS_STATE ephy_settings_get (EPHY_PREFS_STATE_SCHEMA)
+#define EPHY_SETTINGS_SYNC ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA)
GSettings *ephy_settings_get (const char *schema);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 939bfd7..8ca7f32 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -60,6 +60,5 @@ src/resources/gtk/prefs-lang-dialog.ui
src/resources/gtk/search-engine-dialog.ui
src/resources/gtk/shortcuts-dialog.ui
src/search-provider/ephy-search-provider.c
-src/sync/ephy-sync-secret.c
src/sync/ephy-sync-service.c
src/window-commands.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 8cafe93..5e6a866 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -75,12 +75,12 @@ libephymain_la_SOURCES = \
window-commands.h \
sync/ephy-sync-crypto.c \
sync/ephy-sync-crypto.h \
- sync/ephy-sync-secret.c \
- sync/ephy-sync-secret.h \
sync/ephy-sync-service.c \
sync/ephy-sync-service.h \
- sync/ephy-sync-utils.c \
- sync/ephy-sync-utils.h
+ sync/ephy-synchronizable.c \
+ sync/ephy-synchronizable.h \
+ sync/ephy-synchronizable-manager.c \
+ sync/ephy-synchronizable-manager.h
nodist_libephymain_la_SOURCES = \
$(TYPES_SOURCE)
diff --git a/src/bookmarks/ephy-add-bookmark-popover.c b/src/bookmarks/ephy-add-bookmark-popover.c
index 195396d..76019ee 100644
--- a/src/bookmarks/ephy-add-bookmark-popover.c
+++ b/src/bookmarks/ephy-add-bookmark-popover.c
@@ -32,7 +32,6 @@ struct _EphyAddBookmarkPopover {
GtkPopover parent_instance;
char *address;
- gboolean is_new_bookmark;
GtkWidget *grid;
EphyHeaderBar *header_bar;
@@ -113,6 +112,7 @@ static void
ephy_add_bookmark_popover_closed_cb (GtkPopover *popover,
gpointer user_data)
{
+ EphyAddBookmarkPopover *self;
EphyBookmarksManager *manager;
g_assert (EPHY_IS_ADD_BOOKMARK_POPOVER (popover));
@@ -122,6 +122,10 @@ ephy_add_bookmark_popover_closed_cb (GtkPopover *popover,
ephy_bookmarks_manager_save_to_file_async (manager, NULL,
ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
NULL);
+
+ self = EPHY_ADD_BOOKMARK_POPOVER (popover);
+ g_clear_pointer (&self->address, g_free);
+ g_clear_pointer (&self->grid, gtk_widget_destroy);
}
static void
@@ -182,9 +186,6 @@ ephy_add_bookmark_popover_update_bookmarked_status_cb (EphyAddBookmarkPopover *s
NULL);
gtk_widget_hide (GTK_WIDGET (self));
-
- g_clear_pointer (&self->address, g_free);
- g_clear_pointer (&self->grid, gtk_widget_destroy);
}
void
@@ -206,14 +207,17 @@ ephy_add_bookmark_popover_show (EphyAddBookmarkPopover *self)
bookmark = ephy_bookmarks_manager_get_bookmark_by_url (manager, address);
if (!bookmark) {
+ char *id = ephy_sync_crypto_get_random_sync_id ();
bookmark = ephy_bookmark_new (address,
ephy_embed_get_title (embed),
- g_sequence_new (g_free));
+ g_sequence_new (g_free),
+ id);
ephy_bookmarks_manager_add_bookmark (manager, bookmark);
ephy_location_entry_set_bookmark_icon_state (location_entry,
EPHY_LOCATION_ENTRY_BOOKMARK_ICON_BOOKMARKED);
g_object_unref (bookmark);
+ g_free (id);
}
g_signal_connect_object (manager, "bookmark-removed",
@@ -221,12 +225,9 @@ ephy_add_bookmark_popover_show (EphyAddBookmarkPopover *self)
self,
G_CONNECT_SWAPPED);
- if (self->grid)
- gtk_widget_destroy (self->grid);
self->grid = ephy_bookmark_properties_grid_new (bookmark,
EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_POPOVER,
GTK_WIDGET (self));
-
gtk_container_add (GTK_CONTAINER (self), self->grid);
gtk_popover_set_default_widget (GTK_POPOVER (self),
ephy_bookmark_properties_grid_get_add_tag_button
(EPHY_BOOKMARK_PROPERTIES_GRID (self->grid)));
diff --git a/src/bookmarks/ephy-bookmark-properties-grid.c b/src/bookmarks/ephy-bookmark-properties-grid.c
index 8b6b7cb..2a4d9ce 100644
--- a/src/bookmarks/ephy-bookmark-properties-grid.c
+++ b/src/bookmarks/ephy-bookmark-properties-grid.c
@@ -25,7 +25,6 @@
#include "ephy-bookmarks-manager.h"
#include "ephy-debug.h"
#include "ephy-shell.h"
-#include "ephy-sync-service.h"
#include "ephy-type-builtins.h"
#include "ephy-uri-helpers.h"
@@ -36,12 +35,14 @@
struct _EphyBookmarkPropertiesGrid {
GtkGrid parent_instance;
+ EphyBookmarksManager *manager;
EphyBookmark *bookmark;
+ gboolean bookmark_is_modified;
+ gboolean bookmark_is_removed;
+
EphyBookmarkPropertiesGridType type;
GtkWidget *parent;
- EphyBookmarksManager *manager;
-
GtkWidget *popover_bookmark_label;
GtkWidget *name_entry;
GtkWidget *address_entry;
@@ -50,10 +51,6 @@ struct _EphyBookmarkPropertiesGrid {
GtkWidget *add_tag_entry;
GtkWidget *add_tag_button;
GtkWidget *remove_bookmark_button;
-
- char *prev_name;
- char *prev_address;
- GSequence *prev_tags;
};
G_DEFINE_TYPE (EphyBookmarkPropertiesGrid, ephy_bookmark_properties_grid, GTK_TYPE_GRID)
@@ -242,14 +239,12 @@ ephy_bookmarks_properties_grid_actions_remove_bookmark (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
- EphySyncService *service;
EphyBookmarkPropertiesGrid *self = user_data;
g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- ephy_sync_service_delete_bookmark (service, self->bookmark, FALSE);
- ephy_bookmarks_manager_remove_bookmark (self->manager, self->bookmark);
+ self->bookmark_is_removed = TRUE;
+ ephy_bookmarks_manager_remove_bookmark (self->manager, self->bookmark);
if (self->type == EPHY_BOOKMARK_PROPERTIES_GRID_TYPE_DIALOG)
gtk_widget_destroy (self->parent);
@@ -277,6 +272,57 @@ ephy_bookmark_properties_grid_buffer_text_changed_cb (EphyBookmarkPropertiesGrid
}
static void
+ephy_bookmark_properties_grid_bookmark_title_changed_cb (EphyBookmarkPropertiesGrid *self,
+ EphyBookmark *bookmark,
+ EphyBookmarksManager *manager)
+{
+ g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+ self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_url_changed_cb (EphyBookmarkPropertiesGrid *self,
+ EphyBookmark *bookmark,
+ EphyBookmarksManager *manager)
+{
+ g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+ self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_tag_added_cb (EphyBookmarkPropertiesGrid *self,
+ EphyBookmark *bookmark,
+ const char *tag,
+ EphyBookmarksManager *manager)
+{
+ g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+
+ self->bookmark_is_modified = TRUE;
+}
+
+static void
+ephy_bookmark_properties_grid_bookmark_tag_removed_cb (EphyBookmarkPropertiesGrid *self,
+ EphyBookmark *bookmark,
+ const char *tag,
+ EphyBookmarksManager *manager)
+{
+ g_assert (EPHY_IS_BOOKMARK_PROPERTIES_GRID (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (manager));
+ g_assert (tag);
+
+ self->bookmark_is_modified = TRUE;
+}
+
+static void
ephy_bookmark_properties_grid_set_property (GObject *object,
guint prop_id,
const GValue *value,
@@ -322,7 +368,6 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
/* Set text for name entry */
gtk_entry_set_text (GTK_ENTRY (self->name_entry),
ephy_bookmark_get_title (self->bookmark));
- self->prev_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->name_entry)));
g_object_bind_property (GTK_ENTRY (self->name_entry), "text",
self->bookmark, "title",
@@ -335,15 +380,13 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
decoded_address = ephy_uri_decode (address);
gtk_entry_set_text (GTK_ENTRY (self->address_entry), decoded_address);
g_free (decoded_address);
- self->prev_address = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->address_entry)));
g_object_bind_property (GTK_ENTRY (self->address_entry), "text",
- self->bookmark, "url",
+ self->bookmark, "bmkUri",
G_BINDING_DEFAULT);
}
/* Create tag widgets */
- self->prev_tags = g_sequence_new (g_free);
tags = ephy_bookmarks_manager_get_tags (self->manager);
bookmark_tags = ephy_bookmark_get_tags (self->bookmark);
for (iter = g_sequence_get_begin_iter (tags);
@@ -356,11 +399,8 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
if (g_sequence_lookup (bookmark_tags,
(gpointer)tag,
(GCompareDataFunc)ephy_bookmark_tags_compare,
- NULL)) {
+ NULL))
selected = TRUE;
- g_sequence_insert_sorted (self->prev_tags, g_strdup (tag),
- (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
- }
widget = ephy_bookmark_properties_grid_create_tag_widget (self, tag, selected);
gtk_flow_box_insert (GTK_FLOW_BOX (self->tags_box), widget, -1);
@@ -374,56 +414,13 @@ ephy_bookmark_properties_grid_constructed (GObject *object)
}
static void
-ephy_bookmark_properties_grid_check_prev_values (EphyBookmarkPropertiesGrid *self)
-{
- if (ephy_bookmark_is_uploaded (self->bookmark) == FALSE)
- return;
-
- /* Check if any actual changes were made to the name, address or tags. If yes,
- * set the uploaded flag to FALSE. */
-
- if (g_strcmp0 (self->prev_name, ephy_bookmark_get_title (self->bookmark)) != 0) {
- ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
- return;
- }
-
- if (g_strcmp0 (self->prev_address, ephy_bookmark_get_url (self->bookmark)) != 0) {
- ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
- return;
- }
-
- if (self->prev_tags != NULL) {
- GSequence *tags = ephy_bookmark_get_tags (self->bookmark);
- GSequenceIter *iter;
-
- /* Check for added tags. */
- for (iter = g_sequence_get_begin_iter (tags);
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
- if (!g_sequence_lookup (self->prev_tags, g_sequence_get (iter),
- (GCompareDataFunc)ephy_bookmark_tags_compare, NULL)) {
- ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
- return;
- }
- }
-
- /* Check for deleted tags. */
- for (iter = g_sequence_get_begin_iter (self->prev_tags);
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
- if (!g_sequence_lookup (tags, g_sequence_get (iter),
- (GCompareDataFunc)ephy_bookmark_tags_compare, NULL)) {
- ephy_bookmark_set_is_uploaded (self->bookmark, FALSE);
- return;
- }
- }
- }
-}
-
-static void
ephy_bookmark_properties_grid_finalize (GObject *object)
{
EphyBookmarkPropertiesGrid *self = EPHY_BOOKMARK_PROPERTIES_GRID (object);
- ephy_bookmark_properties_grid_check_prev_values (self);
+ if (self->bookmark_is_modified && !self->bookmark_is_removed)
+ g_signal_emit_by_name (self->manager, "synchronizable-modified", self->bookmark);
+
ephy_bookmarks_manager_save_to_file_async (self->manager, NULL,
ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
NULL);
@@ -490,6 +487,26 @@ ephy_bookmark_properties_grid_init (EphyBookmarkPropertiesGrid *self)
gtk_widget_init_template (GTK_WIDGET (self));
self->manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+ g_signal_connect_object (self->manager,
+ "bookmark-title-changed",
+ G_CALLBACK (ephy_bookmark_properties_grid_bookmark_title_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->manager,
+ "bookmark-url-changed",
+ G_CALLBACK (ephy_bookmark_properties_grid_bookmark_url_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->manager,
+ "bookmark-tag-added",
+ G_CALLBACK (ephy_bookmark_properties_grid_bookmark_tag_added_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->manager,
+ "bookmark-tag-removed",
+ G_CALLBACK (ephy_bookmark_properties_grid_bookmark_tag_removed_cb),
+ self,
+ G_CONNECT_SWAPPED);
gtk_flow_box_set_sort_func (GTK_FLOW_BOX (self->tags_box),
(GtkFlowBoxSortFunc)flow_box_sort_func,
diff --git a/src/bookmarks/ephy-bookmark.c b/src/bookmarks/ephy-bookmark.c
index 6b19dae..0bb4cbb 100644
--- a/src/bookmarks/ephy-bookmark.c
+++ b/src/bookmarks/ephy-bookmark.c
@@ -19,16 +19,17 @@
*/
#include "config.h"
-
#include "ephy-bookmark.h"
-#include "ephy-shell.h"
-#include "ephy-sync-crypto.h"
-#include "ephy-sync-utils.h"
+#include "ephy-bookmarks-manager.h"
+#include "ephy-synchronizable.h"
#include <string.h>
-#define ID_LEN 32
+#define BOOKMARK_TYPE_VAL "bookmark"
+#define BOOKMARK_PARENT_ID_VAL "toolbar"
+#define BOOKMARK_PARENT_NAME_VAL "Bookmarks Toolbar"
+#define BOOKMARK_LOAD_IN_SIDEBAR_VAL FALSE
struct _EphyBookmark {
GObject parent_instance;
@@ -38,27 +39,36 @@ struct _EphyBookmark {
GSequence *tags;
gint64 time_added;
- /* Keep the modified timestamp as double, and not float, to
- * preserve the precision enforced by the Storage Server. */
+ /* Firefox Sync specific fields.
+ * Time modified timestamp must be double to match server's precision. */
char *id;
- double modified;
- gboolean uploaded;
+ char *type;
+ char *parent_id;
+ char *parent_name;
+ gboolean load_in_sidebar;
+ double server_time_modified;
};
-static JsonSerializableIface *serializable_iface = NULL;
-
-static void json_serializable_iface_init (gpointer g_iface);
+static void json_serializable_iface_init (JsonSerializableIface *iface);
+static void ephy_synchronizable_iface_init (EphySynchronizableInterface *iface);
G_DEFINE_TYPE_WITH_CODE (EphyBookmark, ephy_bookmark, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE,
- json_serializable_iface_init))
+ json_serializable_iface_init)
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE,
+ ephy_synchronizable_iface_init))
enum {
PROP_0,
- PROP_TAGS,
- PROP_TIME_ADDED,
- PROP_TITLE,
- PROP_URL,
+ PROP_TIME_ADDED, /* Epiphany */
+ PROP_ID, /* Firefox Sync */
+ PROP_TITLE, /* Epiphany && Firefox Sync */
+ PROP_BMK_URI, /* Epiphany && Firefox Sync */
+ PROP_TAGS, /* Epiphany && Firefox Sync */
+ PROP_TYPE, /* Firefox Sync */
+ PROP_PARENT_ID, /* Firefox Sync */
+ PROP_PARENT_NAME, /* Firefox Sync */
+ PROP_LOAD_IN_SIDEBAR, /* Firefox Sync */
LAST_PROP
};
@@ -80,46 +90,79 @@ ephy_bookmark_set_property (GObject *object,
EphyBookmark *self = EPHY_BOOKMARK (object);
switch (prop_id) {
- case PROP_TAGS:
- if (self->tags != NULL)
- g_sequence_free (self->tags);
- self->tags = g_value_get_pointer (value);
- break;
case PROP_TIME_ADDED:
ephy_bookmark_set_time_added (self, g_value_get_int64 (value));
break;
case PROP_TITLE:
ephy_bookmark_set_title (self, g_value_get_string (value));
break;
- case PROP_URL:
+ case PROP_BMK_URI:
ephy_bookmark_set_url (self, g_value_get_string (value));
break;
+ case PROP_TAGS:
+ if (self->tags != NULL)
+ g_sequence_free (self->tags);
+ self->tags = g_value_get_pointer (value);
+ break;
+ case PROP_TYPE:
+ g_free (self->type);
+ self->type = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_PARENT_ID:
+ g_free (self->parent_id);
+ self->parent_id = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_PARENT_NAME:
+ g_free (self->parent_name);
+ self->parent_name = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_LOAD_IN_SIDEBAR:
+ self->load_in_sidebar = g_value_get_boolean (value);
+ break;
+ case PROP_ID:
+ ephy_bookmark_set_id (self, g_value_get_string (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
-ephy_bookmark_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
+ephy_bookmark_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
EphyBookmark *self = EPHY_BOOKMARK (object);
switch (prop_id) {
- case PROP_TAGS:
- g_value_set_pointer (value, ephy_bookmark_get_tags (self));
- break;
case PROP_TIME_ADDED:
g_value_set_int64 (value, ephy_bookmark_get_time_added (self));
break;
case PROP_TITLE:
g_value_set_string (value, ephy_bookmark_get_title (self));
break;
- case PROP_URL:
+ case PROP_BMK_URI:
g_value_set_string (value, ephy_bookmark_get_url (self));
break;
+ case PROP_TAGS:
+ g_value_set_pointer (value, ephy_bookmark_get_tags (self));
+ break;
+ case PROP_TYPE:
+ g_value_set_string (value, self->type);
+ break;
+ case PROP_PARENT_ID:
+ g_value_set_string (value, self->parent_id);
+ break;
+ case PROP_PARENT_NAME:
+ g_value_set_string (value, self->parent_name);
+ break;
+ case PROP_LOAD_IN_SIDEBAR:
+ g_value_set_boolean (value, self->load_in_sidebar);
+ break;
+ case PROP_ID:
+ g_value_set_string (value, ephy_bookmark_get_id (self));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -134,7 +177,8 @@ ephy_bookmark_finalize (GObject *object)
g_free (self->title);
g_free (self->id);
- g_sequence_free (self->tags);
+ if (self->tags)
+ g_sequence_free (self->tags);
G_OBJECT_CLASS (ephy_bookmark_parent_class)->finalize (object);
}
@@ -148,12 +192,6 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
object_class->get_property = ephy_bookmark_get_property;
object_class->finalize = ephy_bookmark_finalize;
- obj_properties[PROP_TAGS] =
- g_param_spec_pointer ("tags",
- "Tags",
- "The bookmark's tags",
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
-
obj_properties[PROP_TIME_ADDED] =
g_param_spec_int64 ("time-added",
"Time added",
@@ -163,6 +201,13 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "The bookmark's id",
+ "Default bookmark id",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
obj_properties[PROP_TITLE] =
g_param_spec_string ("title",
"Title",
@@ -170,13 +215,47 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
"Default bookmark title",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
- obj_properties[PROP_URL] =
- g_param_spec_string ("url",
- "URL",
- "The bookmark's URL",
+ obj_properties[PROP_BMK_URI] =
+ g_param_spec_string ("bmkUri",
+ "URI",
+ "The bookmark's URI",
"about:overview",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+ obj_properties[PROP_TAGS] =
+ g_param_spec_pointer ("tags",
+ "Tags",
+ "The bookmark's tags",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_TYPE] =
+ g_param_spec_string ("type",
+ "Type",
+ "Of type bookmark",
+ "default",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_PARENT_ID] =
+ g_param_spec_string ("parentid",
+ "ParentID",
+ "The parent's id",
+ "default",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_PARENT_NAME] =
+ g_param_spec_string ("parentName",
+ "ParentName",
+ "The parent's name",
+ "default",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_LOAD_IN_SIDEBAR] =
+ g_param_spec_boolean ("loadInSidebar",
+ "LoadInSiderbar",
+ "Load in sidebar",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
signals[TAG_ADDED] =
@@ -201,92 +280,24 @@ ephy_bookmark_class_init (EphyBookmarkClass *klass)
static void
ephy_bookmark_init (EphyBookmark *self)
{
- self->id = g_malloc0 (ID_LEN + 1);
- ephy_sync_crypto_random_hex_gen (NULL, ID_LEN, (guint8 *)self->id);
-}
-
-static JsonNode *
-ephy_bookmark_json_serializable_serialize_property (JsonSerializable *serializable,
- const char *name,
- const GValue *value,
- GParamSpec *pspec)
-{
- JsonNode *node = NULL;
-
- if (g_strcmp0 (name, "tags") == 0) {
- GSequence *tags;
- GSequenceIter *iter;
- JsonArray *array;
-
- node = json_node_new (JSON_NODE_ARRAY);
- array = json_array_new ();
- tags = g_value_get_pointer (value);
-
- for (iter = g_sequence_get_begin_iter (tags);
- !g_sequence_iter_is_end (iter);
- iter = g_sequence_iter_next (iter)) {
- json_array_add_string_element (array, g_sequence_get (iter));
- }
-
- json_node_set_array (node, array);
- } else {
- node = serializable_iface->serialize_property (serializable, name,
- value, pspec);
- }
-
- return node;
-}
-
-static gboolean
-ephy_bookmark_json_serializable_deserialize_property (JsonSerializable *serializable,
- const char *name,
- GValue *value,
- GParamSpec *pspec,
- JsonNode *node)
-{
- if (g_strcmp0 (name, "tags") == 0) {
- GSequence *tags;
- JsonArray *array;
- const char *tag;
-
- g_assert (JSON_NODE_HOLDS_ARRAY (node));
- array = json_node_get_array (node);
- tags = g_sequence_new (g_free);
-
- for (gsize i = 0; i < json_array_get_length (array); i++) {
- tag = json_node_get_string (json_array_get_element (array, i));
- g_sequence_insert_sorted (tags, g_strdup (tag),
- (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
- }
-
- g_value_set_pointer (value, tags);
- } else {
- serializable_iface->deserialize_property (serializable, name,
- value, pspec, node);
- }
-
- return TRUE;
-}
-
-static void
-json_serializable_iface_init (gpointer g_iface)
-{
- JsonSerializableIface *iface = g_iface;
-
- serializable_iface = g_type_default_interface_peek (JSON_TYPE_SERIALIZABLE);
-
- iface->serialize_property = ephy_bookmark_json_serializable_serialize_property;
- iface->deserialize_property = ephy_bookmark_json_serializable_deserialize_property;
}
EphyBookmark *
-ephy_bookmark_new (const char *url, const char *title, GSequence *tags)
+ephy_bookmark_new (const char *url,
+ const char *title,
+ GSequence *tags,
+ const char *id)
{
return g_object_new (EPHY_TYPE_BOOKMARK,
- "url", url,
+ "time-added", g_get_real_time (),
"title", title,
+ "bmkUri", url,
"tags", tags,
- "time-added", g_get_real_time (),
+ "type", BOOKMARK_TYPE_VAL,
+ "parentid", BOOKMARK_PARENT_ID_VAL,
+ "parentName", BOOKMARK_PARENT_NAME_VAL,
+ "loadInSidebar", BOOKMARK_LOAD_IN_SIDEBAR_VAL,
+ "id", id,
NULL);
}
@@ -364,37 +375,23 @@ ephy_bookmark_get_id (EphyBookmark *self)
}
void
-ephy_bookmark_set_modification_time (EphyBookmark *self,
- double modified)
-{
- g_return_if_fail (EPHY_IS_BOOKMARK (self));
-
- self->modified = modified;
-}
-
-double
-ephy_bookmark_get_modification_time (EphyBookmark *self)
-{
- g_return_val_if_fail (EPHY_IS_BOOKMARK (self), -1);
-
- return self->modified;
-}
-
-void
ephy_bookmark_set_is_uploaded (EphyBookmark *self,
gboolean uploaded)
{
- g_return_if_fail (EPHY_IS_BOOKMARK (self));
- self->uploaded = uploaded;
+ /* FIXME: This is no longer used for Firefox Sync, but bookmarks import/export
+ * expects it. We need to delete it and write a migrator for bookmarks. */
+ g_return_if_fail (EPHY_IS_BOOKMARK (self));
}
gboolean
ephy_bookmark_is_uploaded (EphyBookmark *self)
{
+ /* FIXME: This is no longer used for Firefox Sync, but bookmarks import/export
+ * expects it. We need to delete it and write a migrator for bookmarks. */
g_return_val_if_fail (EPHY_IS_BOOKMARK (self), FALSE);
- return self->uploaded;
+ return FALSE;
}
void
@@ -517,90 +514,102 @@ ephy_bookmark_tags_compare (const char *tag1, const char *tag2)
return result;
}
-char *
-ephy_bookmark_to_bso (EphyBookmark *self)
+static JsonNode *
+serializable_serialize_property (JsonSerializable *serializable,
+ const char *name,
+ const GValue *value,
+ GParamSpec *pspec)
{
- EphySyncService *service;
- guint8 *encrypted;
- guint8 *sync_key;
- char *serialized;
- char *payload;
- char *bso;
- gsize length;
+ JsonNode *node = NULL;
- g_return_val_if_fail (EPHY_IS_BOOKMARK (self), NULL);
+ if (g_strcmp0 (name, "tags") == 0) {
+ GSequence *tags;
+ GSequenceIter *iter;
+ JsonArray *array;
+
+ node = json_node_new (JSON_NODE_ARRAY);
+ array = json_array_new ();
+ tags = g_value_get_pointer (value);
- /* Convert a Bookmark object to a BSO (Basic Store Object). That is a generic
- * JSON wrapper around all items passed into and out of the SyncStorage server.
- * The current flow is:
- * 1. Serialize the Bookmark to a JSON string.
- * 2. Encrypt the JSON string using the sync key from the sync service.
- * 3. Encode the encrypted bytes to base64 url safe.
- * 4. Create a new JSON string that contains the id of the Bookmark and the
- encoded bytes as payload. This is actually the BSO that is going to be
- stored on the SyncStorage server.
- * See https://docs.services.mozilla.com/storage/apis-1.5.html
- */
-
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- sync_key = ephy_sync_crypto_decode_hex (ephy_sync_service_get_token (service, TOKEN_KB));
- serialized = json_gobject_to_data (G_OBJECT (self), NULL);
- encrypted = ephy_sync_crypto_aes_256 (AES_256_MODE_ENCRYPT, sync_key,
- (guint8 *)serialized, strlen (serialized), &length);
- payload = ephy_sync_crypto_base64_urlsafe_encode (encrypted, length, FALSE);
- bso = ephy_sync_utils_create_bso_json (self->id, payload);
-
- g_free (sync_key);
- g_free (serialized);
- g_free (encrypted);
- g_free (payload);
-
- return bso;
+ if (tags != NULL) {
+ for (iter = g_sequence_get_begin_iter (tags);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter)) {
+ json_array_add_string_element (array, g_sequence_get (iter));
+ }
+ }
+
+ json_node_set_array (node, array);
+ } else if (!g_strcmp0 (name, "time-added")) {
+ /* This is not a Firefox bookmark property, skip it. */
+ } else {
+ node = json_serializable_default_serialize_property (serializable, name, value, pspec);
+ }
+
+ return node;
}
-EphyBookmark *
-ephy_bookmark_from_bso (JsonObject *bso)
+static gboolean
+serializable_deserialize_property (JsonSerializable *serializable,
+ const char *name,
+ GValue *value,
+ GParamSpec *pspec,
+ JsonNode *node)
{
- EphySyncService *service;
- EphyBookmark *bookmark = NULL;
- GObject *object;
- GError *error = NULL;
- guint8 *sync_key;
- guint8 *decoded;
- gsize decoded_len;
- char *decrypted;
-
- g_return_val_if_fail (bso != NULL, NULL);
-
- /* Convert a BSO to a Bookmark object. The flow is similar to the one from
- * ephy_bookmark_to_bso(), only that the steps are reversed:
- * 1. Decode the payload from base64 url safe to raw bytes.
- * 2. Decrypt the bytes using the sync key to obtain the serialized Bookmark.
- * 3. Deserialize the JSON string into a Bookmark object.
- */
-
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- sync_key = ephy_sync_crypto_decode_hex (ephy_sync_service_get_token (service, TOKEN_KB));
- decoded = ephy_sync_crypto_base64_urlsafe_decode (json_object_get_string_member (bso, "payload"),
- &decoded_len, FALSE);
- decrypted = (char *)ephy_sync_crypto_aes_256 (AES_256_MODE_DECRYPT, sync_key,
- decoded, decoded_len, NULL);
- object = json_gobject_from_data (EPHY_TYPE_BOOKMARK, decrypted, strlen (decrypted), &error);
-
- if (object == NULL) {
- g_warning ("Failed to create GObject from data: %s", error->message);
- g_error_free (error);
- goto out;
+ if (g_strcmp0 (name, "tags") == 0) {
+ GSequence *tags;
+ JsonArray *array;
+ const char *tag;
+
+ g_assert (JSON_NODE_HOLDS_ARRAY (node));
+ array = json_node_get_array (node);
+ tags = g_sequence_new (g_free);
+
+ for (gsize i = 0; i < json_array_get_length (array); i++) {
+ tag = json_node_get_string (json_array_get_element (array, i));
+ g_sequence_insert_sorted (tags, g_strdup (tag),
+ (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
+ }
+
+ g_value_set_pointer (value, tags);
+
+ return TRUE;
}
- bookmark = EPHY_BOOKMARK (object);
- ephy_bookmark_set_id (bookmark, json_object_get_string_member (bso, "id"));
- ephy_bookmark_set_modification_time (bookmark, json_object_get_double_member (bso, "modified"));
- ephy_bookmark_set_is_uploaded (bookmark, TRUE);
+ return json_serializable_default_deserialize_property (serializable, name, value, pspec, node);
+}
-out:
- g_free (decoded);
- g_free (decrypted);
+static void
+json_serializable_iface_init (JsonSerializableIface *iface)
+{
+ iface->serialize_property = serializable_serialize_property;
+ iface->deserialize_property = serializable_deserialize_property;
+}
- return bookmark;
+static const char *
+synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+ return ephy_bookmark_get_id (EPHY_BOOKMARK (synchronizable));
+}
+
+static double
+synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+ return EPHY_BOOKMARK (synchronizable)->server_time_modified;
+}
+
+static void
+synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+ double server_time_modified)
+{
+ EPHY_BOOKMARK (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/src/bookmarks/ephy-bookmark.h b/src/bookmarks/ephy-bookmark.h
index 9e950d9..cb96a4d 100644
--- a/src/bookmarks/ephy-bookmark.h
+++ b/src/bookmarks/ephy-bookmark.h
@@ -29,48 +29,42 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphyBookmark, ephy_bookmark, EPHY, BOOKMARK, GObject)
-EphyBookmark *ephy_bookmark_new (const char *url,
- const char *title,
- GSequence *tags);
-
-void ephy_bookmark_set_time_added (EphyBookmark *self,
- gint64 time_added);
-gint64 ephy_bookmark_get_time_added (EphyBookmark *self);
-
-void ephy_bookmark_set_url (EphyBookmark *self,
- const char *url);
-const char *ephy_bookmark_get_url (EphyBookmark *self);
-
-void ephy_bookmark_set_title (EphyBookmark *self,
- const char *title);
-const char *ephy_bookmark_get_title (EphyBookmark *self);
-
-void ephy_bookmark_set_id (EphyBookmark *self,
- const char *id);
-const char *ephy_bookmark_get_id (EphyBookmark *self);
-
-void ephy_bookmark_set_modification_time (EphyBookmark *self,
- double modified);
-double ephy_bookmark_get_modification_time (EphyBookmark *self);
-
-void ephy_bookmark_set_is_uploaded (EphyBookmark *self,
- gboolean uploaded);
-gboolean ephy_bookmark_is_uploaded (EphyBookmark *self);
-
-void ephy_bookmark_add_tag (EphyBookmark *self,
- const char *tag);
-void ephy_bookmark_remove_tag (EphyBookmark *self,
- const char *tag);
-gboolean ephy_bookmark_has_tag (EphyBookmark *self,
- const char *tag);
-GSequence *ephy_bookmark_get_tags (EphyBookmark *self);
-
-int ephy_bookmark_bookmarks_compare_func (EphyBookmark *bookmark1,
- EphyBookmark *bookmark2);
-int ephy_bookmark_tags_compare (const char *tag1,
- const char *tag2);
-
-char *ephy_bookmark_to_bso (EphyBookmark *self);
-EphyBookmark *ephy_bookmark_from_bso (JsonObject *bso);
+EphyBookmark *ephy_bookmark_new (const char *url,
+ const char *title,
+ GSequence *tags,
+ const char *id);
+
+void ephy_bookmark_set_time_added (EphyBookmark *self,
+ gint64 time_added);
+gint64 ephy_bookmark_get_time_added (EphyBookmark *self);
+
+void ephy_bookmark_set_url (EphyBookmark *self,
+ const char *url);
+const char *ephy_bookmark_get_url (EphyBookmark *self);
+
+void ephy_bookmark_set_title (EphyBookmark *self,
+ const char *title);
+const char *ephy_bookmark_get_title (EphyBookmark *self);
+
+void ephy_bookmark_set_id (EphyBookmark *self,
+ const char *id);
+const char *ephy_bookmark_get_id (EphyBookmark *self);
+
+void ephy_bookmark_set_is_uploaded (EphyBookmark *self,
+ gboolean uploaded);
+gboolean ephy_bookmark_is_uploaded (EphyBookmark *self);
+
+void ephy_bookmark_add_tag (EphyBookmark *self,
+ const char *tag);
+void ephy_bookmark_remove_tag (EphyBookmark *self,
+ const char *tag);
+gboolean ephy_bookmark_has_tag (EphyBookmark *self,
+ const char *tag);
+GSequence *ephy_bookmark_get_tags (EphyBookmark *self);
+
+int ephy_bookmark_bookmarks_compare_func (EphyBookmark *bookmark1,
+ EphyBookmark *bookmark2);
+int ephy_bookmark_tags_compare (const char *tag1,
+ const char *tag2);
G_END_DECLS
diff --git a/src/bookmarks/ephy-bookmarks-export.c b/src/bookmarks/ephy-bookmarks-export.c
index 4e4525b..ae54187 100644
--- a/src/bookmarks/ephy-bookmarks-export.c
+++ b/src/bookmarks/ephy-bookmarks-export.c
@@ -18,8 +18,10 @@
* along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "config.h"
#include "ephy-bookmarks-export.h"
+#include "ephy-synchronizable.h"
#include "gvdb-builder.h"
static void
@@ -45,7 +47,7 @@ build_variant (EphyBookmark *bookmark)
g_variant_builder_add (&builder, "x", ephy_bookmark_get_time_added (bookmark));
g_variant_builder_add (&builder, "s", ephy_bookmark_get_title (bookmark));
g_variant_builder_add (&builder, "s", ephy_bookmark_get_id (bookmark));
- g_variant_builder_add (&builder, "d", ephy_bookmark_get_modification_time (bookmark));
+ g_variant_builder_add (&builder, "d", ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE
(bookmark)));
g_variant_builder_add (&builder, "b", ephy_bookmark_is_uploaded (bookmark));
g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
diff --git a/src/bookmarks/ephy-bookmarks-import.c b/src/bookmarks/ephy-bookmarks-import.c
index 8d252f8..1376b3a 100644
--- a/src/bookmarks/ephy-bookmarks-import.c
+++ b/src/bookmarks/ephy-bookmarks-import.c
@@ -18,9 +18,8 @@
* along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "ephy-bookmarks-import.h"
-
#include "config.h"
+#include "ephy-bookmarks-import.h"
#include "ephy-shell.h"
#include "ephy-sqlite-connection.h"
@@ -59,13 +58,15 @@ get_bookmarks_from_table (GvdbTable *table)
const char *title;
gint64 time_added;
char *id;
- double modified;
- gboolean uploaded;
+ double server_time_modified;
+ gboolean is_uploaded;
/* Obtain the corresponding GVariant. */
value = gvdb_table_get_value (table, list[i]);
- g_variant_get (value, "(x&s&sdbas)", &time_added, &title, &id, &modified, &uploaded, &iter);
+ g_variant_get (value, "(x&s&sdbas)",
+ &time_added, &title, &id,
+ &server_time_modified, &is_uploaded, &iter);
/* Add all stored tags in a GSequence. */
tags = g_sequence_new (g_free);
@@ -77,11 +78,10 @@ get_bookmarks_from_table (GvdbTable *table)
g_variant_iter_free (iter);
/* Create the new bookmark. */
- bookmark = ephy_bookmark_new (list[i], title, tags);
+ bookmark = ephy_bookmark_new (list[i], title, tags, id);
ephy_bookmark_set_time_added (bookmark, time_added);
- ephy_bookmark_set_id (bookmark, id);
- ephy_bookmark_set_modification_time (bookmark, modified);
- ephy_bookmark_set_is_uploaded (bookmark, uploaded);
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), server_time_modified);
+ ephy_bookmark_set_is_uploaded (bookmark, is_uploaded);
g_sequence_prepend (bookmarks, bookmark);
g_variant_unref (value);
@@ -214,7 +214,7 @@ ephy_bookmarks_import_from_firefox (EphyBookmarksManager *manager,
GSequence *bookmarks = NULL;
gboolean ret = TRUE;
gchar *filename;
- const char *statement_str = "SELECT b.id, p.url, b.title, b.dateAdded "
+ const char *statement_str = "SELECT b.id, p.url, b.title, b.dateAdded, b.guid "
"FROM moz_bookmarks b "
"JOIN moz_places p ON b.fk=p.id "
"WHERE b.type=1 AND p.url NOT LIKE 'about%' "
@@ -260,11 +260,12 @@ ephy_bookmarks_import_from_firefox (EphyBookmarksManager *manager,
const char *url = ephy_sqlite_statement_get_column_as_string (statement, 1);
const char *title = ephy_sqlite_statement_get_column_as_string (statement, 2);
gint64 time_added = ephy_sqlite_statement_get_column_as_int64 (statement, 3);
+ const char *guid = ephy_sqlite_statement_get_column_as_string (statement, 4);
EphyBookmark *bookmark;
GSequence *tags;
tags = g_sequence_new (g_free);
- bookmark = ephy_bookmark_new (url, title, tags);
+ bookmark = ephy_bookmark_new (url, title, tags, guid);
ephy_bookmark_set_time_added (bookmark, time_added);
load_tags_for_bookmark (connection, bookmark, bookmark_id);
diff --git a/src/bookmarks/ephy-bookmarks-manager.c b/src/bookmarks/ephy-bookmarks-manager.c
index 1c9b8c0..2e1c503 100644
--- a/src/bookmarks/ephy-bookmarks-manager.c
+++ b/src/bookmarks/ephy-bookmarks-manager.c
@@ -19,13 +19,14 @@
*/
#include "config.h"
-
#include "ephy-bookmarks-manager.h"
#include "ephy-bookmarks-export.h"
#include "ephy-bookmarks-import.h"
#include "ephy-debug.h"
#include "ephy-file-helpers.h"
+#include "ephy-settings.h"
+#include "ephy-synchronizable-manager.h"
#include <string.h>
@@ -41,9 +42,13 @@ struct _EphyBookmarksManager {
};
static void list_model_iface_init (GListModelInterface *iface);
+static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
-G_DEFINE_TYPE_EXTENDED (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT, 0,
- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+G_DEFINE_TYPE_WITH_CODE (EphyBookmarksManager, ephy_bookmarks_manager, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+ list_model_iface_init)
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+ ephy_synchronizable_manager_iface_init))
enum {
BOOKMARK_ADDED,
@@ -71,6 +76,36 @@ ephy_bookmarks_manager_save_to_file (EphyBookmarksManager *self, GTask *task)
}
static void
+ephy_bookmarks_manager_copy_tags_from_bookmark (EphyBookmarksManager *self,
+ EphyBookmark *dest,
+ EphyBookmark *source)
+{
+ GSequenceIter *iter;
+
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_assert (EPHY_IS_BOOKMARK (dest));
+ g_assert (EPHY_IS_BOOKMARK (source));
+
+ for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (source));
+ !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
+ ephy_bookmark_add_tag (dest, g_sequence_get (iter));
+}
+
+static void
+ephy_bookmarks_manager_create_tags_from_bookmark (EphyBookmarksManager *self,
+ EphyBookmark *bookmark)
+{
+ GSequenceIter *iter;
+
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
+
+ for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (bookmark));
+ !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
+ ephy_bookmarks_manager_create_tag (self, g_sequence_get (iter));
+}
+
+static void
ephy_bookmarks_manager_finalize (GObject *object)
{
EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (object);
@@ -188,40 +223,6 @@ ephy_bookmarks_manager_init (EphyBookmarksManager *self)
ephy_bookmarks_manager_load_from_file (self);
}
-static GType
-ephy_bookmarks_manager_list_model_get_item_type (GListModel *model)
-{
- return EPHY_TYPE_BOOKMARK;
-}
-
-static guint
-ephy_bookmarks_manager_list_model_get_n_items (GListModel *model)
-{
- EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
-
- return g_sequence_get_length (self->bookmarks);
-}
-
-static gpointer
-ephy_bookmarks_manager_list_model_get_item (GListModel *model,
- guint position)
-{
- EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
- GSequenceIter *iter;
-
- iter = g_sequence_get_iter_at_pos (self->bookmarks, position);
-
- return g_object_ref (g_sequence_get (iter));
-}
-
-static void
-list_model_iface_init (GListModelInterface *iface)
-{
- iface->get_item_type = ephy_bookmarks_manager_list_model_get_item_type;
- iface->get_n_items = ephy_bookmarks_manager_list_model_get_n_items;
- iface->get_item = ephy_bookmarks_manager_list_model_get_item;
-}
-
static void
bookmark_title_changed_cb (EphyBookmark *bookmark,
GParamSpec *pspec,
@@ -254,7 +255,6 @@ bookmark_tag_removed_cb (EphyBookmark *bookmark,
g_signal_emit (self, signals[BOOKMARK_TAG_REMOVED], 0, bookmark, tag);
}
-
EphyBookmarksManager *
ephy_bookmarks_manager_new (void)
{
@@ -267,7 +267,7 @@ ephy_bookmarks_manager_watch_bookmark (EphyBookmarksManager *self,
{
g_signal_connect_object (bookmark, "notify::title",
G_CALLBACK (bookmark_title_changed_cb), self, 0);
- g_signal_connect_object (bookmark, "notify::url",
+ g_signal_connect_object (bookmark, "notify::bmkUri",
G_CALLBACK (bookmark_url_changed_cb), self, 0);
g_signal_connect_object (bookmark, "tag-added",
G_CALLBACK (bookmark_tag_added_cb), self, 0);
@@ -305,15 +305,16 @@ ephy_bookmarks_search_and_insert_bookmark (GSequence *bookmarks,
return NULL;
}
-void
-ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
- EphyBookmark *bookmark)
+static void
+add_bookmark_internal (EphyBookmarksManager *self,
+ EphyBookmark *bookmark,
+ gboolean should_save)
{
GSequenceIter *iter;
- gint position;
+ int position;
- g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
- g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
iter = ephy_bookmarks_search_and_insert_bookmark (self->bookmarks,
g_object_ref (bookmark));
@@ -323,12 +324,24 @@ ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
g_signal_emit (self, signals[BOOKMARK_ADDED], 0, bookmark);
+ ephy_bookmarks_manager_watch_bookmark (self, bookmark);
+ }
+ if (should_save)
ephy_bookmarks_manager_save_to_file_async (self, NULL,
(GAsyncReadyCallback)ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
NULL);
- ephy_bookmarks_manager_watch_bookmark (self, bookmark);
- }
+}
+
+void
+ephy_bookmarks_manager_add_bookmark (EphyBookmarksManager *self,
+ EphyBookmark *bookmark)
+{
+ g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+
+ add_bookmark_internal (self, bookmark, TRUE);
+ g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
}
void
@@ -336,26 +349,16 @@ ephy_bookmarks_manager_add_bookmarks (EphyBookmarksManager *self,
GSequence *bookmarks)
{
GSequenceIter *iter;
- GSequenceIter *new_iter;
- int position;
g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
g_return_if_fail (bookmarks != NULL);
for (iter = g_sequence_get_begin_iter (bookmarks);
- !g_sequence_iter_is_end (iter);
- iter = g_sequence_iter_next (iter)) {
+ !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
EphyBookmark *bookmark = g_sequence_get (iter);
- new_iter = ephy_bookmarks_search_and_insert_bookmark (self->bookmarks,
- g_object_ref (bookmark));
- if (new_iter) {
- position = g_sequence_iter_get_position (new_iter);
- g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
-
- g_signal_emit (self, signals[BOOKMARK_ADDED], 0, bookmark);
- ephy_bookmarks_manager_watch_bookmark (self, bookmark);
- }
+ add_bookmark_internal (self, bookmark, FALSE);
+ g_signal_emit_by_name (self, "synchronizable-modified", bookmark);
}
ephy_bookmarks_manager_save_to_file_async (self, NULL,
@@ -363,21 +366,21 @@ ephy_bookmarks_manager_add_bookmarks (EphyBookmarksManager *self,
NULL);
}
-void
-ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
- EphyBookmark *bookmark)
+static void
+remove_bookmark_internal (EphyBookmarksManager *self,
+ EphyBookmark *bookmark)
{
GSequenceIter *iter;
gint position;
- g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
- g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_assert (EPHY_IS_BOOKMARK (bookmark));
for (iter = g_sequence_get_begin_iter (self->bookmarks);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter)) {
- if (g_strcmp0 (ephy_bookmark_get_url (g_sequence_get (iter)),
- ephy_bookmark_get_url (bookmark)) == 0)
+ if (g_strcmp0 (ephy_bookmark_get_id (g_sequence_get (iter)),
+ ephy_bookmark_get_id (bookmark)) == 0)
break;
}
g_assert (!g_sequence_iter_is_end (iter));
@@ -400,6 +403,19 @@ ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
g_object_unref (bookmark);
}
+void ephy_bookmarks_manager_remove_bookmark (EphyBookmarksManager *self,
+ EphyBookmark *bookmark)
+{
+ g_return_if_fail (EPHY_IS_BOOKMARKS_MANAGER (self));
+ g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+
+ g_signal_emit_by_name (EPHY_SYNCHRONIZABLE_MANAGER (self),
+ "synchronizable-deleted",
+ bookmark);
+
+ remove_bookmark_internal (self, bookmark);
+}
+
EphyBookmark *
ephy_bookmarks_manager_get_bookmark_by_url (EphyBookmarksManager *self,
const char *url)
@@ -611,3 +627,292 @@ ephy_bookmarks_manager_save_to_file_warn_on_error_cb (GObject *object,
g_error_free (error);
}
}
+
+static GType
+ephy_bookmarks_manager_list_model_get_item_type (GListModel *model)
+{
+ return EPHY_TYPE_BOOKMARK;
+}
+
+static guint
+ephy_bookmarks_manager_list_model_get_n_items (GListModel *model)
+{
+ EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
+
+ return g_sequence_get_length (self->bookmarks);
+}
+
+static gpointer
+ephy_bookmarks_manager_list_model_get_item (GListModel *model,
+ guint position)
+{
+ EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (model);
+ GSequenceIter *iter;
+
+ iter = g_sequence_get_iter_at_pos (self->bookmarks, position);
+
+ return g_object_ref (g_sequence_get (iter));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = ephy_bookmarks_manager_list_model_get_item_type;
+ iface->get_n_items = ephy_bookmarks_manager_list_model_get_n_items;
+ iface->get_item = ephy_bookmarks_manager_list_model_get_item;
+}
+
+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 ? "bookmarks" : "ephy-bookmarks";
+}
+
+static GType
+synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+ return EPHY_TYPE_BOOKMARK;
+}
+
+static gboolean
+synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+ return g_settings_get_boolean (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_BOOKMARKS_INITIAL);
+}
+
+static void
+synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+ gboolean is_initial)
+{
+ g_settings_set_boolean (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_BOOKMARKS_INITIAL,
+ is_initial);
+}
+
+static double
+synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+ return g_settings_get_double (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_BOOKMARKS_TIME);
+}
+
+static void
+synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+ double sync_time)
+{
+ g_settings_set_double (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_BOOKMARKS_TIME,
+ sync_time);
+}
+
+static void
+synchronizable_manager_add (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+ EphyBookmark *bookmark = EPHY_BOOKMARK (synchronizable);
+
+ add_bookmark_internal (self, bookmark, TRUE);
+ ephy_bookmarks_manager_create_tags_from_bookmark (self, bookmark);
+}
+
+static void
+synchronizable_manager_remove (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphyBookmarksManager *self = EPHY_BOOKMARKS_MANAGER (manager);
+ EphyBookmark *bookmark = EPHY_BOOKMARK (synchronizable);
+
+ remove_bookmark_internal (self, bookmark);
+}
+
+static GSList *
+handle_initial_sync (EphyBookmarksManager *self,
+ GSList *remote_bookmarks)
+{
+ GSList *to_upload = NULL;
+ EphyBookmark *bookmark;
+ GSequence *bookmarks;
+ GSequenceIter *iter;
+ GHashTable *dont_upload;
+ double timestamp;
+
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+
+ dont_upload = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (GSList *l = remote_bookmarks; l && l->data; l = l->next) {
+ const char *id;
+ const char *url;
+ char *type;
+ char *parent_id;
+
+ g_object_get (l->data, "type", &type, "parentid", &parent_id, NULL);
+ /* Ignore mobile/unfiled bookmarks and everything that is not of type bookmark. */
+ if (g_strcmp0 (type, "bookmark") ||
+ (!g_strcmp0 (parent_id, "mobile") || !g_strcmp0 (parent_id, "unfiled")))
+ goto next;
+
+ /* Bookmarks from server may miss the time added timestamp. */
+ if (!ephy_bookmark_get_time_added (l->data))
+ ephy_bookmark_set_time_added (l->data, g_get_real_time ());
+
+ id = ephy_bookmark_get_id (l->data);
+ url = ephy_bookmark_get_url (l->data);
+ bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+
+ if (bookmark) {
+ if (!g_strcmp0 (ephy_bookmark_get_url (bookmark), url)) {
+ /* Same id, same url. Merge tags and reupload. */
+ ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+ timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+ } else {
+ /* Same id, different url. Keep both and upload local one with new id. */
+ char *new_id = ephy_sync_crypto_get_random_sync_id ();
+ ephy_bookmark_set_id (bookmark, new_id);
+ add_bookmark_internal (self, l->data, FALSE);
+ g_hash_table_add (dont_upload, (char *)id);
+ g_free (new_id);
+ }
+ } else {
+ bookmark = ephy_bookmarks_manager_get_bookmark_by_url (self, url);
+ if (bookmark) {
+ /* Different id, same url. Keep remote id, merge tags and reupload. */
+ ephy_bookmark_set_id (bookmark, id);
+ ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+ timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+ } else {
+ /* Different id, different url. Add remote bookmark. */
+ add_bookmark_internal (self, l->data, FALSE);
+ g_hash_table_add (dont_upload, (char *)id);
+ }
+ }
+
+ /* In any case, create new tags from the remote bookmark if any. */
+ ephy_bookmarks_manager_create_tags_from_bookmark (self, l->data);
+
+next:
+ g_free (type);
+ g_free (parent_id);
+ }
+
+ bookmarks = ephy_bookmarks_manager_get_bookmarks (self);
+ for (iter = g_sequence_get_begin_iter (bookmarks);
+ !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
+ bookmark = g_sequence_get (iter);
+ if (!g_hash_table_contains (dont_upload, ephy_bookmark_get_id (bookmark)))
+ to_upload = g_slist_prepend (to_upload, g_object_ref (bookmark));
+ }
+
+ /* Commit changes to file. */
+ ephy_bookmarks_manager_save_to_file_async (self, NULL,
+
(GAsyncReadyCallback)ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
+ NULL);
+ g_hash_table_unref (dont_upload);
+
+ return to_upload;
+}
+
+static GSList *
+handle_regular_sync (EphyBookmarksManager *self,
+ GSList *updated_bookmarks,
+ GSList *deleted_bookmarks)
+{
+ GSList *to_upload = NULL;
+ EphyBookmark *bookmark;
+ double timestamp;
+
+ g_assert (EPHY_IS_BOOKMARKS_MANAGER (self));
+
+ for (GSList *l = deleted_bookmarks; l && l->data; l = l->next) {
+ bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, ephy_bookmark_get_id (l->data));
+ if (bookmark)
+ remove_bookmark_internal (self, bookmark);
+ }
+
+ for (GSList *l = updated_bookmarks; l && l->data; l = l->next) {
+ const char *id;
+ const char *url;
+ char *type;
+ char *parent_id;
+
+ g_object_get (l->data, "type", &type, "parentid", &parent_id, NULL);
+ /* Ignore mobile/unfiled bookmarks and everything that is not of type bookmark. */
+ if (g_strcmp0 (type, "bookmark") ||
+ (!g_strcmp0 (parent_id, "mobile") || !g_strcmp0 (parent_id, "unfiled")))
+ goto next;
+
+ /* Bookmarks from server may miss the time added timestamp. */
+ if (!ephy_bookmark_get_time_added (l->data))
+ ephy_bookmark_set_time_added (l->data, g_get_real_time ());
+
+ id = ephy_bookmark_get_id (l->data);
+ url = ephy_bookmark_get_url (l->data);
+ bookmark = ephy_bookmarks_manager_get_bookmark_by_id (self, id);
+
+ if (bookmark) {
+ /* Same id. Overwrite local bookmark. */
+ remove_bookmark_internal (self, bookmark);
+ add_bookmark_internal (self, l->data, FALSE);
+ } else {
+ bookmark = ephy_bookmarks_manager_get_bookmark_by_url (self, url);
+ if (bookmark) {
+ /* Different id, same url. Keep remote id, merge tags and reupload. */
+ ephy_bookmark_set_id (bookmark, id);
+ ephy_bookmarks_manager_copy_tags_from_bookmark (self, bookmark, l->data);
+ timestamp = ephy_synchronizable_get_server_time_modified (l->data);
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (bookmark), timestamp);
+ to_upload = g_slist_prepend (to_upload, g_object_ref (bookmark));
+ } else {
+ /* Different id, different url. Add remote bookmark. */
+ add_bookmark_internal (self, l->data, FALSE);
+ }
+ }
+
+ /* In any case, create new tags from the remote bookmark if any. */
+ ephy_bookmarks_manager_create_tags_from_bookmark (self, l->data);
+
+next:
+ g_free (type);
+ g_free (parent_id);
+ }
+
+ /* Commit changes to file. */
+ ephy_bookmarks_manager_save_to_file_async (self, NULL,
+
(GAsyncReadyCallback)ephy_bookmarks_manager_save_to_file_warn_on_error_cb,
+ NULL);
+
+ return to_upload;
+}
+
+static GSList *
+synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated)
+{
+ if (is_initial)
+ return handle_initial_sync (EPHY_BOOKMARKS_MANAGER (manager), remotes_updated);
+
+ return handle_regular_sync (EPHY_BOOKMARKS_MANAGER (manager), remotes_updated, remotes_deleted);
+}
+
+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/src/ephy-shell.c b/src/ephy-shell.c
index 0b40e4b..4801cfd 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -308,27 +308,6 @@ download_started_cb (WebKitWebContext *web_context,
}
static void
-sync_tokens_load_finished_cb (EphySyncService *service,
- GError *error,
- gpointer user_data)
-{
- EphyNotification *notification;
-
- g_assert (EPHY_IS_SYNC_SERVICE (service));
-
- /* If the tokens were successfully loaded, start the periodical sync.
- * Otherwise, notify the user to sign in again. */
- if (error == NULL) {
- ephy_sync_service_start_periodical_sync (service, TRUE);
- } else {
- notification = ephy_notification_new (error->message,
- _("Please visit Preferences and sign in "
- "again to continue the sync process."));
- ephy_notification_show (notification);
- }
-}
-
-static void
ephy_shell_startup (GApplication *application)
{
EphyEmbedShell *embed_shell = EPHY_EMBED_SHELL (application);
@@ -366,12 +345,11 @@ ephy_shell_startup (GApplication *application)
G_BINDING_SYNC_CREATE);
}
- /* Create the sync service. */
+ /* Create the sync service and register synchronizable managers. */
ephy_shell->sync_service = ephy_sync_service_new ();
- g_signal_connect (ephy_shell->sync_service,
- "sync-tokens-load-finished",
- G_CALLBACK (sync_tokens_load_finished_cb), NULL);
-
+ 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)));
gtk_application_set_app_menu (GTK_APPLICATION (application),
G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
} else {
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index e9d5410..45f3e27 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -38,8 +38,6 @@
#include "ephy-settings.h"
#include "ephy-shell.h"
#include "ephy-string.h"
-#include "ephy-sync-crypto.h"
-#include "ephy-sync-secret.h"
#include "ephy-sync-service.h"
#include "ephy-uri-tester-shared.h"
#include "clear-data-dialog.h"
@@ -54,7 +52,7 @@
#include <string.h>
#define DOWNLOAD_BUTTON_WIDTH 8
-#define FXA_IFRAME_URL "https://accounts.firefox.com/signin?service=sync&context=fx_ios_v1"
+#define FXA_IFRAME_URL "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v1"
enum {
COL_LANG_NAME,
@@ -113,32 +111,29 @@ struct _PrefsDialog {
GHashTable *iso_3166_table;
/* sync */
- GtkWidget *sync_authenticate_box;
- GtkWidget *sync_sign_in_box;
- GtkWidget *sync_sign_in_details;
- GtkWidget *sync_sign_out_box;
- GtkWidget *sync_sign_out_details;
+ EphySyncService *sync_service;
+ GtkWidget *sync_page_box;
+ GtkWidget *sync_firefox_iframe_box;
+ GtkWidget *sync_firefox_iframe_label;
+ GtkWidget *sync_firefox_account_box;
+ GtkWidget *sync_firefox_account_label;
GtkWidget *sync_sign_out_button;
+ GtkWidget *sync_options_box;
+ GtkWidget *sync_with_firefox_checkbutton;
+ GtkWidget *sync_bookmarks_checkbutton;
+ GtkWidget *sync_frequency_5_min_radiobutton;
+ GtkWidget *sync_frequency_15_min_radiobutton;
+ GtkWidget *sync_frequency_30_min_radiobutton;
+ GtkWidget *sync_frequency_60_min_radiobutton;
+ GtkWidget *sync_now_button;
+ guint32 sync_frequency;
+ gboolean sync_was_signed_in;
WebKitWebView *fxa_web_view;
WebKitUserContentManager *fxa_manager;
WebKitUserScript *fxa_script;
- guint fxa_id;
};
-typedef struct {
- PrefsDialog *dialog;
- char *email;
- char *uid;
- char *sessionToken;
- char *keyFetchToken;
- char *unwrapBKey;
- guint8 *tokenID;
- guint8 *reqHMACkey;
- guint8 *respHMACkey;
- guint8 *respXORkey;
-} FxACallbackData;
-
enum {
COL_TITLE_ELIDED,
COL_ENCODING,
@@ -147,57 +142,6 @@ enum {
G_DEFINE_TYPE (PrefsDialog, prefs_dialog, GTK_TYPE_DIALOG)
-static FxACallbackData *
-fxa_callback_data_new (PrefsDialog *dialog,
- const char *email,
- const char *uid,
- const char *sessionToken,
- const char *keyFetchToken,
- const char *unwrapBKey,
- guint8 *tokenID,
- guint8 *reqHMACkey,
- guint8 *respHMACkey,
- guint8 *respXORkey)
-{
- FxACallbackData *data = g_slice_new (FxACallbackData);
-
- data->dialog = g_object_ref (dialog);
- data->email = g_strdup (email);
- data->uid = g_strdup (uid);
- data->sessionToken = g_strdup (sessionToken);
- data->keyFetchToken = g_strdup (keyFetchToken);
- data->unwrapBKey = g_strdup (unwrapBKey);
- data->tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- memcpy (data->tokenID, tokenID, EPHY_SYNC_TOKEN_LENGTH);
- data->reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- memcpy (data->reqHMACkey, reqHMACkey, EPHY_SYNC_TOKEN_LENGTH);
- data->respHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- memcpy (data->respHMACkey, respHMACkey, EPHY_SYNC_TOKEN_LENGTH);
- data->respXORkey = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
- memcpy (data->respXORkey, respXORkey, 2 * EPHY_SYNC_TOKEN_LENGTH);
-
- return data;
-}
-
-static void
-fxa_callback_data_free (FxACallbackData *data)
-{
- g_assert (data != NULL);
-
- g_object_unref (data->dialog);
- g_free (data->email);
- g_free (data->uid);
- g_free (data->sessionToken);
- g_free (data->keyFetchToken);
- g_free (data->unwrapBKey);
- g_free (data->tokenID);
- g_free (data->reqHMACkey);
- g_free (data->respHMACkey);
- g_free (data->respXORkey);
-
- g_slice_free (FxACallbackData, data);
-}
-
static void
prefs_dialog_finalize (GObject *object)
{
@@ -221,103 +165,113 @@ prefs_dialog_finalize (GObject *object)
g_object_unref (dialog->fxa_manager);
}
- if (dialog->fxa_id != 0) {
- g_source_remove (dialog->fxa_id);
- dialog->fxa_id = 0;
+ if (ephy_sync_service_is_signed_in (dialog->sync_service) && !dialog->sync_was_signed_in) {
+ ephy_sync_service_start_periodical_sync (dialog->sync_service);
+ } else if (dialog->sync_frequency != g_settings_get_uint (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_FREQUENCY)) {
+ g_signal_emit_by_name (dialog->sync_service, "sync-frequency-changed");
}
G_OBJECT_CLASS (prefs_dialog_parent_class)->finalize (object);
}
static void
-hide_fxa_iframe (PrefsDialog *dialog,
- const char *email)
+sync_bookmarks_toggled_cb (GtkToggleButton *button,
+ PrefsDialog *dialog)
{
- char *text;
- char *account;
+ EphyBookmarksManager *manager;
- account = g_strdup_printf ("<b>%s</b>", email);
- /* Translators: the %s refers to the email of the currently logged in user. */
- text = g_strdup_printf (_("Currently logged in as %s"), account);
- gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_out_details), text);
+ manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
- dialog->sync_sign_in_box);
- gtk_box_pack_start (GTK_BOX (dialog->sync_authenticate_box),
- dialog->sync_sign_out_box,
- TRUE, TRUE, 0);
-
- g_free (text);
- g_free (account);
+ if (gtk_toggle_button_get_active (button))
+ ephy_sync_service_register_manager (dialog->sync_service,
+ EPHY_SYNCHRONIZABLE_MANAGER (manager));
+ else
+ ephy_sync_service_unregister_manager (dialog->sync_service,
+ EPHY_SYNCHRONIZABLE_MANAGER (manager));
}
static void
-sync_tokens_store_finished_cb (EphySyncService *service,
- GError *error,
- PrefsDialog *dialog)
+sync_finished_cb (EphySyncService *service,
+ PrefsDialog *dialog)
{
g_assert (EPHY_IS_SYNC_SERVICE (service));
g_assert (EPHY_IS_PREFS_DIALOG (dialog));
- if (error == NULL) {
- /* Show the 'Signed in' panel. */
- hide_fxa_iframe (dialog, ephy_sync_service_get_user_email (service));
-
- /* Do a first time sync and set a periodical sync to be executed. */
- ephy_sync_service_sync_bookmarks (service, TRUE);
- ephy_sync_service_start_periodical_sync (service, FALSE);
- } else {
- char *message;
+ gtk_widget_set_sensitive (dialog->sync_now_button, TRUE);
+}
- /* Destroy the current session. */
- ephy_sync_service_destroy_session (service, NULL);
+static void
+sync_sign_in_details_show (PrefsDialog *dialog,
+ const char *text)
+{
+ char *message;
- /* Unset the email and tokens. */
- g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, "");
- ephy_sync_service_clear_tokens (service);
+ g_assert (EPHY_IS_PREFS_DIALOG (dialog));
- /* Display the error message to the user. */
- message = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>", error->message);
- gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), message);
- gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
- webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+ message = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>", text);
+ gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_iframe_label), message);
+ gtk_widget_set_visible (dialog->sync_firefox_iframe_label, TRUE);
- g_free (message);
- }
+ g_free (message);
}
-static gboolean
-poll_fxa_server (gpointer user_data)
+static void
+sync_sign_in_error_cb (EphySyncService *service,
+ const char *error,
+ PrefsDialog *dialog)
{
- FxACallbackData *data;
- EphySyncService *service;
- char *bundle;
+ g_assert (EPHY_IS_SYNC_SERVICE (service));
+ g_assert (EPHY_IS_PREFS_DIALOG (dialog));
- data = (FxACallbackData *)user_data;
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- bundle = ephy_sync_service_start_sign_in (service, data->tokenID, data->reqHMACkey);
+ /* Display the error message and reload the iframe. */
+ sync_sign_in_details_show (dialog, error);
+ webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+}
+
+static void
+sync_secrets_store_finished_cb (EphySyncService *service,
+ GError *error,
+ PrefsDialog *dialog)
+{
+ g_assert (EPHY_IS_SYNC_SERVICE (service));
+ g_assert (EPHY_IS_PREFS_DIALOG (dialog));
- if (bundle != NULL) {
- ephy_sync_service_finish_sign_in (service, data->email, data->uid,
- data->sessionToken, data->keyFetchToken,
- data->unwrapBKey, bundle,
- data->respHMACkey, data->respXORkey);
+ if (!error) {
+ char *text;
+ char *account;
- g_free (bundle);
- fxa_callback_data_free (data);
- data->dialog->fxa_id = 0;
+ /* Show sync options panel. */
+ account = g_strdup_printf ("<b>%s</b>", ephy_sync_service_get_user_email (service));
+ /* Translators: the %s refers to the email of the currently logged in user. */
+ text = g_strdup_printf (_("Currently logged in as %s"), account);
+ gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_account_label), text);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_firefox_iframe_box);
+ gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+ dialog->sync_firefox_account_box,
+ FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+ dialog->sync_options_box,
+ FALSE, FALSE, 0);
+
+ g_settings_set_string (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_USER,
+ ephy_sync_service_get_user_email (service));
- return G_SOURCE_REMOVE;
+ g_free (text);
+ g_free (account);
+ } else {
+ /* Display the error message and reload the iframe. */
+ sync_sign_in_details_show (dialog, error->message);
+ webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
}
-
- return G_SOURCE_CONTINUE;
}
static void
-inject_data_to_server (PrefsDialog *dialog,
- const char *type,
- const char *status,
- const char *data)
+sync_send_data_to_fxa_server (PrefsDialog *dialog,
+ const char *type,
+ const char *status,
+ const char *data)
{
char *json;
char *script;
@@ -341,120 +295,118 @@ inject_data_to_server (PrefsDialog *dialog,
}
static void
-server_message_received_cb (WebKitUserContentManager *manager,
+sync_fxa_server_message_cb (WebKitUserContentManager *manager,
WebKitJavascriptResult *result,
PrefsDialog *dialog)
{
- EphySyncService *service;
- JsonParser *parser;
- JsonObject *object;
- JsonObject *detail;
- char *json_string;
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ JsonObject *detail = NULL;
+ JsonObject *data = NULL;
+ GError *error = NULL;
+ char *json_string = NULL;
const char *type;
const char *command;
+ const char *email;
+ const char *uid;
+ const char *session_token;
+ const char *key_fetch_token;
+ const char *unwrap_b_key;
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
json_string = ephy_embed_utils_get_js_result_as_string (result);
- parser = json_parser_new ();
- json_parser_load_from_data (parser, json_string, -1, NULL);
- object = json_node_get_object (json_parser_get_root (parser));
- type = json_object_get_string_member (object, "type");
-
- /* The only message type we can receive is FirefoxAccountsCommand. */
- if (g_strcmp0 (type, "FirefoxAccountsCommand") != 0) {
+ if (!json_string) {
+ g_warning ("Failed to get JavaScript result as string");
+ goto out_error;
+ }
+ node = json_from_string (json_string, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out_error;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out_error;
+ }
+ type = json_object_get_string_member (json, "type");
+ if (!type) {
+ g_warning ("JSON object has missing or invalid 'type' member");
+ goto out_error;
+ }
+ /* The only message type expected is FirefoxAccountsCommand. */
+ if (g_strcmp0 (type, "FirefoxAccountsCommand")) {
g_warning ("Unknown command type: %s", type);
- goto out;
+ goto out_error;
+ }
+ detail = json_object_get_object_member (json, "detail");
+ if (!detail) {
+ g_warning ("JSON object has missing or invalid 'detail' member");
+ goto out_error;
}
-
- detail = json_object_get_object_member (object, "detail");
command = json_object_get_string_member (detail, "command");
+ if (!command) {
+ g_warning ("JSON object has missing or invalid 'command' member");
+ goto out_error;
+ }
- if (g_strcmp0 (command, "loaded") == 0) {
- LOG ("Loaded Firefox Sign In iframe");
- gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
- } else if (g_strcmp0 (command, "can_link_account") == 0) {
+ if (!g_strcmp0 (command, "loaded")) {
+ LOG ("Firefox Accounts iframe loaded");
+ goto out_no_error;
+ }
+ if (!g_strcmp0 (command, "can_link_account")) {
/* We need to confirm a relink. */
- inject_data_to_server (dialog, "message", "can_link_account", "{'ok': true}");
- } else if (g_strcmp0 (command, "login") == 0) {
- JsonObject *data = json_object_get_object_member (detail, "data");
- const char *email = json_object_get_string_member (data, "email");
- const char *uid = json_object_get_string_member (data, "uid");
- const char *sessionToken = json_object_get_string_member (data, "sessionToken");
- const char *keyFetchToken = json_object_get_string_member (data, "keyFetchToken");
- const char *unwrapBKey = json_object_get_string_member (data, "unwrapBKey");
- guint8 *tokenID;
- guint8 *reqHMACkey;
- guint8 *respHMACkey;
- guint8 *respXORkey;
- char *text;
-
- inject_data_to_server (dialog, "message", "login", NULL);
-
- /* Cannot retrieve the sync keys without keyFetchToken or unwrapBKey. */
- if (keyFetchToken == NULL || unwrapBKey == NULL) {
- g_warning ("Ignoring login with keyFetchToken or unwrapBKey missing!"
- "Cannot retrieve sync keys with one of them missing.");
- ephy_sync_service_destroy_session (service, sessionToken);
-
- text = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>",
- _("Something went wrong, please try again."));
- gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), text);
- gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
- webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
-
- g_free (text);
- goto out;
- }
-
- /* Derive tokenID, reqHMACkey, respHMACkey and respXORkey from the keyFetchToken.
- * tokenID and reqHMACkey are used to make a HAWK request to the "GET /account/keys"
- * API. The server looks up the stored table entry with tokenID, checks the request
- * HMAC for validity, then returns the pre-encrypted response.
- * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys */
- ephy_sync_crypto_process_key_fetch_token (keyFetchToken,
- &tokenID, &reqHMACkey,
- &respHMACkey, &respXORkey);
-
- /* If the account is not verified, then poll the server repeatedly
- * until the verification has finished. */
- if (json_object_get_boolean_member (data, "verified") == FALSE) {
- FxACallbackData *cb_data;
-
- text = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>",
- _("Please don’t leave this page until you have completed the verification."));
- gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_in_details), text);
- gtk_widget_set_visible (dialog->sync_sign_in_details, TRUE);
-
- cb_data = fxa_callback_data_new (dialog, email, uid, sessionToken,
- keyFetchToken, unwrapBKey, tokenID,
- reqHMACkey, respHMACkey, respXORkey);
- dialog->fxa_id = g_timeout_add_seconds (2, (GSourceFunc)poll_fxa_server, cb_data);
-
- g_free (text);
- } else {
- char *bundle;
+ sync_send_data_to_fxa_server (dialog, "message", "can_link_account", "{'ok': true}");
+ goto out_no_error;
+ }
+ if (g_strcmp0 (command, "login")) {
+ g_warning ("Unexepected command: %s", command);
+ goto out_error;
+ }
- bundle = ephy_sync_service_start_sign_in (service, tokenID, reqHMACkey);
- ephy_sync_service_finish_sign_in (service, email, uid, sessionToken, keyFetchToken,
- unwrapBKey, bundle, respHMACkey, respXORkey);
+ /* Login command. */
+ gtk_widget_set_visible (dialog->sync_firefox_iframe_label, FALSE);
+ sync_send_data_to_fxa_server (dialog, "message", "login", NULL);
- g_free (bundle);
- }
- } else if (g_strcmp0 (command, "session_status") == 0) {
- /* We are not signed in at this time, which we signal by returning an error. */
- inject_data_to_server (dialog, "message", "error", NULL);
- } else if (g_strcmp0 (command, "sign_out") == 0) {
- /* We are not signed in at this time. We should never get a sign out message! */
- inject_data_to_server (dialog, "message", "error", NULL);
+ data = json_object_get_object_member (detail, "data");
+ if (!data) {
+ g_warning ("JSON object has invalid 'data' member");
+ goto out_error;
+ }
+ email = json_object_get_string_member (data, "email");
+ uid = json_object_get_string_member (data, "uid");
+ session_token = json_object_get_string_member (data, "sessionToken");
+ key_fetch_token = json_object_get_string_member (data, "keyFetchToken");
+ unwrap_b_key = json_object_get_string_member (data, "unwrapBKey");
+ if (!email || !uid || !session_token || !key_fetch_token || !unwrap_b_key) {
+ g_warning ("JSON object has missing or invalid members");
+ goto out_error;
+ }
+ if (!json_object_has_member (data, "verified") ||
+ !JSON_NODE_HOLDS_VALUE (json_object_get_member (data, "verified"))) {
+ g_warning ("JSON object has missing or invalid 'verified' member");
+ goto out_error;
}
-out:
+ if (!json_object_get_boolean_member (data, "verified"))
+ sync_sign_in_details_show (dialog, _("Please don’t leave this page until "
+ "you have completed the verification."));
+ ephy_sync_service_do_sign_in (dialog->sync_service, email, uid,
+ session_token, key_fetch_token, unwrap_b_key);
+ goto out_no_error;
+
+out_error:
+ sync_sign_in_details_show (dialog, _("Something went wrong, please try again."));
+ webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+out_no_error:
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
g_free (json_string);
- g_object_unref (parser);
}
static void
-setup_fxa_sign_in_view (PrefsDialog *dialog)
+sync_setup_firefox_iframe (PrefsDialog *dialog)
{
EphyEmbedShell *shell;
WebKitWebContext *embed_context;
@@ -466,72 +418,70 @@ setup_fxa_sign_in_view (PrefsDialog *dialog)
"};"
"window.addEventListener(\"FirefoxAccountsCommand\", handleAccountsCommand);";
- dialog->fxa_script = webkit_user_script_new (js,
- WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
- WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
- NULL, NULL);
- dialog->fxa_manager = webkit_user_content_manager_new ();
- webkit_user_content_manager_add_script (dialog->fxa_manager, dialog->fxa_script);
- g_signal_connect (dialog->fxa_manager,
- "script-message-received::accountsCommandHandler",
- G_CALLBACK (server_message_received_cb),
- dialog);
- webkit_user_content_manager_register_script_message_handler (dialog->fxa_manager,
- "accountsCommandHandler");
-
- shell = ephy_embed_shell_get_default ();
- embed_context = ephy_embed_shell_get_web_context (shell);
-
- sync_context = webkit_web_context_new ();
- webkit_web_context_set_preferred_languages (sync_context,
- g_object_get_data (G_OBJECT (embed_context),
"preferred-languages"));
- dialog->fxa_web_view = WEBKIT_WEB_VIEW (g_object_new (WEBKIT_TYPE_WEB_VIEW,
- "user-content-manager", dialog->fxa_manager,
- "settings", ephy_embed_prefs_get_settings (),
- "web-context", sync_context,
- NULL));
- g_object_unref (sync_context);
-
- gtk_widget_set_visible (GTK_WIDGET (dialog->fxa_web_view), TRUE);
- gtk_widget_set_size_request (GTK_WIDGET (dialog->fxa_web_view), 450, 450);
- webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+ if (dialog->fxa_web_view == NULL) {
+ dialog->fxa_script = webkit_user_script_new (js,
+ WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
+ NULL, NULL);
+ dialog->fxa_manager = webkit_user_content_manager_new ();
+ webkit_user_content_manager_add_script (dialog->fxa_manager, dialog->fxa_script);
+ g_signal_connect (dialog->fxa_manager,
+ "script-message-received::accountsCommandHandler",
+ G_CALLBACK (sync_fxa_server_message_cb),
+ dialog);
+ webkit_user_content_manager_register_script_message_handler (dialog->fxa_manager,
+ "accountsCommandHandler");
+
+ shell = ephy_embed_shell_get_default ();
+ embed_context = ephy_embed_shell_get_web_context (shell);
+ sync_context = webkit_web_context_new ();
+ webkit_web_context_set_preferred_languages (sync_context,
+ g_object_get_data (G_OBJECT (embed_context),
"preferred-languages"));
+
+ dialog->fxa_web_view = WEBKIT_WEB_VIEW (g_object_new (WEBKIT_TYPE_WEB_VIEW,
+ "user-content-manager", dialog->fxa_manager,
+ "settings", ephy_embed_prefs_get_settings (),
+ "web-context", sync_context,
+ NULL));
+ gtk_widget_set_visible (GTK_WIDGET (dialog->fxa_web_view), TRUE);
+ gtk_widget_set_size_request (GTK_WIDGET (dialog->fxa_web_view), 450, 450);
+ gtk_box_pack_start (GTK_BOX (dialog->sync_firefox_iframe_box),
+ GTK_WIDGET (dialog->fxa_web_view),
+ FALSE, FALSE, 0);
+
+ g_object_unref (sync_context);
+ }
- gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
- gtk_container_add (GTK_CONTAINER (dialog->sync_sign_in_box),
- GTK_WIDGET (dialog->fxa_web_view));
+ webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+ gtk_widget_set_visible (dialog->sync_firefox_iframe_label, FALSE);
}
static void
on_sync_sign_out_button_clicked (GtkWidget *button,
PrefsDialog *dialog)
{
- EphySyncService *service;
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+ ephy_sync_service_do_sign_out (dialog->sync_service);
- /* Destroy session and delete tokens. */
- ephy_sync_service_stop_periodical_sync (service);
- ephy_sync_service_destroy_session (service, NULL);
- ephy_sync_service_clear_storage_credentials (service);
- ephy_sync_service_clear_tokens (service);
- ephy_sync_secret_forget_tokens ();
- ephy_sync_service_set_user_email (service, NULL);
- ephy_sync_service_set_sync_time (service, 0);
+ /* Show Firefox Accounts iframe. */
+ sync_setup_firefox_iframe (dialog);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_firefox_account_box);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_options_box);
+ gtk_box_pack_start (GTK_BOX (dialog->sync_page_box),
+ dialog->sync_firefox_iframe_box,
+ FALSE, FALSE, 0);
- g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, "");
-
- /* Show sign in box. */
- if (dialog->fxa_web_view == NULL)
- setup_fxa_sign_in_view (dialog);
- else
- webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
+ dialog->sync_was_signed_in = FALSE;
+}
- gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
- dialog->sync_sign_out_box);
- gtk_box_pack_start (GTK_BOX (dialog->sync_authenticate_box),
- dialog->sync_sign_in_box,
- TRUE, TRUE, 0);
- gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
+static void
+on_sync_sync_now_button_clicked (GtkWidget *button,
+ PrefsDialog *dialog)
+{
+ gtk_widget_set_sensitive (button, FALSE);
+ ephy_sync_service_do_sync (dialog->sync_service);
}
static void
@@ -625,17 +575,26 @@ prefs_dialog_class_init (PrefsDialogClass *klass)
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, enable_spell_checking_checkbutton);
/* sync */
- gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_authenticate_box);
- gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_in_box);
- gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_in_details);
- gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_box);
- gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_details);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_page_box);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_iframe_box);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_iframe_label);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_account_box);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_firefox_account_label);
gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_sign_out_button);
+ 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_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);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_frequency_60_min_radiobutton);
+ gtk_widget_class_bind_template_child (widget_class, PrefsDialog, sync_now_button);
gtk_widget_class_bind_template_callback (widget_class, on_manage_cookies_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_manage_passwords_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_search_engine_dialog_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_sync_sign_out_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_sync_sync_now_button_clicked);
}
static void
@@ -1241,6 +1200,28 @@ clear_personal_data_button_clicked_cb (GtkWidget *button,
}
static gboolean
+sync_frequency_get_mapping (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ if (GPOINTER_TO_UINT (user_data) == g_variant_get_uint32 (variant))
+ g_value_set_boolean (value, TRUE);
+
+ return TRUE;
+}
+
+static GVariant *
+sync_frequency_set_mapping (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ if (!g_value_get_boolean (value))
+ return NULL;
+
+ return g_variant_new_uint32 (GPOINTER_TO_UINT (user_data));
+}
+
+static gboolean
cookies_get_mapping (GValue *value,
GVariant *variant,
gpointer user_data)
@@ -1619,32 +1600,96 @@ setup_language_page (PrefsDialog *dialog)
static void
setup_sync_page (PrefsDialog *dialog)
{
- EphySyncService *service;
+ GSettings *sync_settings;
char *account;
char *text;
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+ sync_settings = ephy_settings_get (EPHY_PREFS_SYNC_SCHEMA);
+ dialog->sync_service = ephy_shell_get_sync_service (ephy_shell_get_default ());
+ dialog->sync_was_signed_in = ephy_sync_service_is_signed_in (dialog->sync_service);
- if (ephy_sync_service_is_signed_in (service) == FALSE) {
- setup_fxa_sign_in_view (dialog);
- gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
- dialog->sync_sign_out_box);
+ if (!dialog->sync_was_signed_in) {
+ sync_setup_firefox_iframe (dialog);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_firefox_account_box);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_options_box);
} else {
- gtk_container_remove (GTK_CONTAINER (dialog->sync_authenticate_box),
- dialog->sync_sign_in_box);
+ gtk_container_remove (GTK_CONTAINER (dialog->sync_page_box),
+ dialog->sync_firefox_iframe_box);
- account = g_strdup_printf ("<b>%s</b>", ephy_sync_service_get_user_email (service));
+ account = g_strdup_printf ("<b>%s</b>",
+ ephy_sync_service_get_user_email (dialog->sync_service));
/* Translators: the %s refers to the email of the currently logged in user. */
text = g_strdup_printf (_("Currently logged in as %s"), account);
- gtk_label_set_markup (GTK_LABEL (dialog->sync_sign_out_details), text);
+ gtk_label_set_markup (GTK_LABEL (dialog->sync_firefox_account_label), text);
g_free (text);
g_free (account);
}
- g_signal_connect_object (service, "sync-tokens-store-finished",
- G_CALLBACK (sync_tokens_store_finished_cb),
+ g_signal_connect_object (dialog->sync_service, "sync-secrets-store-finished",
+ G_CALLBACK (sync_secrets_store_finished_cb),
dialog, 0);
+ g_signal_connect_object (dialog->sync_service, "sync-sign-in-error",
+ G_CALLBACK (sync_sign_in_error_cb),
+ dialog, 0);
+ g_signal_connect_object (dialog->sync_service, "sync-finished",
+ G_CALLBACK (sync_finished_cb),
+ dialog, 0);
+ g_signal_connect_object (dialog->sync_bookmarks_checkbutton, "toggled",
+ G_CALLBACK (sync_bookmarks_toggled_cb),
+ dialog, 0);
+
+ g_settings_bind (sync_settings,
+ EPHY_PREFS_SYNC_WITH_FIREFOX,
+ dialog->sync_with_firefox_checkbutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (sync_settings,
+ EPHY_PREFS_SYNC_BOOKMARKS_ENABLED,
+ dialog->sync_bookmarks_checkbutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind_with_mapping (sync_settings,
+ EPHY_PREFS_SYNC_FREQUENCY,
+ dialog->sync_frequency_5_min_radiobutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT,
+ sync_frequency_get_mapping,
+ sync_frequency_set_mapping,
+ GINT_TO_POINTER (5),
+ NULL);
+ g_settings_bind_with_mapping (sync_settings,
+ EPHY_PREFS_SYNC_FREQUENCY,
+ dialog->sync_frequency_15_min_radiobutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT,
+ sync_frequency_get_mapping,
+ sync_frequency_set_mapping,
+ GINT_TO_POINTER (15),
+ NULL);
+ g_settings_bind_with_mapping (sync_settings,
+ EPHY_PREFS_SYNC_FREQUENCY,
+ dialog->sync_frequency_30_min_radiobutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT,
+ sync_frequency_get_mapping,
+ sync_frequency_set_mapping,
+ GINT_TO_POINTER (30),
+ NULL);
+ g_settings_bind_with_mapping (sync_settings,
+ EPHY_PREFS_SYNC_FREQUENCY,
+ dialog->sync_frequency_60_min_radiobutton,
+ "active",
+ G_SETTINGS_BIND_DEFAULT,
+ sync_frequency_get_mapping,
+ sync_frequency_set_mapping,
+ GINT_TO_POINTER (60),
+ NULL);
+
+ dialog->sync_frequency = g_settings_get_uint (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_FREQUENCY);
}
static void
diff --git a/src/profile-migrator/Makefile.am b/src/profile-migrator/Makefile.am
index 8cf59bd..24a2709 100644
--- a/src/profile-migrator/Makefile.am
+++ b/src/profile-migrator/Makefile.am
@@ -13,6 +13,7 @@ ephy_profile_migrator_CPPFLAGS = \
-I$(top_srcdir)/lib/history \
-I$(top_srcdir)/src/ \
-I$(top_srcdir)/src/bookmarks \
+ -I$(top_srcdir)/src/sync \
-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DLOCALEDIR=\"$(localedir)\" \
$(GLIB_CFLAGS) \
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index 94a8eb4..9bdff27 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -41,6 +41,7 @@
#include "ephy-search-engine-manager.h"
#include "ephy-settings.h"
#include "ephy-sqlite-connection.h"
+#include "ephy-sync-crypto.h"
#include "ephy-uri-tester-shared.h"
#include "ephy-web-app-utils.h"
@@ -641,11 +642,15 @@ parse_rdf_item (EphyBookmarksManager *manager,
if (link) {
EphyBookmark *bookmark;
+ char *id;
g_sequence_sort (tags, (GCompareDataFunc)ephy_bookmark_tags_compare, NULL);
- bookmark = ephy_bookmark_new ((const char *)link, (const char *)title, tags);
+ id = ephy_sync_crypto_get_random_sync_id ();
+ bookmark = ephy_bookmark_new ((const char *)link, (const char *)title, tags, id);
ephy_bookmarks_manager_add_bookmark (manager, bookmark);
+
g_object_unref (bookmark);
+ g_free (id);
} else {
g_sequence_free (tags);
}
diff --git a/src/resources/gtk/prefs-dialog.ui b/src/resources/gtk/prefs-dialog.ui
index fabf0cb..09c5ac9 100644
--- a/src/resources/gtk/prefs-dialog.ui
+++ b/src/resources/gtk/prefs-dialog.ui
@@ -770,11 +770,11 @@
</packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkBox" id="sync_page_box">
<property name="visible">True</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
- <property name="spacing">8</property>
+ <property name="spacing">18</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@@ -794,69 +794,188 @@
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
+ <property name="spacing">6</property>
<property name="margin-start">12</property>
- <property name="spacing">8</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="halign">start</property>
- <property name="use-markup">True</property>
<property name="max-width-chars">60</property>
<property name="wrap">True</property>
- <property name="label" translatable="yes">Sign in with your Firefox account to
sync your data with Web on other computers. Web is not Firefox and cannot sync with Firefox. Web is not
produced or endorsed by Mozilla.</property>
+ <property name="label" translatable="yes">Sign in with your Firefox account to
sync your data with Web and Firefox on other computers. Web is not produced or endorsed by Mozilla.</property>
</object>
</child>
</object>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="sync_firefox_iframe_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel" id="sync_firefox_iframe_label">
+ <property name="visible">False</property>
+ <property name="halign">start</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="sync_firefox_account_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Firefox Account</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
<child>
- <object class="GtkBox" id="sync_authenticate_box">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
+ <property name="spacing">6</property>
<property name="margin-start">12</property>
<child>
- <object class="GtkBox" id="sync_sign_in_box">
+ <object class="GtkLabel" id="sync_firefox_account_label">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="halign">center</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkLabel" id="sync_sign_in_details">
- <property name="visible">False</property>
- <property name="halign">start</property>
- </object>
- </child>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="sync_sign_out_button">
+ <property name="label" translatable="yes">Sign _out</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <property name="halign">start</property>
+ <signal name="clicked" handler="on_sync_sign_out_button_clicked"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="sync_options_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Sync Options</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">12</property>
+ <child>
+ <object class="GtkCheckButton" id="sync_with_firefox_checkbutton">
+ <property name="label" translatable="yes">Sync with _Firefox</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Collections</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
</object>
</child>
<child>
- <object class="GtkBox" id="sync_sign_out_box">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
- <property name="spacing">8</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">12</property>
<child>
- <object class="GtkLabel" id="sync_sign_out_details">
+ <object class="GtkCheckButton" id="sync_bookmarks_checkbutton">
+ <property name="label" translatable="yes">_Bookmarks</property>
<property name="visible">True</property>
- <property name="halign">start</property>
- <property name="label" translatable="yes"></property>
+ <property name="use-underline">True</property>
</object>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Frequency</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <property name="margin-start">12</property>
<child>
- <object class="GtkLabel">
+ <object class="GtkBox">
<property name="visible">True</property>
- <property name="halign">start</property>
- <property name="label" translatable="yes">Sign out if you wish to stop
syncing.</property>
+ <child>
+ <object class="GtkRadioButton" id="sync_frequency_5_min_radiobutton">
+ <property name="label" translatable="yes">_5 min</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="sync_frequency_15_min_radiobutton">
+ <property name="label" translatable="yes">_15 min</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <property name="group">sync_frequency_5_min_radiobutton</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="sync_frequency_30_min_radiobutton">
+ <property name="label" translatable="yes">_30 min</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <property name="group">sync_frequency_5_min_radiobutton</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="sync_frequency_60_min_radiobutton">
+ <property name="label" translatable="yes">_60 min</property>
+ <property name="visible">True</property>
+ <property name="use-underline">True</property>
+ <property name="group">sync_frequency_5_min_radiobutton</property>
+ </object>
+ </child>
</object>
</child>
<child>
- <object class="GtkButton" id="sync_sign_out_button">
- <property name="label" translatable="yes">Sign _out</property>
+ <object class="GtkButton" id="sync_now_button">
+ <property name="label" translatable="yes">Sync _now</property>
<property name="visible">True</property>
<property name="use-underline">True</property>
<property name="halign">start</property>
- <property name="width-request">100</property>
- <style>
- <class name="destructive-action"/>
- <class name="text-button"/>
- </style>
+ <signal name="clicked" handler="on_sync_sync_now_button_clicked"/>
</object>
</child>
</object>
diff --git a/src/sync/ephy-sync-crypto.c b/src/sync/ephy-sync-crypto.c
index 0e109f7..7607053 100644
--- a/src/sync/ephy-sync-crypto.c
+++ b/src/sync/ephy-sync-crypto.c
@@ -21,20 +21,21 @@
#include "config.h"
#include "ephy-sync-crypto.h"
-#include "ephy-sync-utils.h"
-
#include <glib/gstdio.h>
#include <inttypes.h>
#include <libsoup/soup.h>
+#include <nettle/cbc.h>
#include <nettle/aes.h>
#include <string.h>
#define HAWK_VERSION 1
#define NONCE_LEN 6
+#define IV_LEN 16
+#define SYNC_ID_LEN 12
static const char hex_digits[] = "0123456789abcdef";
-EphySyncCryptoHawkOptions *
+SyncCryptoHawkOptions *
ephy_sync_crypto_hawk_options_new (const char *app,
const char *dlg,
const char *ext,
@@ -45,9 +46,9 @@ ephy_sync_crypto_hawk_options_new (const char *app,
const char *payload,
const char *timestamp)
{
- EphySyncCryptoHawkOptions *options;
+ SyncCryptoHawkOptions *options;
- options = g_slice_new (EphySyncCryptoHawkOptions);
+ options = g_slice_new (SyncCryptoHawkOptions);
options->app = g_strdup (app);
options->dlg = g_strdup (dlg);
options->ext = g_strdup (ext);
@@ -62,9 +63,9 @@ ephy_sync_crypto_hawk_options_new (const char *app,
}
void
-ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *options)
+ephy_sync_crypto_hawk_options_free (SyncCryptoHawkOptions *options)
{
- g_return_if_fail (options != NULL);
+ g_return_if_fail (options);
g_free (options->app);
g_free (options->dlg);
@@ -76,10 +77,10 @@ ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *options)
g_free (options->payload);
g_free (options->timestamp);
- g_slice_free (EphySyncCryptoHawkOptions, options);
+ g_slice_free (SyncCryptoHawkOptions, options);
}
-static EphySyncCryptoHawkArtifacts *
+static SyncCryptoHawkArtifacts *
ephy_sync_crypto_hawk_artifacts_new (const char *app,
const char *dlg,
const char *ext,
@@ -91,9 +92,9 @@ ephy_sync_crypto_hawk_artifacts_new (const char *app,
const char *resource,
gint64 ts)
{
- EphySyncCryptoHawkArtifacts *artifacts;
+ SyncCryptoHawkArtifacts *artifacts;
- artifacts = g_slice_new (EphySyncCryptoHawkArtifacts);
+ artifacts = g_slice_new (SyncCryptoHawkArtifacts);
artifacts->app = g_strdup (app);
artifacts->dlg = g_strdup (dlg);
artifacts->ext = g_strdup (ext);
@@ -109,9 +110,9 @@ ephy_sync_crypto_hawk_artifacts_new (const char *app,
}
static void
-ephy_sync_crypto_hawk_artifacts_free (EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_hawk_artifacts_free (SyncCryptoHawkArtifacts *artifacts)
{
- g_assert (artifacts != NULL);
+ g_assert (artifacts);
g_free (artifacts->app);
g_free (artifacts->dlg);
@@ -124,61 +125,112 @@ ephy_sync_crypto_hawk_artifacts_free (EphySyncCryptoHawkArtifacts *artifacts)
g_free (artifacts->resource);
g_free (artifacts->ts);
- g_slice_free (EphySyncCryptoHawkArtifacts, artifacts);
+ g_slice_free (SyncCryptoHawkArtifacts, artifacts);
}
-static EphySyncCryptoHawkHeader *
-ephy_sync_crypto_hawk_header_new (char *header,
- EphySyncCryptoHawkArtifacts *artifacts)
+static SyncCryptoHawkHeader *
+ephy_sync_crypto_hawk_header_new (const char *header,
+ SyncCryptoHawkArtifacts *artifacts)
{
- EphySyncCryptoHawkHeader *hheader;
+ SyncCryptoHawkHeader *hawk_header;
- hheader = g_slice_new (EphySyncCryptoHawkHeader);
- hheader->header = header;
- hheader->artifacts = artifacts;
+ hawk_header = g_slice_new (SyncCryptoHawkHeader);
+ hawk_header->header = g_strdup (header);
+ hawk_header->artifacts = artifacts;
- return hheader;
+ return hawk_header;
}
void
-ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *hheader)
+ephy_sync_crypto_hawk_header_free (SyncCryptoHawkHeader *hawk_header)
{
- g_return_if_fail (hheader != NULL);
+ g_return_if_fail (hawk_header);
- g_free (hheader->header);
- ephy_sync_crypto_hawk_artifacts_free (hheader->artifacts);
+ g_free (hawk_header->header);
+ ephy_sync_crypto_hawk_artifacts_free (hawk_header->artifacts);
- g_slice_free (EphySyncCryptoHawkHeader, hheader);
+ g_slice_free (SyncCryptoHawkHeader, hawk_header);
}
-static EphySyncCryptoRSAKeyPair *
+static SyncCryptoRSAKeyPair *
ephy_sync_crypto_rsa_key_pair_new (struct rsa_public_key public,
struct rsa_private_key private)
{
- EphySyncCryptoRSAKeyPair *keypair;
+ SyncCryptoRSAKeyPair *rsa_key_pair;
- keypair = g_slice_new (EphySyncCryptoRSAKeyPair);
- keypair->public = public;
- keypair->private = private;
+ rsa_key_pair = g_slice_new (SyncCryptoRSAKeyPair);
+ rsa_key_pair->public = public;
+ rsa_key_pair->private = private;
- return keypair;
+ return rsa_key_pair;
}
void
-ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *keypair)
+ephy_sync_crypto_rsa_key_pair_free (SyncCryptoRSAKeyPair *rsa_key_pair)
{
- g_return_if_fail (keypair != NULL);
+ g_return_if_fail (rsa_key_pair);
- rsa_public_key_clear (&keypair->public);
- rsa_private_key_clear (&keypair->private);
+ rsa_public_key_clear (&rsa_key_pair->public);
+ rsa_private_key_clear (&rsa_key_pair->private);
- g_slice_free (EphySyncCryptoRSAKeyPair, keypair);
+ g_slice_free (SyncCryptoRSAKeyPair, rsa_key_pair);
+}
+
+static SyncCryptoKeyBundle *
+ephy_sync_crypto_key_bundle_new (const char *aes_key_hex,
+ const char *hmac_key_hex)
+{
+ SyncCryptoKeyBundle *bundle;
+
+ bundle = g_slice_new (SyncCryptoKeyBundle);
+ bundle->aes_key_hex = g_strdup (aes_key_hex);
+ bundle->hmac_key_hex = g_strdup (hmac_key_hex);
+
+ return bundle;
+}
+
+SyncCryptoKeyBundle *
+ephy_sync_crypto_key_bundle_from_array (JsonArray *array)
+{
+ SyncCryptoKeyBundle *bundle;
+ char *aes_key_hex;
+ char *hmac_key_hex;
+ guint8 *aes_key;
+ guint8 *hmac_key;
+ gsize len;
+
+ g_return_val_if_fail (array, NULL);
+ g_return_val_if_fail (json_array_get_length (array) == 2, NULL);
+
+ aes_key = g_base64_decode (json_array_get_string_element (array, 0), &len);
+ hmac_key = g_base64_decode (json_array_get_string_element (array, 1), &len);
+ aes_key_hex = ephy_sync_crypto_encode_hex (aes_key, 32);
+ hmac_key_hex = ephy_sync_crypto_encode_hex (hmac_key, 32);
+ bundle = ephy_sync_crypto_key_bundle_new (aes_key_hex, hmac_key_hex);
+
+ g_free (aes_key);
+ g_free (hmac_key);
+ g_free (aes_key_hex);
+ g_free (hmac_key_hex);
+
+ return bundle;
+}
+
+void
+ephy_sync_crypto_key_bundle_free (SyncCryptoKeyBundle *bundle)
+{
+ g_return_if_fail (bundle);
+
+ g_free (bundle->aes_key_hex);
+ g_free (bundle->hmac_key_hex);
+
+ g_slice_free (SyncCryptoKeyBundle, bundle);
}
static char *
ephy_sync_crypto_kw (const char *name)
{
- g_assert (name != NULL);
+ g_assert (name);
/* Concatenate the given name to the Mozilla prefix.
* See https://raw.githubusercontent.com/wiki/mozilla/fxa-auth-server/images/onepw-create.png
@@ -187,14 +239,14 @@ ephy_sync_crypto_kw (const char *name)
}
static guint8 *
-ephy_sync_crypto_xor (guint8 *a,
- guint8 *b,
- gsize length)
+ephy_sync_crypto_xor (const guint8 *a,
+ const guint8 *b,
+ gsize length)
{
guint8 *xored;
- g_assert (a != NULL);
- g_assert (b != NULL);
+ g_assert (a);
+ g_assert (b);
xored = g_malloc (length);
for (gsize i = 0; i < length; i++)
@@ -204,12 +256,12 @@ ephy_sync_crypto_xor (guint8 *a,
}
static gboolean
-ephy_sync_crypto_equals (guint8 *a,
- guint8 *b,
- gsize length)
+ephy_sync_crypto_equals (const guint8 *a,
+ const guint8 *b,
+ gsize length)
{
- g_assert (a != NULL);
- g_assert (b != NULL);
+ g_assert (a);
+ g_assert (b);
for (gsize i = 0; i < length; i++)
if (a[i] != b[i])
@@ -219,8 +271,45 @@ ephy_sync_crypto_equals (guint8 *a,
}
static char *
-ephy_sync_crypto_normalize_string (const char *type,
- EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_find_and_replace (const char *where,
+ const char *to_find,
+ const char *to_repl)
+{
+ const char *haystack = where;
+ const char *needle = NULL;
+ char *out;
+ gsize haystack_len;
+ gsize to_find_len;
+ gsize to_repl_len;
+ gsize new_len = 0;
+ gsize skip_len = 0;
+
+ g_assert (where);
+ g_assert (to_find);
+ g_assert (to_repl);
+
+ haystack_len = strlen (where);
+ to_find_len = strlen (to_find);
+ to_repl_len = strlen (to_repl);
+ out = g_malloc (haystack_len + 1);
+
+ while ((needle = g_strstr_len (haystack, -1, to_find)) != NULL) {
+ haystack_len += to_find_len - to_repl_len;
+ out = g_realloc (out, haystack_len + 1);
+ skip_len = needle - haystack;
+ memcpy (out + new_len, haystack, skip_len);
+ memcpy (out + new_len + skip_len, to_repl, to_repl_len);
+ new_len += skip_len + to_repl_len;
+ haystack = needle + to_find_len;
+ }
+ strcpy (out + new_len, haystack);
+
+ return out;
+}
+
+static char *
+ephy_sync_crypto_normalize_string (const char *type,
+ SyncCryptoHawkArtifacts *artifacts)
{
char *host;
char *info;
@@ -229,8 +318,8 @@ ephy_sync_crypto_normalize_string (const char *type,
char *normalized;
char *tmp;
- g_assert (type != NULL);
- g_assert (artifacts != NULL);
+ g_assert (type);
+ g_assert (artifacts);
info = g_strdup_printf ("hawk.%d.%s", HAWK_VERSION, type);
method = g_ascii_strup (artifacts->method, -1);
@@ -243,8 +332,8 @@ ephy_sync_crypto_normalize_string (const char *type,
NULL);
if (artifacts->ext && strlen (artifacts->ext) > 0) {
- tmp = ephy_sync_utils_find_and_replace (artifacts->ext, "\\", "\\\\");
- n_ext = ephy_sync_utils_find_and_replace (tmp, "\n", "\\n");
+ tmp = ephy_sync_crypto_find_and_replace (artifacts->ext, "\\", "\\\\");
+ n_ext = ephy_sync_crypto_find_and_replace (tmp, "\n", "\\n");
g_free (tmp);
}
@@ -272,7 +361,7 @@ ephy_sync_crypto_parse_content_type (const char *content_type)
char **tokens;
char *retval;
- g_assert (content_type != NULL);
+ g_assert (content_type);
tokens = g_strsplit (content_type, ";", -1);
retval = g_ascii_strdown (g_strstrip (tokens[0]), -1);
@@ -291,8 +380,8 @@ ephy_sync_crypto_calculate_payload_hash (const char *payload,
char *update;
char *hash;
- g_assert (payload != NULL);
- g_assert (content_type != NULL);
+ g_assert (payload);
+ g_assert (content_type);
content = ephy_sync_crypto_parse_content_type (content_type);
update = g_strdup_printf ("hawk.%d.payload\n%s\n%s\n",
@@ -311,23 +400,25 @@ ephy_sync_crypto_calculate_payload_hash (const char *payload,
}
static char *
-ephy_sync_crypto_calculate_mac (const char *type,
- guint8 *key,
- gsize key_len,
- EphySyncCryptoHawkArtifacts *artifacts)
+ephy_sync_crypto_calculate_mac (const char *type,
+ const guint8 *key,
+ gsize key_len,
+ SyncCryptoHawkArtifacts *artifacts)
{
guint8 *digest;
char *digest_hex;
char *normalized;
char *mac;
- g_assert (type != NULL);
- g_assert (key != NULL);
- g_assert (artifacts != NULL);
+ g_assert (type);
+ g_assert (key);
+ g_assert (artifacts);
/* Serialize the mac type and artifacts into a HAWK string. */
normalized = ephy_sync_crypto_normalize_string (type, artifacts);
- digest_hex = g_compute_hmac_for_string (G_CHECKSUM_SHA256, key, key_len, normalized, -1);
+ digest_hex = g_compute_hmac_for_string (G_CHECKSUM_SHA256,
+ key, key_len,
+ normalized, -1);
digest = ephy_sync_crypto_decode_hex (digest_hex);
mac = g_base64_encode (digest, g_checksum_type_get_length (G_CHECKSUM_SHA256));
@@ -341,14 +432,14 @@ ephy_sync_crypto_calculate_mac (const char *type,
static char *
ephy_sync_crypto_append_to_header (char *header,
const char *name,
- char *value)
+ const char *value)
{
char *new_header;
char *tmp;
- g_assert (header != NULL);
- g_assert (name != NULL);
- g_assert (value != NULL);
+ g_assert (header);
+ g_assert (name);
+ g_assert (value);
tmp = header;
new_header = g_strconcat (header, ", ", name, "=\"", value, "\"", NULL);
@@ -357,15 +448,43 @@ ephy_sync_crypto_append_to_header (char *header,
return new_header;
}
+static guint8 *
+ephy_sync_crypto_concat_bytes (const guint8 *bytes,
+ gsize bytes_len,
+ ...)
+{
+ va_list args;
+ guint8 *next;
+ guint8 *out;
+ gsize next_len;
+ gsize out_len;
+
+ out_len = bytes_len;
+ out = g_malloc (out_len);
+ memcpy (out, bytes, out_len);
+
+ va_start (args, bytes_len);
+ while ((next = va_arg (args, guint8 *)) != NULL) {
+ next_len = va_arg (args, gsize);
+ out = g_realloc (out, out_len + next_len);
+ memcpy (out + out_len, next, next_len);
+ out_len += next_len;
+ }
+
+ va_end (args);
+
+ return out;
+}
+
static void
-ephy_sync_crypto_hkdf (guint8 *in,
- gsize in_len,
- guint8 *salt,
- gsize salt_len,
- guint8 *info,
- gsize info_len,
- guint8 *out,
- gsize out_len)
+ephy_sync_crypto_hkdf (const guint8 *in,
+ gsize in_len,
+ guint8 *salt,
+ gsize salt_len,
+ const guint8 *info,
+ gsize info_len,
+ guint8 *out,
+ gsize out_len)
{
char *prk_hex;
char *tmp_hex;
@@ -378,9 +497,9 @@ ephy_sync_crypto_hkdf (guint8 *in,
gsize data_len;
gsize n;
- g_assert (in != NULL);
- g_assert (info != NULL);
- g_assert (out != NULL);
+ g_assert (in);
+ g_assert (info);
+ g_assert (out);
hash_len = g_checksum_type_get_length (G_CHECKSUM_SHA256);
g_assert (out_len <= hash_len * 255);
@@ -389,13 +508,15 @@ ephy_sync_crypto_hkdf (guint8 *in,
* See https://tools.ietf.org/html/rfc5869 */
/* If salt value was not provided, use an array of hash_len zeros. */
- if (salt == NULL) {
+ if (!salt) {
salt = g_malloc0 (hash_len);
salt_len = hash_len;
}
/* Step 1: Extract */
- prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256, salt, salt_len, in, in_len);
+ prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ salt, salt_len,
+ in, in_len);
prk = ephy_sync_crypto_decode_hex (prk_hex);
/* Step 2: Expand */
@@ -405,16 +526,19 @@ ephy_sync_crypto_hkdf (guint8 *in,
for (gsize i = 0; i < n; i++, counter++) {
if (i == 0) {
- data = ephy_sync_utils_concatenate_bytes (info, info_len, &counter, 1, NULL);
+ data = ephy_sync_crypto_concat_bytes (info, info_len, &counter, 1, NULL);
data_len = info_len + 1;
} else {
- data = ephy_sync_utils_concatenate_bytes (out_full + (i - 1) * hash_len, hash_len,
- info, info_len, &counter, 1,
- NULL);
+ data = ephy_sync_crypto_concat_bytes (out_full + (i - 1) * hash_len, hash_len,
+ info, info_len,
+ &counter, 1,
+ NULL);
data_len = hash_len + info_len + 1;
}
- tmp_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256, prk, hash_len, data, data_len);
+ tmp_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ prk, hash_len,
+ data, data_len);
tmp = ephy_sync_crypto_decode_hex (tmp_hex);
memcpy (out_full + i * hash_len, tmp, hash_len);
@@ -434,7 +558,7 @@ ephy_sync_crypto_hkdf (guint8 *in,
static void
ephy_sync_crypto_b64_to_b64_urlsafe (char *text)
{
- g_assert (text != NULL);
+ g_assert (text);
/* Replace '+' with '-' and '/' with '_' */
g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=/", '-');
@@ -444,173 +568,549 @@ ephy_sync_crypto_b64_to_b64_urlsafe (char *text)
static void
ephy_sync_crypto_b64_urlsafe_to_b64 (char *text)
{
- g_assert (text != NULL);
+ g_assert (text);
/* Replace '-' with '+' and '_' with '/' */
g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=_", '+');
g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+", '/');
}
+static guint8 *
+ephy_sync_crypto_pad (const char *text,
+ gsize block_len,
+ gsize *out_len)
+{
+ guint8 *out;
+ gsize text_len = strlen (text);
+
+ g_assert (text);
+ g_assert (out_len);
+
+ if (text_len % block_len == 0)
+ *out_len = text_len;
+ else
+ *out_len = text_len + block_len - text_len % block_len;
+
+ out = g_malloc (*out_len);
+
+ if (text_len % block_len != 0)
+ memset (out, block_len - text_len % block_len, *out_len);
+
+ memcpy (out, text, text_len);
+
+ return out;
+}
+
+static guint8 *
+ephy_sync_crypto_aes_256_encrypt (const char *text,
+ const guint8 *key,
+ const guint8 *iv,
+ gsize *out_len)
+{
+ guint8 *padded;
+ guint8 *encrypted;
+ gsize padded_len;
+ struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
+
+ g_assert (text);
+ g_assert (key);
+ g_assert (iv);
+ g_assert (out_len);
+
+ padded = ephy_sync_crypto_pad (text, AES_BLOCK_SIZE, &padded_len);
+ encrypted = g_malloc (padded_len);
+
+ aes256_set_encrypt_key(&ctx.ctx, key);
+ CBC_SET_IV(&ctx, iv);
+ CBC_ENCRYPT(&ctx, aes256_encrypt, padded_len, encrypted, padded);
+
+ *out_len = padded_len;
+ g_free (padded);
+
+ return encrypted;
+}
+
+static char *
+ephy_sync_crypto_unpad (const guint8 *data,
+ gsize data_len,
+ gsize block_len)
+{
+ char *out;
+ gsize out_len;
+ gsize padding = data[data_len - 1];
+
+ g_assert (data);
+
+ if (padding >= 1 && padding <= block_len)
+ out_len = data_len - padding;
+ else
+ out_len = data_len;
+
+ out = g_malloc0 (out_len + 1);
+ memcpy (out, data, out_len);
+
+ return out;
+}
+
+static char *
+ephy_sync_crypto_aes_256_decrypt (const guint8 *data,
+ gsize data_len,
+ const guint8 *key,
+ const guint8 *iv)
+{
+ guint8 *decrypted;
+ char *unpadded;
+ struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
+
+ g_assert (data);
+ g_assert (key);
+ g_assert (iv);
+
+ decrypted = g_malloc (data_len);
+
+ aes256_set_decrypt_key (&ctx.ctx, key);
+ CBC_SET_IV (&ctx, iv);
+ CBC_DECRYPT (&ctx, aes256_decrypt, data_len, decrypted, data);
+
+ unpadded = ephy_sync_crypto_unpad (decrypted, data_len, AES_BLOCK_SIZE);
+ g_free (decrypted);
+
+ return unpadded;
+}
+
+static gboolean
+ephy_sync_crypto_hmac_is_valid (const char *text,
+ const guint8 *key,
+ const char *expected)
+{
+ char *hmac;
+ gboolean retval;
+
+ g_assert (text);
+ g_assert (key);
+ g_assert (expected);
+
+ /* SHA256 expects a 32 bytes key. */
+ hmac = g_compute_hmac_for_string (G_CHECKSUM_SHA256, key, 32, text, -1);
+ retval = g_strcmp0 (hmac, expected) == 0;
+ g_free (hmac);
+
+ return retval;
+}
+
+/*
+ * This function is required by Nettle's RSA support.
+ * From Nettle's documentation: random_ctx and random is a randomness generator.
+ * random(random_ctx, length, dst) should generate length random octets and store them at dst.
+ * We don't really use random_ctx, since we have /dev/urandom available.
+ */
+static void
+ephy_sync_crypto_random_bytes_gen (void *random_ctx,
+ gsize length,
+ guint8 *dst)
+{
+ FILE *fp;
+
+ g_assert (length > 0);
+ g_assert (dst);
+
+ fp = fopen ("/dev/urandom", "r");
+ fread (dst, sizeof (guint8), length, fp);
+ fclose (fp);
+}
+
void
-ephy_sync_crypto_process_key_fetch_token (const char *keyFetchToken,
- guint8 **tokenID,
- guint8 **reqHMACkey,
- guint8 **respHMACkey,
- guint8 **respXORkey)
+ephy_sync_crypto_process_key_fetch_token (const char *key_fetch_token,
+ guint8 **token_id,
+ guint8 **req_hmac_key,
+ guint8 **resp_hmac_key,
+ guint8 **resp_xor_key,
+ gsize token_len)
{
guint8 *kft;
guint8 *out1;
guint8 *out2;
- guint8 *keyRequestKey;
+ guint8 *key_request_key;
char *info_kft;
char *info_keys;
- g_return_if_fail (keyFetchToken != NULL);
- g_return_if_fail (tokenID != NULL);
- g_return_if_fail (reqHMACkey != NULL);
- g_return_if_fail (respHMACkey != NULL);
- g_return_if_fail (respXORkey != NULL);
+ g_return_if_fail (key_fetch_token);
+ g_return_if_fail (token_id);
+ g_return_if_fail (req_hmac_key);
+ g_return_if_fail (resp_hmac_key);
+ g_return_if_fail (resp_xor_key);
- kft = ephy_sync_crypto_decode_hex (keyFetchToken);
+ kft = ephy_sync_crypto_decode_hex (key_fetch_token);
info_kft = ephy_sync_crypto_kw ("keyFetchToken");
info_keys = ephy_sync_crypto_kw ("account/keys");
- out1 = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
- out2 = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
+ out1 = g_malloc (3 * token_len);
+ out2 = g_malloc (3 * token_len);
/* Use the keyFetchToken to derive tokenID, reqHMACkey and keyRequestKey. */
- ephy_sync_crypto_hkdf (kft, EPHY_SYNC_TOKEN_LENGTH,
+ ephy_sync_crypto_hkdf (kft, token_len,
NULL, 0,
(guint8 *)info_kft, strlen (info_kft),
- out1, 3 * EPHY_SYNC_TOKEN_LENGTH);
+ out1, 3 * token_len);
- *tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- *reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- keyRequestKey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*tokenID, out1, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*reqHMACkey, out1 + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (keyRequestKey, out1 + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+ *token_id = g_malloc (token_len);
+ *req_hmac_key = g_malloc (token_len);
+ key_request_key = g_malloc (token_len);
+ memcpy (*token_id, out1, token_len);
+ memcpy (*req_hmac_key, out1 + token_len, token_len);
+ memcpy (key_request_key, out1 + 2 * token_len, token_len);
/* Use the keyRequestKey to derive respHMACkey and respXORkey. */
- ephy_sync_crypto_hkdf (keyRequestKey, EPHY_SYNC_TOKEN_LENGTH,
+ ephy_sync_crypto_hkdf (key_request_key, token_len,
NULL, 0,
(guint8 *)info_keys, strlen (info_keys),
- out2, 3 * EPHY_SYNC_TOKEN_LENGTH);
+ out2, 3 * token_len);
- *respHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- *respXORkey = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*respHMACkey, out2, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*respXORkey, out2 + EPHY_SYNC_TOKEN_LENGTH, 2 * EPHY_SYNC_TOKEN_LENGTH);
+ *resp_hmac_key = g_malloc (token_len);
+ *resp_xor_key = g_malloc (2 * token_len);
+ memcpy (*resp_hmac_key, out2, token_len);
+ memcpy (*resp_xor_key, out2 + token_len, 2 * token_len);
g_free (kft);
g_free (out1);
g_free (out2);
g_free (info_kft);
g_free (info_keys);
- g_free (keyRequestKey);
+ g_free (key_request_key);
}
void
-ephy_sync_crypto_process_session_token (const char *sessionToken,
- guint8 **tokenID,
- guint8 **reqHMACkey,
- guint8 **requestKey)
+ephy_sync_crypto_process_session_token (const char *session_token,
+ guint8 **token_id,
+ guint8 **req_hmac_key,
+ guint8 **request_key,
+ gsize token_len)
{
guint8 *st;
guint8 *out;
char *info;
- g_return_if_fail (sessionToken != NULL);
- g_return_if_fail (tokenID != NULL);
- g_return_if_fail (reqHMACkey != NULL);
- g_return_if_fail (requestKey != NULL);
+ g_return_if_fail (session_token);
+ g_return_if_fail (token_id);
+ g_return_if_fail (req_hmac_key);
+ g_return_if_fail (request_key);
- st = ephy_sync_crypto_decode_hex (sessionToken);
+ st = ephy_sync_crypto_decode_hex (session_token);
info = ephy_sync_crypto_kw ("sessionToken");
- out = g_malloc (3 * EPHY_SYNC_TOKEN_LENGTH);
+ out = g_malloc (3 * token_len);
/* Use the sessionToken to derive tokenID, reqHMACkey and requestKey. */
- ephy_sync_crypto_hkdf (st, EPHY_SYNC_TOKEN_LENGTH,
+ ephy_sync_crypto_hkdf (st, token_len,
NULL, 0,
(guint8 *)info, strlen (info),
- out, 3 * EPHY_SYNC_TOKEN_LENGTH);
+ out, 3 * token_len);
- *tokenID = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- *reqHMACkey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- *requestKey = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*tokenID, out, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*reqHMACkey, out + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*requestKey, out + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+ *token_id = g_malloc (token_len);
+ *req_hmac_key = g_malloc (token_len);
+ *request_key = g_malloc (token_len);
+ memcpy (*token_id, out, token_len);
+ memcpy (*req_hmac_key, out + token_len, token_len);
+ memcpy (*request_key, out + 2 * token_len, token_len);
g_free (st);
g_free (out);
g_free (info);
}
-void
-ephy_sync_crypto_compute_sync_keys (const char *bundle,
- guint8 *respHMACkey,
- guint8 *respXORkey,
- guint8 *unwrapBKey,
- guint8 **kA,
- guint8 **kB)
-{
- guint8 *bdl;
+gboolean
+ephy_sync_crypto_compute_sync_keys (const char *bundle_hex,
+ const guint8 *resp_hmac_key,
+ const guint8 *resp_xor_key,
+ const guint8 *unwrap_b_key,
+ guint8 **kA,
+ guint8 **kB,
+ gsize key_len)
+{
+ guint8 *bundle;
guint8 *ciphertext;
- guint8 *respMAC;
- guint8 *respMAC2;
+ guint8 *resp_hmac;
+ guint8 *resp_hmac_2;
guint8 *xored;
- guint8 *wrapKB;
- char *respMAC2_hex;
-
- g_return_if_fail (bundle != NULL);
- g_return_if_fail (respHMACkey != NULL);
- g_return_if_fail (respXORkey != NULL);
- g_return_if_fail (unwrapBKey != NULL);
- g_return_if_fail (kA != NULL);
- g_return_if_fail (kB != NULL);
-
- bdl = ephy_sync_crypto_decode_hex (bundle);
- ciphertext = g_malloc (2 * EPHY_SYNC_TOKEN_LENGTH);
- respMAC = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- wrapKB = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
- *kA = g_malloc (EPHY_SYNC_TOKEN_LENGTH);
+ guint8 *wrap_key_b;
+ char *resp_hmac_2_hex;
+ gboolean retval = TRUE;
+
+ g_return_val_if_fail (bundle_hex, FALSE);
+ g_return_val_if_fail (resp_hmac_key, FALSE);
+ g_return_val_if_fail (resp_xor_key, FALSE);
+ g_return_val_if_fail (unwrap_b_key, FALSE);
+ g_return_val_if_fail (kA, FALSE);
+ g_return_val_if_fail (kB, FALSE);
+
+ bundle = ephy_sync_crypto_decode_hex (bundle_hex);
+ ciphertext = g_malloc (2 * key_len);
+ resp_hmac = g_malloc (key_len);
/* Compute the MAC and compare it to the expected value. */
- memcpy (ciphertext, bdl, 2 * EPHY_SYNC_TOKEN_LENGTH);
- memcpy (respMAC, bdl + 2 * EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
- respMAC2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
- respHMACkey, EPHY_SYNC_TOKEN_LENGTH,
- ciphertext, 2 * EPHY_SYNC_TOKEN_LENGTH);
- respMAC2 = ephy_sync_crypto_decode_hex (respMAC2_hex);
- g_assert (ephy_sync_crypto_equals (respMAC, respMAC2, EPHY_SYNC_TOKEN_LENGTH) == TRUE);
+ memcpy (ciphertext, bundle, 2 * key_len);
+ memcpy (resp_hmac, bundle + 2 * key_len, key_len);
+ resp_hmac_2_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ resp_hmac_key, key_len,
+ ciphertext, 2 * key_len);
+ resp_hmac_2 = ephy_sync_crypto_decode_hex (resp_hmac_2_hex);
+ if (!ephy_sync_crypto_equals (resp_hmac, resp_hmac_2, key_len)) {
+ g_warning ("HMAC values differs from the one expected");
+ retval = FALSE;
+ goto out;
+ }
/* XOR the extracted ciphertext with the respXORkey, then split in into the
* separate kA and wrap(kB) values. */
- xored = ephy_sync_crypto_xor (ciphertext, respXORkey, 2 * EPHY_SYNC_TOKEN_LENGTH);
- memcpy (*kA, xored, EPHY_SYNC_TOKEN_LENGTH);
- memcpy (wrapKB, xored + EPHY_SYNC_TOKEN_LENGTH, EPHY_SYNC_TOKEN_LENGTH);
+ xored = ephy_sync_crypto_xor (ciphertext, resp_xor_key, 2 * key_len);
+ *kA = g_malloc (key_len);
+ memcpy (*kA, xored, key_len);
+ wrap_key_b = g_malloc (key_len);
+ memcpy (wrap_key_b, xored + key_len, key_len);
+ /* XOR wrap(kB) with unwrapBKey to obtain kB. There is no MAC on wrap(kB). */
+ *kB = ephy_sync_crypto_xor (unwrap_b_key, wrap_key_b, key_len);
+
+ g_free (wrap_key_b);
+ g_free (xored);
+out:
+ g_free (resp_hmac_2);
+ g_free (resp_hmac_2_hex);
+ g_free (resp_hmac);
+ g_free (ciphertext);
+ g_free (bundle);
+
+ return retval;
+}
+
+SyncCryptoKeyBundle *
+ephy_sync_crypto_derive_key_bundle (const guint8 *key,
+ gsize key_len)
+{
+ SyncCryptoKeyBundle *bundle;
+ guint8 *salt;
+ guint8 *prk;
+ guint8 *tmp;
+ guint8 *aes_key;
+ char *prk_hex;
+ char *aes_key_hex;
+ char *hmac_key_hex;
+ const char *info = "identity.mozilla.com/picl/v1/oldsync";
+
+ g_return_val_if_fail (key, NULL);
+ g_return_val_if_fail (key_len > 0, NULL);
+
+ /* Perform a two step HKDF with an all-zeros salt.
+ * T(1) will represent the AES key, T(2) will represent the HMAC key. */
+
+ salt = g_malloc0 (key_len);
+ prk_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ salt, key_len,
+ key, key_len);
+ prk = ephy_sync_crypto_decode_hex (prk_hex);
+ tmp = ephy_sync_crypto_concat_bytes ((guint8 *)info, strlen (info),
+ "\x01", 1,
+ NULL);
+ aes_key_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ prk, key_len,
+ tmp, strlen (info) + 1);
+ aes_key = ephy_sync_crypto_decode_hex (aes_key_hex);
+ g_free (tmp);
+ tmp = ephy_sync_crypto_concat_bytes (aes_key, key_len,
+ (guint8 *)info, strlen (info),
+ "\x02", 1,
+ NULL);
+ hmac_key_hex = g_compute_hmac_for_data (G_CHECKSUM_SHA256,
+ prk, key_len,
+ tmp, key_len + strlen (info) + 1);
+ bundle = ephy_sync_crypto_key_bundle_new (aes_key_hex, hmac_key_hex);
+
+ g_free (hmac_key_hex);
+ g_free (tmp);
+ g_free (aes_key_hex);
+ g_free (prk);
+ g_free (prk_hex);
+ g_free (salt);
+
+ return bundle;
+}
+
+char *
+ephy_sync_crypto_generate_crypto_keys (gsize key_len)
+{
+ JsonNode *node;
+ JsonObject *object;
+ JsonArray *array;
+ guint8 *aes_key;
+ guint8 *hmac_key;
+ char *aes_key_b64;
+ char *hmac_key_b64;
+ char *payload;
+
+ aes_key = g_malloc (key_len);
+ ephy_sync_crypto_random_bytes_gen (NULL, key_len, aes_key);
+ aes_key_b64 = g_base64_encode (aes_key, key_len);
+ hmac_key = g_malloc (key_len);
+ ephy_sync_crypto_random_bytes_gen (NULL, key_len, hmac_key);
+ hmac_key_b64 = g_base64_encode (hmac_key, key_len);
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ object = json_object_new ();
+ array = json_array_new ();
+ json_array_add_string_element (array, aes_key_b64);
+ json_array_add_string_element (array, hmac_key_b64);
+ json_object_set_array_member (object, "default", array);
+ json_object_set_object_member (object, "collections", json_object_new ());
+ json_object_set_string_member (object, "collection", "crypto");
+ json_object_set_string_member (object, "id", "keys");
+ json_node_set_object (node, object);
+ payload = json_to_string (node, FALSE);
+
+ json_object_unref (object);
+ json_node_unref (node);
+ g_free (hmac_key_b64);
+ g_free (hmac_key);
+ g_free (aes_key_b64);
+ g_free (aes_key);
+
+ return payload;
+}
- /* Finally, XOR wrap(kB) with unwrapBKey to obtain kB. There is no MAC on wrap(kB). */
- *kB = ephy_sync_crypto_xor (unwrapBKey, wrapKB, EPHY_SYNC_TOKEN_LENGTH);
+char *
+ephy_sync_crypto_decrypt_record (const char *payload,
+ SyncCryptoKeyBundle *bundle)
+{
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ guint8 *aes_key = NULL;
+ guint8 *hmac_key = NULL;
+ guint8 *ciphertext = NULL;
+ guint8 *iv = NULL;
+ char *cleartext = NULL;
+ const char *ciphertext_b64;
+ const char *iv_b64;
+ const char *hmac;
+ gsize ciphertext_len;
+ gsize iv_len;
+
+ g_return_val_if_fail (payload, NULL);
+ g_return_val_if_fail (bundle, NULL);
+
+ /* Extract ciphertext, iv and hmac from payload. */
+ node = json_from_string (payload, &error);
+ if (error) {
+ g_warning ("Payload is not a valid JSON: %s", error->message);
+ goto out;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out;
+ }
+ ciphertext_b64 = json_object_get_string_member (json, "ciphertext");
+ iv_b64 = json_object_get_string_member (json, "IV");
+ hmac = json_object_get_string_member (json, "hmac");
+ if (!ciphertext_b64 || !iv_b64 || !hmac) {
+ g_warning ("JSON object has missing or invalid members");
+ goto out;
+ }
+
+ /* Get the encryption key and the HMAC key. */
+ aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
+ hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+
+ /* Under no circumstances should a client try to decrypt a record
+ * if the HMAC verification fails. */
+ if (!ephy_sync_crypto_hmac_is_valid (ciphertext_b64, hmac_key, hmac)) {
+ g_warning ("Incorrect HMAC value");
+ goto out;
+ }
- g_free (bdl);
+ /* Finally, decrypt the record. */
+ ciphertext = g_base64_decode (ciphertext_b64, &ciphertext_len);
+ iv = g_base64_decode (iv_b64, &iv_len);
+ cleartext = ephy_sync_crypto_aes_256_decrypt (ciphertext, ciphertext_len,
+ aes_key, iv);
+
+out:
g_free (ciphertext);
- g_free (respMAC);
- g_free (respMAC2);
- g_free (xored);
- g_free (wrapKB);
- g_free (respMAC2_hex);
+ g_free (iv);
+ g_free (aes_key);
+ g_free (hmac_key);
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+
+ return cleartext;
+}
+
+char *
+ephy_sync_crypto_encrypt_record (const char *cleartext,
+ SyncCryptoKeyBundle *bundle)
+{
+ JsonNode *node;
+ JsonObject *object;
+ char *payload;
+ char *iv_b64;
+ char *ciphertext_b64;
+ char *hmac;
+ guint8 *aes_key;
+ guint8 *hmac_key;
+ guint8 *ciphertext;
+ guint8 *iv;
+ gsize ciphertext_len;
+
+ g_return_val_if_fail (cleartext, NULL);
+ g_return_val_if_fail (bundle, NULL);
+
+ /* Get the encryption key and the HMAC key. */
+ aes_key = ephy_sync_crypto_decode_hex (bundle->aes_key_hex);
+ hmac_key = ephy_sync_crypto_decode_hex (bundle->hmac_key_hex);
+
+ /* Generate a random 16 bytes initialization vector. */
+ iv = g_malloc (IV_LEN);
+ ephy_sync_crypto_random_bytes_gen (NULL, IV_LEN, iv);
+
+ /* Encrypt the record using the AES key. */
+ ciphertext = ephy_sync_crypto_aes_256_encrypt (cleartext, aes_key,
+ iv, &ciphertext_len);
+ ciphertext_b64 = g_base64_encode (ciphertext, ciphertext_len);
+ iv_b64 = g_base64_encode (iv, IV_LEN);
+ /* SHA256 expects a 32 bytes key. */
+ hmac = g_compute_hmac_for_string (G_CHECKSUM_SHA256,
+ hmac_key, 32,
+ ciphertext_b64, -1);
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ object = json_object_new ();
+ json_object_set_string_member (object, "ciphertext", ciphertext_b64);
+ json_object_set_string_member (object, "IV", iv_b64);
+ json_object_set_string_member (object, "hmac", hmac);
+ json_node_set_object (node, object);
+ payload = json_to_string (node, FALSE);
+
+ json_object_unref (object);
+ json_node_unref (node);
+ g_free (hmac);
+ g_free (iv_b64);
+ g_free (ciphertext_b64);
+ g_free (ciphertext);
+ g_free (iv);
+ g_free (aes_key);
+ g_free (hmac_key);
+
+ return payload;
}
-EphySyncCryptoHawkHeader *
-ephy_sync_crypto_compute_hawk_header (const char *url,
- const char *method,
- const char *id,
- guint8 *key,
- gsize key_len,
- EphySyncCryptoHawkOptions *options)
+SyncCryptoHawkHeader *
+ephy_sync_crypto_compute_hawk_header (const char *url,
+ const char *method,
+ const char *id,
+ const guint8 *key,
+ gsize key_len,
+ SyncCryptoHawkOptions *options)
{
- EphySyncCryptoHawkArtifacts *artifacts;
+ SyncCryptoHawkHeader *hawk_header;
+ SyncCryptoHawkArtifacts *artifacts;
SoupURI *uri;
char *resource;
char *hash;
@@ -619,32 +1119,35 @@ ephy_sync_crypto_compute_hawk_header (const char *url,
char *nonce;
char *payload;
char *timestamp;
+ guint8 *bytes;
gint64 ts;
- g_return_val_if_fail (url != NULL, NULL);
- g_return_val_if_fail (method != NULL, NULL);
- g_return_val_if_fail (id != NULL, NULL);
- g_return_val_if_fail (key != NULL, NULL);
+ g_return_val_if_fail (url, NULL);
+ g_return_val_if_fail (method, NULL);
+ g_return_val_if_fail (id, NULL);
+ g_return_val_if_fail (key, NULL);
- ts = ephy_sync_utils_current_time_seconds ();
+ ts = g_get_real_time () / 1000000;
hash = options ? g_strdup (options->hash) : NULL;
payload = options ? options->payload : NULL;
timestamp = options ? options->timestamp : NULL;
uri = soup_uri_new (url);
- resource = soup_uri_get_query (uri) == NULL ? g_strdup (soup_uri_get_path (uri))
- : g_strconcat (soup_uri_get_path (uri),
- "?",
- soup_uri_get_query (uri),
- NULL);
+ resource = !soup_uri_get_query (uri) ? g_strdup (soup_uri_get_path (uri))
+ : g_strconcat (soup_uri_get_path (uri),
+ "?",
+ soup_uri_get_query (uri),
+ NULL);
- if (options != NULL && options->nonce != NULL) {
+ if (options && options->nonce) {
nonce = g_strdup (options->nonce);
} else {
- nonce = g_malloc0 (NONCE_LEN + 1);
- ephy_sync_crypto_random_hex_gen (NULL, NONCE_LEN, (guint8 *)nonce);
+ bytes = g_malloc (NONCE_LEN / 2);
+ ephy_sync_crypto_random_bytes_gen (NULL, NONCE_LEN / 2, bytes);
+ nonce = ephy_sync_crypto_encode_hex (bytes, NONCE_LEN / 2);
+ g_free (bytes);
}
- if (timestamp != NULL) {
+ if (timestamp) {
char *local_time_offset;
gint64 offset;
@@ -653,14 +1156,14 @@ ephy_sync_crypto_compute_hawk_header (const char *url,
ts = g_ascii_strtoll (timestamp, NULL, 10) + offset;
}
- if (hash == NULL && payload != NULL) {
+ if (!hash && payload) {
const char *content_type = options ? options->content_type : "text/plain";
- /* Calculate the hash for the given payload. */
+ /* Calculate hash for the given payload. */
hash = ephy_sync_crypto_calculate_payload_hash (payload, content_type);
}
- /* Create the artifacts from the options. */
+ /* Create artifacts from options. */
artifacts = ephy_sync_crypto_hawk_artifacts_new (options ? options->app : NULL,
options ? options->dlg : NULL,
options ? options->ext : NULL,
@@ -678,16 +1181,16 @@ ephy_sync_crypto_compute_hawk_header (const char *url,
NULL);
/* Append pre-calculated payload hash if any. */
- if (artifacts->hash != NULL && strlen (artifacts->hash) > 0)
+ if (artifacts->hash && strlen (artifacts->hash) > 0)
header = ephy_sync_crypto_append_to_header (header, "hash", artifacts->hash);
/* Append the application specific data if any. */
- if (artifacts->ext != NULL && strlen (artifacts->ext) > 0) {
+ if (artifacts->ext && strlen (artifacts->ext) > 0) {
char *h_ext;
char *tmp_ext;
- tmp_ext = ephy_sync_utils_find_and_replace (artifacts->ext, "\\", "\\\\");
- h_ext = ephy_sync_utils_find_and_replace (tmp_ext, "\n", "\\n");
+ tmp_ext = ephy_sync_crypto_find_and_replace (artifacts->ext, "\\", "\\\\");
+ h_ext = ephy_sync_crypto_find_and_replace (tmp_ext, "\n", "\\n");
header = ephy_sync_crypto_append_to_header (header, "ext", h_ext);
g_free (h_ext);
@@ -699,29 +1202,32 @@ ephy_sync_crypto_compute_hawk_header (const char *url,
header = ephy_sync_crypto_append_to_header (header, "mac", mac);
/* Append the Oz application id if any. */
- if (artifacts->app != NULL) {
+ if (artifacts->app) {
header = ephy_sync_crypto_append_to_header (header, "app", artifacts->app);
/* Append the Oz delegated-by application id if any. */
- if (artifacts->dlg != NULL)
+ if (artifacts->dlg)
header = ephy_sync_crypto_append_to_header (header, "dlg", artifacts->dlg);
}
+ hawk_header = ephy_sync_crypto_hawk_header_new (header, artifacts);
+
soup_uri_free (uri);
g_free (hash);
g_free (mac);
g_free (nonce);
g_free (resource);
+ g_free (header);
- return ephy_sync_crypto_hawk_header_new (header, artifacts);
+ return hawk_header;
}
-EphySyncCryptoRSAKeyPair *
+SyncCryptoRSAKeyPair *
ephy_sync_crypto_generate_rsa_key_pair (void)
{
struct rsa_public_key public;
struct rsa_private_key private;
- int retval;
+ int success;
rsa_public_key_init (&public);
rsa_private_key_init (&private);
@@ -730,24 +1236,20 @@ ephy_sync_crypto_generate_rsa_key_pair (void)
mpz_set_ui (public.e, 65537);
/* Key sizes below 2048 are considered breakable and should not be used. */
- retval = rsa_generate_keypair (&public, &private,
- NULL, ephy_sync_crypto_random_hex_gen,
- NULL, NULL, 2048, 0);
- if (retval == 0) {
- g_warning ("Failed to generate RSA key pair");
- rsa_public_key_clear (&public);
- rsa_private_key_clear (&private);
- return NULL;
- }
+ success = rsa_generate_keypair (&public, &private,
+ NULL, ephy_sync_crypto_random_bytes_gen,
+ NULL, NULL, 2048, 0);
+ /* Given correct parameters, this never fails. */
+ g_assert (success);
return ephy_sync_crypto_rsa_key_pair_new (public, private);
}
char *
-ephy_sync_crypto_create_assertion (const char *certificate,
- const char *audience,
- guint64 duration,
- EphySyncCryptoRSAKeyPair *keypair)
+ephy_sync_crypto_create_assertion (const char *certificate,
+ const char *audience,
+ guint64 seconds,
+ SyncCryptoRSAKeyPair *rsa_key_pair)
{
mpz_t signature;
const char *header = "{\"alg\": \"RS256\"}";
@@ -755,21 +1257,22 @@ ephy_sync_crypto_create_assertion (const char *certificate,
char *body_b64;
char *header_b64;
char *to_sign;
- char *sig_b64 = NULL;
- char *assertion = NULL;
+ char *sig_b64;
+ char *assertion;
char *digest_hex;
guint8 *digest;
- guint8 *sig = NULL;
+ guint8 *sig;
guint64 expires_at;
gsize expected_size;
gsize count;
+ int success;
- g_return_val_if_fail (certificate != NULL, NULL);
- g_return_val_if_fail (audience != NULL, NULL);
- g_return_val_if_fail (keypair != NULL, NULL);
+ g_return_val_if_fail (certificate, NULL);
+ g_return_val_if_fail (audience, NULL);
+ g_return_val_if_fail (rsa_key_pair, NULL);
/* Encode the header and body to base64 url safe and join them. */
- expires_at = g_get_real_time () / 1000 + duration * 1000;
+ expires_at = g_get_real_time () / 1000 + seconds * 1000;
body = g_strdup_printf ("{\"exp\": %lu, \"aud\": \"%s\"}", expires_at, audience);
body_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)body, strlen (body), TRUE);
header_b64 = ephy_sync_crypto_base64_urlsafe_encode ((guint8 *)header, strlen (header), TRUE);
@@ -781,27 +1284,22 @@ ephy_sync_crypto_create_assertion (const char *certificate,
/* Use the provided key pair to RSA sign the message. */
mpz_init (signature);
- if (rsa_sha256_sign_digest_tr (&keypair->public, &keypair->private,
- NULL, ephy_sync_crypto_random_hex_gen,
- digest, signature) == 0) {
- g_warning ("Failed to sign the message. Giving up.");
- goto out;
- }
+ success = rsa_sha256_sign_digest_tr (&rsa_key_pair->public, &rsa_key_pair->private,
+ NULL, ephy_sync_crypto_random_bytes_gen,
+ digest, signature);
+ /* Given correct parameters, this never fails. */
+ g_assert (success);
expected_size = (mpz_sizeinbase (signature, 2) + 7) / 8;
sig = g_malloc (expected_size);
mpz_export (sig, &count, 1, sizeof (guint8), 0, 0, signature);
+ /* Given correct parameters, this never fails. */
+ g_assert (count == expected_size);
- if (count != expected_size) {
- g_warning ("Expected %lu bytes, got %lu. Giving up.", count, expected_size);
- goto out;
- }
-
- /* Finally, join certificate, header, body and signed message to create the assertion. */
+ /* Join certificate, header, body and signed message to create the assertion. */
sig_b64 = ephy_sync_crypto_base64_urlsafe_encode (sig, count, TRUE);
assertion = g_strdup_printf ("%s~%s.%s.%s", certificate, header_b64, body_b64, sig_b64);
-out:
g_free (body);
g_free (body_b64);
g_free (header_b64);
@@ -815,49 +1313,23 @@ out:
return assertion;
}
-void
-ephy_sync_crypto_random_hex_gen (void *ctx,
- gsize length,
- guint8 *dst)
-{
- FILE *fp;
- gsize num_bytes;
- guint8 *bytes;
- char *hex;
-
- g_assert (length > 0);
- num_bytes = (length + 1) / 2;
- bytes = g_malloc (num_bytes);
-
- fp = fopen ("/dev/urandom", "r");
- fread (bytes, sizeof (guint8), num_bytes, fp);
- hex = ephy_sync_crypto_encode_hex (bytes, num_bytes);
-
- for (gsize i = 0; i < length; i++)
- dst[i] = hex[i];
-
- g_free (bytes);
- g_free (hex);
- fclose (fp);
-}
-
char *
-ephy_sync_crypto_base64_urlsafe_encode (guint8 *data,
- gsize data_len,
- gboolean strip)
+ephy_sync_crypto_base64_urlsafe_encode (const guint8 *data,
+ gsize data_len,
+ gboolean strip)
{
char *base64;
char *out;
gsize start = 0;
gssize end;
- g_return_val_if_fail (data != NULL, NULL);
+ g_return_val_if_fail (data, NULL);
base64 = g_base64_encode (data, data_len);
end = strlen (base64) - 1;
/* Strip the data of any leading or trailing '=' characters. */
- if (strip == TRUE) {
+ if (strip) {
while (start < strlen (base64) && base64[start] == '=')
start++;
@@ -882,11 +1354,11 @@ ephy_sync_crypto_base64_urlsafe_decode (const char *text,
char *to_decode;
char *suffix = NULL;
- g_return_val_if_fail (text != NULL, NULL);
- g_return_val_if_fail (out_len != NULL, NULL);
+ g_return_val_if_fail (text, NULL);
+ g_return_val_if_fail (out_len, NULL);
/* Fill the text with trailing '=' characters up to the proper length. */
- if (fill == TRUE)
+ if (fill)
suffix = g_strnfill ((4 - strlen (text) % 4) % 4, '=');
to_decode = g_strconcat (text, suffix, NULL);
@@ -899,71 +1371,22 @@ ephy_sync_crypto_base64_urlsafe_decode (const char *text,
return out;
}
-guint8 *
-ephy_sync_crypto_aes_256 (EphySyncCryptoAES256Mode mode,
- const guint8 *key,
- const guint8 *data,
- gsize data_len,
- gsize *out_len)
-{
- struct aes256_ctx aes;
- gsize padded_len = data_len;
- guint8 *padded_data;
- guint8 *out;
-
- g_return_val_if_fail (key != NULL, NULL);
- g_return_val_if_fail (data != NULL, NULL);
-
- /* Since Nettle enforces the length of the data to be a multiple of
- * AES_BLOCK_SIZE, the data needs to be padded accordingly. Because any
- * data that is decrypted has to be encrypted first, crash if the length
- * is incorrect at decryption.
- */
- if (mode == AES_256_MODE_ENCRYPT)
- padded_len = data_len + (AES_BLOCK_SIZE - data_len % AES_BLOCK_SIZE);
- else if (mode == AES_256_MODE_DECRYPT)
- g_assert (data_len % AES_BLOCK_SIZE == 0);
-
- out = g_malloc0 (padded_len);
- padded_data = g_malloc0 (padded_len);
- memcpy (padded_data, data, data_len);
-
- if (mode == AES_256_MODE_ENCRYPT) {
- aes256_set_encrypt_key (&aes, key);
- aes256_encrypt (&aes, padded_len, out, padded_data);
- } else if (mode == AES_256_MODE_DECRYPT) {
- aes256_set_decrypt_key (&aes, key);
- aes256_decrypt (&aes, padded_len, out, padded_data);
- }
-
- if (out_len != NULL)
- *out_len = padded_len;
-
- g_free (padded_data);
-
- return out;
-}
-
char *
-ephy_sync_crypto_encode_hex (guint8 *data,
- gsize data_len)
+ephy_sync_crypto_encode_hex (const guint8 *data,
+ gsize data_len)
{
char *retval;
- gsize length;
-
- g_return_val_if_fail (data != NULL, NULL);
- length = data_len == 0 ? EPHY_SYNC_TOKEN_LENGTH : data_len;
- retval = g_malloc (length * 2 + 1);
+ g_return_val_if_fail (data, NULL);
- for (gsize i = 0; i < length; i++) {
+ retval = g_malloc (data_len * 2 + 1);
+ for (gsize i = 0; i < data_len; i++) {
guint8 byte = data[i];
retval[2 * i] = hex_digits[byte >> 4];
retval[2 * i + 1] = hex_digits[byte & 0xf];
}
-
- retval[length * 2] = 0;
+ retval[data_len * 2] = 0;
return retval;
}
@@ -972,14 +1395,35 @@ guint8 *
ephy_sync_crypto_decode_hex (const char *hex)
{
guint8 *retval;
- gsize hex_len = strlen (hex);
- g_return_val_if_fail (hex != NULL, NULL);
- g_return_val_if_fail (hex_len % 2 == 0, NULL);
+ g_return_val_if_fail (hex, NULL);
- retval = g_malloc (hex_len / 2);
- for (gsize i = 0, j = 0; i < hex_len; i += 2, j++)
- sscanf(hex + i, "%2hhx", retval + j);
+ retval = g_malloc (strlen (hex) / 2);
+ for (gsize i = 0, j = 0; i < strlen (hex); i += 2, j++)
+ sscanf (hex + i, "%2hhx", retval + j);
return retval;
}
+
+char *
+ephy_sync_crypto_get_random_sync_id (void)
+{
+ char *id;
+ char *base64;
+ guint8 *bytes;
+ gsize bytes_len;
+
+ /* The sync id is a base64-urlsafe string. Base64 uses 4 chars to represent 3 bytes,
+ * therefore we need ceil(len * 3 / 4) bytes to cover the requested length. */
+ bytes_len = (SYNC_ID_LEN + 3) / 4 * 3;
+ bytes = g_malloc (bytes_len);
+
+ ephy_sync_crypto_random_bytes_gen (NULL, bytes_len, bytes);
+ base64 = ephy_sync_crypto_base64_urlsafe_encode (bytes, bytes_len, FALSE);
+ id = g_strndup (base64, SYNC_ID_LEN);
+
+ g_free (base64);
+ g_free (bytes);
+
+ return id;
+}
diff --git a/src/sync/ephy-sync-crypto.h b/src/sync/ephy-sync-crypto.h
index 18368c4..f753e3d 100644
--- a/src/sync/ephy-sync-crypto.h
+++ b/src/sync/ephy-sync-crypto.h
@@ -21,17 +21,11 @@
#pragma once
#include <glib-object.h>
+#include <json-glib/json-glib.h>
#include <nettle/rsa.h>
G_BEGIN_DECLS
-#define EPHY_SYNC_TOKEN_LENGTH 32
-
-typedef enum {
- AES_256_MODE_ENCRYPT,
- AES_256_MODE_DECRYPT
-} EphySyncCryptoAES256Mode;
-
typedef struct {
char *app;
char *dlg;
@@ -42,7 +36,7 @@ typedef struct {
char *nonce;
char *payload;
char *timestamp;
-} EphySyncCryptoHawkOptions;
+} SyncCryptoHawkOptions;
typedef struct {
char *app;
@@ -55,72 +49,82 @@ typedef struct {
char *port;
char *resource;
char *ts;
-} EphySyncCryptoHawkArtifacts;
+} SyncCryptoHawkArtifacts;
typedef struct {
char *header;
- EphySyncCryptoHawkArtifacts *artifacts;
-} EphySyncCryptoHawkHeader;
+ SyncCryptoHawkArtifacts *artifacts;
+} SyncCryptoHawkHeader;
typedef struct {
struct rsa_public_key public;
struct rsa_private_key private;
-} EphySyncCryptoRSAKeyPair;
+} SyncCryptoRSAKeyPair;
+
+typedef struct {
+ char *aes_key_hex;
+ char *hmac_key_hex;
+} SyncCryptoKeyBundle;
-EphySyncCryptoHawkOptions *ephy_sync_crypto_hawk_options_new (const char *app,
- const char *dlg,
- const char *ext,
- const char *content_type,
- const char *hash,
- const char *local_time_offset,
- const char *nonce,
- const char *payload,
- const char *timestamp);
-void ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *options);
-void ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *header);
-void ephy_sync_crypto_rsa_key_pair_free (EphySyncCryptoRSAKeyPair *keypair);
-void ephy_sync_crypto_process_key_fetch_token (const char *keyFetchToken,
- guint8 **tokenID,
- guint8 **reqHMACkey,
- guint8 **respHMACkey,
- guint8 **respXORkey);
-void ephy_sync_crypto_process_session_token (const char *sessionToken,
- guint8 **tokenID,
- guint8 **reqHMACkey,
- guint8 **requestKey);
-void ephy_sync_crypto_compute_sync_keys (const char *bundle,
- guint8 *respHMACkey,
- guint8 *respXORkey,
- guint8 *unwrapBKey,
- guint8 **kA,
- guint8 **kB);
-EphySyncCryptoHawkHeader *ephy_sync_crypto_compute_hawk_header (const char *url,
- const char *method,
- const char *id,
- guint8 *key,
- gsize key_len,
- EphySyncCryptoHawkOptions *options);
-EphySyncCryptoRSAKeyPair *ephy_sync_crypto_generate_rsa_key_pair (void);
-char *ephy_sync_crypto_create_assertion (const char *certificate,
- const char *audience,
- guint64 duration,
- EphySyncCryptoRSAKeyPair *keypair);
-void ephy_sync_crypto_random_hex_gen (void *ctx,
- gsize length,
- guint8 *dst);
-char *ephy_sync_crypto_base64_urlsafe_encode (guint8 *data,
- gsize data_len,
- gboolean strip);
-guint8 *ephy_sync_crypto_base64_urlsafe_decode (const char *text,
- gsize *out_len,
- gboolean fill);
-guint8 *ephy_sync_crypto_aes_256 (EphySyncCryptoAES256Mode mode,
- const guint8 *key,
- const guint8 *data,
- gsize data_len,
- gsize *out_len);
-char *ephy_sync_crypto_encode_hex (guint8 *data,
- gsize data_len);
-guint8 *ephy_sync_crypto_decode_hex (const char *hex);
+SyncCryptoHawkOptions *ephy_sync_crypto_hawk_options_new (const char *app,
+ const char *dlg,
+ const char *ext,
+ const char *content_type,
+ const char *hash,
+ const char *local_time_offset,
+ const char *nonce,
+ const char *payload,
+ const char *timestamp);
+void ephy_sync_crypto_hawk_options_free (SyncCryptoHawkOptions *options);
+void ephy_sync_crypto_hawk_header_free (SyncCryptoHawkHeader *header);
+void ephy_sync_crypto_rsa_key_pair_free (SyncCryptoRSAKeyPair *rsa_key_pair);
+SyncCryptoKeyBundle *ephy_sync_crypto_key_bundle_from_array (JsonArray *array);
+void ephy_sync_crypto_key_bundle_free (SyncCryptoKeyBundle *bundle);
+void ephy_sync_crypto_process_key_fetch_token (const char *key_fetch_token,
+ guint8 **token_id,
+ guint8 **req_hmac_key,
+ guint8 **resp_hmac_key,
+ guint8 **resp_xor_key,
+ gsize token_len);
+void ephy_sync_crypto_process_session_token (const char *session_token,
+ guint8 **token_id,
+ guint8 **req_hmac_key,
+ guint8 **requestKey,
+ gsize token_len);
+gboolean ephy_sync_crypto_compute_sync_keys (const char *bundle_hex,
+ const guint8 *resp_hmac_key,
+ const guint8 *resp_xor_key,
+ const guint8 *unwrap_b_key,
+ guint8 **key_a,
+ guint8 **key_b,
+ gsize key_len);
+SyncCryptoKeyBundle *ephy_sync_crypto_derive_key_bundle (const guint8 *key,
+ gsize key_len);
+char *ephy_sync_crypto_generate_crypto_keys (gsize key_len);
+char *ephy_sync_crypto_decrypt_record (const char *payload,
+ SyncCryptoKeyBundle *bundle);
+char *ephy_sync_crypto_encrypt_record (const char *cleartext,
+ SyncCryptoKeyBundle *bundle);
+SyncCryptoHawkHeader *ephy_sync_crypto_compute_hawk_header (const char *url,
+ const char *method,
+ const char *id,
+ const guint8 *key,
+ gsize key_len,
+ SyncCryptoHawkOptions *options);
+SyncCryptoRSAKeyPair *ephy_sync_crypto_generate_rsa_key_pair (void);
+char *ephy_sync_crypto_create_assertion (const char *certificate,
+ const char *audience,
+ guint64 duration,
+ SyncCryptoRSAKeyPair *rsa_key_pair);
+char *ephy_sync_crypto_base64_urlsafe_encode (const guint8 *data,
+ gsize data_len,
+ gboolean strip);
+guint8 *ephy_sync_crypto_base64_urlsafe_decode (const char *text,
+ gsize *out_len,
+ gboolean fill);
+char *ephy_sync_crypto_encode_hex (const guint8 *data,
+ gsize data_len);
+guint8 *ephy_sync_crypto_decode_hex (const char *hex);
+char *ephy_sync_crypto_get_random_sync_id (void);
G_END_DECLS
diff --git a/src/sync/ephy-sync-service.c b/src/sync/ephy-sync-service.c
index 13d2eff..172088b 100644
--- a/src/sync/ephy-sync-service.c
+++ b/src/sync/ephy-sync-service.c
@@ -21,24 +21,40 @@
#include "config.h"
#include "ephy-sync-service.h"
-#include "ephy-bookmark.h"
-#include "ephy-bookmarks-manager.h"
#include "ephy-debug.h"
#include "ephy-embed-prefs.h"
#include "ephy-notification.h"
#include "ephy-settings.h"
-#include "ephy-shell.h"
#include "ephy-sync-crypto.h"
-#include "ephy-sync-secret.h"
#include <glib/gi18n.h>
#include <json-glib/json-glib.h>
+#include <libsecret/secret.h>
+#include <libsoup/soup.h>
#include <string.h>
-#define MOZILLA_TOKEN_SERVER_URL "https://token.services.mozilla.com/1.0/sync/1.5"
-#define MOZILLA_FXA_SERVER_URL "https://api.accounts.firefox.com/v1/"
-#define EPHY_BOOKMARKS_COLLECTION "ephy-bookmarks"
-#define SYNC_FREQUENCY (15 * 60) /* seconds */
+#define TOKEN_SERVER_URL "https://token.services.mozilla.com/1.0/sync/1.5"
+#define FIREFOX_ACCOUNTS_SERVER_URL "https://api.accounts.firefox.com/v1/"
+
+#define STORAGE_VERSION 5
+
+#define ACCOUNT_KEY "firefox_account"
+
+static const SecretSchema *
+ephy_sync_service_get_secret_schema (void)
+{
+ static const SecretSchema schema = {
+ "org.epiphany.SyncSecrets", SECRET_SCHEMA_NONE,
+ {
+ { ACCOUNT_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { "NULL", 0 },
+ }
+ };
+
+ return &schema;
+}
+
+#define EPHY_SYNC_SECRET_SCHEMA (ephy_sync_service_get_secret_schema ())
struct _EphySyncService {
GObject parent_instance;
@@ -46,16 +62,9 @@ struct _EphySyncService {
SoupSession *session;
guint source_id;
- char *uid;
- char *sessionToken;
- char *keyFetchToken;
- char *unwrapBKey;
- char *kA;
- char *kB;
-
- char *user_email;
- double sync_time;
- gint64 auth_at;
+ char *account;
+ GHashTable *secrets;
+ GSList *managers;
gboolean locked;
char *storage_endpoint;
@@ -64,47 +73,86 @@ struct _EphySyncService {
gint64 storage_credentials_expiry_time;
GQueue *storage_queue;
- char *certificate;
- EphySyncCryptoRSAKeyPair *keypair;
+ char *certificate;
+ SyncCryptoRSAKeyPair *rsa_key_pair;
};
G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
enum {
+ UID,
+ SESSION_TOKEN,
+ MASTER_KEY,
+ CRYPTO_KEYS,
+ LAST_SECRET
+};
+
+static const char * const secrets[LAST_SECRET] = {
+ "uid",
+ "session_token",
+ "master_key",
+ "crypto_keys"
+};
+
+enum {
STORE_FINISHED,
- LOAD_FINISHED,
+ SIGN_IN_ERROR,
+ SYNC_FREQUENCY_CHANGED,
+ SYNC_FINISHED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
typedef struct {
- EphySyncService *service;
char *endpoint;
- const char *method;
+ char *method;
char *request_body;
double modified_since;
double unmodified_since;
SoupSessionCallback callback;
gpointer user_data;
-} StorageServerRequestAsyncData;
+} StorageRequestAsyncData;
-static StorageServerRequestAsyncData *
-storage_server_request_async_data_new (EphySyncService *service,
- char *endpoint,
- const char *method,
- char *request_body,
- double modified_since,
- double unmodified_since,
- SoupSessionCallback callback,
- gpointer user_data)
+typedef struct {
+ EphySyncService *service;
+ char *email;
+ char *uid;
+ char *session_token;
+ char *unwrap_b_key;
+ char *token_id_hex;
+ guint8 *req_hmac_key;
+ guint8 *resp_hmac_key;
+ guint8 *resp_xor_key;
+} SignInAsyncData;
+
+typedef struct {
+ EphySyncService *service;
+ EphySynchronizableManager *manager;
+ gboolean is_initial;
+ gboolean is_last;
+} SyncCollectionAsyncData;
+
+typedef struct {
+ EphySyncService *service;
+ EphySynchronizableManager *manager;
+ EphySynchronizable *synchronizable;
+} SyncAsyncData;
+
+static StorageRequestAsyncData *
+storage_request_async_data_new (const char *endpoint,
+ const char *method,
+ const char *request_body,
+ double modified_since,
+ double unmodified_since,
+ SoupSessionCallback callback,
+ gpointer user_data)
{
- StorageServerRequestAsyncData *data;
+ StorageRequestAsyncData *data;
- data = g_slice_new (StorageServerRequestAsyncData);
- data->service = g_object_ref (service);
+ data = g_slice_new (StorageRequestAsyncData);
data->endpoint = g_strdup (endpoint);
- data->method = method;
+ data->method = g_strdup (method);
data->request_body = g_strdup (request_body);
data->modified_since = modified_since;
data->unmodified_since = unmodified_since;
@@ -115,53 +163,193 @@ storage_server_request_async_data_new (EphySyncService *service,
}
static void
-storage_server_request_async_data_free (StorageServerRequestAsyncData *data)
+storage_request_async_data_free (StorageRequestAsyncData *data)
{
- g_assert (data != NULL);
+ g_assert (data);
- g_object_unref (data->service);
g_free (data->endpoint);
+ g_free (data->method);
g_free (data->request_body);
- g_slice_free (StorageServerRequestAsyncData, data);
+ g_slice_free (StorageRequestAsyncData, data);
+}
+
+static SignInAsyncData *
+sign_in_async_data_new (EphySyncService *service,
+ const char *email,
+ const char *uid,
+ const char *session_token,
+ const char *unwrap_b_key,
+ const char *token_id_hex,
+ const guint8 *req_hmac_key,
+ const guint8 *resp_hmac_key,
+ const guint8 *resp_xor_key)
+{
+ SignInAsyncData *data;
+
+ data = g_slice_new (SignInAsyncData);
+ data->service = g_object_ref (service);
+ data->email = g_strdup (email);
+ data->uid = g_strdup (uid);
+ data->session_token = g_strdup (session_token);
+ data->unwrap_b_key = g_strdup (unwrap_b_key);
+ data->token_id_hex = g_strdup (token_id_hex);
+ data->req_hmac_key = g_malloc (32);
+ memcpy (data->req_hmac_key, req_hmac_key, 32);
+ data->resp_hmac_key = g_malloc (32);
+ memcpy (data->resp_hmac_key, resp_hmac_key, 32);
+ data->resp_xor_key = g_malloc (2 * 32);
+ memcpy (data->resp_xor_key, resp_xor_key, 2 * 32);
+
+ return data;
}
static void
-destroy_session_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+sign_in_async_data_free (SignInAsyncData *data)
{
- JsonParser *parser;
+ g_assert (data);
+
+ g_object_unref (data->service);
+ g_free (data->email);
+ g_free (data->uid);
+ g_free (data->session_token);
+ g_free (data->unwrap_b_key);
+ g_free (data->token_id_hex);
+ g_free (data->req_hmac_key);
+ g_free (data->resp_hmac_key);
+ g_free (data->resp_xor_key);
+ g_slice_free (SignInAsyncData, data);
+}
+
+static SyncCollectionAsyncData *
+sync_collection_async_data_new (EphySyncService *service,
+ EphySynchronizableManager *manager,
+ gboolean is_initial,
+ gboolean is_last)
+{
+ SyncCollectionAsyncData *data;
+
+ data = g_slice_new (SyncCollectionAsyncData);
+ data->service = g_object_ref (service);
+ data->manager = g_object_ref (manager);
+ data->is_initial = is_initial;
+ data->is_last = is_last;
+
+ return data;
+}
+
+static void
+sync_collection_async_data_free (SyncCollectionAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->service);
+ g_object_unref (data->manager);
+ g_slice_free (SyncCollectionAsyncData, data);
+}
+
+static SyncAsyncData *
+sync_async_data_new (EphySyncService *service,
+ EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ SyncAsyncData *data;
+
+ data = g_slice_new (SyncAsyncData);
+ data->service = g_object_ref (service);
+ data->manager = g_object_ref (manager);
+ data->synchronizable = g_object_ref (synchronizable);
+
+ return data;
+}
+
+static void
+sync_async_data_free (SyncAsyncData *data)
+{
+ g_assert (data);
+
+ g_object_unref (data->service);
+ g_object_unref (data->manager);
+ g_object_unref (data->synchronizable);
+ g_slice_free (SyncAsyncData, data);
+}
+
+static const char *
+ephy_sync_service_get_secret (EphySyncService *self,
+ const char *name)
+{
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (name);
+
+ return g_hash_table_lookup (self->secrets, name);
+}
+
+static void
+ephy_sync_service_set_secret (EphySyncService *self,
+ const char *name,
+ const char *value)
+{
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (name);
+ g_assert (value);
+
+ g_hash_table_replace (self->secrets, g_strdup (name), g_strdup (value));
+}
+
+static SyncCryptoKeyBundle *
+ephy_sync_service_get_key_bundle (EphySyncService *self,
+ const char *collection)
+{
+ SyncCryptoKeyBundle *bundle = NULL;
+ JsonNode *node;
JsonObject *json;
+ JsonObject *collections;
+ JsonArray *array;
+ GError *error = NULL;
+ const char *crypto_keys;
- if (msg->status_code == 200) {
- LOG ("Session destroyed");
- return;
- }
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (collection);
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
- json = json_node_get_object (json_parser_get_root (parser));
+ crypto_keys = ephy_sync_service_get_secret (self, secrets[CRYPTO_KEYS]);
+ node = json_from_string (crypto_keys, &error);
+ g_assert (!error);
+ json = json_node_get_object (node);
+ collections = json_object_get_object_member (json, "collections");
+ array = json_object_has_member (collections, collection) ?
+ json_object_get_array_member (collections, collection) :
+ json_object_get_array_member (json, "default");
+ bundle = ephy_sync_crypto_key_bundle_from_array (array);
- g_warning ("Failed to destroy session: errno: %ld, errmsg: %s",
- json_object_get_int_member (json, "errno"),
- json_object_get_string_member (json, "message"));
+ json_node_unref (node);
- g_object_unref (parser);
+ return bundle;
+}
+
+static void
+ephy_sync_service_clear_storage_credentials (EphySyncService *self)
+{
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+ g_clear_pointer (&self->certificate, g_free);
+ g_clear_pointer (&self->storage_endpoint, g_free);
+ g_clear_pointer (&self->storage_credentials_id, g_free);
+ g_clear_pointer (&self->storage_credentials_key, g_free);
+ self->storage_credentials_expiry_time = 0;
}
static gboolean
ephy_sync_service_storage_credentials_is_expired (EphySyncService *self)
{
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), TRUE);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- if (self->storage_credentials_id == NULL || self->storage_credentials_key == NULL)
+ if (!self->storage_credentials_id || !self->storage_credentials_key)
return TRUE;
if (self->storage_credentials_expiry_time == 0)
return TRUE;
/* Consider a 60 seconds safety interval. */
- return self->storage_credentials_expiry_time < ephy_sync_utils_current_time_seconds () - 60;
+ return self->storage_credentials_expiry_time < g_get_real_time () / 1000000 - 60;
}
static void
@@ -169,134 +357,138 @@ ephy_sync_service_fxa_hawk_post_async (EphySyncService *self,
const char *endpoint,
const char *id,
guint8 *key,
- gsize key_length,
+ gsize key_len,
char *request_body,
SoupSessionCallback callback,
gpointer user_data)
{
- EphySyncCryptoHawkOptions *hoptions;
- EphySyncCryptoHawkHeader *hheader;
+ SyncCryptoHawkOptions *hawk_options;
+ SyncCryptoHawkHeader *hawk_header;
SoupMessage *msg;
char *url;
const char *content_type = "application/json";
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (endpoint != NULL);
- g_return_if_fail (id != NULL);
- g_return_if_fail (key != NULL);
- g_return_if_fail (request_body != NULL);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (endpoint);
+ g_assert (id);
+ g_assert (key);
+ g_assert (request_body);
- url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
+ url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_SERVER_URL, endpoint);
msg = soup_message_new (SOUP_METHOD_POST, url);
soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
request_body, strlen (request_body));
- hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
- NULL, NULL, NULL, request_body, NULL);
- hheader = ephy_sync_crypto_compute_hawk_header (url, "POST", id, key, key_length, hoptions);
- soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
- soup_message_headers_append (msg->request_headers, "content-type", content_type);
+ hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+ content_type,
+ NULL, NULL, NULL,
+ request_body,
+ NULL);
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, "POST", id,
+ key, key_len,
+ hawk_options);
+ soup_message_headers_append (msg->request_headers,
+ "authorization", hawk_header->header);
+ soup_message_headers_append (msg->request_headers,
+ "content-type", content_type);
soup_session_queue_message (self->session, msg, callback, user_data);
g_free (url);
- ephy_sync_crypto_hawk_options_free (hoptions);
- ephy_sync_crypto_hawk_header_free (hheader);
+ ephy_sync_crypto_hawk_options_free (hawk_options);
+ ephy_sync_crypto_hawk_header_free (hawk_header);
}
-static guint
-ephy_sync_service_fxa_hawk_get_sync (EphySyncService *self,
- const char *endpoint,
- const char *id,
- guint8 *key,
- gsize key_length,
- JsonNode **node)
+static void
+ephy_sync_service_fxa_hawk_get_async (EphySyncService *self,
+ const char *endpoint,
+ const char *id,
+ guint8 *key,
+ gsize key_len,
+ SoupSessionCallback callback,
+ gpointer user_data)
{
- EphySyncCryptoHawkHeader *hheader;
+ SyncCryptoHawkHeader *hawk_header;
SoupMessage *msg;
- JsonParser *parser;
char *url;
- guint retval;
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), 0);
- g_return_val_if_fail (endpoint != NULL, 0);
- g_return_val_if_fail (id != NULL, 0);
- g_return_val_if_fail (key != NULL, 0);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (endpoint);
+ g_assert (id);
+ g_assert (key);
- url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
+ url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_SERVER_URL, endpoint);
msg = soup_message_new (SOUP_METHOD_GET, url);
- hheader = ephy_sync_crypto_compute_hawk_header (url, "GET", id, key, key_length, NULL);
- soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
- soup_session_send_message (self->session, msg);
-
- if (node != NULL) {
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
- *node = json_node_copy (json_parser_get_root (parser));
- g_object_unref (parser);
- }
-
- retval = msg->status_code;
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, "GET", id,
+ key, key_len,
+ NULL);
+ soup_message_headers_append (msg->request_headers, "authorization", hawk_header->header);
+ soup_session_queue_message (self->session, msg, callback, user_data);
g_free (url);
- g_object_unref (msg);
- ephy_sync_crypto_hawk_header_free (hheader);
-
- return retval;
+ ephy_sync_crypto_hawk_header_free (hawk_header);
}
static void
-ephy_sync_service_send_storage_request (EphySyncService *self,
- StorageServerRequestAsyncData *data)
+ephy_sync_service_send_storage_request (EphySyncService *self,
+ StorageRequestAsyncData *data)
{
- EphySyncCryptoHawkOptions *hoptions = NULL;
- EphySyncCryptoHawkHeader *hheader;
+ SyncCryptoHawkOptions *hawk_options = NULL;
+ SyncCryptoHawkHeader *hawk_header;
SoupMessage *msg;
char *url;
char *if_modified_since = NULL;
char *if_unmodified_since = NULL;
const char *content_type = "application/json";
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (data != NULL);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (data);
url = g_strdup_printf ("%s/%s", self->storage_endpoint, data->endpoint);
msg = soup_message_new (data->method, url);
- if (data->request_body != NULL) {
- hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
- NULL, NULL, NULL, data->request_body, NULL);
+ if (data->request_body) {
+ hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+ content_type,
+ NULL, NULL, NULL,
+ data->request_body,
+ NULL);
soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
data->request_body, strlen (data->request_body));
}
- if (g_strcmp0 (data->method, SOUP_METHOD_POST) == 0)
- soup_message_headers_append (msg->request_headers, "content-type", content_type);
+ if (!g_strcmp0 (data->method, SOUP_METHOD_POST))
+ soup_message_headers_append (msg->request_headers,
+ "content-type", content_type);
if (data->modified_since >= 0) {
if_modified_since = g_strdup_printf ("%.2lf", data->modified_since);
- soup_message_headers_append (msg->request_headers, "X-If-Modified-Since", if_modified_since);
+ soup_message_headers_append (msg->request_headers,
+ "X-If-Modified-Since", if_modified_since);
}
if (data->unmodified_since >= 0) {
if_unmodified_since = g_strdup_printf ("%.2lf", data->unmodified_since);
- soup_message_headers_append (msg->request_headers, "X-If-Unmodified-Since", if_unmodified_since);
+ soup_message_headers_append (msg->request_headers,
+ "X-If-Unmodified-Since", if_unmodified_since);
}
- hheader = ephy_sync_crypto_compute_hawk_header (url, data->method, self->storage_credentials_id,
- (guint8 *)self->storage_credentials_key,
- strlen (self->storage_credentials_key),
- hoptions);
- soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, data->method,
+ self->storage_credentials_id,
+ (guint8 *)self->storage_credentials_key,
+ strlen (self->storage_credentials_key),
+ hawk_options);
+ soup_message_headers_append (msg->request_headers,
+ "authorization", hawk_header->header);
soup_session_queue_message (self->session, msg, data->callback, data->user_data);
- if (hoptions != NULL)
- ephy_sync_crypto_hawk_options_free (hoptions);
+ if (hawk_options)
+ ephy_sync_crypto_hawk_options_free (hawk_options);
g_free (url);
g_free (if_modified_since);
g_free (if_unmodified_since);
- ephy_sync_crypto_hawk_header_free (hheader);
- storage_server_request_async_data_free (data);
+ ephy_sync_crypto_hawk_header_free (hawk_header);
+ storage_request_async_data_free (data);
}
static gboolean
@@ -306,141 +498,262 @@ ephy_sync_service_certificate_is_valid (EphySyncService *self,
JsonParser *parser;
JsonObject *json;
JsonObject *principal;
+ GError *error = NULL;
SoupURI *uri;
char **pieces;
char *header;
char *payload;
- char *uid_email = NULL;
+ char *expected = NULL;
const char *alg;
const char *email;
gsize len;
gboolean retval = FALSE;
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
- g_return_val_if_fail (certificate != NULL, FALSE);
-
- /* Check if the certificate is something that we were expecting, i.e.
- * if the algorithm and email fields match the expected values. */
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (ephy_sync_service_get_secret (self, secrets[UID]));
+ g_assert (certificate);
- uri = soup_uri_new (MOZILLA_FXA_SERVER_URL);
pieces = g_strsplit (certificate, ".", 0);
header = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[0], &len, TRUE);
payload = (char *)ephy_sync_crypto_base64_urlsafe_decode (pieces[1], &len, TRUE);
-
parser = json_parser_new ();
- json_parser_load_from_data (parser, header, -1, NULL);
+
+ json_parser_load_from_data (parser, header, -1, &error);
+ if (error) {
+ g_warning ("Header is not a valid JSON: %s", error->message);
+ goto out;
+ }
json = json_node_get_object (json_parser_get_root (parser));
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out;
+ }
alg = json_object_get_string_member (json, "alg");
-
- if (g_strcmp0 (alg, "RS256") != 0) {
- g_warning ("Expected algorithm RS256, found %s. Giving up.", alg);
+ if (!alg) {
+ g_warning ("JSON object has missing or invalid 'alg' member");
+ goto out;
+ }
+ if (g_strcmp0 (alg, "RS256")) {
+ g_warning ("Expected algorithm RS256, found %s", alg);
+ goto out;
+ }
+ json_parser_load_from_data (parser, payload, -1, &error);
+ if (error) {
+ g_warning ("Payload is not a valid JSON: %s", error->message);
goto out;
}
-
- json_parser_load_from_data (parser, payload, -1, NULL);
json = json_node_get_object (json_parser_get_root (parser));
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out;
+ }
principal = json_object_get_object_member (json, "principal");
+ if (!principal) {
+ g_warning ("JSON object has missing or invalid 'principal' member");
+ goto out;
+ }
email = json_object_get_string_member (principal, "email");
- uid_email = g_strdup_printf ("%s@%s", self->uid, soup_uri_get_host (uri));
-
- if (g_strcmp0 (uid_email, email) != 0) {
- g_warning ("Expected email %s, found %s. Giving up.", uid_email, email);
+ if (!email) {
+ g_warning ("JSON object has missing or invalid 'email' member");
goto out;
}
-
- self->auth_at = json_object_get_int_member (json, "fxa-lastAuthAt");
- retval = TRUE;
+ uri = soup_uri_new (FIREFOX_ACCOUNTS_SERVER_URL);
+ expected = g_strdup_printf ("%s@%s",
+ ephy_sync_service_get_secret (self, secrets[UID]),
+ soup_uri_get_host (uri));
+ retval = g_strcmp0 (email, expected) == 0;
out:
- g_free (header);
+ g_free (expected);
+ g_object_unref (parser);
g_free (payload);
- g_free (uid_email);
+ g_free (header);
g_strfreev (pieces);
- g_object_unref (parser);
- soup_uri_free (uri);
+ if (uri)
+ soup_uri_free (uri);
+ if (error)
+ g_error_free (error);
return retval;
}
static void
-obtain_storage_credentials_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+destroy_session_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- StorageServerRequestAsyncData *data;
- EphySyncService *service;
- JsonParser *parser;
- JsonObject *json;
- JsonObject *errors;
- JsonArray *array;
+ if (msg->status_code != 200)
+ g_warning ("Failed to destroy session. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ else
+ LOG ("Successfully destroyed session");
+}
- data = (StorageServerRequestAsyncData *)user_data;
- service = EPHY_SYNC_SERVICE (data->service);
+static void
+ephy_sync_service_destroy_session (EphySyncService *self,
+ const char *session_token)
+{
+ SyncCryptoHawkOptions *hawk_options;
+ SyncCryptoHawkHeader *hawk_header;
+ SoupMessage *msg;
+ guint8 *token_id;
+ guint8 *req_hmac_key;
+ guint8 *request_key;
+ char *token_id_hex;
+ char *url;
+ const char *content_type = "application/json";
+ const char *request_body = "{}";
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
- json = json_node_get_object (json_parser_get_root (parser));
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ if (!session_token)
+ session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
+ g_assert (session_token);
- if (msg->status_code == 200) {
- service->storage_endpoint = g_strdup (json_object_get_string_member (json, "api_endpoint"));
- service->storage_credentials_id = g_strdup (json_object_get_string_member (json, "id"));
- service->storage_credentials_key = g_strdup (json_object_get_string_member (json, "key"));
- service->storage_credentials_expiry_time = json_object_get_int_member (json, "duration") +
- ephy_sync_utils_current_time_seconds ();
- ephy_sync_service_send_storage_request (service, data);
- } else if (msg->status_code == 401) {
- array = json_object_get_array_member (json, "errors");
- errors = json_node_get_object (json_array_get_element (array, 0));
- g_warning ("Failed to talk to the Token Server: %s: %s",
- json_object_get_string_member (json, "status"),
- json_object_get_string_member (errors, "description"));
- storage_server_request_async_data_free (data);
- service->locked = FALSE;
- } else {
- g_warning ("Failed to talk to the Token Server, status code %u. "
- "See https://docs.services.mozilla.com/token/apis.html#error-responses",
- msg->status_code);
- storage_server_request_async_data_free (data);
- service->locked = FALSE;
+ url = g_strdup_printf ("%ssession/destroy", FIREFOX_ACCOUNTS_SERVER_URL);
+ ephy_sync_crypto_process_session_token (session_token, &token_id,
+ &req_hmac_key, &request_key, 32);
+ token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+ msg = soup_message_new (SOUP_METHOD_POST, url);
+ soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
+ request_body, strlen (request_body));
+ hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+ content_type,
+ NULL, NULL, NULL,
+ request_body,
+ NULL);
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, "POST", token_id_hex,
+ req_hmac_key, 32,
+ hawk_options);
+ soup_message_headers_append (msg->request_headers,
+ "authorization", hawk_header->header);
+ soup_message_headers_append (msg->request_headers,
+ "content-type", content_type);
+ soup_session_queue_message (self->session, msg, destroy_session_cb, NULL);
+
+ g_free (token_id_hex);
+ g_free (token_id);
+ g_free (req_hmac_key);
+ g_free (request_key);
+ g_free (url);
+ ephy_sync_crypto_hawk_options_free (hawk_options);
+ ephy_sync_crypto_hawk_header_free (hawk_header);
+}
+
+static void
+obtain_storage_credentials_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ const char *api_endpoint;
+ const char *id;
+ const char *key;
+ int duration;
+
+ if (msg->status_code != 200) {
+ g_warning ("Failed to obtain storage credentials. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ goto out;
+ }
+ node = json_from_string (msg->response_body->data, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out;
+ }
+ api_endpoint = json_object_get_string_member (json, "api_endpoint");
+ id = json_object_get_string_member (json, "id");
+ key = json_object_get_string_member (json, "key");
+ duration = json_object_get_int_member (json, "duration");
+ if (!api_endpoint || !id || !key || !duration) {
+ g_warning ("JSON object has missing or invalid members");
+ goto out;
}
- g_object_unref (parser);
+ self->storage_endpoint = g_strdup (api_endpoint);
+ self->storage_credentials_id = g_strdup (id);
+ self->storage_credentials_key = g_strdup (key);
+ self->storage_credentials_expiry_time = duration + g_get_real_time () / 1000000;
+
+ while (!g_queue_is_empty (self->storage_queue))
+ ephy_sync_service_send_storage_request (self, g_queue_pop_head (self->storage_queue));
+
+out:
+ self->locked = FALSE;
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+}
+
+static char *
+get_audience (const char *url)
+{
+ SoupURI *uri;
+ const char *scheme;
+ const char *host;
+ char *audience;
+ char *port;
+
+ g_assert (url);
+
+ uri = soup_uri_new (url);
+ scheme = soup_uri_get_scheme (uri);
+ host = soup_uri_get_host (uri);
+ /* soup_uri_get_port returns the default port if URI does not have any port. */
+ port = g_strdup_printf (":%u", soup_uri_get_port (uri));
+
+ if (g_strstr_len (url, -1, port))
+ audience = g_strdup_printf ("%s://%s%s", scheme, host, port);
+ else
+ audience = g_strdup_printf ("%s://%s", scheme, host);
+
+ g_free (port);
+ soup_uri_free (uri);
+
+ return audience;
}
static void
-ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
- gpointer user_data)
+ephy_sync_service_obtain_storage_credentials (EphySyncService *self)
{
SoupMessage *msg;
- guint8 *kB;
- char *hashed_kB;
+ guint8 *key_b;
+ char *hashed_key_b;
char *client_state;
char *audience;
char *assertion;
char *authorization;
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (self->certificate != NULL);
- g_return_if_fail (self->keypair != NULL);
-
- audience = ephy_sync_utils_make_audience (MOZILLA_TOKEN_SERVER_URL);
- assertion = ephy_sync_crypto_create_assertion (self->certificate, audience, 300, self->keypair);
- g_return_if_fail (assertion != NULL);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (self->certificate);
+ g_assert (self->rsa_key_pair);
- kB = ephy_sync_crypto_decode_hex (self->kB);
- hashed_kB = g_compute_checksum_for_data (G_CHECKSUM_SHA256, kB, EPHY_SYNC_TOKEN_LENGTH);
- client_state = g_strndup (hashed_kB, EPHY_SYNC_TOKEN_LENGTH);
+ audience = get_audience (TOKEN_SERVER_URL);
+ assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
+ 300, self->rsa_key_pair);
+ key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+ hashed_key_b = g_compute_checksum_for_data (G_CHECKSUM_SHA256, key_b, 32);
+ client_state = g_strndup (hashed_key_b, 32);
authorization = g_strdup_printf ("BrowserID %s", assertion);
- msg = soup_message_new (SOUP_METHOD_GET, MOZILLA_TOKEN_SERVER_URL);
+ msg = soup_message_new (SOUP_METHOD_GET, TOKEN_SERVER_URL);
/* We need to add the X-Client-State header so that the Token Server will
* recognize accounts that were previously used to sync Firefox data too. */
soup_message_headers_append (msg->request_headers, "X-Client-State", client_state);
soup_message_headers_append (msg->request_headers, "authorization", authorization);
- soup_session_queue_message (self->session, msg, obtain_storage_credentials_response_cb, user_data);
+ soup_session_queue_message (self->session, msg, obtain_storage_credentials_cb, self);
- g_free (kB);
- g_free (hashed_kB);
+ g_free (key_b);
+ g_free (hashed_key_b);
g_free (client_state);
g_free (audience);
g_free (assertion);
@@ -448,1009 +761,1445 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self,
}
static void
-obtain_signed_certificate_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+obtain_signed_certificate_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- StorageServerRequestAsyncData *data;
- EphySyncService *service;
- JsonParser *parser;
- JsonObject *json;
- const char *certificate;
-
- data = (StorageServerRequestAsyncData *)user_data;
- service = EPHY_SYNC_SERVICE (data->service);
+ EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ const char *suggestion = NULL;
+ const char *message = NULL;
+ const char *certificate = NULL;
+
+ node = json_from_string (msg->response_body->data, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out_error;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out_error;
+ }
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
- json = json_node_get_object (json_parser_get_root (parser));
+ if (msg->status_code == 200) {
+ certificate = json_object_get_string_member (json, "cert");
+ if (!certificate) {
+ g_warning ("JSON object has missing or invalid 'cert' member");
+ goto out_error;
+ }
+ if (!ephy_sync_service_certificate_is_valid (self, certificate)) {
+ g_warning ("Invalid certificate");
+ ephy_sync_crypto_rsa_key_pair_free (self->rsa_key_pair);
+ goto out_error;
+ }
+ self->certificate = g_strdup (certificate);
+ ephy_sync_service_obtain_storage_credentials (self);
+ goto out_no_error;
+ }
/* Since a new Firefox Account password implies new tokens, this will fail
* with an error code 110 (Invalid authentication token in request signature)
* if the user has changed his password since the last time he signed in.
* When this happens, notify the user to sign in with the new password. */
- if (msg->status_code == 401 && json_object_get_int_member (json, "errno") == 110) {
- char *error = g_strdup_printf (_("The password of your Firefox account %s "
- "seems to have been changed."),
- ephy_sync_service_get_user_email (service));
- const char *suggestion = _("Please visit Preferences and sign in with "
- "the new password to continue the sync process.");
-
- ephy_notification_show (ephy_notification_new (error, suggestion));
-
- storage_server_request_async_data_free (data);
- g_free (error);
- service->locked = FALSE;
- goto out;
- }
-
- if (msg->status_code != 200) {
- g_warning ("FxA server errno: %ld, errmsg: %s",
- json_object_get_int_member (json, "errno"),
- json_object_get_string_member (json, "message"));
- storage_server_request_async_data_free (data);
- service->locked = FALSE;
- goto out;
- }
-
- certificate = json_object_get_string_member (json, "cert");
-
- if (ephy_sync_service_certificate_is_valid (service, certificate) == FALSE) {
- ephy_sync_crypto_rsa_key_pair_free (service->keypair);
- storage_server_request_async_data_free (data);
- service->locked = FALSE;
- goto out;
+ if (json_object_get_int_member (json, "errno") == 110) {
+ message = _("The password of your Firefox account seems to have been changed.");
+ suggestion = _("Please visit Preferences and sign in with the new password to continue syncing.");
+ ephy_sync_service_do_sign_out (self);
}
- service->certificate = g_strdup (certificate);
-
- /* See the comment in ephy_sync_service_send_storage_message(). */
- ephy_sync_service_obtain_storage_credentials (service, user_data);
-
-out:
- g_object_unref (parser);
+ g_warning ("Failed to sign certificate. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+
+out_error:
+ message = message ? message : _("Something went wrong while syncing.");
+ suggestion = suggestion ? suggestion : _("Please visit Preferences and sign in again.");
+ ephy_notification_show (ephy_notification_new (message, suggestion));
+ self->locked = FALSE;
+out_no_error:
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
}
static void
-ephy_sync_service_obtain_signed_certificate (EphySyncService *self,
- gpointer user_data)
-{
- guint8 *tokenID;
- guint8 *reqHMACkey;
- guint8 *requestKey;
- char *tokenID_hex;
- char *public_key_json;
+ephy_sync_service_obtain_signed_certificate (EphySyncService *self)
+{
+ JsonNode *node;
+ JsonObject *object_key;
+ JsonObject *object_body;
+ guint8 *token_id;
+ guint8 *req_hmac_key;
+ guint8 *request_key;
+ const char *session_token;
+ char *token_id_hex;
char *request_body;
char *n;
char *e;
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (self->sessionToken != NULL);
-
- /* Generate a new RSA key pair that is going to be used to sign the new certificate. */
- if (self->keypair != NULL)
- ephy_sync_crypto_rsa_key_pair_free (self->keypair);
-
- self->keypair = ephy_sync_crypto_generate_rsa_key_pair ();
- g_return_if_fail (self->keypair != NULL);
-
- /* Derive tokenID, reqHMACkey and requestKey from the sessionToken. */
- ephy_sync_crypto_process_session_token (self->sessionToken, &tokenID, &reqHMACkey, &requestKey);
- tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
-
- n = mpz_get_str (NULL, 10, self->keypair->public.n);
- e = mpz_get_str (NULL, 10, self->keypair->public.e);
- public_key_json = ephy_sync_utils_build_json_string ("algorithm", "RS", "n", n, "e", e, NULL);
- /* Duration is the lifetime of the certificate in milliseconds. The FxA server
- * limits the duration to 24 hours. For our purposes, a duration of 30 minutes
- * will suffice. */
- request_body = g_strdup_printf ("{\"publicKey\": %s, \"duration\": %d}",
- public_key_json, 30 * 60 * 1000);
- ephy_sync_service_fxa_hawk_post_async (self, "certificate/sign", tokenID_hex,
- reqHMACkey, EPHY_SYNC_TOKEN_LENGTH, request_body,
- obtain_signed_certificate_response_cb, user_data);
-
- g_free (tokenID);
- g_free (reqHMACkey);
- g_free (requestKey);
- g_free (tokenID_hex);
- g_free (public_key_json);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+ /* Generate a new RSA key pair to sign the new certificate. */
+ if (self->rsa_key_pair)
+ ephy_sync_crypto_rsa_key_pair_free (self->rsa_key_pair);
+ self->rsa_key_pair = ephy_sync_crypto_generate_rsa_key_pair ();
+
+ /* Derive tokenID, reqHMACkey and requestKey from sessionToken. */
+ session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
+ ephy_sync_crypto_process_session_token (session_token, &token_id,
+ &req_hmac_key, &request_key, 32);
+ token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+ n = mpz_get_str (NULL, 10, self->rsa_key_pair->public.n);
+ e = mpz_get_str (NULL, 10, self->rsa_key_pair->public.e);
+ node = json_node_new (JSON_NODE_OBJECT);
+ object_body = json_object_new ();
+ /* Milliseconds, limited to 24 hours. */
+ json_object_set_int_member (object_body, "duration", 1 * 60 * 60 * 1000);
+ object_key = json_object_new ();
+ json_object_set_string_member (object_key, "algorithm", "RS");
+ json_object_set_string_member (object_key, "n", n);
+ json_object_set_string_member (object_key, "e", e);
+ json_object_set_object_member (object_body, "publicKey", object_key);
+ json_node_set_object (node, object_body);
+ request_body = json_to_string (node, FALSE);
+ ephy_sync_service_fxa_hawk_post_async (self, "certificate/sign", token_id_hex,
+ req_hmac_key, 32, request_body,
+ obtain_signed_certificate_cb, self);
+
g_free (request_body);
- g_free (n);
+ json_object_unref (object_body);
+ json_node_unref (node);
g_free (e);
+ g_free (n);
+ g_free (token_id_hex);
+ g_free (request_key);
+ g_free (req_hmac_key);
+ g_free (token_id);
}
static void
-ephy_sync_service_issue_storage_request (EphySyncService *self,
- StorageServerRequestAsyncData *data)
+ephy_sync_service_queue_storage_request (EphySyncService *self,
+ const char *endpoint,
+ const char *method,
+ const char *request_body,
+ double modified_since,
+ double unmodified_since,
+ SoupSessionCallback callback,
+ gpointer user_data)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (data != NULL);
-
- if (ephy_sync_service_storage_credentials_is_expired (self) == TRUE) {
- ephy_sync_service_clear_storage_credentials (self);
-
- /* The only purpose of certificates is to obtain a signed BrowserID that is
- * needed to talk to the Token Server. From the Token Server we will obtain
- * the credentials needed to talk to the Storage Server. Since both
- * ephy_sync_service_obtain_signed_certificate() and
- * ephy_sync_service_obtain_storage_credentials() complete asynchronously,
- * we need to entrust them the task of sending the request to the Storage
- * Server. */
- ephy_sync_service_obtain_signed_certificate (self, data);
- } else {
+ StorageRequestAsyncData *data;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (endpoint);
+ g_assert (method);
+
+ data = storage_request_async_data_new (endpoint, method, request_body,
+ modified_since, unmodified_since,
+ callback, user_data);
+
+ /* If the storage credentials are valid, then directly send the request.
+ * Otherwise, the request will remain queued and scheduled to be sent when
+ * the new credentials are obtained. */
+ if (!ephy_sync_service_storage_credentials_is_expired (self)) {
ephy_sync_service_send_storage_request (self, data);
+ } else {
+ g_queue_push_tail (self->storage_queue, data);
+ if (!self->locked) {
+ /* Mark as locked so other requests won't lead to conflicts while obtaining
+ * new storage credentials. */
+ self->locked = TRUE;
+ ephy_sync_service_clear_storage_credentials (self);
+ ephy_sync_service_obtain_signed_certificate (self);
+ }
}
}
static void
-ephy_sync_service_finalize (GObject *object)
+delete_synchronizable_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- EphySyncService *self = EPHY_SYNC_SERVICE (object);
-
- if (self->keypair != NULL)
- ephy_sync_crypto_rsa_key_pair_free (self->keypair);
-
- g_queue_free_full (self->storage_queue, (GDestroyNotify) storage_server_request_async_data_free);
-
- G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
+ if (msg->status_code == 200) {
+ LOG ("Successfully deleted from server");
+ } else {
+ g_warning ("Failed to delete object. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ }
}
static void
-ephy_sync_service_dispose (GObject *object)
+ephy_sync_service_delete_synchronizable (EphySyncService *self,
+ EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
{
- EphySyncService *self = EPHY_SYNC_SERVICE (object);
+ JsonNode *node;
+ JsonObject *object;
+ SyncCryptoKeyBundle *bundle;
+ char *endpoint;
+ char *record;
+ char *payload;
+ char *body;
+ const char *collection;
+ const char *id;
- ephy_sync_service_stop_periodical_sync (self);
- ephy_sync_service_clear_storage_credentials (self);
- ephy_sync_service_clear_tokens (self);
- g_clear_pointer (&self->user_email, g_free);
- g_clear_object (&self->session);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ 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);
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ object = json_object_new ();
+ json_node_set_object (node, object);
+ json_object_set_string_member (object, "id", id);
+ json_object_set_boolean_member (object, "deleted", TRUE);
+ record = json_to_string (node, FALSE);
+ bundle = ephy_sync_service_get_key_bundle (self, collection);
+ payload = ephy_sync_crypto_encrypt_record (record, bundle);
+ json_object_remove_member (object, "type");
+ json_object_remove_member (object, "deleted");
+ json_object_set_string_member (object, "payload", payload);
+ body = json_to_string (node, FALSE);
+
+ LOG ("Deleting object with id %s from collection %s...", id, collection);
+ ephy_sync_service_queue_storage_request (self, endpoint,
+ SOUP_METHOD_PUT, body, -1, -1,
+ delete_synchronizable_cb, NULL);
- G_OBJECT_CLASS (ephy_sync_service_parent_class)->dispose (object);
+ g_free (endpoint);
+ g_free (record);
+ g_free (payload);
+ g_free (body);
+ json_object_unref (object);
+ json_node_unref (node);
+ ephy_sync_crypto_key_bundle_free (bundle);
}
static void
-ephy_sync_service_class_init (EphySyncServiceClass *klass)
+download_synchronizable_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ SyncAsyncData *data = (SyncAsyncData *)user_data;
+ EphySynchronizable *synchronizable;
+ SyncCryptoKeyBundle *bundle = NULL;
+ JsonNode *node = NULL;
+ GError *error = NULL;
+ GType type;
+ const char *collection;
+ gboolean is_deleted;
- object_class->finalize = ephy_sync_service_finalize;
- object_class->dispose = ephy_sync_service_dispose;
+ if (msg->status_code != 200) {
+ g_warning ("Failed to download object. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ goto out;
+ }
+ node = json_from_string (msg->response_body->data, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON");
+ goto out;
+ }
+ type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
+ collection = ephy_synchronizable_manager_get_collection_name (data->manager);
+ bundle = ephy_sync_service_get_key_bundle (data->service, collection);
+ synchronizable = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (node, type, bundle, &is_deleted));
+ if (!synchronizable) {
+ g_warning ("Failed to create synchronizable object from BSO");
+ goto out;
+ }
- signals[STORE_FINISHED] =
- g_signal_new ("sync-tokens-store-finished",
- EPHY_TYPE_SYNC_SERVICE,
- G_SIGNAL_RUN_LAST,
- 0, NULL, NULL, NULL,
- G_TYPE_NONE, 1,
- G_TYPE_ERROR);
+ /* Delete the local object and add the remote one if it is not marked as deleted. */
+ ephy_synchronizable_manager_remove (data->manager, data->synchronizable);
+ if (!is_deleted) {
+ ephy_synchronizable_manager_add (data->manager, synchronizable);
+ LOG ("Successfully downloaded from server");
+ } else {
+ LOG ("The newer version was a deleted object");
+ }
- signals[LOAD_FINISHED] =
- g_signal_new ("sync-tokens-load-finished",
- EPHY_TYPE_SYNC_SERVICE,
- G_SIGNAL_RUN_LAST,
- 0, NULL, NULL, NULL,
- G_TYPE_NONE, 1,
- G_TYPE_ERROR);
+ g_object_unref (synchronizable);
+out:
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+ if (bundle)
+ ephy_sync_crypto_key_bundle_free (bundle);
+ sync_async_data_free (data);
}
static void
-ephy_sync_service_init (EphySyncService *self)
+ephy_sync_service_download_synchronizable (EphySyncService *self,
+ EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
{
- char *email;
- const char *user_agent;
- WebKitSettings *settings;
+ SyncAsyncData *data;
+ char *endpoint;
+ const char *collection;
+ const char *id;
- self->session = soup_session_new ();
- self->storage_queue = g_queue_new ();
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+ g_assert (ephy_sync_service_is_signed_in (self));
- settings = ephy_embed_prefs_get_settings ();
- user_agent = webkit_settings_get_user_agent (settings);
- g_object_set (self->session, "user-agent", user_agent, NULL);
+ id = ephy_synchronizable_get_id (synchronizable);
+ collection = ephy_synchronizable_manager_get_collection_name (manager);
+ endpoint = g_strdup_printf ("storage/%s/%s", collection, id);
+ data = sync_async_data_new (self, manager, synchronizable);
- email = g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER);
+ LOG ("Downloading object with id %s...", id);
+ ephy_sync_service_queue_storage_request (self, endpoint,
+ SOUP_METHOD_GET, NULL, -1, -1,
+ download_synchronizable_cb, data);
- if (g_strcmp0 (email, "") != 0) {
- ephy_sync_service_set_user_email (self, email);
- ephy_sync_secret_load_tokens (self);
+ g_free (endpoint);
+}
+
+static void
+upload_synchronizable_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ SyncAsyncData *data = (SyncAsyncData *)user_data;
+ double time_modified;
+
+ /* Code 412 means that there is a more recent version of the object
+ * on the server. Download it. */
+ if (msg->status_code == 412) {
+ LOG ("Found a newer version of the object on the server, downloading it...");
+ ephy_sync_service_download_synchronizable (data->service, data->manager, data->synchronizable);
+ } else if (msg->status_code == 200) {
+ LOG ("Successfully uploaded to server");
+ time_modified = g_ascii_strtod (msg->response_body->data, NULL);
+ /* FIXME: Make sure the synchronizable manager commits this change to file/database. */
+ ephy_synchronizable_set_server_time_modified (data->synchronizable, time_modified);
+ } else {
+ g_warning ("Failed to upload object. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
}
- g_free (email);
+ sync_async_data_free (data);
}
-EphySyncService *
-ephy_sync_service_new (void)
+static void
+ephy_sync_service_upload_synchronizable (EphySyncService *self,
+ EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
{
- return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+ SyncCryptoKeyBundle *bundle;
+ SyncAsyncData *data;
+ JsonNode *bso;
+ char *endpoint;
+ char *body;
+ const char *collection;
+ const char *id;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+ g_assert (ephy_sync_service_is_signed_in (self));
+
+ collection = ephy_synchronizable_manager_get_collection_name (manager);
+ 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);
+ data = sync_async_data_new (self, manager, synchronizable);
+ body = json_to_string (bso, FALSE);
+
+ LOG ("Uploading object with id %s...", id);
+ ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT, body, -1,
+ ephy_synchronizable_get_server_time_modified (synchronizable),
+ upload_synchronizable_cb, data);
+
+ g_free (body);
+ g_free (endpoint);
+ json_node_unref (bso);
+ ephy_sync_crypto_key_bundle_free (bundle);
}
-gboolean
-ephy_sync_service_is_signed_in (EphySyncService *self)
+static void
+sync_collection_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
+ SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
+ EphySynchronizable *remote;
+ SyncCryptoKeyBundle *bundle;
+ 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;
+ gboolean is_deleted;
+
+ collection = ephy_synchronizable_manager_get_collection_name (data->manager);
- return self->user_email != NULL;
+ 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;
+ }
+ node = json_from_string (msg->response_body->data, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out;
+ }
+ array = json_node_get_array (node);
+ if (!array) {
+ g_warning ("JSON node does not hold an array");
+ goto out;
+ }
+
+ 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++) {
+ remote = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (json_array_get_element (array, i),
+ type, bundle, &is_deleted));
+ if (!remote) {
+ g_warning ("Failed to create synchronizable object from BSO, skipping...");
+ continue;
+ }
+ if (is_deleted)
+ remotes_deleted = g_slist_prepend (remotes_deleted, remote);
+ else
+ remotes_updated = g_slist_prepend (remotes_updated, remote);
+ }
+
+ to_upload = ephy_synchronizable_manager_merge_remotes (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);
+
+ /* 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:
+ 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);
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+ sync_collection_async_data_free (data);
}
-char *
-ephy_sync_service_get_user_email (EphySyncService *self)
+static void
+ephy_sync_service_sync_collection (EphySyncService *self,
+ EphySynchronizableManager *manager,
+ gboolean is_last)
{
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+ SyncCollectionAsyncData *data;
+ const char *collection;
+ char *endpoint;
+ gboolean is_initial;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_assert (ephy_sync_service_is_signed_in (self));
- return self->user_email;
+ collection = ephy_synchronizable_manager_get_collection_name (manager);
+ is_initial = ephy_synchronizable_manager_is_initial_sync (manager);
+
+ if (is_initial) {
+ endpoint = g_strdup_printf ("storage/%s?full=true", collection);
+ } else {
+ endpoint = g_strdup_printf ("storage/%s?newer=%.2lf&full=true", collection,
+ ephy_synchronizable_manager_get_sync_time (manager));
+ }
+
+ LOG ("Syncing %s collection %s...", collection, is_initial ? "initial" : "regular");
+ data = sync_collection_async_data_new (self, manager, is_initial, is_last);
+ ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_GET,
+ NULL, -1, -1,
+ sync_collection_cb, data);
+
+ g_free (endpoint);
}
-void
-ephy_sync_service_set_user_email (EphySyncService *self,
- const char *email)
+static gboolean
+ephy_sync_service_sync (gpointer user_data)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+ guint index = 0;
+ guint num_managers;
+
+ g_assert (ephy_sync_service_is_signed_in (self));
- g_free (self->user_email);
- self->user_email = g_strdup (email);
+ if (!self->managers) {
+ g_signal_emit (self, signals[SYNC_FINISHED], 0);
+ return G_SOURCE_CONTINUE;
+ }
+
+ num_managers = g_slist_length (self->managers);
+ for (GSList *l = self->managers; l && l->data; l = l->next)
+ ephy_sync_service_sync_collection (self, l->data, ++index == num_managers);
+
+ return G_SOURCE_CONTINUE;
}
-double
-ephy_sync_service_get_sync_time (EphySyncService *self)
+static void
+ephy_sync_service_unregister_client_id (EphySyncService *self)
{
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), 0);
+ char *client_id;
+ char *endpoint;
- if (self->sync_time != 0)
- return self->sync_time;
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- self->sync_time = g_settings_get_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME);
- return self->sync_time;
-}
+ client_id = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID);
+ endpoint = g_strdup_printf ("storage/clients/%s", client_id);
+ ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_DELETE,
+ NULL, -1, -1, NULL, NULL);
+ g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID, "");
-void
-ephy_sync_service_set_sync_time (EphySyncService *self,
- double time)
+ g_free (endpoint);
+ g_free (client_id);
+}
+
+static void
+ephy_sync_service_register_client_id (EphySyncService *self)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ SyncCryptoKeyBundle *bundle;
+ JsonNode *node;
+ JsonObject *record;
+ JsonObject *payload;
+ JsonArray *array;
+ char *client_id;
+ char *name;
+ char *protocol;
+ char *payload_clear;
+ char *payload_cipher;
+ char *body;
+ char *endpoint;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ record = json_object_new ();
+ payload = json_object_new ();
+ array = json_array_new ();
+ protocol = g_strdup_printf ("1.%d", STORAGE_VERSION);
+ json_array_add_string_element (array, protocol);
+ json_object_set_array_member (payload, "protocols", array);
+ client_id = ephy_sync_crypto_get_random_sync_id ();
+ json_object_set_string_member (payload, "id", client_id);
+ name = g_strdup_printf ("%s on Epiphany", client_id);
+ json_object_set_string_member (payload, "name", name);
+ json_object_set_string_member (payload, "type", "desktop");
+ json_object_set_string_member (payload, "os", "Linux");
+ json_object_set_string_member (payload, "application", "Epiphany");
+ json_object_set_string_member (payload, "fxaDeviceId",
+ ephy_sync_service_get_secret (self, secrets[UID]));
+ json_node_set_object (node, payload);
+ payload_clear = json_to_string (node, FALSE);
+ bundle = ephy_sync_service_get_key_bundle (self, "clients");
+ payload_cipher = ephy_sync_crypto_encrypt_record (payload_clear, bundle);
+ json_object_set_string_member (record, "id", client_id);
+ json_object_set_string_member (record, "payload", payload_cipher);
+ json_node_set_object (node, record);
+ body = json_to_string (node, FALSE);
+ endpoint = g_strdup_printf ("storage/clients/%s", client_id);
+
+ ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT,
+ body, -1, -1, NULL, NULL);
+ g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_CLIENT_ID, client_id);
- self->sync_time = time;
- g_settings_set_double (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_TIME, time);
+ g_free (endpoint);
+ g_free (body);
+ g_free (payload_cipher);
+ ephy_sync_crypto_key_bundle_free (bundle);
+ g_free (payload_clear);
+ g_free (name);
+ g_free (client_id);
+ g_free (protocol);
+ json_object_unref(payload);
+ json_object_unref(record);
+ json_node_unref (node);
}
-char *
-ephy_sync_service_get_token (EphySyncService *self,
- EphySyncTokenType type)
+static void
+ephy_sync_service_stop_periodical_sync (EphySyncService *self)
{
- g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- switch (type) {
- case TOKEN_UID:
- return self->uid;
- case TOKEN_SESSIONTOKEN:
- return self->sessionToken;
- case TOKEN_KEYFETCHTOKEN:
- return self->keyFetchToken;
- case TOKEN_UNWRAPBKEY:
- return self->unwrapBKey;
- case TOKEN_KA:
- return self->kA;
- case TOKEN_KB:
- return self->kB;
- default:
- g_assert_not_reached ();
+ if (self->source_id != 0) {
+ g_source_remove (self->source_id);
+ self->source_id = 0;
}
}
-void
-ephy_sync_service_set_token (EphySyncService *self,
- const char *value,
- EphySyncTokenType type)
+static void
+ephy_sync_service_schedule_periodical_sync (EphySyncService *self)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (value != NULL);
-
- switch (type) {
- case TOKEN_UID:
- g_free (self->uid);
- self->uid = g_strdup (value);
- break;
- case TOKEN_SESSIONTOKEN:
- g_free (self->sessionToken);
- self->sessionToken = g_strdup (value);
- break;
- case TOKEN_KEYFETCHTOKEN:
- g_free (self->keyFetchToken);
- self->keyFetchToken = g_strdup (value);
- break;
- case TOKEN_UNWRAPBKEY:
- g_free (self->unwrapBKey);
- self->unwrapBKey = g_strdup (value);
- break;
- case TOKEN_KA:
- g_free (self->kA);
- self->kA = g_strdup (value);
- break;
- case TOKEN_KB:
- g_free (self->kB);
- self->kB = g_strdup (value);
- break;
- default:
- g_assert_not_reached ();
- }
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+ self->source_id = g_timeout_add_seconds (g_settings_get_uint (EPHY_SETTINGS_SYNC,
+ EPHY_PREFS_SYNC_FREQUENCY) * 60,
+ ephy_sync_service_sync,
+ self);
+ LOG ("Scheduled new sync with frequency %u mins",
+ g_settings_get_uint (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_FREQUENCY));
}
-void
-ephy_sync_service_clear_storage_credentials (EphySyncService *self)
+static void
+sync_frequency_changed_cb (EphySyncService *self)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- g_clear_pointer (&self->certificate, g_free);
- g_clear_pointer (&self->storage_endpoint, g_free);
- g_clear_pointer (&self->storage_credentials_id, g_free);
- g_clear_pointer (&self->storage_credentials_key, g_free);
- self->storage_credentials_expiry_time = 0;
+ ephy_sync_service_stop_periodical_sync (self);
+ ephy_sync_service_schedule_periodical_sync (self);
}
-void
-ephy_sync_service_clear_tokens (EphySyncService *self)
+static void
+forget_secrets_cb (SecretService *service,
+ GAsyncResult *result,
+ gpointer user_data)
{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ GError *error = NULL;
- g_clear_pointer (&self->uid, g_free);
- g_clear_pointer (&self->sessionToken, g_free);
- g_clear_pointer (&self->keyFetchToken, g_free);
- g_clear_pointer (&self->unwrapBKey, g_free);
- g_clear_pointer (&self->kA, g_free);
- g_clear_pointer (&self->kB, g_free);
+ secret_service_clear_finish (service, result, &error);
+ if (error) {
+ g_warning ("Failed to clear sync secrets: %s", error->message);
+ g_error_free (error);
+ }
}
-void
-ephy_sync_service_destroy_session (EphySyncService *self,
- const char *sessionToken)
+static void
+ephy_sync_service_forget_secrets (EphySyncService *self)
{
- EphySyncCryptoHawkOptions *hoptions;
- EphySyncCryptoHawkHeader *hheader;
- SoupMessage *msg;
- guint8 *tokenID;
- guint8 *reqHMACkey;
- guint8 *requestKey;
- char *tokenID_hex;
- char *url;
- const char *content_type = "application/json";
- const char *endpoint = "session/destroy";
- const char *request_body = "{}";
+ GHashTable *attributes;
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (self->secrets);
- if (sessionToken == NULL)
- sessionToken = ephy_sync_service_get_token (self, TOKEN_SESSIONTOKEN);
- g_return_if_fail (sessionToken != NULL);
+ attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+ ACCOUNT_KEY, self->account,
+ NULL);
+ secret_service_clear (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes, NULL,
+ (GAsyncReadyCallback)forget_secrets_cb, NULL);
+ g_hash_table_remove_all (self->secrets);
- url = g_strdup_printf ("%s%s", MOZILLA_FXA_SERVER_URL, endpoint);
- ephy_sync_crypto_process_session_token (sessionToken, &tokenID, &reqHMACkey, &requestKey);
- tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
-
- msg = soup_message_new (SOUP_METHOD_POST, url);
- soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
- request_body, strlen (request_body));
- hoptions = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
- NULL, NULL, NULL, request_body, NULL);
- hheader = ephy_sync_crypto_compute_hawk_header (url, "POST", tokenID_hex,
- reqHMACkey, EPHY_SYNC_TOKEN_LENGTH,
- hoptions);
- soup_message_headers_append (msg->request_headers, "authorization", hheader->header);
- soup_message_headers_append (msg->request_headers, "content-type", content_type);
- soup_session_queue_message (self->session, msg, destroy_session_response_cb, NULL);
-
- ephy_sync_crypto_hawk_options_free (hoptions);
- ephy_sync_crypto_hawk_header_free (hheader);
- g_free (tokenID_hex);
- g_free (tokenID);
- g_free (reqHMACkey);
- g_free (requestKey);
- g_free (url);
+ g_hash_table_unref (attributes);
}
-char *
-ephy_sync_service_start_sign_in (EphySyncService *self,
- guint8 *tokenID,
- guint8 *reqHMACkey)
+static void
+load_secrets_cb (SecretService *service,
+ GAsyncResult *result,
+ EphySyncService *self)
{
- JsonNode *node;
- JsonObject *json;
- char *tokenID_hex;
- char *bundle = NULL;
- guint status_code;
-
- /* Retrieve the sync keys bundle from the /account/keys endpoint. */
- tokenID_hex = ephy_sync_crypto_encode_hex (tokenID, 0);
- status_code = ephy_sync_service_fxa_hawk_get_sync (self, "account/keys", tokenID_hex,
- reqHMACkey, EPHY_SYNC_TOKEN_LENGTH,
- &node);
- json = json_node_get_object (node);
+ SecretValue *value = NULL;
+ JsonNode *node = NULL;
+ JsonObject *object;
+ GList *res = NULL;
+ GError *error = NULL;
+ const char *message;
+ const char *suggestion;
+
+ res = secret_service_search_finish (service, result, &error);
+ if (error) {
+ g_warning ("Failed to search for sync secrets: %s", error->message);
+ g_error_free (error);
+ message = _("Could not find the sync secrets for the current sync user.");
+ goto out_error;
+ }
- if (status_code == 200) {
- bundle = g_strdup (json_object_get_string_member (json, "bundle"));
- } else {
- LOG ("Failed to retrieve sync keys bundle: code: %ld, errno: %ld, error: '%s', message: '%s'",
- json_object_get_int_member (json, "code"),
- json_object_get_int_member (json, "errno"),
- json_object_get_string_member (json, "error"),
- json_object_get_string_member (json, "message"));
+ if (!(res && res->data)) {
+ message = _("The sync secrets for the current sync user are null.");
+ goto out_error;
}
- g_free (tokenID_hex);
- json_node_free (node);
+ value = secret_item_get_secret ((SecretItem *)res->data);
+ node = json_from_string (secret_value_get_text (value), &error);
+ if (error) {
+ g_warning ("Sync secrets are not a valid JSON: %s", error->message);
+ g_error_free (error);
+ message = _("The sync secrets for the current sync user are invalid.");
+ goto out_error;
+ }
- return bundle;
+ /* Set secrets and start periodical sync. */
+ object = json_node_get_object (node);
+ for (GList *l = json_object_get_members (object); l && l->data; l = l->next)
+ ephy_sync_service_set_secret (self, l->data,
+ json_object_get_string_member (object, l->data));
+
+ ephy_sync_service_start_periodical_sync (self);
+ goto out_no_error;
+
+out_error:
+ suggestion = _("Please visit Preferences and sign in again to continue syncing.");
+ ephy_notification_show (ephy_notification_new (message, suggestion));
+out_no_error:
+ if (value)
+ secret_value_unref (value);
+ if (res)
+ g_list_free_full (res, g_object_unref);
+ if (node)
+ json_node_unref (node);
}
-void
-ephy_sync_service_finish_sign_in (EphySyncService *self,
- const char *email,
- const char *uid,
- const char *sessionToken,
- const char *keyFetchToken,
- const char *unwrapBKey,
- char *bundle,
- guint8 *respHMACkey,
- guint8 *respXORkey)
-{
- guint8 *unwrapKB;
- guint8 *kA;
- guint8 *kB;
- char *kA_hex;
- char *kB_hex;
+static void
+ephy_sync_service_load_secrets (EphySyncService *self)
+{
+ GHashTable *attributes;
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (email != NULL);
- g_return_if_fail (uid != NULL);
- g_return_if_fail (sessionToken != NULL);
- g_return_if_fail (keyFetchToken != NULL);
- g_return_if_fail (unwrapBKey != NULL);
- g_return_if_fail (bundle != NULL);
- g_return_if_fail (respHMACkey != NULL);
- g_return_if_fail (respXORkey != NULL);
-
- /* Derive the sync keys form the received key bundle. */
- unwrapKB = ephy_sync_crypto_decode_hex (unwrapBKey);
- ephy_sync_crypto_compute_sync_keys (bundle,
- respHMACkey, respXORkey, unwrapKB,
- &kA, &kB);
- kA_hex = ephy_sync_crypto_encode_hex (kA, 0);
- kB_hex = ephy_sync_crypto_encode_hex (kB, 0);
-
- /* Save the email and the tokens. */
- g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, email);
- ephy_sync_service_set_user_email (self, email);
- ephy_sync_service_set_token (self, uid, TOKEN_UID);
- ephy_sync_service_set_token (self, sessionToken, TOKEN_SESSIONTOKEN);
- ephy_sync_service_set_token (self, keyFetchToken, TOKEN_KEYFETCHTOKEN);
- ephy_sync_service_set_token (self, unwrapBKey, TOKEN_UNWRAPBKEY);
- ephy_sync_service_set_token (self, kA_hex, TOKEN_KA);
- ephy_sync_service_set_token (self, kB_hex, TOKEN_KB);
-
- /* Store the tokens in the secret schema. */
- ephy_sync_secret_store_tokens (self, email, uid, sessionToken,
- keyFetchToken, unwrapBKey, kA_hex, kB_hex);
-
- g_free (kA);
- g_free (kB);
- g_free (kA_hex);
- g_free (kB_hex);
- g_free (unwrapKB);
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (self->secrets);
+
+ attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+ ACCOUNT_KEY, self->account,
+ NULL);
+ secret_service_search (NULL, EPHY_SYNC_SECRET_SCHEMA, attributes,
+ SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+ NULL, (GAsyncReadyCallback)load_secrets_cb, self);
+
+ g_hash_table_unref (attributes);
}
-void
-ephy_sync_service_send_storage_message (EphySyncService *self,
- char *endpoint,
- const char *method,
- char *request_body,
- double modified_since,
- double unmodified_since,
- SoupSessionCallback callback,
- gpointer user_data)
+static void
+store_secrets_cb (SecretService *service,
+ GAsyncResult *result,
+ EphySyncService *self)
{
- StorageServerRequestAsyncData *data;
-
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (endpoint != NULL);
- g_return_if_fail (method != NULL);
-
- data = storage_server_request_async_data_new (self, endpoint, method, request_body,
- modified_since, unmodified_since,
- callback, user_data);
-
- /* If there is currently another message being transmitted, then the new
- * message has to wait in the queue, otherwise, it is free to go. */
- if (self->locked == FALSE) {
- self->locked = TRUE;
- ephy_sync_service_issue_storage_request (self, data);
+ GError *error = NULL;
+
+ secret_service_store_finish (service, result, &error);
+ if (error) {
+ g_warning ("Failed to store sync secrets: %s", error->message);
+ ephy_sync_service_destroy_session (self, NULL);
+ g_clear_pointer (&self->account, g_free);
+ g_hash_table_remove_all (self->secrets);
} else {
- g_queue_push_tail (self->storage_queue, data);
+ ephy_sync_service_register_client_id (self);
}
-}
-void
-ephy_sync_service_release_next_storage_message (EphySyncService *self)
-{
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- /* We should never reach this with the service not being locked. */
- g_assert (self->locked == TRUE);
+ g_signal_emit (self, signals[STORE_FINISHED], 0, error);
- /* If there are other messages waiting in the queue, we release the next one
- * and keep the service locked, else, we mark the service as not locked. */
- if (g_queue_is_empty (self->storage_queue) == FALSE)
- ephy_sync_service_issue_storage_request (self, g_queue_pop_head (self->storage_queue));
- else
- self->locked = FALSE;
+ if (error)
+ g_error_free (error);
}
static void
-upload_bookmark_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+ephy_sync_service_store_secrets (EphySyncService *self)
{
- EphySyncService *service;
- EphyBookmarksManager *manager;
- EphyBookmark *bookmark;
- double last_modified;
+ JsonNode *node;
+ JsonObject *object;
+ SecretValue *secret;
+ GHashTable *attributes;
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+ char *json_string;
+ char *label;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (self->secrets);
+ g_assert (self->account);
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ object = json_object_new ();
+ g_hash_table_iter_init (&iter, self->secrets);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ json_object_set_string_member (object, key, value);
+ json_node_set_object (node, object);
+ json_string = json_to_string (node, FALSE);
+
+ secret = secret_value_new (json_string, -1, "text/plain");
+ attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+ ACCOUNT_KEY, self->account,
+ NULL);
+ /* Translators: %s is the email of the user. */
+ label = g_strdup_printf (_("The sync secrets of %s"), self->account);
+
+ secret_service_store (NULL, EPHY_SYNC_SECRET_SCHEMA,
+ attributes, NULL, label, secret, NULL,
+ (GAsyncReadyCallback)store_secrets_cb, self);
+
+ g_free (label);
+ g_free (json_string);
+ secret_value_unref (secret);
+ g_hash_table_unref (attributes);
+ json_object_unref (object);
+ json_node_unref (node);
+}
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- bookmark = EPHY_BOOKMARK (user_data);
+static void
+ephy_sync_service_dispose (GObject *object)
+{
+ EphySyncService *self = EPHY_SYNC_SERVICE (object);
- if (msg->status_code == 200) {
- last_modified = g_ascii_strtod (msg->response_body->data, NULL);
- ephy_bookmark_set_modification_time (bookmark, last_modified);
- ephy_bookmark_set_is_uploaded (bookmark, TRUE);
- ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+ if (ephy_sync_service_is_signed_in (self))
+ ephy_sync_service_stop_periodical_sync (self);
- LOG ("Successfully uploaded to server");
- } else if (msg->status_code == 412) {
- ephy_sync_service_download_bookmark (service, bookmark);
- } else {
- LOG ("Failed to upload to server. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
- }
+ ephy_sync_service_clear_storage_credentials (self);
+ g_clear_object (&self->session);
+ g_clear_pointer (&self->account, g_free);
+ g_clear_pointer (&self->rsa_key_pair, ephy_sync_crypto_rsa_key_pair_free);
+ g_clear_pointer (&self->secrets, g_hash_table_destroy);
+ g_clear_pointer (&self->managers, g_slist_free);
+ g_queue_free_full (self->storage_queue, (GDestroyNotify)storage_request_async_data_free);
+ self->storage_queue = NULL;
- ephy_sync_service_release_next_storage_message (service);
+ G_OBJECT_CLASS (ephy_sync_service_parent_class)->dispose (object);
}
-void
-ephy_sync_service_upload_bookmark (EphySyncService *self,
- EphyBookmark *bookmark,
- gboolean force)
+static void
+ephy_sync_service_class_init (EphySyncServiceClass *klass)
{
- char *endpoint;
- char *bso;
- double modified;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (ephy_sync_service_is_signed_in (self));
- g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
-
- endpoint = g_strdup_printf ("storage/%s/%s",
- EPHY_BOOKMARKS_COLLECTION,
- ephy_bookmark_get_id (bookmark));
- bso = ephy_bookmark_to_bso (bookmark);
- modified = ephy_bookmark_get_modification_time (bookmark);
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_PUT, bso, -1,
- force ? -1 : modified,
- upload_bookmark_response_cb,
- bookmark);
+ object_class->dispose = ephy_sync_service_dispose;
- g_free (endpoint);
- g_free (bso);
+ signals[STORE_FINISHED] =
+ g_signal_new ("sync-secrets-store-finished",
+ EPHY_TYPE_SYNC_SERVICE,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_ERROR);
+
+ signals[SIGN_IN_ERROR] =
+ g_signal_new ("sync-sign-in-error",
+ EPHY_TYPE_SYNC_SERVICE,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ signals[SYNC_FREQUENCY_CHANGED] =
+ g_signal_new ("sync-frequency-changed",
+ EPHY_TYPE_SYNC_SERVICE,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[SYNC_FINISHED] =
+ g_signal_new ("sync-finished",
+ EPHY_TYPE_SYNC_SERVICE,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
}
static void
-download_bookmark_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+ephy_sync_service_init (EphySyncService *self)
{
- EphySyncService *service;
- EphyBookmarksManager *manager;
- EphyBookmark *bookmark;
- GSequenceIter *iter;
- JsonParser *parser;
- JsonObject *bso;
- const char *id;
+ char *email;
+ const char *user_agent;
+ WebKitSettings *settings;
- if (msg->status_code != 200) {
- LOG ("Failed to download from server. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
- goto out;
+ self->session = soup_session_new ();
+ 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);
+
+ email = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
+
+ if (g_strcmp0 (email, "")) {
+ self->account = g_strdup (email);
+ ephy_sync_service_load_secrets (self);
}
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
- bso = json_node_get_object (json_parser_get_root (parser));
- bookmark = ephy_bookmark_from_bso (bso);
- id = ephy_bookmark_get_id (bookmark);
-
- /* Overwrite any local bookmark. */
- manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- ephy_bookmarks_manager_remove_bookmark (manager,
- ephy_bookmarks_manager_get_bookmark_by_id (manager, id));
- ephy_bookmarks_manager_add_bookmark (manager, bookmark);
-
- /* We have to manually add the tags to the bookmarks manager. */
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (bookmark));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
- g_object_unref (bookmark);
- g_object_unref (parser);
+ g_signal_connect (self, "sync-frequency-changed",
+ G_CALLBACK (sync_frequency_changed_cb),
+ NULL);
-out:
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- ephy_sync_service_release_next_storage_message (service);
+ g_free (email);
}
-void
-ephy_sync_service_download_bookmark (EphySyncService *self,
- EphyBookmark *bookmark)
+EphySyncService *
+ephy_sync_service_new (void)
{
- char *endpoint;
+ return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE, NULL));
+}
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (ephy_sync_service_is_signed_in (self));
- g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+gboolean
+ephy_sync_service_is_signed_in (EphySyncService *self)
+{
+ g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), FALSE);
+
+ return self->account != NULL;
+}
- endpoint = g_strdup_printf ("storage/%s/%s",
- EPHY_BOOKMARKS_COLLECTION,
- ephy_bookmark_get_id (bookmark));
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_GET, NULL, -1, -1,
- download_bookmark_response_cb, NULL);
+const char *
+ephy_sync_service_get_user_email (EphySyncService *self)
+{
+ g_return_val_if_fail (EPHY_IS_SYNC_SERVICE (self), NULL);
- g_free (endpoint);
+ return self->account;
}
static void
-delete_bookmark_conditional_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+ephy_sync_service_report_sign_in_error (EphySyncService *self,
+ const char *message,
+ const char *session_token,
+ gboolean clear_secrets)
{
- EphySyncService *service;
- EphyBookmark *bookmark;
- EphyBookmarksManager *manager;
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (message);
- bookmark = EPHY_BOOKMARK (user_data);
- manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
+ g_signal_emit (self, signals[SIGN_IN_ERROR], 0, message);
+ ephy_sync_service_destroy_session (self, session_token);
- if (msg->status_code == 404) {
- ephy_bookmarks_manager_remove_bookmark (manager, bookmark);
- } else if (msg->status_code == 200) {
- LOG ("The bookmark still exists on the server, don't delete it");
- } else {
- LOG ("Failed to delete conditionally. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
+ if (clear_secrets) {
+ g_clear_pointer (&self->account, g_free);
+ g_hash_table_remove_all (self->secrets);
}
+}
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- ephy_sync_service_release_next_storage_message (service);
+static char *
+ephy_sync_service_upload_crypto_keys_record (EphySyncService *self)
+{
+ SyncCryptoKeyBundle *bundle;
+ JsonNode *node;
+ JsonObject *record;
+ char *payload_clear;
+ char *payload_cipher;
+ char *body;
+ const char *master_key_hex;
+ guint8 *master_key;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ master_key_hex = ephy_sync_service_get_secret (self, secrets[MASTER_KEY]);
+ g_assert (master_key_hex);
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ record = json_object_new ();
+ payload_clear = ephy_sync_crypto_generate_crypto_keys (32);
+ master_key = ephy_sync_crypto_decode_hex (master_key_hex);
+ bundle = ephy_sync_crypto_derive_key_bundle (master_key, 32);
+ payload_cipher = ephy_sync_crypto_encrypt_record (payload_clear, bundle);
+ json_object_set_string_member (record, "payload", payload_cipher);
+ json_object_set_string_member (record, "id", "keys");
+ json_node_set_object (node, record);
+ body = json_to_string (node, FALSE);
+
+ ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+ SOUP_METHOD_PUT, body,
+ -1, -1, NULL, NULL);
+
+ g_free (body);
+ g_free (payload_cipher);
+ g_free (master_key);
+ json_object_unref (record);
+ json_node_unref (node);
+ ephy_sync_crypto_key_bundle_free (bundle);
+
+ return payload_clear;
}
static void
-delete_bookmark_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+obtain_crypto_keys_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- EphySyncService *service;
+ EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+ SyncCryptoKeyBundle *bundle = NULL;
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ const char *payload;
+ char *crypto_keys = NULL;
+ guint8 *key_b = NULL;
- if (msg->status_code == 200)
- LOG ("Successfully deleted the bookmark from the server");
- else
- LOG ("Failed to delete. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
+ if (msg->status_code == 404) {
+ crypto_keys = ephy_sync_service_upload_crypto_keys_record (self);
+ goto store_secrets;
+ }
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- ephy_sync_service_release_next_storage_message (service);
+ if (msg->status_code != 200) {
+ g_warning ("Failed to get crypto/keys record. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ 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_error;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold an object");
+ goto out_error;
+ }
+ payload = json_object_get_string_member (json, "payload");
+ if (!payload) {
+ g_warning ("JSON object has missing or invalid 'payload' member");
+ goto out_error;
+ }
+ /* Derive the Sync Key bundle from kB. The bundle consists of two 32 bytes keys:
+ * the first one used as a symmetric encryption key (AES) and the second one
+ * used as a HMAC key. */
+ key_b = ephy_sync_crypto_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
+ bundle = ephy_sync_crypto_derive_key_bundle (key_b, 32);
+ crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
+ if (!crypto_keys) {
+ g_warning ("Failed to decrypt crypto/keys record");
+ goto out_error;
+ }
+
+store_secrets:
+ ephy_sync_service_set_secret (self, secrets[CRYPTO_KEYS], crypto_keys);
+ ephy_sync_service_store_secrets (self);
+ goto out_no_error;
+out_error:
+ ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve crypto keys."),
+ NULL, TRUE);
+out_no_error:
+ if (bundle)
+ ephy_sync_crypto_key_bundle_free (bundle);
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+ g_free (crypto_keys);
+ g_free (key_b);
}
-void
-ephy_sync_service_delete_bookmark (EphySyncService *self,
- EphyBookmark *bookmark,
- gboolean conditional)
+static void
+ephy_sync_service_obtain_crypto_keys (EphySyncService *self)
{
- char *endpoint;
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (EPHY_IS_BOOKMARK (bookmark));
+ ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
+ SOUP_METHOD_GET, NULL, -1, -1,
+ obtain_crypto_keys_cb, self);
+}
- if (ephy_sync_service_is_signed_in (self) == FALSE)
- return;
+static JsonObject *
+make_engine_object (int version)
+{
+ JsonObject *object;
+ char *sync_id;
- endpoint = g_strdup_printf ("storage/%s/%s",
- EPHY_BOOKMARKS_COLLECTION,
- ephy_bookmark_get_id (bookmark));
-
- /* If the bookmark does not exist on the server, delete it from the local
- * instance too. */
- if (conditional == TRUE) {
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_GET, NULL, -1, -1,
- delete_bookmark_conditional_response_cb,
- bookmark);
- } else {
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_DELETE, NULL, -1, -1,
- delete_bookmark_response_cb, NULL);
- }
+ object = json_object_new ();
+ sync_id = ephy_sync_crypto_get_random_sync_id ();
+ json_object_set_int_member (object, "version", version);
+ json_object_set_string_member (object, "syncID", sync_id);
- g_free (endpoint);
+ g_free (sync_id);
+
+ return object;
}
static void
-sync_bookmarks_first_time_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+ephy_sync_service_upload_meta_global_record (EphySyncService *self)
{
- EphySyncService *service;
- EphyBookmarksManager *manager;
- GSequence *bookmarks;
- GSequenceIter *iter;
- GHashTable *marked;
- JsonParser *parser;
- JsonArray *array;
- const char *timestamp;
- double server_time;
+ JsonNode *node;
+ JsonObject *record;
+ JsonObject *payload;
+ JsonObject *engines;
+ JsonArray *declined;
+ char *sync_id;
+ char *payload_str;
+ char *body;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+
+ node = json_node_new (JSON_NODE_OBJECT);
+ record = json_object_new ();
+ payload = json_object_new ();
+ engines = json_object_new ();
+ declined = json_array_new ();
+ json_array_add_string_element (declined, "addons");
+ json_array_add_string_element (declined, "prefs");
+ json_array_add_string_element (declined, "tabs");
+ json_object_set_array_member (payload, "declined", declined);
+ json_object_set_object_member (engines, "clients", make_engine_object (1));
+ json_object_set_object_member (engines, "bookmarks", make_engine_object (2));
+ json_object_set_object_member (engines, "history", make_engine_object (1));
+ json_object_set_object_member (engines, "passwords", make_engine_object (1));
+ json_object_set_object_member (engines, "forms", make_engine_object (1));
+ json_object_set_object_member (payload, "engines", engines);
+ json_object_set_int_member (payload, "storageVersion", STORAGE_VERSION);
+ sync_id = ephy_sync_crypto_get_random_sync_id ();
+ json_object_set_string_member (payload, "syncID", sync_id);
+ json_node_set_object (node, payload);
+ payload_str = json_to_string (node, FALSE);
+ json_object_set_string_member (record, "payload", payload_str);
+ json_object_set_string_member (record, "id", "global");
+ json_node_set_object (node, record);
+ body = json_to_string (node, FALSE);
+
+ ephy_sync_service_queue_storage_request (self, "storage/meta/global",
+ SOUP_METHOD_PUT, body,
+ -1, -1, NULL, NULL);
+
+ g_free (body);
+ g_free (payload_str);
+ g_free (sync_id);
+ json_object_unref (payload);
+ json_object_unref (record);
+ json_node_unref (node);
+}
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- bookmarks = ephy_bookmarks_manager_get_bookmarks (manager);
- marked = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
+static void
+check_storage_version_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
+ JsonParser *parser = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ char *payload = NULL;
+ char *message = NULL;
+ int storage_version;
- if (msg->status_code != 200) {
- LOG ("Failed to do a first time sync. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
- goto out;
+ if (msg->status_code == 404) {
+ ephy_sync_service_upload_meta_global_record (self);
+ goto obtain_crypto_keys;
}
- array = json_node_get_array (json_parser_get_root (parser));
- for (gsize i = 0; i < json_array_get_length (array); i++) {
- JsonObject *bso = json_array_get_object_element (array, i);
- EphyBookmark *remote = ephy_bookmark_from_bso (bso);
- EphyBookmark *local;
+ if (msg->status_code != 200) {
+ g_warning ("Failed to get meta/global record. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+ goto out_error;
+ }
- if (remote == NULL)
- continue;
+ parser = json_parser_new ();
+ json_parser_load_from_data (parser, msg->response_body->data, -1, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out_error;
+ }
+ json = json_node_get_object (json_parser_get_root (parser));
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out_error;
+ }
+ if (!json_object_get_string_member (json, "payload")) {
+ g_warning ("JSON object has missing or invalid 'payload' member");
+ goto out_error;
+ }
+ payload = g_strdup (json_object_get_string_member (json, "payload"));
+ json_parser_load_from_data (parser, payload, -1, &error);
+ if (error) {
+ g_warning ("Payload is not a valid JSON: %s", error->message);
+ goto out_error;
+ }
+ json = json_node_get_object (json_parser_get_root (parser));
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out_error;
+ }
+ if (!json_object_get_int_member (json, "storageVersion")) {
+ g_warning ("JSON object has missing or invalid 'storageVersion' member");
+ goto out_error;
+ }
+ storage_version = json_object_get_int_member (json, "storageVersion");
+ if (storage_version != STORAGE_VERSION) {
+ /* Translators: the %d is the storage version, the \n is a newline character. */
+ message = g_strdup_printf (_("Your Firefox Account uses storage version %d "
+ "which Epiphany does not support.\n"
+ "Create a new account to use the latest storage version."),
+ storage_version);
+ goto out_error;
+ }
- local = ephy_bookmarks_manager_get_bookmark_by_id (manager, ephy_bookmark_get_id (remote));
-
- if (local == NULL) {
- local = ephy_bookmarks_manager_get_bookmark_by_url (manager, ephy_bookmark_get_url (remote));
-
- /* If there is no local equivalent of the remote bookmark, then add it to
- * the local instance together with its tags. */
- if (local == NULL) {
- ephy_bookmarks_manager_add_bookmark (manager, remote);
-
- /* We have to manually add the tags to the bookmarks manager. */
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
- g_hash_table_add (marked, g_object_ref (remote));
- }
- /* If there is a local bookmark with the same url as the remote one, then
- * merge tags into the local one, keep the remote id and upload it to the
- * server. */
- else {
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
- ephy_bookmark_add_tag (local, g_sequence_get (iter));
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
- }
-
- ephy_bookmark_set_id (local, ephy_bookmark_get_id (remote));
- ephy_sync_service_upload_bookmark (service, local, TRUE);
- g_hash_table_add (marked, g_object_ref (local));
- }
- }
- /* Having a local bookmark with the same id as the remote one means that the
- * bookmark has been synced before in the past. Keep the one with the most
- * recent modified timestamp. */
- else {
- if (ephy_bookmark_get_modification_time (remote) > ephy_bookmark_get_modification_time (local)) {
- ephy_bookmarks_manager_remove_bookmark (manager, local);
- ephy_bookmarks_manager_add_bookmark (manager, remote);
-
- /* We have to manually add the tags to the bookmarks manager. */
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
-
- g_hash_table_add (marked, g_object_ref (remote));
- } else {
- if (ephy_bookmark_get_modification_time (local) > ephy_bookmark_get_modification_time (remote))
- ephy_sync_service_upload_bookmark (service, local, TRUE);
-
- g_hash_table_add (marked, g_object_ref (local));
- }
- }
+obtain_crypto_keys:
+ ephy_sync_service_obtain_crypto_keys (self);
+ goto out_no_error;
+out_error:
+ message = message ? message : _("Failed to verify storage version.");
+ ephy_sync_service_report_sign_in_error (self, message, NULL, TRUE);
+out_no_error:
+ if (parser)
+ g_object_unref (parser);
+ if (error)
+ g_error_free (error);
+ g_free (payload);
+ g_free (message);
+}
- g_object_unref (remote);
- }
+static void
+ephy_sync_service_check_storage_version (EphySyncService *self)
+{
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- /* Upload the remaining local bookmarks to the server. */
- for (iter = g_sequence_get_begin_iter (bookmarks);
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
- EphyBookmark *bookmark = g_sequence_get (iter);
+ ephy_sync_service_queue_storage_request (self, "storage/meta/global",
+ SOUP_METHOD_GET, NULL, -1, -1,
+ check_storage_version_cb, self);
+}
- if (g_hash_table_contains (marked, bookmark) == FALSE)
- ephy_sync_service_upload_bookmark (service, bookmark, TRUE);
+static void
+ephy_sync_service_conclude_sign_in (EphySyncService *self,
+ SignInAsyncData *data,
+ const char *bundle)
+{
+ guint8 *unwrap_key_b;
+ guint8 *key_a;
+ guint8 *key_b;
+ char *key_b_hex;
+
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
+ g_assert (data);
+ g_assert (bundle);
+
+ /* Derive the master sync keys form the key bundle. */
+ unwrap_key_b = ephy_sync_crypto_decode_hex (data->unwrap_b_key);
+ if (!ephy_sync_crypto_compute_sync_keys (bundle, data->resp_hmac_key,
+ data->resp_xor_key, unwrap_key_b,
+ &key_a, &key_b, 32)) {
+ ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve the Sync Key"),
+ data->session_token, FALSE);
+ goto out;
}
- /* Save changes to file. */
- ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+ /* Save email and tokens. */
+ self->account = g_strdup (data->email);
+ ephy_sync_service_set_secret (self, secrets[UID], data->uid);
+ ephy_sync_service_set_secret (self, secrets[SESSION_TOKEN], data->session_token);
+ key_b_hex = ephy_sync_crypto_encode_hex (key_b, 32);
+ ephy_sync_service_set_secret (self, secrets[MASTER_KEY], key_b_hex);
- /* Set the sync time. */
- timestamp = soup_message_headers_get_one (msg->response_headers, "X-Weave-Timestamp");
- server_time = g_ascii_strtod (timestamp, NULL);
- ephy_sync_service_set_sync_time (service, server_time);
+ ephy_sync_service_check_storage_version (self);
+ g_free (key_b_hex);
+ g_free (key_b);
+ g_free (key_a);
out:
- g_object_unref (parser);
- g_hash_table_unref (marked);
-
- ephy_sync_service_release_next_storage_message (service);
+ g_free (unwrap_key_b);
+ sign_in_async_data_free (data);
}
static void
-sync_bookmarks_response_cb (SoupSession *session,
- SoupMessage *msg,
- gpointer user_data)
+get_account_keys_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
{
- EphySyncService *service;
- EphyBookmarksManager *manager;
- GSequence *bookmarks;
- GSequenceIter *iter;
- JsonParser *parser;
- JsonArray *array;
- const char *timestamp;
- double server_time;
-
- service = ephy_shell_get_sync_service (ephy_shell_get_default ());
- manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- bookmarks = ephy_bookmarks_manager_get_bookmarks (manager);
- parser = json_parser_new ();
- json_parser_load_from_data (parser, msg->response_body->data, -1, NULL);
+ SignInAsyncData *data = (SignInAsyncData *)user_data;
+ JsonNode *node = NULL;
+ JsonObject *json = NULL;
+ GError *error = NULL;
+ const char *bundle;
+
+ node = json_from_string (msg->response_body->data, &error);
+ if (error) {
+ g_warning ("Response is not a valid JSON: %s", error->message);
+ goto out_error;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out_error;
+ }
- /* Code 304 indicates that the resource has not been modified. Therefore,
- * only upload the local bookmarks that were not uploaded. */
- if (msg->status_code == 304)
- goto handle_local_bookmarks;
+ if (msg->status_code == 200) {
+ bundle = json_object_get_string_member (json, "bundle");
+ if (!bundle) {
+ g_warning ("JSON object has invalid or missing 'bundle' member");
+ goto out_error;
+ }
+ /* Extract the master sync keys from the bundle and save tokens. */
+ ephy_sync_service_conclude_sign_in (data->service, data, bundle);
+ goto out_no_error;
+ }
- if (msg->status_code != 200) {
- LOG ("Failed to sync bookmarks. Status code: %u, response: %s",
- msg->status_code, msg->response_body->data);
- goto out;
+ /* If account in not verified, poll the Firefox Accounts Server until the
+ * verification has completed. */
+ if (json_object_get_int_member (json, "errno") == 104) {
+ LOG ("Account not verified, retrying...");
+ ephy_sync_service_fxa_hawk_get_async (data->service, "account/keys",
+ data->token_id_hex, data->req_hmac_key,
+ 32, get_account_keys_cb, data);
+ goto out_no_error;
}
- array = json_node_get_array (json_parser_get_root (parser));
- for (gsize i = 0; i < json_array_get_length (array); i++) {
- JsonObject *bso = json_array_get_object_element (array, i);
- EphyBookmark *remote = ephy_bookmark_from_bso (bso);
- EphyBookmark *local;
+ g_warning ("Failed to get /account/keys. Status code: %u, response: %s",
+ msg->status_code, msg->response_body->data);
+
+out_error:
+ ephy_sync_service_report_sign_in_error (data->service,
+ _("Failed to retrieve the Sync Key"),
+ data->session_token, FALSE);
+ sign_in_async_data_free (data);
+out_no_error:
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+}
- if (remote == NULL)
- continue;
+void
+ephy_sync_service_do_sign_in (EphySyncService *self,
+ const char *email,
+ const char *uid,
+ const char *session_token,
+ const char *key_fetch_token,
+ const char *unwrap_b_key)
+{
+ SignInAsyncData *data;
+ guint8 *token_id;
+ guint8 *req_hmac_key;
+ guint8 *resp_hmac_key;
+ guint8 *resp_xor_key;
+ char *token_id_hex;
- local = ephy_bookmarks_manager_get_bookmark_by_id (manager, ephy_bookmark_get_id (remote));
-
- if (local == NULL) {
- ephy_bookmarks_manager_add_bookmark (manager, remote);
-
- /* We have to manually add the tags to the bookmarks manager. */
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
- } else {
- if (ephy_bookmark_get_modification_time (remote) > ephy_bookmark_get_modification_time (local)) {
- ephy_bookmarks_manager_remove_bookmark (manager, local);
- ephy_bookmarks_manager_add_bookmark (manager, remote);
-
- /* We have to manually add the tags to the bookmarks manager. */
- for (iter = g_sequence_get_begin_iter (ephy_bookmark_get_tags (remote));
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
- ephy_bookmarks_manager_create_tag (manager, g_sequence_get (iter));
- } else {
- if (ephy_bookmark_get_modification_time (local) > ephy_bookmark_get_modification_time (remote))
- ephy_sync_service_upload_bookmark (service, local, TRUE);
-
- g_object_unref (remote);
- }
- }
- }
+ g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ g_return_if_fail (email);
+ g_return_if_fail (uid);
+ g_return_if_fail (session_token);
+ g_return_if_fail (key_fetch_token);
+ g_return_if_fail (unwrap_b_key);
+
+ /* Derive tokenID, reqHMACkey, respHMACkey and respXORkey from keyFetchToken.
+ * tokenID and reqHMACkey are used to sign a HAWK GET requests to the /account/keys
+ * endpoint. The server looks up the stored table entry with tokenID, checks
+ * the request HMAC for validity, then returns the pre-encrypted response.
+ * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys */
+ ephy_sync_crypto_process_key_fetch_token (key_fetch_token, &token_id, &req_hmac_key,
+ &resp_hmac_key, &resp_xor_key, 32);
+ token_id_hex = ephy_sync_crypto_encode_hex (token_id, 32);
+
+ /* Get the master sync key bundle from the /account/keys endpoint. */
+ data = sign_in_async_data_new (self, email, uid,
+ session_token, unwrap_b_key,
+ token_id_hex, req_hmac_key,
+ resp_hmac_key, resp_xor_key);
+ ephy_sync_service_fxa_hawk_get_async (self, "account/keys", token_id_hex,
+ req_hmac_key, 32,
+ get_account_keys_cb, data);
+
+ g_free (token_id_hex);
+ g_free (token_id);
+ g_free (req_hmac_key);
+ g_free (resp_hmac_key);
+ g_free (resp_xor_key);
+}
-handle_local_bookmarks:
- for (iter = g_sequence_get_begin_iter (bookmarks);
- !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) {
- EphyBookmark *bookmark = EPHY_BOOKMARK (g_sequence_get (iter));
+static void
+synchronizable_deleted_cb (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable,
+ EphySyncService *self)
+{
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
- if (ephy_bookmark_is_uploaded (bookmark) == TRUE)
- ephy_sync_service_delete_bookmark (service, bookmark, TRUE);
- else
- ephy_sync_service_upload_bookmark (service, bookmark, FALSE);
- }
+ if (!ephy_sync_service_is_signed_in (self))
+ return;
- /* Save changes to file. */
- ephy_bookmarks_manager_save_to_file_async (manager, NULL, NULL, NULL);
+ ephy_sync_service_delete_synchronizable (self, manager, synchronizable);
+}
- /* Set the sync time. */
- timestamp = soup_message_headers_get_one (msg->response_headers, "X-Weave-Timestamp");
- server_time = g_ascii_strtod (timestamp, NULL);
- ephy_sync_service_set_sync_time (service, server_time);
+static void
+synchronizable_modified_cb (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable,
+ EphySyncService *self)
+{
+ g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+ g_assert (EPHY_IS_SYNC_SERVICE (self));
-out:
- g_object_unref (parser);
+ if (!ephy_sync_service_is_signed_in (self))
+ return;
- ephy_sync_service_release_next_storage_message (service);
+ ephy_sync_service_upload_synchronizable (self, manager, synchronizable);
}
void
-ephy_sync_service_sync_bookmarks (EphySyncService *self,
- gboolean first)
+ephy_sync_service_register_manager (EphySyncService *self,
+ EphySynchronizableManager *manager)
{
- char *endpoint;
-
g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- g_return_if_fail (ephy_sync_service_is_signed_in (self));
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
- endpoint = g_strdup_printf ("storage/%s?full=true", EPHY_BOOKMARKS_COLLECTION);
+ if (!g_slist_find (self->managers, manager)) {
+ self->managers = g_slist_prepend (self->managers, manager);
- if (first == TRUE) {
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_GET, NULL, -1, -1,
- sync_bookmarks_first_time_response_cb, NULL);
- } else {
- ephy_sync_service_send_storage_message (self, endpoint,
- SOUP_METHOD_GET, NULL,
- ephy_sync_service_get_sync_time (self), -1,
- sync_bookmarks_response_cb, NULL);
+ g_signal_connect (manager, "synchronizable-deleted",
+ G_CALLBACK (synchronizable_deleted_cb), self);
+ g_signal_connect (manager, "synchronizable-modified",
+ G_CALLBACK (synchronizable_modified_cb), self);
}
-
- g_free (endpoint);
}
-static gboolean
-do_periodical_sync (gpointer user_data)
+void
+ephy_sync_service_unregister_manager (EphySyncService *self,
+ EphySynchronizableManager *manager)
{
- EphySyncService *service = EPHY_SYNC_SERVICE (user_data);
+ g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
- ephy_sync_service_sync_bookmarks (service, FALSE);
+ self->managers = g_slist_remove (self->managers, manager);
- return G_SOURCE_CONTINUE;
+ g_signal_handlers_disconnect_by_func (manager, synchronizable_deleted_cb, self);
+ g_signal_handlers_disconnect_by_func (manager, synchronizable_modified_cb, self);
}
+
void
-ephy_sync_service_start_periodical_sync (EphySyncService *self,
- gboolean now)
+ephy_sync_service_do_sign_out (EphySyncService *self)
{
g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
- if (ephy_sync_service_is_signed_in (self) == FALSE)
- return;
+ ephy_sync_service_unregister_client_id (self);
+ ephy_sync_service_stop_periodical_sync (self);
+ ephy_sync_service_destroy_session (self, NULL);
+ ephy_sync_service_clear_storage_credentials (self);
+ ephy_sync_service_forget_secrets (self);
+ g_clear_pointer (&self->account, g_free);
+
+ /* Clear storage messages queue. */
+ while (!g_queue_is_empty (self->storage_queue))
+ storage_request_async_data_free (g_queue_pop_head (self->storage_queue));
- if (now == TRUE)
- do_periodical_sync (self);
+ /* Clear managers. */
+ for (GSList *l = self->managers; l && l->data; l = l->next) {
+ g_signal_handlers_disconnect_by_func (l->data, synchronizable_deleted_cb, self);
+ g_signal_handlers_disconnect_by_func (l->data, synchronizable_modified_cb, self);
+ }
+ g_slist_free (self->managers);
+ self->managers = NULL;
- self->source_id = g_timeout_add_seconds (SYNC_FREQUENCY, do_periodical_sync, 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);
}
void
-ephy_sync_service_stop_periodical_sync (EphySyncService *self)
+ephy_sync_service_do_sync (EphySyncService *self)
{
g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+ g_return_if_fail (ephy_sync_service_is_signed_in (self));
- if (ephy_sync_service_is_signed_in (self) == FALSE)
- return;
+ ephy_sync_service_sync (self);
+}
- if (self->source_id != 0) {
- g_source_remove (self->source_id);
- self->source_id = 0;
- }
+void
+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));
+
+ ephy_sync_service_sync (self);
+ ephy_sync_service_schedule_periodical_sync (self);
}
diff --git a/src/sync/ephy-sync-service.h b/src/sync/ephy-sync-service.h
index cd1fabb..925fc31 100644
--- a/src/sync/ephy-sync-service.h
+++ b/src/sync/ephy-sync-service.h
@@ -20,11 +20,9 @@
#pragma once
-#include "ephy-bookmark.h"
-#include "ephy-sync-utils.h"
+#include "ephy-synchronizable-manager.h"
#include <glib-object.h>
-#include <libsoup/soup.h>
G_BEGIN_DECLS
@@ -32,56 +30,21 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphySyncService, ephy_sync_service, EPHY, SYNC_SERVICE, GObject)
-EphySyncService *ephy_sync_service_new (void);
-gboolean ephy_sync_service_is_signed_in (EphySyncService *self);
-char *ephy_sync_service_get_user_email (EphySyncService *self);
-void ephy_sync_service_set_user_email (EphySyncService *self,
- const char *email);
-double ephy_sync_service_get_sync_time (EphySyncService *self);
-void ephy_sync_service_set_sync_time (EphySyncService *self,
- double time);
-char *ephy_sync_service_get_token (EphySyncService *self,
- EphySyncTokenType type);
-void ephy_sync_service_set_token (EphySyncService *self,
- const char *value,
- EphySyncTokenType type);
-void ephy_sync_service_clear_storage_credentials (EphySyncService *self);
-void ephy_sync_service_clear_tokens (EphySyncService *self);
-void ephy_sync_service_destroy_session (EphySyncService *self,
- const char *sessionToken);
-char *ephy_sync_service_start_sign_in (EphySyncService *self,
- guint8 *tokenID,
- guint8 *reqHMACkey);
-void ephy_sync_service_finish_sign_in (EphySyncService *self,
- const char *email,
- const char *uid,
- const char *sessionToken,
- const char *keyFetchToken,
- const char *unwrapBKey,
- char *bundle,
- guint8 *respHMACkey,
- guint8 *respXORkey);
-void ephy_sync_service_send_storage_message (EphySyncService *self,
- char *endpoint,
- const char *method,
- char *request_body,
- double modified_since,
- double unmodified_since,
- SoupSessionCallback callback,
- gpointer user_data);
-void ephy_sync_service_release_next_storage_message (EphySyncService *self);
-void ephy_sync_service_upload_bookmark (EphySyncService *self,
- EphyBookmark *bookmark,
- gboolean force);
-void ephy_sync_service_download_bookmark (EphySyncService *self,
- EphyBookmark *bookmark);
-void ephy_sync_service_delete_bookmark (EphySyncService *self,
- EphyBookmark *bookmark,
- gboolean conditional);
-void ephy_sync_service_sync_bookmarks (EphySyncService *self,
- gboolean first);
-void ephy_sync_service_start_periodical_sync (EphySyncService *self,
- gboolean now);
-void ephy_sync_service_stop_periodical_sync (EphySyncService *self);
+EphySyncService *ephy_sync_service_new (void);
+gboolean ephy_sync_service_is_signed_in (EphySyncService *self);
+const char *ephy_sync_service_get_user_email (EphySyncService *self);
+void ephy_sync_service_do_sign_in (EphySyncService *self,
+ const char *email,
+ const char *uid,
+ const char *session_token,
+ const char *key_fetch_token,
+ const char *unwrap_b_key);
+void ephy_sync_service_do_sign_out (EphySyncService *self);
+void ephy_sync_service_do_sync (EphySyncService *self);
+void ephy_sync_service_start_periodical_sync (EphySyncService *self);
+void ephy_sync_service_register_manager (EphySyncService *self,
+ EphySynchronizableManager *manager);
+void ephy_sync_service_unregister_manager (EphySyncService *self,
+ EphySynchronizableManager *manager);
G_END_DECLS
diff --git a/src/sync/ephy-synchronizable-manager.c b/src/sync/ephy-synchronizable-manager.c
new file mode 100644
index 0000000..a1def1d
--- /dev/null
+++ b/src/sync/ephy-synchronizable-manager.c
@@ -0,0 +1,249 @@
+/* -*- 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-synchronizable-manager.h"
+
+G_DEFINE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, G_TYPE_OBJECT);
+
+enum {
+ SYNCHRONIZABLE_DELETED,
+ SYNCHRONIZABLE_MODIFIED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+ephy_synchronizable_manager_default_init (EphySynchronizableManagerInterface *iface)
+{
+ iface->get_collection_name = ephy_synchronizable_manager_get_collection_name;
+ iface->get_synchronizable_type = ephy_synchronizable_manager_get_synchronizable_type;
+ iface->is_initial_sync = ephy_synchronizable_manager_is_initial_sync;
+ iface->set_is_initial_sync = ephy_synchronizable_manager_set_is_initial_sync;
+ iface->get_sync_time = ephy_synchronizable_manager_get_sync_time;
+ iface->set_sync_time = ephy_synchronizable_manager_set_sync_time;
+ iface->add = ephy_synchronizable_manager_add;
+ iface->remove = ephy_synchronizable_manager_remove;
+ iface->merge_remotes = ephy_synchronizable_manager_merge_remotes;
+
+ signals[SYNCHRONIZABLE_DELETED] =
+ g_signal_new ("synchronizable-deleted",
+ EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ EPHY_TYPE_SYNCHRONIZABLE);
+
+ signals[SYNCHRONIZABLE_MODIFIED] =
+ g_signal_new ("synchronizable-modified",
+ EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ EPHY_TYPE_SYNCHRONIZABLE);
+}
+
+/**
+ * ephy_synchronizable_manager_get_collection_name:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the name of the collection managed by @manager.
+ *
+ * Return value: (transfer none): @manager's collection name
+ **/
+const char *
+ephy_synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ return iface->get_collection_name (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_get_synchronizable_type:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the #GType of the #EphySynchronizable objects managed by @manager.
+ *
+ * Return value: the #GType of @manager's objects
+ **/
+GType
+ephy_synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), 0);
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ return iface->get_synchronizable_type (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_is_initial_sync:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns a boolean saying whether the collection managed by @manager requires
+ * an initial sync (i.e. a first time sync).
+ *
+ * Return value: %TRUE is @manager's collections requires an initial sync, %FALSE otherwise
+ **/
+gboolean
+ephy_synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), FALSE);
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ return iface->is_initial_sync (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_set_is_initial_sync:
+ * @manager: an #EphySynchronizableManager
+ * @is_initial: a boolean saying whether the collection managed by @manager
+ * requires an initial sync (i.e. a first time sync)
+ *
+ * Sets @manager's 'requires initial sync' flag.
+ **/
+void
+ephy_synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+ gboolean is_initial)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ iface->set_is_initial_sync (manager, is_initial);
+}
+
+/**
+ * ephy_synchronizable_manager_get_sync_time:
+ * @manager: an #EphySynchronizableManager
+ *
+ * Returns the timestamp at which @manager's collection was last synced,
+ * in seconds since UNIX epoch.
+ *
+ * Return value: the timestamp of @manager's collection last sync.
+ **/
+double
+ephy_synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), 0);
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ return iface->get_sync_time (manager);
+}
+
+/**
+ * ephy_synchronizable_manager_set_sync_time:
+ * @manager: an #EphySynchronizableManager
+ * @sync_time: the timestamp of the last sync, in seconds since UNIX epoch
+ *
+ * Sets the timestamp at which @manager's collection was last synced.
+ **/
+void
+ephy_synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+ double sync_time)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ iface->set_sync_time (manager, sync_time);
+}
+
+/**
+ * ephy_synchronizable_manager_add:
+ * @manager: an #EphySynchronizableManager
+ * @synchronizable: (transfer none): an #EphySynchronizable
+ *
+ * Adds @synchronizable to the local instance of the collection managed by @manager.
+ **/
+void
+ephy_synchronizable_manager_add (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ iface->add (manager, synchronizable);
+}
+
+/**
+ * ephy_synchronizable_manager_remove:
+ * @manager: an #EphySynchronizableManager
+ * @synchronizable: (transfer full): an #EphySynchronizable
+ *
+ * Removes @synchronizable from the local instance of the collection managed by @manager.
+ **/
+void
+ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ iface->remove (manager, synchronizable);
+}
+
+/**
+ * ephy_synchronizable_manager_merge_remotes:
+ * @manager: an #EphySynchronizableManager
+ * @is_initial: a boolean saying whether the collection managed by @manager
+ * requires an initial sync (i.e. a first time sync)
+ * @remotes_deleted: (transfer none): a #GSList holding the #EphySynchronizable
+ * 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.
+ *
+ * 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.
+ **/
+GSList *
+ephy_synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated)
+{
+ EphySynchronizableManagerInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager), NULL);
+
+ iface = EPHY_SYNCHRONIZABLE_MANAGER_GET_IFACE (manager);
+ return iface->merge_remotes (manager, is_initial, remotes_deleted, remotes_updated);
+}
diff --git a/src/sync/ephy-synchronizable-manager.h b/src/sync/ephy-synchronizable-manager.h
new file mode 100644
index 0000000..a59a2a0
--- /dev/null
+++ b/src/sync/ephy-synchronizable-manager.h
@@ -0,0 +1,71 @@
+/* -*- 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-synchronizable.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNCHRONIZABLE_MANAGER (ephy_synchronizable_manager_get_type ())
+
+G_DECLARE_INTERFACE (EphySynchronizableManager, ephy_synchronizable_manager, EPHY, SYNCHRONIZABLE_MANAGER,
GObject)
+
+struct _EphySynchronizableManagerInterface {
+ GTypeInterface parent_iface;
+
+ const char * (*get_collection_name) (EphySynchronizableManager *manager);
+ GType (*get_synchronizable_type) (EphySynchronizableManager *manager);
+ gboolean (*is_initial_sync) (EphySynchronizableManager *manager);
+ void (*set_is_initial_sync) (EphySynchronizableManager *manager,
+ gboolean is_initial);
+ double (*get_sync_time) (EphySynchronizableManager *manager);
+ void (*set_sync_time) (EphySynchronizableManager *manager,
+ double sync_time);
+ void (*add) (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable);
+ void (*remove) (EphySynchronizableManager *manager,
+ EphySynchronizable *synchronizable);
+ GSList * (*merge_remotes) (EphySynchronizableManager *manager,
+ gboolean is_initial,
+ GSList *remotes_deleted,
+ GSList *remotes_updated);
+};
+
+const char *ephy_synchronizable_manager_get_collection_name (EphySynchronizableManager *manager);
+GType ephy_synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager);
+gboolean ephy_synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager);
+void ephy_synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
+ gboolean
is_initial);
+double ephy_synchronizable_manager_get_sync_time (EphySynchronizableManager *manager);
+void ephy_synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
+ double
sync_time);
+void ephy_synchronizable_manager_add (EphySynchronizableManager *manager,
+ EphySynchronizable
*synchronizable);
+void ephy_synchronizable_manager_remove (EphySynchronizableManager *manager,
+ EphySynchronizable
*synchronizable);
+GSList *ephy_synchronizable_manager_merge_remotes (EphySynchronizableManager *manager,
+ gboolean
is_initial,
+ GSList
*remotes_deleted,
+ GSList
*remotes_updated);
+
+G_END_DECLS
diff --git a/src/sync/ephy-synchronizable.c b/src/sync/ephy-synchronizable.c
new file mode 100644
index 0000000..410b54e
--- /dev/null
+++ b/src/sync/ephy-synchronizable.c
@@ -0,0 +1,246 @@
+/* -*- 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-synchronizable.h"
+
+G_DEFINE_INTERFACE (EphySynchronizable, ephy_synchronizable, JSON_TYPE_SERIALIZABLE);
+
+static void
+ephy_synchronizable_default_init (EphySynchronizableInterface *iface)
+{
+ iface->get_id = ephy_synchronizable_get_id;
+ iface->get_server_time_modified = ephy_synchronizable_get_server_time_modified;
+ iface->set_server_time_modified = ephy_synchronizable_set_server_time_modified;
+ iface->to_bso = ephy_synchronizable_to_bso;
+}
+
+/**
+ * ephy_synchronizable_get_id:
+ * @synchronizable: an #EphySynchronizable
+ *
+ * Returns @synchronizable's id.
+ *
+ * Return value: (transfer none): @synchronizable's id
+ **/
+const char *
+ephy_synchronizable_get_id (EphySynchronizable *synchronizable)
+{
+ EphySynchronizableInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+
+ iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+ return iface->get_id (synchronizable);
+}
+
+/**
+ * ephy_synchronizable_get_server_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ *
+ * Returns @synchronizable's last modification time on the storage server.
+ *
+ * Return value: @synchronizable's last modification time
+ **/
+double
+ephy_synchronizable_get_server_time_modified (EphySynchronizable *synchronizable)
+{
+ EphySynchronizableInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), 0);
+
+ iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+ return iface->get_server_time_modified (synchronizable);
+}
+
+/**
+ * ephy_synchronizable_set_server_time_modified:
+ * @synchronizable: an #EphySynchronizable
+ * @server_time_modified: the last modification time on the storage server
+ *
+ * Sets @server_time_modified as @synchronizable's last modification time.
+ **/
+void
+ephy_synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+ double server_time_modified)
+{
+ EphySynchronizableInterface *iface;
+
+ g_return_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable));
+
+ iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+ iface->set_server_time_modified (synchronizable, server_time_modified);
+}
+
+/**
+ * ephy_synchronizable_to_bso:
+ * @synchronizable: an #EphySynchronizable
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ * used to validate and encrypt the Basic Storage Object
+ *
+ * Converts an #EphySynchronizable into its JSON string representation
+ * of a Basic Storage Object from the client's point of view
+ * (i.e. the %modified field is missing). Check the BSO format documentation
+ * (https://docs.services.mozilla.com/storage/apis-1.5.html#basic-storage-object)
+ * for more details.
+ *
+ * Return value: (transfer full): @synchronizable's BSO representation as a #JsonNode
+ **/
+JsonNode *
+ephy_synchronizable_to_bso (EphySynchronizable *synchronizable,
+ SyncCryptoKeyBundle *bundle)
+{
+ EphySynchronizableInterface *iface;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+ g_return_val_if_fail (bundle, NULL);
+
+ iface = EPHY_SYNCHRONIZABLE_GET_IFACE (synchronizable);
+ return iface->to_bso (synchronizable, bundle);
+}
+
+/**
+ * ephy_synchronizable_from_bso:
+ * @bso: a #JsonNode representing the Basic Storage Object
+ * @gtype: the #GType of object to construct
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ * used to validate and decrypt the Basic Storage Object
+ * @is_deleted: return value for a flag that says whether the object
+ * was marked as deleted
+ *
+ * Converts a JSON object representing the Basic Storage Object
+ * from the server's point of view (i.e. the %modified field is present)
+ * into an object of type @gtype. See the BSO format documentation
+ * (https://docs.services.mozilla.com/storage/apis-1.5.html#basic-storage-object)
+ * for more details.
+ *
+ * Note: The @gtype must be a sub-type of #EphySynchronizable (i.e. must
+ * implement the #EphySynchronizable interface). It is up to the caller to cast
+ * the returned #GObject to the type of @gtype.
+ *
+ * Return value: (transfer full): a #GObject or %NULL
+ **/
+GObject *
+ephy_synchronizable_from_bso (JsonNode *bso,
+ GType gtype,
+ SyncCryptoKeyBundle *bundle,
+ gboolean *is_deleted)
+{
+ GObject *object = NULL;
+ GError *error = NULL;
+ JsonNode *node = NULL;
+ JsonObject *json;
+ char *serialized = NULL;
+ const char *payload = NULL;
+ double server_time_modified;
+
+ g_return_val_if_fail (bso, NULL);
+ g_return_val_if_fail (bundle, NULL);
+ g_return_val_if_fail (is_deleted, NULL);
+
+ json = json_node_get_object (bso);
+ if (!json) {
+ g_warning ("JSON node does not hold a JSON object");
+ goto out;
+ }
+ payload = json_object_get_string_member (json, "payload");
+ server_time_modified = json_object_get_double_member (json, "modified");
+ if (!payload || !server_time_modified) {
+ g_warning ("JSON object has missing or invalid members");
+ goto out;
+ }
+
+ serialized = ephy_sync_crypto_decrypt_record (payload, bundle);
+ if (!serialized) {
+ g_warning ("Failed to decrypt the BSO payload");
+ goto out;
+ }
+ node = json_from_string (serialized, &error);
+ if (error) {
+ g_warning ("Decrypted text is not a valid JSON: %s", error->message);
+ goto out;
+ }
+ json = json_node_get_object (node);
+ if (!json) {
+ g_warning ("Decrypted JSON node does not hold a JSON object");
+ goto out;
+ }
+ *is_deleted = json_object_has_member (json, "deleted");
+
+ object = json_gobject_from_data (gtype, serialized, -1, &error);
+ if (error) {
+ g_warning ("Failed to create GObject from BSO: %s", error->message);
+ goto out;
+ }
+
+ ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (object),
+ server_time_modified);
+
+out:
+ if (node)
+ json_node_unref (node);
+ if (error)
+ g_error_free (error);
+ g_free (serialized);
+
+ return object;
+}
+
+/**
+ * ephy_synchronizable_default_to_bso:
+ * @synchronizable: an #EphySynchronizable
+ * @bundle: a %SyncCryptoKeyBundle holding the encryption key and the HMAC key
+ * used to validate and encrypt the Basic Storage Object
+ *
+ * Calls the default implementation of the #EphySynchronizable
+ * #EphySynchronizableInterface.to_bso() virtual function.
+ *
+ * This function can be used inside a custom implementation of the
+ * #EphySynchronizableInterface.to_bso() virtual function in lieu of
+ * calling the default implementation through g_type_default_interface_peek().
+ *
+ * Return value: (transfer full): @synchronizable's BSO representation as a #JsonNode
+ **/
+JsonNode *
+ephy_synchronizable_default_to_bso (EphySynchronizable *synchronizable,
+ SyncCryptoKeyBundle *bundle)
+{
+ JsonNode *bso;
+ JsonObject *object;
+ char *serialized;
+ char *payload;
+
+ g_return_val_if_fail (EPHY_IS_SYNCHRONIZABLE (synchronizable), NULL);
+ g_return_val_if_fail (bundle, NULL);
+
+ serialized = json_gobject_to_data (G_OBJECT (synchronizable), NULL);
+ payload = ephy_sync_crypto_encrypt_record (serialized, bundle);
+ bso = json_node_new (JSON_NODE_OBJECT);
+ object = json_object_new ();
+ json_object_set_string_member (object, "id", ephy_synchronizable_get_id (synchronizable));
+ json_object_set_string_member (object, "payload", payload);
+ json_node_set_object (bso, object);
+
+ json_object_unref (object);
+ g_free (payload);
+ g_free (serialized);
+
+ return bso;
+}
diff --git a/src/sync/ephy-synchronizable.h b/src/sync/ephy-synchronizable.h
new file mode 100644
index 0000000..84f77da
--- /dev/null
+++ b/src/sync/ephy-synchronizable.h
@@ -0,0 +1,59 @@
+/* -*- 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-sync-crypto.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNCHRONIZABLE (ephy_synchronizable_get_type ())
+
+G_DECLARE_INTERFACE (EphySynchronizable, ephy_synchronizable, EPHY, SYNCHRONIZABLE, JsonSerializable)
+
+struct _EphySynchronizableInterface {
+ GTypeInterface parent_iface;
+
+ const char * (*get_id) (EphySynchronizable *synchronizable);
+ double (*get_server_time_modified) (EphySynchronizable *synchronizable);
+ void (*set_server_time_modified) (EphySynchronizable *synchronizable,
+ double time_modified);
+ JsonNode * (*to_bso) (EphySynchronizable *synchronizable,
+ SyncCryptoKeyBundle *bundle);
+};
+
+const char *ephy_synchronizable_get_id (EphySynchronizable *synchronizable);
+double ephy_synchronizable_get_server_time_modified (EphySynchronizable *synchronizable);
+void ephy_synchronizable_set_server_time_modified (EphySynchronizable *synchronizable,
+ double time_modified);
+JsonNode *ephy_synchronizable_to_bso (EphySynchronizable *synchronizable,
+ SyncCryptoKeyBundle *bundle);
+/* This can't be an interface method because we lack the EphySynchronizable object. */
+GObject *ephy_synchronizable_from_bso (JsonNode *bso,
+ GType gtype,
+ SyncCryptoKeyBundle *bundle,
+ gboolean *is_deleted);
+/* Default implementations. */
+JsonNode *ephy_synchronizable_default_to_bso (EphySynchronizable *synchronizable,
+ SyncCryptoKeyBundle *bundle);
+
+G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]