[epiphany/wip/sync: 1/18] sync: Add debug functions under ephy-sync-debug
- From: Gabriel Ivașcu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/sync: 1/18] sync: Add debug functions under ephy-sync-debug
- Date: Sat, 24 Jun 2017 23:44:35 +0000 (UTC)
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]