[epiphany/wip/ephy-sync: 1/48] ephy-sync: Add basic sync service and sync window



commit c2577444c34f9c0d77019aec66e37e0357100dd5
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Sun May 22 22:55:21 2016 +0300

    ephy-sync: Add basic sync service and sync window

 configure.ac                               |    4 +
 src/Makefile.am                            |    6 +
 src/ephy-shell.c                           |   57 +++++++
 src/ephy-shell.h                           |    4 +
 src/ephy-sync-crypto.c                     |  141 +++++++++++++++++
 src/ephy-sync-crypto.h                     |   52 +++++++
 src/ephy-sync-service.c                    |  164 ++++++++++++++++++++
 src/ephy-sync-service.h                    |   46 ++++++
 src/ephy-sync-window.c                     |  227 ++++++++++++++++++++++++++++
 src/ephy-sync-window.h                     |   36 +++++
 src/epiphany.gresource.xml                 |    1 +
 src/resources/epiphany-application-menu.ui |    5 +
 src/resources/sync-dialog.ui               |  113 ++++++++++++++
 src/window-commands.c                      |   16 ++
 src/window-commands.h                      |    3 +
 15 files changed, 875 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1b2c67b..40c22ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -82,6 +82,8 @@ LIBNOTIFY_REQUIRED=0.5.1
 GCR_REQUIRED=3.5.5
 AVAHI_REQUIRED=0.6.22
 GVDB_REQUIRED="glib-2.0 >= $GLIB_REQUIRED"
+NETTLE_REQUIRED=3.2
+JSON_GLIB_MIN_VERSION=0.14
 
 # Tests
 
@@ -114,6 +116,8 @@ PKG_CHECK_MODULES([DEPENDENCIES], [
                  gcr-3 >= $GCR_REQUIRED
                  avahi-gobject >= $AVAHI_REQUIRED
                  avahi-client >= $AVAHI_REQUIRED
+                 nettle >= NETTLE_REQUIRED
+                 json-glib-1.0 >= $JSON_GLIB_MIN_VERSION
                  ])
 
 # Check requirements for gvdb
diff --git a/src/Makefile.am b/src/Makefile.am
index 22711cc..c2b936d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -55,6 +55,12 @@ libephymain_la_SOURCES = \
        ephy-encoding-row.h                     \
        ephy-history-window.c                   \
        ephy-history-window.h                   \
+       ephy-sync-window.c                      \
+       ephy-sync-window.h                      \
+       ephy-sync-service.c                     \
+       ephy-sync-service.h                     \
+       ephy-sync-crypto.c                      \
+       ephy-sync-crypto.h                      \
        ephy-link.c                             \
        ephy-link.h                             \
        ephy-location-controller.c              \
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index 7c51744..14c7c8c 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -30,6 +30,7 @@
 #include "ephy-file-helpers.h"
 #include "ephy-gui.h"
 #include "ephy-history-window.h"
+#include "ephy-sync-window.h"
 #include "ephy-lockdown.h"
 #include "ephy-prefs.h"
 #include "ephy-private.h"
@@ -49,6 +50,7 @@ struct _EphyShell {
   EphyEmbedShell parent_instance;
 
   EphySession *session;
+  EphySyncService *global_sync_service;
   GList *windows;
   GObject *lockdown;
   EphyBookmarks *bookmarks;
@@ -56,6 +58,7 @@ struct _EphyShell {
   GNetworkMonitor *network_monitor;
   GtkWidget *bme;
   GtkWidget *history_window;
+  GtkWidget *sync_window;
   GObject *prefs_dialog;
   EphyShellStartupContext *local_startup_context;
   EphyShellStartupContext *remote_startup_context;
@@ -183,6 +186,20 @@ show_history (GSimpleAction *action,
 }
 
 static void
+show_sync (GSimpleAction *action,
+           GVariant      *parameter,
+           gpointer      user_data)
+{
+  GtkWindow *window;
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell));
+
+  window_cmd_edit_sync (NULL, EPHY_WINDOW (window));
+}
+
+static void
 show_preferences (GSimpleAction *action,
                   GVariant      *parameter,
                   gpointer       user_data)
