[epiphany] sync: Fix endless sign-in confirmation loop



commit 8497b9819576e71535a805bd26e4bb50e98781ca
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Mon Oct 10 00:31:38 2016 +0300

    sync: Fix endless sign-in confirmation loop
    
    The initial approach of sign-in was a faulty, since it didn't take into
    consideration the "sign-in confirmation email" feature that Mozilla was
    going to introduce.
    
    Instead of destroying the session and reloading the Firefox Accounts iframe
    in case of an unverified account, keep the session alive and, with the
    help of keyFetchToken, poll the '/account/keys' endpoint repeatedly until
    the verification had finished, without reloading the iframe. When a 200 OK
    response is received from the server, proceed to derive the sync keys
    and display the 'signed in as' panel.

 src/prefs-dialog.c           |  202 ++++++++++++++++++++++++++++++------------
 src/sync/ephy-sync-service.c |   88 ++++++++++++++++++
 src/sync/ephy-sync-service.h |   12 +++
 3 files changed, 247 insertions(+), 55 deletions(-)
---
diff --git a/src/prefs-dialog.c b/src/prefs-dialog.c
index b85f203..2d7e809 100644
--- a/src/prefs-dialog.c
+++ b/src/prefs-dialog.c
@@ -36,6 +36,7 @@
 #include "ephy-session.h"
 #include "ephy-settings.h"
 #include "ephy-shell.h"
+#include "ephy-sync-crypto.h"
 #include "ephy-sync-secret.h"
 #include "ephy-sync-service.h"
 #include "clear-data-dialog.h"
@@ -113,8 +114,22 @@ struct _PrefsDialog {
   WebKitWebView *fxa_web_view;
   WebKitUserContentManager *fxa_manager;
   WebKitUserScript *fxa_script;
+  guint source_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 {
   SEARCH_ENGINE_COL_NAME,
   SEARCH_ENGINE_COL_STOCK_URL,
@@ -124,6 +139,57 @@ 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)
 {
@@ -147,10 +213,64 @@ prefs_dialog_finalize (GObject *object)
     g_object_unref (dialog->fxa_manager);
   }
 
+  if (dialog->source_id != 0) {
+    g_source_remove (dialog->source_id);
+    dialog->source_id = 0;
+  }
+
   G_OBJECT_CLASS (prefs_dialog_parent_class)->finalize (object);
 }
 
 static void
