[epiphany/wip/sync: 1/18] sync: Add debug functions under ephy-sync-debug



commit 574b67d5d04bd8cb215e00f34cecb5ac2398cb4e
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date:   Sun Jun 4 20:55:49 2017 +0300

    sync: Add debug functions under ephy-sync-debug
    
    All these functions use synchronous API calls and should not be used in
    regular code.

 lib/ephy-sync-utils.c            |   43 ++
 lib/ephy-sync-utils.h            |   36 +-
 lib/sync/debug/ephy-sync-debug.c |  937 ++++++++++++++++++++++++++++++++++++++
 lib/sync/debug/ephy-sync-debug.h |   47 ++
 lib/sync/ephy-sync-service.c     |   61 +---
 lib/sync/meson.build             |    4 +-
 6 files changed, 1058 insertions(+), 70 deletions(-)
---
diff --git a/lib/ephy-sync-utils.c b/lib/ephy-sync-utils.c
index b97fc54..011e925 100644
--- a/lib/ephy-sync-utils.c
+++ b/lib/ephy-sync-utils.c
@@ -21,6 +21,7 @@
 #include "config.h"
 #include "ephy-sync-utils.h"
 
+#include <libsoup/soup.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -28,6 +29,20 @@
 
 static const char hex_digits[] = "0123456789abcdef";
 