@@ -243,6 +260,7 @@ static GActionEntry app_entries[] = {
   { "new-incognito", new_incognito_window, NULL, NULL, NULL },
   { "bookmarks", show_bookmarks, NULL, NULL, NULL },
   { "history", show_history, NULL, NULL, NULL },
+  { "sync", show_sync, NULL, NULL, NULL },
   { "preferences", show_preferences, NULL, NULL, NULL },
   { "shortcuts", show_shortcuts, NULL, NULL, NULL },
   { "help", show_help, NULL, NULL, NULL },
@@ -797,6 +815,26 @@ ephy_shell_get_bookmarks_editor (EphyShell *shell)
 }
 
 /**
+ * ephy_shell_get_global_sync_service:
+ *
+ * Return value: (transfer none):
+ **/
+GObject *
+ephy_shell_get_global_sync_service (EphyShell *shell)
+{
+  g_return_val_if_fail (EPHY_IS_SHELL (shell), NULL);
+
+  if (shell->global_sync_service == NULL) {
+LOG ("%s:%d", __func__, __LINE__);
+    shell->global_sync_service = ephy_sync_service_new ();
+    g_return_val_if_fail (shell->global_sync_service, NULL);
+  }
+
+LOG ("%s:%d", __func__, __LINE__);
+  return G_OBJECT (shell->global_sync_service);
+}
+
+/**
  * ephy_shell_get_history_window:
  *
  * Return value: (transfer none):
@@ -822,6 +860,25 @@ ephy_shell_get_history_window (EphyShell *shell)
   return shell->history_window;
 }
 
+GtkWidget *
+ephy_shell_get_sync_window (EphyShell *shell)
+{
+  EphySyncService *sync_service;
+
+  if (shell->sync_window == NULL) {
+LOG ("%s:%d", __func__, __LINE__);
+    sync_service = EPHY_SYNC_SERVICE (ephy_shell_get_global_sync_service (shell));
+    shell->sync_window = ephy_sync_window_new (sync_service);
+    g_signal_connect (shell->sync_window,
+                      "destroy",
+                      G_CALLBACK (gtk_widget_destroyed),
+                      &shell->sync_window);
+  }
+
+LOG ("%s:%d", __func__, __LINE__);
+  return shell->sync_window;
+}
+
 /**
  * ephy_shell_get_prefs_dialog:
  *
diff --git a/src/ephy-shell.h b/src/ephy-shell.h
index 9ce4487..7c6b4f2 100644
--- a/src/ephy-shell.h
+++ b/src/ephy-shell.h
@@ -105,8 +105,12 @@ GtkWidget       *ephy_shell_get_bookmarks_editor         (EphyShell *shell);
 
 EphyBookmarksManager *ephy_shell_get_bookmarks_manager   (EphyShell *shell);
 
+GObject         *ephy_shell_get_global_sync_service      (EphyShell *shell);
+
 GtkWidget       *ephy_shell_get_history_window           (EphyShell *shell);
 
+GtkWidget       *ephy_shell_get_sync_window              (EphyShell *shell);
+
 GObject         *ephy_shell_get_prefs_dialog             (EphyShell *shell);
 
 guint           ephy_shell_get_n_windows                (EphyShell *shell);
diff --git a/src/ephy-sync-crypto.c b/src/ephy-sync-crypto.c
new file mode 100644
index 0000000..056c7ce
--- /dev/null
+++ b/src/ephy-sync-crypto.c
@@ -0,0 +1,141 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ephy-debug.h"
+#include "ephy-sync-crypto.h"
+
+#include <nettle/hmac.h>
+#include <nettle/pbkdf2.h>
+#include <string.h>
+
+static const gchar hex_digits[] = "0123456789abcdef";
+
+gchar *
+ephy_sync_crypto_kw (const gchar *name)
+{
+  return g_strconcat ("identity.mozilla.com/picl/v1/", name, NULL);
+}
+
+gchar *
+ephy_sync_crypto_kwe (const gchar *name,
+                      const gchar *emailUTF8)
+{
+  return g_strconcat ("identity.mozilla.com/picl/v1/", name, ":", emailUTF8, NULL);
+}
+
+gchar *
+ephy_sync_crypto_encode_hex (guint8 *data,
+                             gsize   data_length)
+{
+  gchar *retval = g_malloc (data_length * 2 + 1);
+
+  for (gsize i = 0; i < data_length; i++) {
+    guint8 byte = data[i];
+
+    retval[2 * i] = hex_digits[byte >> 4];
+    retval[2 * i + 1] = hex_digits[byte & 0xf];
+  }
+
+  retval[data_length * 2] = 0;
+
+  return retval;
+}
+
+/*
+ * Runs 1000 iterations of PBKDF2.
+ * Uses sha256 as hash function.
+ */
+void
+ephy_sync_crypto_pbkdf2_1k (guint8 *key,
+                            gsize   key_length,
+                            guint8 *salt,
+                            gsize   salt_length,
+                            guint8 *out,
+                            gsize   out_length)
+{
+  pbkdf2_hmac_sha256 (key_length, key, 1000, salt_length, salt, out_length, out);
+}
+
+/*
+ * HMAC-based Extract-and-Expand Key Derivation Function.
+ * Uses sha256 as hash function.
+ * https://tools.ietf.org/html/rfc5869
+ */
+void
+ephy_sync_crypto_hkdf (guint8 *in,
+                       gsize   in_length,
+                       guint8 *salt,
+                       gsize   salt_length,
+                       guint8 *info,
+                       gsize   info_length,
+                       guint8 *out,
+                       gsize   out_length)
+{
+  struct hmac_sha256_ctx ctx;
+  const gsize hash_length = 32;
+  gsize i, offset = 0;
+  guint8 *tmp, *prk;
+  guint8 counter;
+
+  if (out_length > hash_length * 255)
+    return;
+
+  /* If salt value was not provided, use an array of hash_length zeros */
+  if (salt == NULL) {
+    salt = g_malloc0 (hash_length);
+    salt_length = hash_length;
+  }
+
+  tmp = g_malloc0 (hash_length + info_length + 1);
+  prk = g_malloc0 (hash_length);
+
+  /* Step 1: Extract */
+  hmac_sha256_set_key (&ctx, salt_length, salt);
+  hmac_sha256_update (&ctx, in_length, in);
+  hmac_sha256_digest (&ctx, hash_length, prk);
+
+  /* Step 2: Expand */
+  hmac_sha256_set_key (&ctx, hash_length, prk);
+
+  for (i = 0, counter = 1; i < out_length; i += hash_length, counter++) {
+    memcpy (tmp + offset, info, info_length);
+    tmp[offset + info_length] = counter;
+
+    hmac_sha256_update (&ctx, offset + info_length + 1, tmp);
+    hmac_sha256_digest (&ctx, hash_length, tmp);
+
+    offset = hash_length;
+
+    memcpy (out + i, tmp, hash_length);
+  }
+
+  g_free (salt);
+  g_free (tmp);
+  g_free (prk);
+}
+
+/* FIXME: Only for debugging, remove when no longer needed */
+void
+ephy_sync_crypto_display_hex (guint8      *data,
+                              gsize        data_length,
+                              const gchar *data_name)
+{
+LOG ("%s:", data_name);
+for (gsize i = 0; i < data_length; i++)
+  LOG ("%02x", data[i]);
+}
diff --git a/src/ephy-sync-crypto.h b/src/ephy-sync-crypto.h
new file mode 100644
index 0000000..cb83395
--- /dev/null
+++ b/src/ephy-sync-crypto.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EPHY_SYNC_CRYPTO_H
+#define EPHY_SYNC_CRYPTO_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+gchar *ephy_sync_crypto_kw          (const gchar *name);
+gchar *ephy_sync_crypto_kwe         (const gchar *name,
+                                     const gchar *emailUTF8);
+gchar *ephy_sync_crypto_encode_hex  (guint8 *data,
+                                     gsize   data_length);
+void   ephy_sync_crypto_pbkdf2_1k   (guint8 *key,
+                                     gsize   key_length,
+                                     guint8 *salt,
+                                     gsize   salt_length,
+                                     guint8 *out,
+                                     gsize   out_length);
+void   ephy_sync_crypto_hkdf        (guint8 *in,
+                                     gsize   in_length,
+                                     guint8 *salt,
+                                     gsize   salt_length,
+                                     guint8 *info,
+                                     gsize   info_length,
+                                     guint8 *out,
+                                     gsize   out_length);
+/* FIXME: Only for debugging, remove when no longer needed */
+void   ephy_sync_crypto_display_hex (guint8      *data,
+                                     gsize        data_length,
+                                     const gchar *data_name);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ephy-sync-service.c b/src/ephy-sync-service.c
new file mode 100644
index 0000000..a610f9a
--- /dev/null
+++ b/src/ephy-sync-service.c
@@ -0,0 +1,164 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ephy-debug.h"
+#include "ephy-sync-crypto.h"
+#include "ephy-sync-service.h"
+
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+#include <string.h>
+
+struct _EphySyncService {
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
+
+static void
+ephy_sync_service_class_init (EphySyncServiceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class = object_class; // suppress warnings
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  // TODO: Set finalize, dispose, set/get property methods
+}
+
+static void
+ephy_sync_service_init (EphySyncService *self)
+{
+LOG ("%s:%d", __func__, __LINE__);
+}
+
+static void
+server_response_cb (SoupSession *session,
+                    SoupMessage *message,
+                    gpointer user_data)
+{
+  if (message->status_code == 200) {
+LOG ("response body: %s", message->response_body->data);
+    // TODO: parse response data using JsonParser
+  } else {
+LOG ("Error response from server: [%u] %s", message->status_code, message->reason_phrase);
+  }
+}
+
+EphySyncService *
+ephy_sync_service_new (void)
+{
+LOG ("%s:%d", __func__, __LINE__);
+
+  return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE,
+                                          NULL));
+}
+
+void
+ephy_sync_service_try_login (EphySyncService *self,
+                             gboolean login_with_keys,
+                             const gchar *emailUTF8,
+                             guint8 *authPW,
+                             guint8 *sessionToken,
+                             guint8 *keyFetchToken)
+{
+  SoupSession *session;
+  SoupMessage *message;
+  char *request_body;
+  char *authPW_hex;
+
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
+                                           "test-json",
+                                           NULL);
+  message = soup_message_new (SOUP_METHOD_POST,
+                              "https://api.accounts.firefox.com/v1/account/login";);
+
+  authPW_hex = ephy_sync_crypto_encode_hex (authPW, EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  request_body = g_strconcat ("{\"authPW\": \"",
+                              authPW_hex,
+                              "\", \"email\": \"",
+                              emailUTF8,
+                              "\"}",
+                              NULL);
+
+  soup_message_set_request (message,
+                            "application/json",
+                            SOUP_MEMORY_COPY,
+                            request_body,
+                            strlen (request_body));
+
+  soup_session_queue_message (session, message, server_response_cb, NULL);
+
+  // TODO: find a way to safely free authPW_hex, request_body
+  // TODO: find a way to safely destroy session, message
+}
+
+void
+ephy_sync_service_stretch (EphySyncService *self,
+                           const gchar *emailUTF8,
+                           const gchar *passwordUTF8,
+                           guint8 *authPW,
+                           guint8 *unwrapBKey)
+{
+  gchar *salt_stretch;
+  gchar *info_auth;
+  gchar *info_unwrap;
+  guint8 *quickStretchedPW;
+
+  g_return_if_fail (EPHY_IS_SYNC_SERVICE (self));
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  salt_stretch = ephy_sync_crypto_kwe ("quickStretch", emailUTF8);
+  quickStretchedPW = g_malloc (EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  ephy_sync_crypto_pbkdf2_1k ((guint8 *) passwordUTF8,
+                              strlen (passwordUTF8),
+                              (guint8 *) salt_stretch,
+                              strlen (salt_stretch),
+                              quickStretchedPW,
+                              EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+
+ephy_sync_crypto_display_hex (quickStretchedPW, EPHY_SYNC_SERVICE_TOKEN_LENGTH, "quickStretchedPW");
+
+  info_auth = ephy_sync_crypto_kw ("authPW");
+  ephy_sync_crypto_hkdf (quickStretchedPW,
+                         EPHY_SYNC_SERVICE_TOKEN_LENGTH,
+                         NULL, 0,
+                         (guint8 *) info_auth,
+                         strlen (info_auth),
+                         authPW,
+                         EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+
+  info_unwrap = ephy_sync_crypto_kw ("unwrapBkey");
+  ephy_sync_crypto_hkdf (quickStretchedPW,
+                         EPHY_SYNC_SERVICE_TOKEN_LENGTH,
+                         NULL, 0,
+                         (guint8 *) info_unwrap,
+                         strlen (info_unwrap),
+                         unwrapBKey,
+                         EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+
+  g_free (salt_stretch);
+  g_free (info_unwrap);
+  g_free (info_auth);
+  g_free (quickStretchedPW);
+}
diff --git a/src/ephy-sync-service.h b/src/ephy-sync-service.h
new file mode 100644
index 0000000..9f6b2a7
--- /dev/null
+++ b/src/ephy-sync-service.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EPHY_SYNC_SERVICE_H
+#define EPHY_SYNC_SERVICE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNC_SERVICE          (ephy_sync_service_get_type ())
+#define EPHY_SYNC_SERVICE_TOKEN_LENGTH  32
+
+G_DECLARE_FINAL_TYPE (EphySyncService, ephy_sync_service, EPHY, SYNC_SERVICE, GObject)
+
+EphySyncService *ephy_sync_service_new          (void);
+void             ephy_sync_service_stretch      (EphySyncService *self,
+                                                 const gchar *emailUTF8,
+                                                 const gchar *passwordUTF8,
+                                                 guint8 *authPW,
+                                                 guint8 *unwrapBKey);
+void             ephy_sync_service_try_login    (EphySyncService *self,
+                                                 gboolean login_with_keys,
+                                                 const gchar *emailUTF8,
+                                                 guint8 *authPW,
+                                                 guint8 *sessionToken,
+                                                 guint8 *keyFetchToken);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ephy-sync-window.c b/src/ephy-sync-window.c
new file mode 100644
index 0000000..51dc2be
--- /dev/null
+++ b/src/ephy-sync-window.c
@@ -0,0 +1,227 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ephy-debug.h"
+#include "ephy-gui.h"
+#include "ephy-sync-crypto.h"
+#include "ephy-sync-window.h"
+
+#include <gtk/gtk.h>
+#include <string.h>
+
+struct _EphySyncWindow {
+  GtkDialog parent_instance;
+
+  EphySyncService *sync_service;
+  GCancellable *cancellable;
+
+  GtkWidget *entry_email;
+  GtkWidget *entry_password;
+  GtkButton *btn_submit;
+
+  GActionGroup *action_group;
+};
+
+G_DEFINE_TYPE (EphySyncWindow, ephy_sync_window, GTK_TYPE_DIALOG)
+
+enum {
+  PROP_0,
+  PROP_SYNC_SERVICE,
+  PROP_LAST
+};
+
+static GParamSpec *obj_properties[PROP_LAST];
+
+static void
+submit_action (GSimpleAction *action,
+               GVariant      *parameter,
+               gpointer       user_data)
+{
+  const gchar *emailUTF8;
+  const gchar *passwordUTF8;
+  guint8 *authPW;
+  guint8 *unwrapBKey;
+  guint8 *sessionToken;
+  guint8 *keyFetchToken;
+  EphySyncWindow *self = EPHY_SYNC_WINDOW (user_data);
+
+  emailUTF8 = gtk_entry_get_text (GTK_ENTRY (self->entry_email));
+LOG ("email: %s", emailUTF8);
+  passwordUTF8 = gtk_entry_get_text (GTK_ENTRY (self->entry_password));
+LOG ("password: %s", passwordUTF8);
+
+  /* Only for easy testing */
+  if (!strlen (emailUTF8) && !strlen (passwordUTF8)) {
+    emailUTF8 = g_strdup ("andré@example.org");
+    passwordUTF8 = g_strdup ("pässwörd");
+  }
+
+  authPW = g_malloc (EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  unwrapBKey = g_malloc (EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  ephy_sync_service_stretch (self->sync_service,
+                             emailUTF8,
+                             passwordUTF8,
+                             authPW,
+                             unwrapBKey);
+ephy_sync_crypto_display_hex (authPW, EPHY_SYNC_SERVICE_TOKEN_LENGTH, "authPW");
+ephy_sync_crypto_display_hex (unwrapBKey, EPHY_SYNC_SERVICE_TOKEN_LENGTH, "unwrapBKey");
+
+  sessionToken = g_malloc (EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  keyFetchToken = g_malloc (EPHY_SYNC_SERVICE_TOKEN_LENGTH);
+  ephy_sync_service_try_login (self->sync_service,
+                               FALSE,
+                               emailUTF8,
+                               authPW,
+                               sessionToken,
+                               keyFetchToken);
+ephy_sync_crypto_display_hex (sessionToken, EPHY_SYNC_SERVICE_TOKEN_LENGTH, "sessionToken");
+ephy_sync_crypto_display_hex (keyFetchToken, EPHY_SYNC_SERVICE_TOKEN_LENGTH, "keyFetchToken");
+
+  g_free (authPW);
+  g_free (unwrapBKey);
+  g_free (sessionToken);
+  g_free (keyFetchToken);
+}
+
+static void
+set_sync_service (EphySyncWindow  *self,
+                  EphySyncService *sync_service)
+{
+  if (sync_service == self->sync_service)
+    return;
+
+  if (self->sync_service != NULL) {
+    // TODO: Disconnect signal handlers, if any
+    g_clear_object (&self->sync_service);
+  }
+
+  if (sync_service != NULL) {
+    self->sync_service = g_object_ref (sync_service);
+    // TODO: Connect signal handlers, if any
+  }
+}
+
+static void
+ephy_sync_window_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  EphySyncWindow *self = EPHY_SYNC_WINDOW (object);
+
+  switch (prop_id) {
+    case PROP_SYNC_SERVICE:
+      set_sync_service (self, g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+ephy_sync_window_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  EphySyncWindow *self = EPHY_SYNC_WINDOW (object);
+
+  switch (prop_id) {
+    case PROP_SYNC_SERVICE:
+      g_value_set_object (value, self->sync_service);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GActionGroup *
+create_action_group (EphySyncWindow *self)
+{
+  GSimpleActionGroup *group;
+
+  const GActionEntry entries[] = {
+    { "submit_action", submit_action }
+  };
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
+
+  return G_ACTION_GROUP (group);
+}
+
+static void
+ephy_sync_window_class_init (EphySyncWindowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  object_class->set_property = ephy_sync_window_set_property;
+  object_class->get_property = ephy_sync_window_get_property;
+  // TODO: Set dispose method
+
+  obj_properties[PROP_SYNC_SERVICE] =
+    g_param_spec_object ("sync-service",
+                         "Sync service",
+                         "Sync Service",
+                         EPHY_TYPE_SYNC_SERVICE,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, PROP_LAST, obj_properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/epiphany/sync-dialog.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, EphySyncWindow, entry_email);
+  gtk_widget_class_bind_template_child (widget_class, EphySyncWindow, entry_password);
+  gtk_widget_class_bind_template_child (widget_class, EphySyncWindow, btn_submit);
+}
+
+static void
+ephy_sync_window_init (EphySyncWindow *self)
+{
+LOG ("%s:%d", __func__, __LINE__);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->cancellable = g_cancellable_new ();
+
+  ephy_gui_ensure_window_group (GTK_WINDOW (self));
+
+  self->action_group = create_action_group (self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "sync", self->action_group);
+}
+
+GtkWidget *
+ephy_sync_window_new (EphySyncService *sync_service)
+{
+  EphySyncWindow *self;
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  self = g_object_new (EPHY_TYPE_SYNC_WINDOW,
+                       "use-header-bar", TRUE,
+                       "sync-service", sync_service,
+                       NULL);
+
+  return GTK_WIDGET (self);
+}
diff --git a/src/ephy-sync-window.h b/src/ephy-sync-window.h
new file mode 100644
index 0000000..0f95448
--- /dev/null
+++ b/src/ephy-sync-window.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2016 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This program 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 2, or (at your option)
+ *  any later version.
+ *
+ *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EPHY_SYNC_WINDOW_H
+#define EPHY_SYNC_WINDOW_H
+
+#include <gtk/gtk.h>
+
+#include "ephy-sync-service.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SYNC_WINDOW (ephy_sync_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphySyncWindow, ephy_sync_window, EPHY, SYNC_WINDOW, GtkDialog)
+
+GtkWidget       *ephy_sync_window_new        (EphySyncService *sync_service);
+
+G_END_DECLS
+
+#endif
diff --git a/src/epiphany.gresource.xml b/src/epiphany.gresource.xml
index 3212832..8c9b9cf 100644
--- a/src/epiphany.gresource.xml
+++ b/src/epiphany.gresource.xml
@@ -10,6 +10,7 @@
     <file preprocess="xml-stripblanks" compressed="true">clear-data-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">cookies-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">history-dialog.ui</file>
+    <file preprocess="xml-stripblanks" compressed="true">sync-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">passwords-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">shortcuts-dialog.ui</file>
     <file preprocess="xml-stripblanks" compressed="true">gtk/bookmark-properties-grid.ui</file>
diff --git a/src/resources/epiphany-application-menu.ui b/src/resources/epiphany-application-menu.ui
index bb0da59..c839232 100644
--- a/src/resources/epiphany-application-menu.ui
+++ b/src/resources/epiphany-application-menu.ui
@@ -29,6 +29,11 @@
         <attribute name="action">app.history</attribute>
         <attribute name="accel">&lt;Primary&gt;h</attribute>
       </item>
+      <item>
+        <attribute name="label" translatable="yes">_Sync</attribute>
+        <attribute name="action">app.sync</attribute>
+        <attribute name="accel">&lt;Primary&gt;s</attribute>
+      </item>
     </section>
     <section>
       <item>
diff --git a/src/resources/sync-dialog.ui b/src/resources/sync-dialog.ui
new file mode 100644
index 0000000..df88b37
--- /dev/null
+++ b/src/resources/sync-dialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+
+  <template class="EphySyncWindow" parent="GtkDialog">
+    <property name="height_request">400</property>
+    <property name="modal">True</property>
+    <property name="window_position">center</property>
+    <property name="default_width">600</property>
+    <property name="default_height">400</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="headerbar">
+      <object class="GtkHeaderBar">
+        <property name="title" translatable="yes">Sync</property>
+        <property name="show-close-button">True</property>
+      </object>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog_vbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkGrid" id="grid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">10</property>
+            <property name="row-spacing">6</property>
+            <property name="column-spacing">12</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Email</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0.5</property>
+                <property name="yalign">0.5</property>
+                <property name="halign">start</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="entry_email">
+                <property name="visible">True</property>
+                <property name="max-length">64</property>
+                <property name="width_request">200</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Password</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0.5</property>
+                <property name="yalign">0.5</property>
+                <property name="halign">start</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="entry_password">
+                <property name="visible">True</property>
+                <property name="max-length">64</property>
+                <property name="width_request">200</property>
+                <property name="visibility">False</property>
+                <property name="caps-lock-warning">True</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="btn_submit">
+                <property name="label" translatable="yes">_Submit</property>
+                <property name="visible">True</property>
+                <property name="use-underline">True</property>
+                <property name="sensitive">True</property>
+                <property name="valign">center</property>
+                <property name="action-name">sync.submit_action</property>
+                <style>
+                  <class name="suggested-action"/>
+                  <class name="text-button"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/window-commands.c b/src/window-commands.c
index 6b14b7e..cf16fcc 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -1334,6 +1334,22 @@ window_cmd_zoom_in (GSimpleAction *action,
 }
 
 void
+window_cmd_edit_sync (GtkAction  *action,
+                      EphyWindow *window)
+{
+  GtkWidget *swindow;
+
+LOG ("%s:%d", __func__, __LINE__);
+
+  swindow = ephy_shell_get_sync_window (ephy_shell_get_default ());
+
+  if (GTK_WINDOW (window) != gtk_window_get_transient_for (GTK_WINDOW (swindow)))
+    gtk_window_set_transient_for (GTK_WINDOW (swindow),
+                                  GTK_WINDOW (window));
+  gtk_window_present (GTK_WINDOW (swindow));
+}
+
+void
 window_cmd_zoom_out (GSimpleAction *action,
                      GVariant      *parameter,
                      gpointer       user_data)
diff --git a/src/window-commands.h b/src/window-commands.h
index deaaa4c..6a6930d 100644
--- a/src/window-commands.h
+++ b/src/window-commands.h
@@ -177,6 +177,9 @@ void window_cmd_tabs_detach                     (GSimpleAction *action,
 void window_cmd_tabs_close                      (GSimpleAction *action,
                                                  GVariant      *parameter,
                                                  gpointer       user_data);
+void window_cmd_edit_sync                 (GtkAction  *action,
+                                           EphyWindow *window);
+
 G_END_DECLS
 
 #endif


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