+hide_fxa_iframe (PrefsDialog *dialog,
+                 const char  *email)
+{
+  char *text;
+  char *account;
+
+  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);
+
+  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);
+}
+
+static gboolean
+poll_fxa_server (gpointer user_data)
+{
+  FxACallbackData *data;
+  EphySyncService *service;
+  char *bundle;
+
+  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);
+
+  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);
+    hide_fxa_iframe (data->dialog, data->email);
+
+    fxa_callback_data_free (data);
+    data->dialog->source_id = 0;
+
+    return G_SOURCE_REMOVE;
+  }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
 inject_data_to_server (PrefsDialog *dialog,
                        const char  *type,
                        const char  *status,
@@ -177,14 +297,6 @@ inject_data_to_server (PrefsDialog *dialog,
   g_free (script);
 }
 
-static gboolean
-sync_fxa_load_sign_in_url (PrefsDialog *dialog)
-{
-  webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
-
-  return G_SOURCE_REMOVE;
-}
-
 static void
 server_message_received_cb (WebKitUserContentManager *manager,
                             WebKitJavascriptResult   *result,
@@ -216,6 +328,7 @@ server_message_received_cb (WebKitUserContentManager *manager,
 
   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) {
     /* We need to confirm a relink. */
     inject_data_to_server (dialog, "message", "can_link_account", "{'ok': true}");
@@ -226,11 +339,13 @@ server_message_received_cb (WebKitUserContentManager *manager,
     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");
-    char *account;
+    guint8 *tokenID;
+    guint8 *reqHMACkey;
+    guint8 *respHMACkey;
+    guint8 *respXORkey;
     char *text;
 
     inject_data_to_server (dialog, "message", "login", NULL);
-    gtk_widget_set_visible (dialog->sync_sign_in_details, FALSE);
 
     /* Cannot retrieve the sync keys without keyFetchToken or unwrapBKey. */
     if (keyFetchToken == NULL || unwrapBKey == NULL) {
@@ -242,66 +357,42 @@ server_message_received_cb (WebKitUserContentManager *manager,
                               _("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);
-      g_timeout_add_seconds (3, (GSourceFunc) sync_fxa_load_sign_in_url, dialog);
+      webkit_web_view_load_uri (dialog->fxa_web_view, FXA_IFRAME_URL);
 
       g_free (text);
       goto out;
     }
 
-    /* Cannot retrieve the sync keys if account is not verified. */
+    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) {
-      g_warning ("Attempt to operate on an unverified account, giving up.");
-      ephy_sync_service_destroy_session (service, sessionToken);
+      FxACallbackData *cb_data;
 
       text = g_strdup_printf ("<span fgcolor='#e6780b'>%s</span>",
-                              _("Please verify your account before you sign in."));
+                              _("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);
-      g_timeout_add_seconds (3, (GSourceFunc) sync_fxa_load_sign_in_url, dialog);
 
-      g_free (text);
-      goto out;
-    }
-
-    /* We cannot sync without the sync keys. */
-    if (ephy_sync_service_fetch_sync_keys (service, email, keyFetchToken, unwrapBKey) == FALSE) {
-      g_warning ("Failed to retrieve the sync keys, giving up.");
-      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);
-      g_timeout_add_seconds (3, (GSourceFunc) sync_fxa_load_sign_in_url, dialog);
+      cb_data = fxa_callback_data_new (dialog, email, uid, sessionToken,
+                                       keyFetchToken, unwrapBKey, tokenID,
+                                       reqHMACkey, respHMACkey, respXORkey);
+      dialog->source_id = g_timeout_add_seconds (2, (GSourceFunc)poll_fxa_server, cb_data);
 
       g_free (text);
-      goto out;
-    }
-
-    /* Everything OK, save tokens. */
-    g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_SYNC_USER, email);
-    ephy_sync_service_set_and_store_tokens (service,
-                                            g_strdup (uid), TOKEN_UID,
-                                            g_strdup (sessionToken), TOKEN_SESSIONTOKEN,
-                                            NULL);
-
-    /* Do a first time sync and set a periodical sync afterwards. */
-    ephy_sync_service_sync_bookmarks (service, TRUE);
-    ephy_sync_service_start_periodical_sync (service, FALSE);
-
-    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);
+    } else {
+      char *bundle;
 
-    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);
+      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);
+      hide_fxa_iframe (dialog, email);
 
-    g_free (text);
-    g_free (account);
+      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);
@@ -386,6 +477,7 @@ on_sync_sign_out_button_clicked (GtkWidget   *button,
   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
diff --git a/src/sync/ephy-sync-service.c b/src/sync/ephy-sync-service.c
index 908f1dc..a26e144 100644
--- a/src/sync/ephy-sync-service.c
+++ b/src/sync/ephy-sync-service.c
@@ -845,6 +845,94 @@ ephy_sync_service_destroy_session (EphySyncService *self,
   g_free (url);
 }
 
+char *
+ephy_sync_service_start_sign_in (EphySyncService  *self,
+                                 guint8           *tokenID,
+                                 guint8           *reqHMACkey)
+{
+  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);
+
+  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"));
+  }
+
+  g_free (tokenID_hex);
+  json_node_free (node);
+
+  return bundle;
+}
+
+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;
+
+  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);
+
+  /* Save 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_and_store_tokens (self,
+                                          g_strdup (uid), TOKEN_UID,
+                                          g_strdup (sessionToken), TOKEN_SESSIONTOKEN,
+                                          g_strdup (keyFetchToken), TOKEN_KEYFETCHTOKEN,
+                                          g_strdup (unwrapBKey), TOKEN_UNWRAPBKEY,
+                                          ephy_sync_crypto_encode_hex (kA, 0), TOKEN_KA,
+                                          ephy_sync_crypto_encode_hex (kB, 0), TOKEN_KB,
+                                          NULL);
+
+  /* Do a first time sync and set a periodical sync afterwards. */
+  ephy_sync_service_sync_bookmarks (self, TRUE);
+  ephy_sync_service_start_periodical_sync (self, FALSE);
+
+  g_free (kA);
+  g_free (kB);
+  g_free (bundle);
+  g_free (unwrapKB);
+}
+
 gboolean
 ephy_sync_service_fetch_sync_keys (EphySyncService *self,
                                    const char      *email,
diff --git a/src/sync/ephy-sync-service.h b/src/sync/ephy-sync-service.h
index dbfe3ce..29fc494 100644
--- a/src/sync/ephy-sync-service.h
+++ b/src/sync/ephy-sync-service.h
@@ -53,6 +53,18 @@ void             ephy_sync_service_clear_storage_credentials    (EphySyncService
 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);
 gboolean         ephy_sync_service_fetch_sync_keys              (EphySyncService *self,
                                                                  const char      *email,
                                                                  const char      *keyFetchToken,


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