+const SecretSchema *
+ephy_sync_utils_get_secret_schema (void)
+{
+  static const SecretSchema schema = {
+    "org.epiphany.SyncSecrets", SECRET_SCHEMA_NONE,
+    {
+      { ACCOUNT_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
+      { "NULL", 0 },
+    }
+  };
+
+  return &schema;
+}
+
 char *
 ephy_sync_utils_encode_hex (const guint8 *data,
                             gsize         data_len)
@@ -162,6 +177,34 @@ ephy_sync_utils_generate_random_bytes (void   *random_ctx,
 }
 
 char *
+ephy_sync_utils_get_audience (const char *url)
+{
+  SoupURI *uri;
+  const char *scheme;
+  const char *host;
+  char *audience;
+  char *port;
+
+  g_return_val_if_fail (url, NULL);
+
+  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;
+}
+
+char *
 ephy_sync_utils_get_random_sync_id (void)
 {
   char *id;
diff --git a/lib/ephy-sync-utils.h b/lib/ephy-sync-utils.h
index d459535..502b85b 100644
--- a/lib/ephy-sync-utils.h
+++ b/lib/ephy-sync-utils.h
@@ -21,21 +21,31 @@
 #pragma once
 
 #include <glib.h>
+#include <libsecret/secret.h>
 
 G_BEGIN_DECLS
 
-char   *ephy_sync_utils_encode_hex            (const guint8 *data,
-                                               gsize         data_len);
-guint8 *ephy_sync_utils_decode_hex            (const char   *hex);
-char   *ephy_sync_utils_base64_urlsafe_encode (const guint8 *data,
-                                               gsize         data_len,
-                                               gboolean      should_strip);
-guint8 *ephy_sync_utils_base64_urlsafe_decode (const char   *text,
-                                               gsize        *out_len,
-                                               gboolean      should_fill);
-void    ephy_sync_utils_generate_random_bytes (void         *random_ctx,
-                                               gsize         num_bytes,
-                                               guint8       *out);
-char   *ephy_sync_utils_get_random_sync_id    (void);
+const SecretSchema *ephy_sync_utils_get_secret_schema (void) G_GNUC_CONST;
+
+#define ACCOUNT_KEY "firefox_account"
+#define EPHY_SYNC_SECRET_SCHEMA (ephy_sync_utils_get_secret_schema ())
+
+#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";
+
+char   *ephy_sync_utils_encode_hex                  (const guint8 *data,
+                                                     gsize         data_len);
+guint8 *ephy_sync_utils_decode_hex                  (const char   *hex);
+char   *ephy_sync_utils_base64_urlsafe_encode       (const guint8 *data,
+                                                     gsize         data_len,
+                                                     gboolean      should_strip);
+guint8 *ephy_sync_utils_base64_urlsafe_decode       (const char   *text,
+                                                     gsize        *out_len,
+                                                     gboolean      should_fill);
+void    ephy_sync_utils_generate_random_bytes       (void         *random_ctx,
+                                                     gsize         num_bytes,
+                                                     guint8       *out);
+char   *ephy_sync_utils_get_audience                (const char   *url);
+char   *ephy_sync_utils_get_random_sync_id          (void);
 
 G_END_DECLS
diff --git a/lib/sync/debug/ephy-sync-debug.c b/lib/sync/debug/ephy-sync-debug.c
new file mode 100644
index 0000000..51f0daf
--- /dev/null
+++ b/lib/sync/debug/ephy-sync-debug.c
@@ -0,0 +1,937 @@
+/* -*- 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-sync-debug.h"
+
+#include "ephy-debug.h"
+#include "ephy-settings.h"
+#include "ephy-sync-crypto.h"
+#include "ephy-sync-utils.h"
+
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+#include <string.h>
+
+static JsonObject *
+ephy_sync_debug_load_secrets (void)
+{
+  GHashTable *attributes;
+  JsonObject *secrets = NULL;
+  SecretValue *value;
+  JsonNode *node;
+  GError *error = NULL;
+  GList *result;
+  char *user;
+
+  user = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
+  if (!g_strcmp0 (user, "")) {
+    LOG ("There is no sync user signed in.");
+    goto free_user;
+  }
+
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
+                                        ACCOUNT_KEY, user,
+                                        NULL);
+  result = secret_service_search_sync (NULL,
+                                       EPHY_SYNC_SECRET_SCHEMA,
+                                       attributes,
+                                       SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+                                       NULL,
+                                       &error);
+  if (error) {
+    LOG ("Error searching sync secrets: %s", error->message);
+    g_error_free (error);
+    goto free_result;
+  }
+
+  value = secret_item_get_secret (result->data);
+  node = json_from_string (secret_value_get_text (value), &error);
+  if (error) {
+    LOG ("Sync secrets are not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_value;
+  }
+
+  secrets = json_node_dup_object (node);
+
+  json_node_unref (node);
+free_value:
+  secret_value_unref (value);
+free_result:
+  g_list_free_full (result, g_object_unref);
+  g_hash_table_unref (attributes);
+free_user:
+  g_free (user);
+
+  return secrets;
+}
+
+static SyncCryptoKeyBundle *
+ephy_sync_debug_get_bundle_for_collection (const char *collection)
+{
+  SyncCryptoKeyBundle *bundle = NULL;
+  JsonObject *secrets;
+  JsonNode *node;
+  JsonObject *json;
+  JsonObject *collections;
+  JsonArray *array;
+  GError *error = NULL;
+  const char *crypto_keys;
+
+  g_assert (collection);
+
+  secrets = ephy_sync_debug_load_secrets ();
+  if (!secrets)
+    return NULL;
+
+  crypto_keys = json_object_get_string_member (secrets, "crypto_keys");
+  node = json_from_string (crypto_keys, &error);
+  if (error) {
+    LOG ("Crypto keys are not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_secrets;
+  }
+
+  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);
+
+  json_node_unref (node);
+free_secrets:
+  json_object_unref (secrets);
+
+  return bundle;
+}
+
+static char *
+ephy_sync_debug_get_body_for_delete (const char          *id,
+                                     SyncCryptoKeyBundle *bundle)
+{
+  JsonNode *node;
+  JsonObject *json;
+  char *record;
+  char *payload;
+  char *body;
+
+  g_assert (id);
+  g_assert (bundle);
+
+  record = g_strdup_printf ("{\"id\": \"%s\", \"deleted\": true}", id);
+  payload = ephy_sync_crypto_encrypt_record (record,  bundle);
+  json = json_object_new ();
+  json_object_set_string_member (json, "id", id);
+  json_object_set_string_member (json, "payload", payload);
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (node, json);
+  body = json_to_string (node, FALSE);
+
+  g_free (record);
+  g_free (payload);
+  json_object_unref (json);
+  json_node_unref (node);
+
+  return body;
+}
+
+static char *
+ephy_sync_debug_decrypt_record (const char          *payload,
+                                SyncCryptoKeyBundle *bundle)
+{
+  JsonNode *node;
+  GError *error = NULL;
+  char *record;
+  char *prettified = NULL;
+
+  g_assert (payload);
+  g_assert (bundle);
+
+  record = ephy_sync_crypto_decrypt_record (payload, bundle);
+  if (!record)
+    return NULL;
+
+  node = json_from_string (record, &error);
+  if (error) {
+    LOG ("Record is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_record;
+  }
+
+  prettified = json_to_string (node, TRUE);
+
+  json_node_unref (node);
+free_record:
+  g_free (record);
+
+  return prettified;
+}
+
+static SoupMessage *
+ephy_sync_debug_prepare_soup_message (const char   *url,
+                                      const char   *method,
+                                      const char   *body,
+                                      const char   *hawk_id,
+                                      const guint8 *hawk_key,
+                                      gsize         hawk_key_len)
+{
+  SyncCryptoHawkOptions *options = NULL;
+  SyncCryptoHawkHeader *header;
+  SoupMessage *msg;
+  const char *content_type = "application/json";
+
+  g_assert (url);
+  g_assert (method);
+  g_assert (g_strcmp0 (method, "PUT") || body);
+  g_assert (g_strcmp0 (method, "POST") || body);
+  g_assert (hawk_id);
+  g_assert (hawk_key && hawk_key_len > 0);
+
+  msg = soup_message_new (method, url);
+
+  if (body) {
+    options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
+                                                 NULL, NULL, NULL, body, NULL);
+    soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY, body, strlen (body));
+  }
+
+  if (!g_strcmp0 (method, "PUT") || !g_strcmp0(method, "POST"))
+    soup_message_headers_append (msg->request_headers, "content-type", content_type);
+
+  header = ephy_sync_crypto_compute_hawk_header (url, method, hawk_id,
+                                                 hawk_key, hawk_key_len,
+                                                 options);
+  soup_message_headers_append (msg->request_headers, "authorization", header->header);
+
+  ephy_sync_crypto_hawk_header_free (header);
+  if (options)
+    ephy_sync_crypto_hawk_options_free (options);
+
+  return msg;
+}
+
+static char *
+ephy_sync_debug_get_signed_certificate (const char           *session_token,
+                                        SyncCryptoRSAKeyPair *keypair)
+{
+  SoupSession *session;
+  SoupMessage *msg;
+  JsonNode *node;
+  JsonNode *response;
+  JsonObject *json;
+  JsonObject *public_key;
+  JsonObject *json_body;
+  GError *error = NULL;
+  guint8 *id;
+  guint8 *key;
+  guint8 *tmp;
+  char *certificate = NULL;
+  char *id_hex;
+  char *body;
+  char *url;
+  char *n;
+  char *e;
+  guint status_code;
+
+  g_assert (session_token);
+  g_assert (keypair);
+
+  ephy_sync_crypto_process_session_token (session_token, &id, &key, &tmp, 32);
+  id_hex = ephy_sync_utils_encode_hex (id, 32);
+  n = mpz_get_str (NULL, 10, keypair->public.n);
+  e = mpz_get_str (NULL, 10, keypair->public.e);
+
+  public_key = json_object_new ();
+  json_object_set_string_member (public_key, "algorithm", "RS");
+  json_object_set_string_member (public_key, "n", n);
+  json_object_set_string_member (public_key, "e", e);
+  json_body = json_object_new ();
+  json_object_set_int_member (json_body, "duration", 5 * 60 * 1000);
+  json_object_set_object_member (json_body, "publicKey", public_key);
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (node, json_body);
+  body = json_to_string (node, FALSE);
+
+  url = g_strdup_printf ("%s/certificate/sign", FIREFOX_ACCOUNTS_SERVER_URL);
+  msg = ephy_sync_debug_prepare_soup_message (url, "POST", body,
+                                              id_hex, key, 32);
+  session = soup_session_new ();
+  status_code = soup_session_send_message (session, msg);
+
+  if (status_code != 200) {
+    LOG ("Failed to get signed certificate: %s", msg->response_body->data);
+    goto free_session;
+  }
+
+  response = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_session;
+  }
+
+  json = json_node_get_object (response);
+  certificate = g_strdup (json_object_get_string_member (json, "cert"));
+
+  json_node_unref (response);
+free_session:
+  g_object_unref (session);
+  g_object_unref (msg);
+  g_free (url);
+  g_free (body);
+  json_node_unref (node);
+  json_object_unref (json_body);
+  g_free (e);
+  g_free (n);
+  g_free (id_hex);
+  g_free (id);
+  g_free (key);
+  g_free (tmp);
+
+  return certificate;
+}
+
+static gboolean
+ephy_sync_debug_get_storage_credentials (char **storage_endpoint,
+                                         char **storage_id,
+                                         char **storage_key)
+{
+  SyncCryptoRSAKeyPair *keypair;
+  SoupSession *session;
+  SoupMessage *msg;
+  JsonNode *response;
+  JsonObject *secrets;
+  JsonObject *json;
+  GError *error = NULL;
+  char *certificate;
+  char *audience;
+  char *assertion;
+  char *hashed_key_b;
+  char *client_state;
+  char *authorization;
+  guint8 *key_b;
+  const char *session_token;
+  guint status_code;
+  gboolean success = FALSE;
+
+  secrets = ephy_sync_debug_load_secrets ();
+  if (!secrets)
+    return FALSE;
+
+  keypair = ephy_sync_crypto_generate_rsa_key_pair ();
+  session_token = json_object_get_string_member (secrets, "session_token");
+  certificate = ephy_sync_debug_get_signed_certificate (session_token, keypair);
+  if (!certificate)
+    goto free_keypair;
+
+  audience = ephy_sync_utils_get_audience (TOKEN_SERVER_URL);
+  assertion = ephy_sync_crypto_create_assertion (certificate, audience, 300, keypair);
+  key_b = ephy_sync_utils_decode_hex (json_object_get_string_member (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 ("GET", TOKEN_SERVER_URL);
+  soup_message_headers_append (msg->request_headers, "X-Client-State", client_state);
+  soup_message_headers_append (msg->request_headers, "authorization", authorization);
+  session = soup_session_new ();
+  status_code = soup_session_send_message (session, msg);
+
+  if (status_code != 200) {
+    LOG ("Failed to get storage credentials: %s", msg->response_body->data);
+    goto free_session;
+  }
+
+  response = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_session;
+  }
+
+  json = json_node_get_object (response);
+  *storage_endpoint = g_strdup (json_object_get_string_member (json, "api_endpoint"));
+  *storage_id = g_strdup (json_object_get_string_member (json, "id"));
+  *storage_key = g_strdup (json_object_get_string_member (json, "key"));
+  success = TRUE;
+
+  json_node_unref (response);
+free_session:
+  g_object_unref (session);
+  g_object_unref (msg);
+  g_free (authorization);
+  g_free (client_state);
+  g_free (hashed_key_b);
+  g_free (key_b);
+  g_free (assertion);
+  g_free (audience);
+  g_free (certificate);
+free_keypair:
+  ephy_sync_crypto_rsa_key_pair_free (keypair);
+  json_object_unref (secrets);
+
+  return success;
+}
+
+static char *
+ephy_sync_debug_send_request (const char *endpoint,
+                              const char *method,
+                              const char *body)
+{
+  SoupSession *session;
+  SoupMessage *msg;
+  char *response = NULL;
+  char *storage_endpoint;
+  char *storage_id;
+  char *storage_key;
+  char *url;
+  guint status_code;
+
+  g_assert (endpoint);
+  g_assert (method);
+  g_assert (g_strcmp0 (method, "PUT") || body);
+  g_assert (g_strcmp0 (method, "POST") || body);
+
+  if (!ephy_sync_debug_get_storage_credentials (&storage_endpoint,
+                                                &storage_id,
+                                                &storage_key)) {
+    LOG ("Failed to get storage credentials.");
+    return NULL;
+  }
+
+  url = g_strdup_printf ("%s/%s", storage_endpoint, endpoint);
+  msg = ephy_sync_debug_prepare_soup_message (url, method, body, storage_id,
+                                              (const guint8 *)storage_key,
+                                              strlen (storage_key));
+  session = soup_session_new ();
+  status_code = soup_session_send_message (session, msg);
+
+  if (status_code == 200)
+    response = g_strdup (msg->response_body->data);
+  else
+    LOG ("Failed to send storage request: %s", msg->response_body->data);
+
+  g_free (url);
+  g_free (storage_endpoint);
+  g_free (storage_id);
+  g_free (storage_key);
+  g_object_unref (session);
+  g_object_unref (msg);
+
+  return response;
+}
+
+/**
+ * ephy_sync_debug_view_secrets:
+ *
+ * Displays the sync secrets stored in the sync secret schema.
+ **/
+void
+ephy_sync_debug_view_secrets (void)
+{
+  GHashTable *attributes;
+  SecretValue *value;
+  GList *result;
+  GError *error = NULL;
+
+  attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA, NULL);
+  result = secret_service_search_sync (NULL,
+                                       EPHY_SYNC_SECRET_SCHEMA,
+                                       attributes,
+                                       SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
+                                       NULL,
+                                       &error);
+  if (error) {
+    LOG ("Error searching sync secrets: %s", error->message);
+    g_error_free (error);
+    goto free_attributes;
+  }
+
+  /* All sync secrets are stored as a whole, as a JSON object, therefore
+   * this list would normally have only one element, but for the sake of
+   * debugging purposes we iterate through all the list.
+   */
+  for (GList *l = result; l && l->data; l = l->next) {
+    GHashTable *attrs = secret_item_get_attributes (result->data);
+    const char *account = g_hash_table_lookup (attrs, ACCOUNT_KEY);
+    value = secret_item_get_secret (result->data);
+    LOG ("Sync secrets of %s: %s", account, secret_value_get_text (value));
+    secret_value_unref (value);
+    g_hash_table_unref (attrs);
+  }
+
+  g_list_free_full (result, g_object_unref);
+free_attributes:
+  g_hash_table_unref (attributes);
+}
+
+/**
+ * ephy_sync_debug_view_collection:
+ * @collection: the collection name
+ * @decrypt: whether the records should be displayed decrypted or not
+ *
+ * Displays records in @collection.
+ **/
+void
+ephy_sync_debug_view_collection (const char *collection,
+                                 gboolean    decrypt)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonArray *array;
+  GError *error = NULL;
+  char *endpoint;
+  char *response;
+
+  g_return_if_fail (collection);
+
+  endpoint = g_strdup_printf ("storage/%s?full=true", collection);
+  response = ephy_sync_debug_send_request (endpoint, "GET", NULL);
+
+  if (!response)
+    goto free_endpoint;
+
+  node = json_from_string (response, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_response;
+  }
+
+  if (!decrypt) {
+    char *records = json_to_string (node, TRUE);
+    LOG ("%s", records);
+    g_free (records);
+    goto free_node;
+  }
+
+  bundle = ephy_sync_debug_get_bundle_for_collection (collection);
+  if (!bundle)
+    goto free_node;
+
+  array = json_node_get_array (node);
+  for (guint i = 0; i < json_array_get_length (array); i++) {
+    JsonObject *json = json_array_get_object_element (array, i);
+    const char *payload = json_object_get_string_member (json, "payload");
+    char *record = ephy_sync_debug_decrypt_record (payload, bundle);
+    LOG ("%s\n", record);
+    g_free (record);
+  }
+
+  ephy_sync_crypto_key_bundle_free (bundle);
+free_node:
+  json_node_unref (node);
+free_response:
+  g_free (response);
+free_endpoint:
+  g_free (endpoint);
+}
+
+/**
+ * ephy_sync_debug_view_record:
+ * @collection: the collection name
+ * @id: the record id
+ * @decrypt: whether the record should be displayed decrypted or not
+ *
+ * Displays record with id @id from collection @collection.
+ **/
+void
+ephy_sync_debug_view_record (const char *collection,
+                             const char *id,
+                             gboolean    decrypt)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonObject *json;
+  JsonNode *node;
+  GError *error = NULL;
+  char *id_safe;
+  char *endpoint;
+  char *response;
+  char *record;
+  const char *payload;
+
+  g_return_if_fail (collection);
+  g_return_if_fail (id);
+
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
+  response = ephy_sync_debug_send_request (endpoint, "GET", NULL);
+
+  if (!response)
+    goto free_endpoint;
+
+  node = json_from_string (response, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_response;
+  }
+
+  if (!decrypt) {
+    record = json_to_string (node, TRUE);
+    LOG ("%s", record);
+    g_free (record);
+    goto free_node;
+  }
+
+  bundle = ephy_sync_debug_get_bundle_for_collection (collection);
+  if (!bundle)
+    goto free_node;
+
+  json = json_node_get_object (node);
+  payload = json_object_get_string_member (json, "payload");
+  record = ephy_sync_debug_decrypt_record (payload, bundle);
+  LOG ("%s", record);
+
+  g_free (record);
+  ephy_sync_crypto_key_bundle_free (bundle);
+free_node:
+  json_node_unref (node);
+free_response:
+  g_free (response);
+free_endpoint:
+  g_free (endpoint);
+  g_free (id_safe);
+}
+
+/**
+ * ephy_sync_debug_delete_collection:
+ * @collection: the collection name
+ *
+ * Marks all records in @collection as deleted.
+ * Contrast this with ephy_sync_debug_erase_collection(), which
+ * will permanently erase the collection and its records from the storage server.
+ **/
+void
+ephy_sync_debug_delete_collection (const char *collection)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonArray *array;
+  GError *error = NULL;
+  char *endpoint;
+  char *response;
+
+  g_return_if_fail (collection);
+
+  endpoint = g_strdup_printf ("storage/%s", collection);
+  response = ephy_sync_debug_send_request (endpoint, "GET", NULL);
+  if (!response)
+    goto free_endpoint;
+
+  node = json_from_string (response, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_response;
+  }
+
+  bundle = ephy_sync_debug_get_bundle_for_collection (collection);
+  if (!bundle)
+    goto free_node;
+
+  array = json_node_get_array (node);
+  for (guint i = 0; i < json_array_get_length (array); i++) {
+    const char *id = json_array_get_string_element (array, i);
+    char *id_safe = soup_uri_encode (id, NULL);
+    char *body = ephy_sync_debug_get_body_for_delete (id, bundle);
+    char *to = g_strdup_printf ("storage/%s/%s", collection, id_safe);
+    char *resp = ephy_sync_debug_send_request (to, "PUT", body);
+
+    LOG ("%s", resp);
+
+    g_free (id_safe);
+    g_free (body);
+    g_free (to);
+    g_free (resp);
+  }
+
+  ephy_sync_crypto_key_bundle_free (bundle);
+free_node:
+  json_node_unref (node);
+free_response:
+  g_free (response);
+free_endpoint:
+  g_free (endpoint);
+}
+
+/**
+ * ephy_sync_debug_delete_record:
+ * @collection: the collection name
+ * @id: the record id
+ *
+ * Marks record with id @id from collection @collection as deleted.
+ * Contrast this with ephy_sync_debug_erase_record(), which
+ * will permanently erase the record from the storage server.
+ **/
+void
+ephy_sync_debug_delete_record (const char *collection,
+                               const char *id)
+{
+  SyncCryptoKeyBundle *bundle;
+  char *id_safe;
+  char *endpoint;
+  char *body;
+  char *response;
+
+  g_return_if_fail (collection);
+  g_return_if_fail (id);
+
+  bundle = ephy_sync_debug_get_bundle_for_collection (collection);
+  if (!bundle)
+    return;
+
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
+  body = ephy_sync_debug_get_body_for_delete (id, bundle);
+  response = ephy_sync_debug_send_request (endpoint, "PUT", body);
+
+  LOG ("%s", response);
+
+  g_free (id_safe);
+  g_free (endpoint);
+  g_free (body);
+  g_free (response);
+  ephy_sync_crypto_key_bundle_free (bundle);
+}
+
+/**
+ * ephy_sync_debug_erase_collection:
+ * @collection: the collection name
+ *
+ * Deletes @collection and all its contained records from the storage server.
+ * Contrast this with ephy_sync_debug_delete_collection(), which will only mark
+ * the records in @collection as deleted.
+ *
+ * Note: After executing this, @collection will no longer appear in the output
+ * of GET /info/collections and calls to GET /storage/@collection will return
+ * an empty list.
+ **/
+void
+ephy_sync_debug_erase_collection (const char *collection)
+{
+  char *endpoint;
+  char *response;
+
+  g_return_if_fail (collection);
+
+  endpoint = g_strdup_printf ("storage/%s", collection);
+  response = ephy_sync_debug_send_request (endpoint, "DELETE", NULL);
+
+  LOG ("%s", response);
+
+  g_free (endpoint);
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_erase_record:
+ * @collection: the collection name
+ * @id: the record id
+ *
+ * Deletes record with id @id from collection @collection from the storage server.
+ * Contrast this with ephy_sync_debug_delete_record(), which will only mark
+ * the record as deleted.
+ *
+ * Note: After executing this, the record will no longer appear in the output
+ * of GET /storage/@collection and GET /storage/@collection/@id will return a
+ * 404 HTTP status code.
+ **/
+void
+ephy_sync_debug_erase_record (const char *collection,
+                              const char *id)
+{
+  char *id_safe;
+  char *endpoint;
+  char *response;
+
+  g_return_if_fail (collection);
+  g_return_if_fail (id);
+
+  id_safe = soup_uri_encode (id, NULL);
+  endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
+  response = ephy_sync_debug_send_request (endpoint, "DELETE", NULL);
+
+  LOG ("%s", response);
+
+  g_free (id_safe);
+  g_free (endpoint);
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_collection_info:
+ *
+ * Displays the name of each existing collection on the storage server together
+ * with the last-modified time of each collection.
+ **/
+void
+ephy_sync_debug_view_collection_info (void)
+{
+  char *response;
+
+  response = ephy_sync_debug_send_request ("info/collections", "GET", NULL);
+  LOG ("%s", response);
+
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_quota_info:
+ *
+ * Displays the current usage and quota (in KB) associated with the account.
+ * The second item will be null if the storage server does not enforce quotas.
+ **/
+void
+ephy_sync_debug_view_quota_info (void)
+{
+  char *response;
+
+  response = ephy_sync_debug_send_request ("info/quota", "GET", NULL);
+  LOG ("%s", response);
+
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_collection_usage:
+ *
+ * Displays the name of each existing collection on the storage server together
+ * with the data volume used for each collection (in KB).
+ **/
+void
+ephy_sync_debug_view_collection_usage (void)
+{
+  char *response;
+
+  response = ephy_sync_debug_send_request ("info/collection_usage", "GET", NULL);
+  LOG ("%s", response);
+
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_collection_counts:
+ *
+ * Displays the name of each existing collection on the storage server together
+ * with the number of records in each collection.
+ **/
+void
+ephy_sync_debug_view_collection_counts (void)
+{
+  char *response;
+
+  response = ephy_sync_debug_send_request ("info/collection_counts", "GET", NULL);
+  LOG ("%s", response);
+
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_configuration_info:
+ *
+ * Displays information about the configuration of the storage server regarding
+ * various protocol and size limits.
+ **/
+void
+ephy_sync_debug_view_configuration_info (void)
+{
+  char *response;
+
+  response = ephy_sync_debug_send_request ("info/configuration", "GET", NULL);
+  LOG ("%s", response);
+
+  g_free (response);
+}
+
+/**
+ * ephy_sync_debug_view_meta_global_record:
+ *
+ * Displays the meta/global record on the storage server that contains general
+ * metadata to describe the state of data on the server. This includes things
+ * like the global storage version and the set of available collections on the
+ * server.
+ **/
+void
+ephy_sync_debug_view_meta_global_record (void)
+{
+  ephy_sync_debug_view_record ("meta", "global", FALSE);
+}
+
+/**
+ * ephy_sync_debug_view_crypto_keys_record:
+ *
+ * Displays the crypto/keys record on the storage server that contains the
+ * base64-encoded key bundles used to encrypt, decrypt and verify records in
+ * each collection. The crypto/keys record itself is encrypted with a key bundle
+ * derived from the master sync key which is only available to the sync clients.
+ **/
+void
+ephy_sync_debug_view_crypto_keys_record (void)
+{
+  SyncCryptoKeyBundle *bundle;
+  JsonNode *node;
+  JsonObject *secrets;
+  JsonObject *json;
+  GError *error = NULL;
+  char *response;
+  char *crypto_keys;
+  const char *payload;
+  const char *key_b_hex;
+  guint8 *key_b;
+
+  secrets = ephy_sync_debug_load_secrets ();
+  if (!secrets)
+    return;
+
+  response = ephy_sync_debug_send_request ("storage/crypto/keys", "GET", NULL);
+  if (!response)
+    goto free_secrets;
+
+  node = json_from_string (response, &error);
+  if (error) {
+    LOG ("Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto free_response;
+  }
+
+  json = json_node_get_object (node);
+  payload = json_object_get_string_member (json, "payload");
+  key_b_hex = json_object_get_string_member (secrets, "master_key");
+  key_b = ephy_sync_utils_decode_hex (key_b_hex);
+  bundle = ephy_sync_crypto_derive_key_bundle (key_b, 32);
+  crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
+
+  if (!crypto_keys)
+    goto free_bundle;
+
+  LOG ("%s", crypto_keys);
+
+  g_free (crypto_keys);
+free_bundle:
+  ephy_sync_crypto_key_bundle_free (bundle);
+  g_free (key_b);
+  json_node_unref (node);
+free_response:
+  g_free (response);
+free_secrets:
+  json_object_unref (secrets);
+}
diff --git a/lib/sync/debug/ephy-sync-debug.h b/lib/sync/debug/ephy-sync-debug.h
new file mode 100644
index 0000000..189712a
--- /dev/null
+++ b/lib/sync/debug/ephy-sync-debug.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <ivascu gabriel59 gmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void ephy_sync_debug_view_secrets             (void);
+void ephy_sync_debug_view_collection          (const char *collection,
+                                               gboolean    decrypt);
+void ephy_sync_debug_view_record              (const char *collection,
+                                               const char *id,
+                                               gboolean    decrypt);
+void ephy_sync_debug_delete_collection        (const char *collection);
+void ephy_sync_debug_delete_record            (const char *collection,
+                                               const char *id);
+void ephy_sync_debug_erase_collection         (const char *collection);
+void ephy_sync_debug_erase_record             (const char *collection,
+                                               const char *id);
+void ephy_sync_debug_view_collection_info     (void);
+void ephy_sync_debug_view_quota_info          (void);
+void ephy_sync_debug_view_collection_usage    (void);
+void ephy_sync_debug_view_collection_counts   (void);
+void ephy_sync_debug_view_configuration_info  (void);
+void ephy_sync_debug_view_meta_global_record  (void);
+void ephy_sync_debug_view_crypto_keys_record  (void);
+
+G_END_DECLS
diff --git a/lib/sync/ephy-sync-service.c b/lib/sync/ephy-sync-service.c
index bb56707..6c9301f 100644
--- a/lib/sync/ephy-sync-service.c
+++ b/lib/sync/ephy-sync-service.c
@@ -30,33 +30,10 @@
 
 #include <glib/gi18n.h>
 #include <json-glib/json-glib.h>
-#include <libsecret/secret.h>
-#include <libsoup/soup.h>
 #include <string.h>
 
-#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;
 
@@ -425,7 +402,7 @@ ephy_sync_service_fxa_hawk_post_async (EphySyncService     *self,
   g_assert (key);
   g_assert (request_body);
 
-  url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_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));
@@ -467,7 +444,7 @@ ephy_sync_service_fxa_hawk_get_async (EphySyncService     *self,
   g_assert (id);
   g_assert (key);
 
-  url = g_strdup_printf ("%s%s", FIREFOX_ACCOUNTS_SERVER_URL, endpoint);
+  url = g_strdup_printf ("%s/%s", FIREFOX_ACCOUNTS_SERVER_URL, endpoint);
   msg = soup_message_new (SOUP_METHOD_GET, url);
   hawk_header = ephy_sync_crypto_compute_hawk_header (url, "GET", id,
                                                       key, key_len,
@@ -660,7 +637,7 @@ ephy_sync_service_destroy_session (EphySyncService *self,
     session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
   g_assert (session_token);
 
-  url = g_strdup_printf ("%ssession/destroy", FIREFOX_ACCOUNTS_SERVER_URL);
+  url = g_strdup_printf ("%s/session/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_utils_encode_hex (token_id, 32);
@@ -775,34 +752,6 @@ out:
     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)
 {
@@ -818,7 +767,7 @@ ephy_sync_service_obtain_storage_credentials (EphySyncService *self)
   g_assert (self->certificate);
   g_assert (self->rsa_key_pair);
 
-  audience = get_audience (TOKEN_SERVER_URL);
+  audience = ephy_sync_utils_get_audience (TOKEN_SERVER_URL);
   assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
                                                  300, self->rsa_key_pair);
   key_b = ephy_sync_utils_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
@@ -1644,7 +1593,7 @@ ephy_sync_service_store_secrets (EphySyncService *self)
   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);
+  json_string = json_to_string (node, TRUE);
 
   secret = secret_value_new (json_string, -1, "text/plain");
   attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
diff --git a/lib/sync/meson.build b/lib/sync/meson.build
index b728c39..728ab12 100644
--- a/lib/sync/meson.build
+++ b/lib/sync/meson.build
@@ -1,4 +1,5 @@
 libephysync_sources = [
+  'debug/ephy-sync-debug.c',
   'ephy-history-manager.c',
   'ephy-history-record.c',
   'ephy-password-manager.c',
@@ -26,7 +27,8 @@ libephysync_includes = include_directories(
   '../history',
   '../widgets',
   '../..',
-  '../../embed'
+  '../../embed',
+  'debug'
 )
 
 libephysync = static_library('ephysync',



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