1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2016 Gabriel Ivascu <gabrielivascu@gnome.org>
4  *
5  *  This file is part of Epiphany.
6  *
7  *  Epiphany is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include "ephy-sync-service.h"
23 
24 #include "ephy-debug.h"
25 #include "ephy-notification.h"
26 #include "ephy-settings.h"
27 #include "ephy-sync-crypto.h"
28 #include "ephy-sync-utils.h"
29 #include "ephy-user-agent.h"
30 
31 #include <glib/gi18n.h>
32 #include <json-glib/json-glib.h>
33 #include <inttypes.h>
34 #include <libsoup/soup.h>
35 #include <math.h>
36 #include <string.h>
37 
38 struct _EphySyncService {
39   GObject parent_instance;
40 
41   SoupSession *session;
42   guint source_id;
43 
44   GCancellable *cancellable;
45 
46   char *user;
47   char *crypto_keys;
48   GHashTable *secrets;
49   GSList *managers;
50 
51   gboolean locked;
52   char *storage_endpoint;
53   char *storage_credentials_id;
54   char *storage_credentials_key;
55   gint64 storage_credentials_expiry_time;
56   GQueue *storage_queue;
57 
58   char *certificate;
59   SyncCryptoRSAKeyPair *key_pair;
60 
61   gboolean sync_periodically;
62   gboolean is_signing_in;
63 };
64 
65 G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
66 
67 enum {
68   UID,
69   SESSION_TOKEN,
70   MASTER_KEY,
71   CRYPTO_KEYS,
72   LAST_SECRET
73 };
74 
75 static const char * const secrets[LAST_SECRET] = {
76   "uid",
77   "session_token",
78   "master_key",
79   "crypto_keys"
80 };
81 
82 enum {
83   PROP_0,
84   PROP_SYNC_PERIODICALLY,
85   LAST_PROP
86 };
87 
88 static GParamSpec *obj_properties[LAST_PROP];
89 
90 enum {
91   STORE_FINISHED,
92   LOAD_FINISHED,
93   SIGN_IN_ERROR,
94   SYNC_FINISHED,
95   LAST_SIGNAL
96 };
97 
98 static guint signals[LAST_SIGNAL];
99 
100 #if SOUP_CHECK_VERSION (2, 99, 4)
101 typedef void (*SoupSessionCallback) (SoupSession *session,
102                                      SoupMessage *msg,
103                                      gpointer     user_data);
104 #endif
105 
106 typedef struct {
107   char *endpoint;
108   char *method;
109   char *request_body;
110   gint64 modified_since;
111   gint64 unmodified_since;
112   SoupSessionCallback callback;
113   gpointer user_data;
114 } StorageRequestAsyncData;
115 
116 typedef struct {
117   EphySyncService *service;
118   char *email;
119   char *uid;
120   char *session_token;
121   char *unwrap_kb;
122   char *token_id_hex;
123   guint8 *req_hmac_key;
124   guint8 *resp_hmac_key;
125   guint8 *resp_xor_key;
126 } SignInAsyncData;
127 
128 typedef struct {
129   EphySyncService *service;
130   EphySynchronizableManager *manager;
131   gboolean is_initial;
132   gboolean is_last;
133   GList *remotes_deleted;
134   GList *remotes_updated;
135 } SyncCollectionAsyncData;
136 
137 typedef struct {
138   EphySyncService *service;
139   EphySynchronizableManager *manager;
140   EphySynchronizable *synchronizable;
141 } SyncAsyncData;
142 
143 typedef struct {
144   EphySyncService *service;
145   EphySynchronizableManager *manager;
146   GPtrArray *synchronizables;
147   guint start;
148   guint end;
149   char *batch_id;
150   gboolean batch_is_last;
151   gboolean sync_done;
152 } BatchUploadAsyncData;
153 
154 static StorageRequestAsyncData *
storage_request_async_data_new(const char * endpoint,const char * method,const char * request_body,gint64 modified_since,gint64 unmodified_since,SoupSessionCallback callback,gpointer user_data)155 storage_request_async_data_new (const char          *endpoint,
156                                 const char          *method,
157                                 const char          *request_body,
158                                 gint64               modified_since,
159                                 gint64               unmodified_since,
160                                 SoupSessionCallback  callback,
161                                 gpointer             user_data)
162 {
163   StorageRequestAsyncData *data;
164 
165   data = g_new (StorageRequestAsyncData, 1);
166   data->endpoint = g_strdup (endpoint);
167   data->method = g_strdup (method);
168   data->request_body = g_strdup (request_body);
169   data->modified_since = modified_since;
170   data->unmodified_since = unmodified_since;
171   data->callback = callback;
172   data->user_data = user_data;
173 
174   return data;
175 }
176 
177 static void
storage_request_async_data_free(StorageRequestAsyncData * data)178 storage_request_async_data_free (StorageRequestAsyncData *data)
179 {
180   g_assert (data);
181 
182   g_free (data->endpoint);
183   g_free (data->method);
184   g_free (data->request_body);
185   g_free (data);
186 }
187 
188 #if SOUP_CHECK_VERSION (2, 99, 4)
189 typedef struct {
190   SoupSessionCallback callback;
191   gpointer user_data;
192 } SendAndReadAsyncData;
193 
194 static SendAndReadAsyncData *
send_and_read_async_data_new(SoupSessionCallback callback,gpointer user_data)195 send_and_read_async_data_new (SoupSessionCallback callback,
196                               gpointer            user_data)
197 {
198   SendAndReadAsyncData *data;
199 
200   data = g_new (SendAndReadAsyncData, 1);
201   data->callback = callback;
202   data->user_data = user_data;
203 
204   return data;
205 }
206 
207 static void
send_and_read_async_ready_cb(SoupSession * session,GAsyncResult * result,SendAndReadAsyncData * data)208 send_and_read_async_ready_cb (SoupSession          *session,
209                               GAsyncResult         *result,
210                               SendAndReadAsyncData *data)
211 {
212   GBytes *bytes;
213   SoupMessage *msg;
214   GError *error = NULL;
215 
216   bytes = soup_session_send_and_read_finish (session, result, &error);
217   if (!bytes) {
218     g_warning ("Failed to send request: %s", error->message);
219     g_error_free (error);
220   }
221 
222   msg = soup_session_get_async_result_message (session, result);
223   g_object_set_data_full (G_OBJECT (msg),
224                           "ephy-request-body", bytes ? bytes : g_bytes_new (NULL, 0),
225                           (GDestroyNotify)g_bytes_unref);
226   data->callback (session, msg, data->user_data);
227   g_free (data);
228 }
229 
230 static void
storage_request_async_ready_cb(SoupSession * session,GAsyncResult * result,StorageRequestAsyncData * data)231 storage_request_async_ready_cb (SoupSession             *session,
232                                 GAsyncResult            *result,
233                                 StorageRequestAsyncData *data)
234 {
235   GBytes *bytes;
236   SoupMessage *msg;
237   GError *error = NULL;
238 
239   bytes = soup_session_send_and_read_finish (session, result, &error);
240   if (!bytes) {
241     g_warning ("Failed to send storage request: %s", error->message);
242     g_error_free (error);
243   }
244 
245   msg = soup_session_get_async_result_message (session, result);
246   g_object_set_data_full (G_OBJECT (msg),
247                           "ephy-request-body", bytes ? bytes : g_bytes_new (NULL, 0),
248                           (GDestroyNotify)g_bytes_unref);
249   data->callback (session, msg, data->user_data);
250   storage_request_async_data_free (data);
251 }
252 #endif
253 
254 static SignInAsyncData *
sign_in_async_data_new(EphySyncService * service,const char * email,const char * uid,const char * session_token,const char * unwrap_kb,const char * token_id_hex,const guint8 * req_hmac_key,const guint8 * resp_hmac_key,const guint8 * resp_xor_key)255 sign_in_async_data_new (EphySyncService *service,
256                         const char      *email,
257                         const char      *uid,
258                         const char      *session_token,
259                         const char      *unwrap_kb,
260                         const char      *token_id_hex,
261                         const guint8    *req_hmac_key,
262                         const guint8    *resp_hmac_key,
263                         const guint8    *resp_xor_key)
264 {
265   SignInAsyncData *data;
266 
267   data = g_new (SignInAsyncData, 1);
268   data->service = g_object_ref (service);
269   data->email = g_strdup (email);
270   data->uid = g_strdup (uid);
271   data->session_token = g_strdup (session_token);
272   data->unwrap_kb = g_strdup (unwrap_kb);
273   data->token_id_hex = g_strdup (token_id_hex);
274   data->req_hmac_key = g_malloc (32);
275   memcpy (data->req_hmac_key, req_hmac_key, 32);
276   data->resp_hmac_key = g_malloc (32);
277   memcpy (data->resp_hmac_key, resp_hmac_key, 32);
278   data->resp_xor_key = g_malloc (2 * 32);
279   memcpy (data->resp_xor_key, resp_xor_key, 2 * 32);
280 
281   return data;
282 }
283 
284 static void
sign_in_async_data_free(SignInAsyncData * data)285 sign_in_async_data_free (SignInAsyncData *data)
286 {
287   g_assert (data);
288 
289   g_object_unref (data->service);
290   g_free (data->email);
291   g_free (data->uid);
292   g_free (data->session_token);
293   g_free (data->unwrap_kb);
294   g_free (data->token_id_hex);
295   g_free (data->req_hmac_key);
296   g_free (data->resp_hmac_key);
297   g_free (data->resp_xor_key);
298   g_free (data);
299 }
300 
301 static SyncCollectionAsyncData *
sync_collection_async_data_new(EphySyncService * service,EphySynchronizableManager * manager,gboolean is_initial,gboolean is_last)302 sync_collection_async_data_new (EphySyncService           *service,
303                                 EphySynchronizableManager *manager,
304                                 gboolean                   is_initial,
305                                 gboolean                   is_last)
306 {
307   SyncCollectionAsyncData *data;
308 
309   data = g_new (SyncCollectionAsyncData, 1);
310   data->service = g_object_ref (service);
311   data->manager = g_object_ref (manager);
312   data->is_initial = is_initial;
313   data->is_last = is_last;
314   data->remotes_deleted = NULL;
315   data->remotes_updated = NULL;
316 
317   return data;
318 }
319 
320 static void
sync_collection_async_data_free(SyncCollectionAsyncData * data)321 sync_collection_async_data_free (SyncCollectionAsyncData *data)
322 {
323   g_assert (data);
324 
325   g_object_unref (data->service);
326   g_object_unref (data->manager);
327   g_list_free_full (data->remotes_deleted, g_object_unref);
328   g_list_free_full (data->remotes_updated, g_object_unref);
329   g_free (data);
330 }
331 
332 static SyncAsyncData *
sync_async_data_new(EphySyncService * service,EphySynchronizableManager * manager,EphySynchronizable * synchronizable)333 sync_async_data_new (EphySyncService           *service,
334                      EphySynchronizableManager *manager,
335                      EphySynchronizable        *synchronizable)
336 {
337   SyncAsyncData *data;
338 
339   data = g_new (SyncAsyncData, 1);
340   data->service = g_object_ref (service);
341   data->manager = g_object_ref (manager);
342   data->synchronizable = g_object_ref (synchronizable);
343 
344   return data;
345 }
346 
347 static void
sync_async_data_free(SyncAsyncData * data)348 sync_async_data_free (SyncAsyncData *data)
349 {
350   g_assert (data);
351 
352   g_object_unref (data->service);
353   g_object_unref (data->manager);
354   g_object_unref (data->synchronizable);
355   g_free (data);
356 }
357 
358 static inline BatchUploadAsyncData *
batch_upload_async_data_new(EphySyncService * service,EphySynchronizableManager * manager,GPtrArray * synchronizables,guint start,guint end,const char * batch_id,gboolean batch_is_last,gboolean sync_done)359 batch_upload_async_data_new (EphySyncService           *service,
360                              EphySynchronizableManager *manager,
361                              GPtrArray                 *synchronizables,
362                              guint                      start,
363                              guint                      end,
364                              const char                *batch_id,
365                              gboolean                   batch_is_last,
366                              gboolean                   sync_done)
367 {
368   BatchUploadAsyncData *data;
369 
370   data = g_new (BatchUploadAsyncData, 1);
371   data->service = g_object_ref (service);
372   data->manager = g_object_ref (manager);
373   data->synchronizables = g_ptr_array_ref (synchronizables);
374   data->start = start;
375   data->end = end;
376   data->batch_id = g_strdup (batch_id);
377   data->batch_is_last = batch_is_last;
378   data->sync_done = sync_done;
379 
380   return data;
381 }
382 
383 static inline BatchUploadAsyncData *
batch_upload_async_data_dup(BatchUploadAsyncData * data)384 batch_upload_async_data_dup (BatchUploadAsyncData *data)
385 {
386   g_assert (data);
387 
388   return batch_upload_async_data_new (data->service, data->manager,
389                                       data->synchronizables, data->start,
390                                       data->end, data->batch_id,
391                                       data->batch_is_last, data->sync_done);
392 }
393 
394 static inline void
batch_upload_async_data_free(BatchUploadAsyncData * data)395 batch_upload_async_data_free (BatchUploadAsyncData *data)
396 {
397   g_assert (data);
398 
399   g_object_unref (data->service);
400   g_object_unref (data->manager);
401   g_ptr_array_unref (data->synchronizables);
402   g_free (data->batch_id);
403   g_free (data);
404 }
405 
406 static void
ephy_sync_service_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)407 ephy_sync_service_set_property (GObject      *object,
408                                 guint         prop_id,
409                                 const GValue *value,
410                                 GParamSpec   *pspec)
411 {
412   EphySyncService *self = EPHY_SYNC_SERVICE (object);
413 
414   switch (prop_id) {
415     case PROP_SYNC_PERIODICALLY:
416       self->sync_periodically = g_value_get_boolean (value);
417       break;
418     default:
419       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
420   }
421 }
422 
423 static void
ephy_sync_service_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)424 ephy_sync_service_get_property (GObject    *object,
425                                 guint       prop_id,
426                                 GValue     *value,
427                                 GParamSpec *pspec)
428 {
429   EphySyncService *self = EPHY_SYNC_SERVICE (object);
430 
431   switch (prop_id) {
432     case PROP_SYNC_PERIODICALLY:
433       g_value_set_boolean (value, self->sync_periodically);
434       break;
435     default:
436       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
437   }
438 }
439 
440 static const char *
ephy_sync_service_get_secret(EphySyncService * self,const char * name)441 ephy_sync_service_get_secret (EphySyncService *self,
442                               const char      *name)
443 {
444   g_assert (EPHY_IS_SYNC_SERVICE (self));
445   g_assert (name);
446 
447   return g_hash_table_lookup (self->secrets, name);
448 }
449 
450 static void
ephy_sync_service_set_secret(EphySyncService * self,const char * name,const char * value)451 ephy_sync_service_set_secret (EphySyncService *self,
452                               const char      *name,
453                               const char      *value)
454 {
455   g_assert (EPHY_IS_SYNC_SERVICE (self));
456   g_assert (name);
457   g_assert (value);
458 
459   g_hash_table_replace (self->secrets, g_strdup (name), g_strdup (value));
460 }
461 
462 static SyncCryptoKeyBundle *
ephy_sync_service_get_key_bundle(EphySyncService * self,const char * collection)463 ephy_sync_service_get_key_bundle (EphySyncService *self,
464                                   const char      *collection)
465 {
466   SyncCryptoKeyBundle *bundle = NULL;
467   JsonNode *node;
468   JsonObject *json;
469   JsonObject *collections;
470   JsonArray *array;
471   GError *error = NULL;
472   const char *crypto_keys;
473 
474   g_assert (EPHY_IS_SYNC_SERVICE (self));
475   g_assert (collection);
476 
477   crypto_keys = ephy_sync_service_get_secret (self, secrets[CRYPTO_KEYS]);
478   if (!crypto_keys) {
479     g_warning ("Missing crypto-keys secret");
480     return NULL;
481   }
482 
483   node = json_from_string (crypto_keys, &error);
484   g_assert (!error);
485   json = json_node_get_object (node);
486   collections = json_object_get_object_member (json, "collections");
487   array = json_object_has_member (collections, collection) ?
488           json_object_get_array_member (collections, collection) :
489           json_object_get_array_member (json, "default");
490   bundle = ephy_sync_crypto_key_bundle_new (json_array_get_string_element (array, 0),
491                                             json_array_get_string_element (array, 1));
492 
493   json_node_unref (node);
494 
495   return bundle;
496 }
497 
498 static void
ephy_sync_service_clear_storage_credentials(EphySyncService * self)499 ephy_sync_service_clear_storage_credentials (EphySyncService *self)
500 {
501   g_assert (EPHY_IS_SYNC_SERVICE (self));
502 
503   g_clear_pointer (&self->certificate, g_free);
504   g_clear_pointer (&self->storage_endpoint, g_free);
505   g_clear_pointer (&self->storage_credentials_id, g_free);
506   g_clear_pointer (&self->storage_credentials_key, g_free);
507   self->storage_credentials_expiry_time = 0;
508 }
509 
510 static gboolean
ephy_sync_service_storage_credentials_is_expired(EphySyncService * self)511 ephy_sync_service_storage_credentials_is_expired (EphySyncService *self)
512 {
513   g_assert (EPHY_IS_SYNC_SERVICE (self));
514 
515   if (!self->storage_credentials_id || !self->storage_credentials_key)
516     return TRUE;
517 
518   if (self->storage_credentials_expiry_time == 0)
519     return TRUE;
520 
521   /* Consider a 60 seconds safety interval. */
522   return self->storage_credentials_expiry_time < g_get_real_time () / 1000000 - 60;
523 }
524 
525 static void
ephy_sync_service_fxa_hawk_post(EphySyncService * self,const char * endpoint,const char * id,guint8 * key,gsize key_len,const char * request_body,SoupSessionCallback callback,gpointer user_data)526 ephy_sync_service_fxa_hawk_post (EphySyncService     *self,
527                                  const char          *endpoint,
528                                  const char          *id,
529                                  guint8              *key,
530                                  gsize                key_len,
531                                  const char          *request_body,
532                                  SoupSessionCallback  callback,
533                                  gpointer             user_data)
534 {
535   SyncCryptoHawkOptions *options;
536   SyncCryptoHawkHeader *header;
537   SoupMessage *msg;
538   SoupMessageHeaders *request_headers;
539   char *url;
540   const char *content_type = "application/json; charset=utf-8";
541   g_autofree char *accounts_server = NULL;
542 #if SOUP_CHECK_VERSION (2, 99, 4)
543   g_autoptr (GBytes) bytes = NULL;
544 #endif
545 
546   g_assert (EPHY_IS_SYNC_SERVICE (self));
547   g_assert (endpoint);
548   g_assert (id);
549   g_assert (key);
550   g_assert (request_body);
551 
552   accounts_server = ephy_sync_utils_get_accounts_server ();
553   url = g_strdup_printf ("%s/%s", accounts_server, endpoint);
554   msg = soup_message_new (SOUP_METHOD_POST, url);
555 #if SOUP_CHECK_VERSION (2, 99, 4)
556   bytes = g_bytes_new (request_body, strlen (request_body));
557   soup_message_set_request_body_from_bytes (msg, content_type, bytes);
558   request_headers = soup_message_get_request_headers (msg);
559 #else
560   soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
561                             request_body, strlen (request_body));
562   request_headers = msg->request_headers;
563 #endif
564 
565   options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
566                                                NULL, NULL, NULL, request_body,
567                                                NULL);
568   header = ephy_sync_crypto_hawk_header_new (url, "POST", id, key, key_len, options);
569   soup_message_headers_append (request_headers, "authorization", header->header);
570   soup_message_headers_append (request_headers, "content-type", content_type);
571 #if SOUP_CHECK_VERSION (2, 99, 4)
572   soup_session_send_and_read_async (self->session, msg, G_PRIORITY_DEFAULT, NULL,
573                                     (GAsyncReadyCallback)send_and_read_async_ready_cb,
574                                     send_and_read_async_data_new (callback, user_data));
575 #else
576   soup_session_queue_message (self->session, msg, callback, user_data);
577 #endif
578 
579   g_free (url);
580   ephy_sync_crypto_hawk_options_free (options);
581   ephy_sync_crypto_hawk_header_free (header);
582 }
583 
584 static void
ephy_sync_service_fxa_hawk_get(EphySyncService * self,const char * endpoint,const char * id,guint8 * key,gsize key_len,SoupSessionCallback callback,gpointer user_data)585 ephy_sync_service_fxa_hawk_get (EphySyncService     *self,
586                                 const char          *endpoint,
587                                 const char          *id,
588                                 guint8              *key,
589                                 gsize                key_len,
590                                 SoupSessionCallback  callback,
591                                 gpointer             user_data)
592 {
593   SyncCryptoHawkHeader *header;
594   SoupMessage *msg;
595   SoupMessageHeaders *request_headers;
596   char *url;
597   g_autofree char *accounts_server = NULL;
598 
599   g_assert (EPHY_IS_SYNC_SERVICE (self));
600   g_assert (endpoint);
601   g_assert (id);
602   g_assert (key);
603 
604   accounts_server = ephy_sync_utils_get_accounts_server ();
605   url = g_strdup_printf ("%s/%s", accounts_server, endpoint);
606   msg = soup_message_new (SOUP_METHOD_GET, url);
607   header = ephy_sync_crypto_hawk_header_new (url, "GET", id, key, key_len, NULL);
608 #if SOUP_CHECK_VERSION (2, 99, 4)
609   request_headers = soup_message_get_request_headers (msg);
610 #else
611   request_headers = msg->request_headers;
612 #endif
613   soup_message_headers_append (request_headers, "authorization", header->header);
614 #if SOUP_CHECK_VERSION (2, 99, 4)
615   soup_session_send_and_read_async (self->session, msg, G_PRIORITY_DEFAULT, NULL,
616                                     (GAsyncReadyCallback)send_and_read_async_ready_cb,
617                                     send_and_read_async_data_new (callback, user_data));
618 #else
619   soup_session_queue_message (self->session, msg, callback, user_data);
620 #endif
621 
622   g_free (url);
623   ephy_sync_crypto_hawk_header_free (header);
624 }
625 
626 static void
ephy_sync_service_send_storage_request(EphySyncService * self,StorageRequestAsyncData * data)627 ephy_sync_service_send_storage_request (EphySyncService         *self,
628                                         StorageRequestAsyncData *data)
629 {
630   SyncCryptoHawkOptions *options = NULL;
631   SyncCryptoHawkHeader *header;
632   SoupMessage *msg;
633   SoupMessageHeaders *request_headers;
634   char *url;
635   char *if_modified_since = NULL;
636   char *if_unmodified_since = NULL;
637   const char *content_type = "application/json; charset=utf-8";
638 
639   g_assert (EPHY_IS_SYNC_SERVICE (self));
640   g_assert (data);
641 
642   url = g_strdup_printf ("%s/%s", self->storage_endpoint, data->endpoint);
643   msg = soup_message_new (data->method, url);
644 
645   if (data->request_body) {
646 #if SOUP_CHECK_VERSION (2, 99, 4)
647     g_autoptr (GBytes) bytes = NULL;
648 #endif
649     options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
650                                                  NULL, NULL, NULL, data->request_body,
651                                                  NULL);
652 #if SOUP_CHECK_VERSION (2, 99, 4)
653     bytes = g_bytes_new (data->request_body, strlen (data->request_body));
654     soup_message_set_request_body_from_bytes (msg, content_type, bytes);
655 #else
656     soup_message_set_request (msg, content_type, SOUP_MEMORY_COPY,
657                               data->request_body, strlen (data->request_body));
658 #endif
659   }
660 
661 #if SOUP_CHECK_VERSION (2, 99, 4)
662   request_headers = soup_message_get_request_headers (msg);
663 #else
664   request_headers = msg->request_headers;
665 #endif
666 
667 
668   if (!g_strcmp0 (data->method, SOUP_METHOD_PUT) || !g_strcmp0 (data->method, SOUP_METHOD_POST))
669     soup_message_headers_append (request_headers, "content-type", content_type);
670 
671   if (data->modified_since >= 0) {
672     if_modified_since = g_strdup_printf ("%" PRId64, data->modified_since);
673     soup_message_headers_append (request_headers, "X-If-Modified-Since", if_modified_since);
674   }
675 
676   if (data->unmodified_since >= 0) {
677     if_unmodified_since = g_strdup_printf ("%" PRId64, data->unmodified_since);
678     soup_message_headers_append (request_headers, "X-If-Unmodified-Since", if_unmodified_since);
679   }
680 
681   header = ephy_sync_crypto_hawk_header_new (url, data->method,
682                                              self->storage_credentials_id,
683                                              (guint8 *)self->storage_credentials_key,
684                                              strlen (self->storage_credentials_key),
685                                              options);
686   soup_message_headers_append (request_headers, "authorization", header->header);
687 #if SOUP_CHECK_VERSION (2, 99, 4)
688   soup_session_send_and_read_async (self->session, msg, G_PRIORITY_DEFAULT, NULL,
689                                     (GAsyncReadyCallback)storage_request_async_ready_cb,
690                                     data);
691 #else
692   soup_session_queue_message (self->session, msg, data->callback, data->user_data);
693   storage_request_async_data_free (data);
694 #endif
695 
696   g_free (url);
697   g_free (if_modified_since);
698   g_free (if_unmodified_since);
699   ephy_sync_crypto_hawk_header_free (header);
700   if (options)
701     ephy_sync_crypto_hawk_options_free (options);
702 }
703 
704 static void
ephy_sync_service_send_all_storage_requests(EphySyncService * self)705 ephy_sync_service_send_all_storage_requests (EphySyncService *self)
706 {
707   StorageRequestAsyncData *data;
708 
709   g_assert (EPHY_IS_SYNC_SERVICE (self));
710 
711   while (!g_queue_is_empty (self->storage_queue)) {
712     data = g_queue_pop_head (self->storage_queue);
713     ephy_sync_service_send_storage_request (self, data);
714   }
715 }
716 
717 static void
ephy_sync_service_clear_storage_queue(EphySyncService * self)718 ephy_sync_service_clear_storage_queue (EphySyncService *self)
719 {
720   g_assert (EPHY_IS_SYNC_SERVICE (self));
721 
722   while (!g_queue_is_empty (self->storage_queue))
723     storage_request_async_data_free (g_queue_pop_head (self->storage_queue));
724 }
725 
726 static gboolean
ephy_sync_service_verify_certificate(EphySyncService * self,const char * certificate)727 ephy_sync_service_verify_certificate (EphySyncService *self,
728                                       const char      *certificate)
729 {
730   JsonParser *parser;
731   JsonObject *json;
732   JsonObject *principal;
733   GError *error = NULL;
734   g_autoptr (GUri) uri = NULL;
735   char **pieces;
736   char *header;
737   char *payload;
738   char *expected = NULL;
739   const char *alg;
740   const char *email;
741   gsize len;
742   gboolean retval = FALSE;
743   g_autofree char *accounts_server = NULL;
744 
745   g_assert (EPHY_IS_SYNC_SERVICE (self));
746   g_assert (ephy_sync_service_get_secret (self, secrets[UID]));
747   g_assert (certificate);
748 
749   pieces = g_strsplit (certificate, ".", 0);
750   header = (char *)ephy_sync_utils_base64_urlsafe_decode (pieces[0], &len, TRUE);
751   payload = (char *)ephy_sync_utils_base64_urlsafe_decode (pieces[1], &len, TRUE);
752   parser = json_parser_new ();
753 
754   json_parser_load_from_data (parser, header, -1, &error);
755   if (error) {
756     g_warning ("Header is not a valid JSON: %s", error->message);
757     goto out;
758   }
759   json = json_node_get_object (json_parser_get_root (parser));
760   if (!json) {
761     g_warning ("JSON node does not hold a JSON object");
762     goto out;
763   }
764   alg = json_object_get_string_member (json, "alg");
765   if (!alg) {
766     g_warning ("JSON object has missing or invalid 'alg' member");
767     goto out;
768   }
769   if (g_strcmp0 (alg, "RS256")) {
770     g_warning ("Expected algorithm RS256, found %s", alg);
771     goto out;
772   }
773   json_parser_load_from_data (parser, payload, -1, &error);
774   if (error) {
775     g_warning ("Payload is not a valid JSON: %s", error->message);
776     goto out;
777   }
778   json = json_node_get_object (json_parser_get_root (parser));
779   if (!json) {
780     g_warning ("JSON node does not hold a JSON object");
781     goto out;
782   }
783   principal = json_object_get_object_member (json, "principal");
784   if (!principal) {
785     g_warning ("JSON object has missing or invalid 'principal' member");
786     goto out;
787   }
788   email = json_object_get_string_member (principal, "email");
789   if (!email) {
790     g_warning ("JSON object has missing or invalid 'email' member");
791     goto out;
792   }
793   accounts_server = ephy_sync_utils_get_accounts_server ();
794   uri = g_uri_parse (accounts_server, G_URI_FLAGS_NONE, NULL);
795   expected = g_strdup_printf ("%s@%s",
796                               ephy_sync_service_get_secret (self, secrets[UID]),
797                               g_uri_get_host (uri));
798   retval = g_strcmp0 (email, expected) == 0;
799 
800 out:
801   g_free (expected);
802   g_object_unref (parser);
803   g_free (payload);
804   g_free (header);
805   g_strfreev (pieces);
806   if (error)
807     g_error_free (error);
808 
809   return retval;
810 }
811 
812 static void
forget_secrets_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)813 forget_secrets_cb (GObject      *source_object,
814                    GAsyncResult *result,
815                    gpointer      user_data)
816 {
817   GError *error = NULL;
818 
819   secret_password_clear_finish (result, &error);
820   if (error) {
821     if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
822       g_warning ("Failed to clear sync secrets: %s", error->message);
823     g_error_free (error);
824   } else {
825     LOG ("Successfully cleared sync secrets");
826   }
827 }
828 
829 static void
ephy_sync_service_forget_secrets(EphySyncService * self)830 ephy_sync_service_forget_secrets (EphySyncService *self)
831 {
832   GHashTable *attributes;
833   char *user;
834 
835   g_assert (EPHY_IS_SYNC_SERVICE (self));
836   g_assert (self->secrets);
837 
838   user = ephy_sync_utils_get_sync_user ();
839   g_assert (user);
840 
841   attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
842                                         EPHY_SYNC_SECRET_ACCOUNT_KEY, user,
843                                         NULL);
844   secret_password_clearv (EPHY_SYNC_SECRET_SCHEMA, attributes, self->cancellable,
845                           (GAsyncReadyCallback)forget_secrets_cb, NULL);
846   g_hash_table_remove_all (self->secrets);
847 
848   g_hash_table_unref (attributes);
849   g_free (user);
850 }
851 
852 static void
destroy_session_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)853 destroy_session_cb (SoupSession *session,
854                     SoupMessage *msg,
855                     gpointer     user_data)
856 {
857   guint status_code;
858   g_autoptr (GBytes) response_body = NULL;
859 
860 #if SOUP_CHECK_VERSION (2, 99, 4)
861   status_code = soup_message_get_status (msg);
862   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
863 #else
864   status_code = msg->status_code;
865   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
866 #endif
867 
868   if (status_code != 200) {
869     g_warning ("Failed to destroy session. Status code: %u, response: %s",
870                status_code, (const char *)g_bytes_get_data (response_body, NULL));
871   } else {
872     LOG ("Successfully destroyed session");
873   }
874 }
875 
876 #if SOUP_CHECK_VERSION (2, 99, 4)
877 static void
destroy_session_send_and_read_ready_cb(SoupSession * session,GAsyncResult * result,gpointer user_data)878 destroy_session_send_and_read_ready_cb (SoupSession  *session,
879                                         GAsyncResult *result,
880                                         gpointer      user_data)
881 {
882   GBytes *bytes;
883   SoupMessage *msg;
884   g_autoptr (GError) error = NULL;
885 
886   bytes = soup_session_send_and_read_finish (session, result, &error);
887   if (!bytes)
888     g_warning ("Failed to send request: %s", error->message);
889 
890   msg = soup_session_get_async_result_message (session, result);
891   g_object_set_data_full (G_OBJECT (msg),
892                           "ephy-request-body", bytes ? bytes : g_bytes_new (NULL, 0),
893                           (GDestroyNotify)g_bytes_unref);
894   destroy_session_cb (session, msg, user_data);
895 }
896 #endif
897 
898 static void
ephy_sync_service_destroy_session(EphySyncService * self,const char * session_token)899 ephy_sync_service_destroy_session (EphySyncService *self,
900                                    const char      *session_token)
901 {
902   SyncCryptoHawkOptions *options;
903   SyncCryptoHawkHeader *header;
904   SoupMessage *msg;
905   SoupMessageHeaders *request_headers;
906   guint8 *token_id;
907   guint8 *req_hmac_key;
908   guint8 *tmp;
909   char *token_id_hex;
910   char *url;
911   const char *content_type = "application/json; charset=utf-8";
912   const char *request_body = "{}";
913   g_autofree char *accounts_server = NULL;
914 #if SOUP_CHECK_VERSION (2, 99, 4)
915   g_autoptr (GBytes) bytes = NULL;
916 #endif
917 
918   g_assert (EPHY_IS_SYNC_SERVICE (self));
919   if (!session_token)
920     session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
921   g_assert (session_token);
922 
923   /* This also destroys the device associated with the session token. */
924   accounts_server = ephy_sync_utils_get_accounts_server ();
925   url = g_strdup_printf ("%s/session/destroy", accounts_server);
926   ephy_sync_crypto_derive_session_token (session_token, &token_id,
927                                          &req_hmac_key, &tmp);
928   token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
929 
930   msg = soup_message_new (SOUP_METHOD_POST, url);
931 #if SOUP_CHECK_VERSION (2, 99, 4)
932   bytes = g_bytes_new_static (request_body, strlen (request_body));
933   soup_message_set_request_body_from_bytes (msg, content_type, bytes);
934   request_headers = soup_message_get_request_headers (msg);
935 #else
936   soup_message_set_request (msg, content_type, SOUP_MEMORY_STATIC,
937                             request_body, strlen (request_body));
938   request_headers = msg->request_headers;
939 #endif
940   options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL, content_type,
941                                                NULL, NULL, NULL, request_body,
942                                                NULL);
943   header = ephy_sync_crypto_hawk_header_new (url, "POST", token_id_hex,
944                                              req_hmac_key, 32, options);
945   soup_message_headers_append (request_headers, "authorization", header->header);
946   soup_message_headers_append (request_headers, "content-type", content_type);
947 #if SOUP_CHECK_VERSION (2, 99, 4)
948   soup_session_send_and_read_async (self->session, msg, G_PRIORITY_DEFAULT, NULL,
949                                     (GAsyncReadyCallback)destroy_session_send_and_read_ready_cb,
950                                     NULL);
951 #else
952   soup_session_queue_message (self->session, msg, destroy_session_cb, NULL);
953 #endif
954 
955   g_free (token_id_hex);
956   g_free (token_id);
957   g_free (req_hmac_key);
958   g_free (tmp);
959   g_free (url);
960   ephy_sync_crypto_hawk_options_free (options);
961   ephy_sync_crypto_hawk_header_free (header);
962 }
963 
964 static void
ephy_sync_service_report_sign_in_error(EphySyncService * self,const char * message,const char * session_token,gboolean clear_secrets)965 ephy_sync_service_report_sign_in_error (EphySyncService *self,
966                                         const char      *message,
967                                         const char      *session_token,
968                                         gboolean         clear_secrets)
969 {
970   g_assert (EPHY_IS_SYNC_SERVICE (self));
971   g_assert (message);
972 
973   g_signal_emit (self, signals[SIGN_IN_ERROR], 0, message);
974   ephy_sync_service_destroy_session (self, session_token);
975 
976   if (clear_secrets) {
977     g_clear_pointer (&self->user, g_free);
978     g_hash_table_remove_all (self->secrets);
979   }
980 
981   self->is_signing_in = FALSE;
982 }
983 
984 static void
get_storage_credentials_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)985 get_storage_credentials_cb (SoupSession *session,
986                             SoupMessage *msg,
987                             gpointer     user_data)
988 {
989   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
990   JsonNode *node = NULL;
991   JsonObject *json = NULL;
992   GError *error = NULL;
993   const char *api_endpoint;
994   const char *id;
995   const char *key;
996   const char *message;
997   const char *suggestion;
998   int duration;
999   guint status_code;
1000   g_autoptr (GBytes) response_body = NULL;
1001 
1002 #if SOUP_CHECK_VERSION (2, 99, 4)
1003   status_code = soup_message_get_status (msg);
1004   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1005 #else
1006   status_code = msg->status_code;
1007   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1008 #endif
1009 
1010   if (status_code != 200) {
1011     g_warning ("Failed to obtain storage credentials. Status code: %u, response: %s",
1012                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1013     goto out_error;
1014   }
1015   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
1016   if (error) {
1017     g_warning ("Response is not a valid JSON: %s", error->message);
1018     goto out_error;
1019   }
1020   json = json_node_get_object (node);
1021   if (!json) {
1022     g_warning ("JSON node does not hold a JSON object");
1023     goto out_error;
1024   }
1025   api_endpoint = json_object_get_string_member (json, "api_endpoint");
1026   id = json_object_get_string_member (json, "id");
1027   key = json_object_get_string_member (json, "key");
1028   duration = json_object_get_int_member (json, "duration");
1029   if (!api_endpoint || !id || !key || !duration) {
1030     g_warning ("JSON object has missing or invalid members");
1031     goto out_error;
1032   }
1033 
1034   self->storage_endpoint = g_strdup (api_endpoint);
1035   self->storage_credentials_id = g_strdup (id);
1036   self->storage_credentials_key = g_strdup (key);
1037   self->storage_credentials_expiry_time = duration + g_get_real_time () / 1000000;
1038 
1039   ephy_sync_service_send_all_storage_requests (self);
1040   goto out;
1041 
1042 out_error:
1043   message = _("Failed to obtain storage credentials.");
1044   suggestion = _("Please visit Firefox Sync and sign in again to continue syncing.");
1045 
1046   if (self->is_signing_in)
1047     ephy_sync_service_report_sign_in_error (self, message, NULL, TRUE);
1048   else
1049     ephy_notification_show (ephy_notification_new (message, suggestion));
1050 
1051   ephy_sync_service_clear_storage_queue (self);
1052 
1053 out:
1054   self->locked = FALSE;
1055 
1056   if (node)
1057     json_node_unref (node);
1058   if (error)
1059     g_error_free (error);
1060 }
1061 
1062 #if SOUP_CHECK_VERSION (2, 99, 4)
1063 static void
get_storage_credentials_ready_cb(SoupSession * session,GAsyncResult * result,gpointer user_data)1064 get_storage_credentials_ready_cb (SoupSession  *session,
1065                                   GAsyncResult *result,
1066                                   gpointer      user_data)
1067 {
1068   GBytes *bytes;
1069   SoupMessage *msg;
1070   g_autoptr (GError) error = NULL;
1071 
1072   bytes = soup_session_send_and_read_finish (session, result, &error);
1073   if (!bytes)
1074     g_warning ("Failed to send store credentials request: %s\n", error->message);
1075 
1076   msg = soup_session_get_async_result_message (session, result);
1077   g_object_set_data_full (G_OBJECT (msg),
1078                           "ephy-request-body", bytes ? bytes : g_bytes_new (NULL, 0),
1079                           (GDestroyNotify)g_bytes_unref);
1080   get_storage_credentials_cb (session, msg, user_data);
1081 }
1082 #endif
1083 
1084 static void
ephy_sync_service_trade_browserid_assertion(EphySyncService * self)1085 ephy_sync_service_trade_browserid_assertion (EphySyncService *self)
1086 {
1087   SoupMessage *msg;
1088   SoupMessageHeaders *request_headers;
1089   guint8 *kb;
1090   char *hashed_kb;
1091   char *client_state;
1092   char *audience;
1093   char *assertion;
1094   char *authorization;
1095   g_autofree char *token_server = NULL;
1096 
1097   g_assert (EPHY_IS_SYNC_SERVICE (self));
1098   g_assert (self->certificate);
1099   g_assert (self->key_pair);
1100 
1101   token_server = ephy_sync_utils_get_token_server ();
1102   audience = ephy_sync_utils_get_audience (token_server);
1103   assertion = ephy_sync_crypto_create_assertion (self->certificate, audience,
1104                                                  300, self->key_pair);
1105   kb = ephy_sync_utils_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
1106   hashed_kb = g_compute_checksum_for_data (G_CHECKSUM_SHA256, kb, 32);
1107   client_state = g_strndup (hashed_kb, 32);
1108   authorization = g_strdup_printf ("BrowserID %s", assertion);
1109 
1110   msg = soup_message_new (SOUP_METHOD_GET, token_server);
1111 #if SOUP_CHECK_VERSION (2, 99, 4)
1112   request_headers = soup_message_get_request_headers (msg);
1113 #else
1114   request_headers = msg->request_headers;
1115 #endif
1116   /* We need to add the X-Client-State header so that the Token Server will
1117    * recognize accounts that were previously used to sync Firefox data too.
1118    */
1119   soup_message_headers_append (request_headers, "X-Client-State", client_state);
1120   soup_message_headers_append (request_headers, "authorization", authorization);
1121 #if SOUP_CHECK_VERSION (2, 99, 4)
1122   soup_session_send_and_read_async (self->session, msg, G_PRIORITY_DEFAULT, NULL,
1123                                     (GAsyncReadyCallback)get_storage_credentials_ready_cb,
1124                                     self);
1125 #else
1126   soup_session_queue_message (self->session, msg, get_storage_credentials_cb, self);
1127 #endif
1128 
1129   g_free (kb);
1130   g_free (hashed_kb);
1131   g_free (client_state);
1132   g_free (audience);
1133   g_free (assertion);
1134   g_free (authorization);
1135 }
1136 
1137 static void
get_signed_certificate_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1138 get_signed_certificate_cb (SoupSession *session,
1139                            SoupMessage *msg,
1140                            gpointer     user_data)
1141 {
1142   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
1143   JsonNode *node = NULL;
1144   JsonObject *json = NULL;
1145   GError *error = NULL;
1146   const char *suggestion = NULL;
1147   const char *message = NULL;
1148   const char *certificate = NULL;
1149   guint status_code;
1150   g_autoptr (GBytes) response_body = NULL;
1151 
1152 #if SOUP_CHECK_VERSION (2, 99, 4)
1153   status_code = soup_message_get_status (msg);
1154   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1155 #else
1156   status_code = msg->status_code;
1157   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1158 #endif
1159 
1160   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
1161   if (error) {
1162     g_warning ("Response is not a valid JSON: %s", error->message);
1163     goto out_error;
1164   }
1165   json = json_node_get_object (node);
1166   if (!json) {
1167     g_warning ("JSON node does not hold a JSON object");
1168     goto out_error;
1169   }
1170 
1171   if (status_code == 200) {
1172     certificate = json_object_get_string_member (json, "cert");
1173     if (!certificate) {
1174       g_warning ("JSON object has missing or invalid 'cert' member");
1175       goto out_error;
1176     }
1177 
1178     if (!ephy_sync_service_verify_certificate (self, certificate)) {
1179       g_warning ("Invalid certificate");
1180       ephy_sync_crypto_rsa_key_pair_free (self->key_pair);
1181       goto out_error;
1182     }
1183 
1184     self->certificate = g_strdup (certificate);
1185     ephy_sync_service_trade_browserid_assertion (self);
1186     goto out_no_error;
1187   }
1188 
1189   /* Since a new Firefox Account password implies new tokens, this will fail
1190    * with an error code 110 (Invalid authentication token in request signature)
1191    * if the user has changed his password since the last time he signed in.
1192    * When this happens, notify the user to sign in with the new password.
1193    */
1194   if (json_object_get_int_member (json, "errno") == 110) {
1195     message = _("The password of your Firefox account seems to have been changed.");
1196     suggestion = _("Please visit Firefox Sync and sign in with the new password to continue syncing.");
1197     ephy_sync_service_sign_out (self);
1198   }
1199 
1200   g_warning ("Failed to sign certificate. Status code: %u, response: %s",
1201              status_code, (const char *)g_bytes_get_data (response_body, NULL));
1202 
1203 out_error:
1204   message = message ? message : _("Failed to obtain signed certificate.");
1205   suggestion = suggestion ? suggestion : _("Please visit Firefox Sync and sign in again to continue syncing.");
1206 
1207   if (self->is_signing_in)
1208     ephy_sync_service_report_sign_in_error (self, message, NULL, TRUE);
1209   else
1210     ephy_notification_show (ephy_notification_new (message, suggestion));
1211 
1212   ephy_sync_service_clear_storage_queue (self);
1213   self->locked = FALSE;
1214 
1215 out_no_error:
1216   if (node)
1217     json_node_unref (node);
1218   if (error)
1219     g_error_free (error);
1220 }
1221 
1222 static void
ephy_sync_service_get_storage_credentials(EphySyncService * self)1223 ephy_sync_service_get_storage_credentials (EphySyncService *self)
1224 {
1225   JsonNode *node;
1226   JsonObject *object_key;
1227   JsonObject *object_body;
1228   guint8 *token_id;
1229   guint8 *req_hmac_key;
1230   guint8 *tmp;
1231   const char *session_token;
1232   char *token_id_hex;
1233   char *request_body;
1234   char *n;
1235   char *e;
1236 
1237   g_assert (EPHY_IS_SYNC_SERVICE (self));
1238 
1239   /* To get the storage credentials from the Token Server, we need to create a
1240    * BrowserID assertion. For that we need to obtain an identity certificate
1241    * signed by the Firefox Accounts Server.
1242    */
1243 
1244   /* Generate a new RSA key pair to sign the new certificate. */
1245   if (self->key_pair)
1246     ephy_sync_crypto_rsa_key_pair_free (self->key_pair);
1247   self->key_pair = ephy_sync_crypto_rsa_key_pair_new ();
1248 
1249   /* Derive tokenID and reqHMACkey from sessionToken. */
1250   session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
1251   if (!session_token)
1252     return;
1253 
1254   ephy_sync_crypto_derive_session_token (session_token, &token_id,
1255                                          &req_hmac_key, &tmp);
1256   token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
1257 
1258   n = mpz_get_str (NULL, 10, self->key_pair->public.n);
1259   e = mpz_get_str (NULL, 10, self->key_pair->public.e);
1260   node = json_node_new (JSON_NODE_OBJECT);
1261   object_body = json_object_new ();
1262   /* Milliseconds, limited to 24 hours. */
1263   json_object_set_int_member (object_body, "duration", 1 * 60 * 60 * 1000);
1264   object_key = json_object_new ();
1265   json_object_set_string_member (object_key, "algorithm", "RS");
1266   json_object_set_string_member (object_key, "n", n);
1267   json_object_set_string_member (object_key, "e", e);
1268   json_object_set_object_member (object_body, "publicKey", object_key);
1269   json_node_set_object (node, object_body);
1270   request_body = json_to_string (node, FALSE);
1271   ephy_sync_service_fxa_hawk_post (self, "certificate/sign", token_id_hex,
1272                                    req_hmac_key, 32, request_body,
1273                                    get_signed_certificate_cb, self);
1274 
1275   g_free (request_body);
1276   json_object_unref (object_body);
1277   json_node_unref (node);
1278   g_free (e);
1279   g_free (n);
1280   g_free (token_id_hex);
1281   g_free (tmp);
1282   g_free (req_hmac_key);
1283   g_free (token_id);
1284 }
1285 
1286 static void
ephy_sync_service_queue_storage_request(EphySyncService * self,const char * endpoint,const char * method,const char * request_body,gint64 modified_since,gint64 unmodified_since,SoupSessionCallback callback,gpointer user_data)1287 ephy_sync_service_queue_storage_request (EphySyncService     *self,
1288                                          const char          *endpoint,
1289                                          const char          *method,
1290                                          const char          *request_body,
1291                                          gint64               modified_since,
1292                                          gint64               unmodified_since,
1293                                          SoupSessionCallback  callback,
1294                                          gpointer             user_data)
1295 {
1296   StorageRequestAsyncData *data;
1297 
1298   g_assert (EPHY_IS_SYNC_SERVICE (self));
1299   g_assert (endpoint);
1300   g_assert (method);
1301 
1302   data = storage_request_async_data_new (endpoint, method, request_body,
1303                                          modified_since, unmodified_since,
1304                                          callback, user_data);
1305 
1306   /* If the storage credentials are valid, then directly send the request.
1307    * Otherwise, the request will remain queued and scheduled to be sent when
1308    * the new credentials are obtained.
1309    */
1310   if (!ephy_sync_service_storage_credentials_is_expired (self)) {
1311     ephy_sync_service_send_storage_request (self, data);
1312   } else {
1313     g_queue_push_tail (self->storage_queue, data);
1314     if (!self->locked) {
1315       /* Mark as locked so other requests won't lead to conflicts while
1316        * obtaining new storage credentials.
1317        */
1318       self->locked = TRUE;
1319       ephy_sync_service_clear_storage_credentials (self);
1320       ephy_sync_service_get_storage_credentials (self);
1321     }
1322   }
1323 }
1324 
1325 static void
delete_synchronizable_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1326 delete_synchronizable_cb (SoupSession *session,
1327                           SoupMessage *msg,
1328                           gpointer     user_data)
1329 {
1330   guint status_code;
1331   g_autoptr (GBytes) response_body = NULL;
1332 
1333 #if SOUP_CHECK_VERSION (2, 99, 4)
1334   status_code = soup_message_get_status (msg);
1335   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1336 #else
1337   status_code = msg->status_code;
1338   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1339 #endif
1340 
1341   if (status_code == 200) {
1342     LOG ("Successfully deleted from server");
1343   } else {
1344     g_warning ("Failed to delete object. Status code: %u, response: %s",
1345                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1346   }
1347 }
1348 
1349 static void
ephy_sync_service_delete_synchronizable(EphySyncService * self,EphySynchronizableManager * manager,EphySynchronizable * synchronizable)1350 ephy_sync_service_delete_synchronizable (EphySyncService           *self,
1351                                          EphySynchronizableManager *manager,
1352                                          EphySynchronizable        *synchronizable)
1353 {
1354   JsonNode *node;
1355   JsonObject *object;
1356   SyncCryptoKeyBundle *bundle;
1357   char *endpoint;
1358   char *record;
1359   char *payload;
1360   char *body;
1361   char *id_safe;
1362   const char *collection;
1363   const char *id;
1364 
1365   g_assert (EPHY_IS_SYNC_SERVICE (self));
1366   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
1367   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
1368   g_assert (ephy_sync_utils_user_is_signed_in ());
1369 
1370   collection = ephy_synchronizable_manager_get_collection_name (manager);
1371   bundle = ephy_sync_service_get_key_bundle (self, collection);
1372   if (!bundle)
1373     return;
1374 
1375   id = ephy_synchronizable_get_id (synchronizable);
1376   /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
1377    * Curly braces are unsafe characters in URLs so they must be encoded.
1378    */
1379   id_safe = g_uri_escape_string (id, NULL, TRUE);
1380   endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
1381 
1382   node = json_node_new (JSON_NODE_OBJECT);
1383   object = json_object_new ();
1384   json_node_set_object (node, object);
1385   json_object_set_string_member (object, "id", id);
1386   json_object_set_boolean_member (object, "deleted", TRUE);
1387   record = json_to_string (node, FALSE);
1388   payload = ephy_sync_crypto_encrypt_record (record, bundle);
1389   json_object_remove_member (object, "deleted");
1390   json_object_set_string_member (object, "payload", payload);
1391   body = json_to_string (node, FALSE);
1392 
1393   LOG ("Deleting object with id %s from collection %s...", id, collection);
1394   ephy_sync_service_queue_storage_request (self, endpoint,
1395                                            SOUP_METHOD_PUT, body, -1, -1,
1396                                            delete_synchronizable_cb, NULL);
1397 
1398   g_free (id_safe);
1399   g_free (endpoint);
1400   g_free (record);
1401   g_free (payload);
1402   g_free (body);
1403   json_object_unref (object);
1404   json_node_unref (node);
1405   ephy_sync_crypto_key_bundle_free (bundle);
1406 }
1407 
1408 static void
download_synchronizable_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1409 download_synchronizable_cb (SoupSession *session,
1410                             SoupMessage *msg,
1411                             gpointer     user_data)
1412 {
1413   SyncAsyncData *data = (SyncAsyncData *)user_data;
1414   EphySynchronizable *synchronizable;
1415   SyncCryptoKeyBundle *bundle = NULL;
1416   JsonNode *node = NULL;
1417   GError *error = NULL;
1418   GType type;
1419   const char *collection;
1420   gboolean is_deleted;
1421   guint status_code;
1422   g_autoptr (GBytes) response_body = NULL;
1423 
1424 #if SOUP_CHECK_VERSION (2, 99, 4)
1425   status_code = soup_message_get_status (msg);
1426   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1427 #else
1428   status_code = msg->status_code;
1429   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1430 #endif
1431 
1432   if (status_code != 200) {
1433     g_warning ("Failed to download object. Status code: %u, response: %s",
1434                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1435     goto out;
1436   }
1437   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
1438   if (error) {
1439     g_warning ("Response is not a valid JSON");
1440     goto out;
1441   }
1442   type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
1443   collection = ephy_synchronizable_manager_get_collection_name (data->manager);
1444   bundle = ephy_sync_service_get_key_bundle (data->service, collection);
1445   if (!bundle)
1446     goto out;
1447 
1448   synchronizable = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (node, type, bundle, &is_deleted));
1449   if (!synchronizable) {
1450     g_warning ("Failed to create synchronizable object from BSO");
1451     goto out;
1452   }
1453 
1454   /* Delete the local object and add the remote one if it is not marked as deleted. */
1455   ephy_synchronizable_manager_remove (data->manager, data->synchronizable);
1456   if (!is_deleted) {
1457     ephy_synchronizable_manager_add (data->manager, synchronizable);
1458     LOG ("Successfully downloaded from server");
1459   } else {
1460     LOG ("The newer version was a deleted object");
1461   }
1462 
1463   g_object_unref (synchronizable);
1464 out:
1465   if (node)
1466     json_node_unref (node);
1467   if (error)
1468     g_error_free (error);
1469   if (bundle)
1470     ephy_sync_crypto_key_bundle_free (bundle);
1471   sync_async_data_free (data);
1472 }
1473 
1474 static void
ephy_sync_service_download_synchronizable(EphySyncService * self,EphySynchronizableManager * manager,EphySynchronizable * synchronizable)1475 ephy_sync_service_download_synchronizable (EphySyncService           *self,
1476                                            EphySynchronizableManager *manager,
1477                                            EphySynchronizable        *synchronizable)
1478 {
1479   SyncAsyncData *data;
1480   char *endpoint;
1481   char *id_safe;
1482   const char *collection;
1483   const char *id;
1484 
1485   g_assert (EPHY_IS_SYNC_SERVICE (self));
1486   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
1487   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
1488   g_assert (ephy_sync_utils_user_is_signed_in ());
1489 
1490   id = ephy_synchronizable_get_id (synchronizable);
1491   collection = ephy_synchronizable_manager_get_collection_name (manager);
1492   /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
1493    * Curly braces are unsafe characters in URLs so they must be encoded.
1494    */
1495   id_safe = g_uri_escape_string (id, NULL, TRUE);
1496   endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
1497   data = sync_async_data_new (self, manager, synchronizable);
1498 
1499   LOG ("Downloading object with id %s...", id);
1500   ephy_sync_service_queue_storage_request (self, endpoint,
1501                                            SOUP_METHOD_GET, NULL, -1, -1,
1502                                            download_synchronizable_cb, data);
1503 
1504   g_free (endpoint);
1505   g_free (id_safe);
1506 }
1507 
1508 static void
upload_synchronizable_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1509 upload_synchronizable_cb (SoupSession *session,
1510                           SoupMessage *msg,
1511                           gpointer     user_data)
1512 {
1513   SyncAsyncData *data = (SyncAsyncData *)user_data;
1514   gint64 time_modified;
1515   guint status_code;
1516   g_autoptr (GBytes) response_body = NULL;
1517 
1518 #if SOUP_CHECK_VERSION (2, 99, 4)
1519   status_code = soup_message_get_status (msg);
1520   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1521 #else
1522   status_code = msg->status_code;
1523   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1524 #endif
1525 
1526   /* Code 412 means that there is a more recent version on the server.
1527    * Download it.
1528    */
1529   if (status_code == 412) {
1530     LOG ("Found a newer version of the object on the server, downloading it...");
1531     ephy_sync_service_download_synchronizable (data->service, data->manager, data->synchronizable);
1532   } else if (status_code == 200) {
1533     LOG ("Successfully uploaded to server");
1534     time_modified = ceil (g_ascii_strtod (g_bytes_get_data (response_body, NULL), NULL));
1535     ephy_synchronizable_set_server_time_modified (data->synchronizable, time_modified);
1536     ephy_synchronizable_manager_save (data->manager, data->synchronizable);
1537   } else {
1538     g_warning ("Failed to upload object. Status code: %u, response: %s",
1539                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1540   }
1541 
1542   sync_async_data_free (data);
1543 }
1544 
1545 static void
ephy_sync_service_upload_synchronizable(EphySyncService * self,EphySynchronizableManager * manager,EphySynchronizable * synchronizable,gboolean should_force)1546 ephy_sync_service_upload_synchronizable (EphySyncService           *self,
1547                                          EphySynchronizableManager *manager,
1548                                          EphySynchronizable        *synchronizable,
1549                                          gboolean                   should_force)
1550 {
1551   SyncCryptoKeyBundle *bundle;
1552   SyncAsyncData *data;
1553   JsonNode *bso;
1554   char *endpoint;
1555   char *body;
1556   char *id_safe;
1557   const char *collection;
1558   const char *id;
1559   gint64 time_modified;
1560 
1561   g_assert (EPHY_IS_SYNC_SERVICE (self));
1562   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
1563   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
1564   g_assert (ephy_sync_utils_user_is_signed_in ());
1565 
1566   collection = ephy_synchronizable_manager_get_collection_name (manager);
1567   bundle = ephy_sync_service_get_key_bundle (self, collection);
1568   if (!bundle)
1569     return;
1570 
1571   bso = ephy_synchronizable_to_bso (synchronizable, bundle);
1572   id = ephy_synchronizable_get_id (synchronizable);
1573   /* Firefox uses UUIDs with curly braces as IDs for saved passwords records.
1574    * Curly braces are unsafe characters in URLs so they must be encoded.
1575    */
1576   id_safe = g_uri_escape_string (id, NULL, TRUE);
1577   endpoint = g_strdup_printf ("storage/%s/%s", collection, id_safe);
1578   data = sync_async_data_new (self, manager, synchronizable);
1579   body = json_to_string (bso, FALSE);
1580 
1581   LOG ("Uploading object with id %s...", id);
1582   time_modified = ephy_synchronizable_get_server_time_modified (synchronizable);
1583   ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_PUT, body,
1584                                            -1, should_force ? -1 : time_modified,
1585                                            upload_synchronizable_cb, data);
1586 
1587   g_free (id_safe);
1588   g_free (body);
1589   g_free (endpoint);
1590   json_node_unref (bso);
1591   ephy_sync_crypto_key_bundle_free (bundle);
1592 }
1593 
1594 static GPtrArray *
ephy_sync_service_split_into_batches(EphySyncService * self,EphySynchronizableManager * manager,GPtrArray * synchronizables,guint start,guint end)1595 ephy_sync_service_split_into_batches (EphySyncService           *self,
1596                                       EphySynchronizableManager *manager,
1597                                       GPtrArray                 *synchronizables,
1598                                       guint                      start,
1599                                       guint                      end)
1600 {
1601   SyncCryptoKeyBundle *bundle;
1602   GPtrArray *batches;
1603   const char *collection;
1604 
1605   g_assert (EPHY_IS_SYNC_SERVICE (self));
1606   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
1607   g_assert (synchronizables);
1608 
1609   collection = ephy_synchronizable_manager_get_collection_name (manager);
1610   bundle = ephy_sync_service_get_key_bundle (self, collection);
1611   if (!bundle)
1612     return NULL;
1613 
1614   batches = g_ptr_array_new_with_free_func (g_free);
1615 
1616   for (guint i = start; i < end; i += EPHY_SYNC_BATCH_SIZE) {
1617     JsonNode *node = json_node_new (JSON_NODE_ARRAY);
1618     JsonArray *array = json_array_new ();
1619 
1620     for (guint k = i; k < MIN (i + EPHY_SYNC_BATCH_SIZE, end); k++) {
1621       EphySynchronizable *synchronizable = g_ptr_array_index (synchronizables, k);
1622       JsonNode *bso = ephy_synchronizable_to_bso (synchronizable, bundle);
1623       JsonObject *object = json_object_ref (json_node_get_object (bso));
1624 
1625       json_array_add_object_element (array, object);
1626       json_node_unref (bso);
1627     }
1628 
1629     json_node_take_array (node, array);
1630     g_ptr_array_add (batches, json_to_string (node, FALSE));
1631     json_node_unref (node);
1632   }
1633 
1634   ephy_sync_crypto_key_bundle_free (bundle);
1635 
1636   return batches;
1637 }
1638 
1639 static void
commit_batch_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1640 commit_batch_cb (SoupSession *session,
1641                  SoupMessage *msg,
1642                  gpointer     user_data)
1643 {
1644   BatchUploadAsyncData *data = user_data;
1645   const char *last_modified;
1646   guint status_code;
1647   SoupMessageHeaders *response_headers;
1648   g_autoptr (GBytes) response_body = NULL;
1649 
1650 #if SOUP_CHECK_VERSION (2, 99, 4)
1651   status_code = soup_message_get_status (msg);
1652   response_headers = soup_message_get_response_headers (msg);
1653   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1654 #else
1655   status_code = msg->status_code;
1656   response_headers = msg->response_headers;
1657   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1658 #endif
1659 
1660   if (status_code != 200) {
1661     g_warning ("Failed to commit batch. Status code: %u, response: %s",
1662                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1663   } else {
1664     LOG ("Successfully committed batches");
1665     /* Update sync time. */
1666     last_modified = soup_message_headers_get_one (response_headers, "X-Last-Modified");
1667     ephy_synchronizable_manager_set_sync_time (data->manager, g_ascii_strtod (last_modified, NULL));
1668   }
1669 
1670   if (data->sync_done)
1671     g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
1672   batch_upload_async_data_free (data);
1673 }
1674 
1675 static void
upload_batch_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1676 upload_batch_cb (SoupSession *session,
1677                  SoupMessage *msg,
1678                  gpointer     user_data)
1679 {
1680   BatchUploadAsyncData *data = user_data;
1681   const char *collection;
1682   char *endpoint = NULL;
1683   guint status_code;
1684   g_autoptr (GBytes) response_body = NULL;
1685 
1686 #if SOUP_CHECK_VERSION (2, 99, 4)
1687   status_code = soup_message_get_status (msg);
1688   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1689 #else
1690   status_code = msg->status_code;
1691   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1692 #endif
1693 
1694   /* Note: "202 Accepted" status code. */
1695   if (status_code != 202) {
1696     g_warning ("Failed to upload batch. Status code: %u, response: %s",
1697                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1698   } else {
1699     LOG ("Successfully uploaded batch");
1700   }
1701 
1702   if (!data->batch_is_last)
1703     goto out;
1704 
1705   collection = ephy_synchronizable_manager_get_collection_name (data->manager);
1706   endpoint = g_strdup_printf ("storage/%s?commit=true&batch=%s", collection, data->batch_id);
1707   ephy_sync_service_queue_storage_request (data->service, endpoint,
1708                                            SOUP_METHOD_POST, "[]", -1, -1,
1709                                            commit_batch_cb,
1710                                            batch_upload_async_data_dup (data));
1711 
1712 out:
1713   g_free (endpoint);
1714   /* Remove last reference to the array with the items to upload. */
1715   if (data->batch_is_last)
1716     g_ptr_array_unref (data->synchronizables);
1717   batch_upload_async_data_free (data);
1718 }
1719 
1720 static void
start_batch_upload_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1721 start_batch_upload_cb (SoupSession *session,
1722                        SoupMessage *msg,
1723                        gpointer     user_data)
1724 {
1725   BatchUploadAsyncData *data = user_data;
1726   GPtrArray *batches = NULL;
1727   JsonNode *node = NULL;
1728   JsonObject *object;
1729   g_autoptr (GError) error = NULL;
1730   const char *collection;
1731   char *endpoint = NULL;
1732   guint status_code;
1733   g_autoptr (GBytes) response_body = NULL;
1734 
1735 #if SOUP_CHECK_VERSION (2, 99, 4)
1736   status_code = soup_message_get_status (msg);
1737   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1738 #else
1739   status_code = msg->status_code;
1740   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1741 #endif
1742 
1743   /* Note: "202 Accepted" status code. */
1744   if (status_code != 202) {
1745     g_warning ("Failed to start batch upload. Status code: %u, response: %s",
1746                status_code, (const char *)g_bytes_get_data (response_body, NULL));
1747     goto out;
1748   }
1749 
1750   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
1751   if (error) {
1752     g_warning ("Response is not a valid JSON: %s", error->message);
1753     goto out;
1754   }
1755 
1756   object = json_node_get_object (node);
1757   data->batch_id = g_uri_escape_string (json_object_get_string_member (object, "batch"),
1758                                         NULL, TRUE);
1759   collection = ephy_synchronizable_manager_get_collection_name (data->manager);
1760   endpoint = g_strdup_printf ("storage/%s?batch=%s", collection, data->batch_id);
1761 
1762   batches = ephy_sync_service_split_into_batches (data->service, data->manager,
1763                                                   data->synchronizables,
1764                                                   data->start, data->end);
1765   for (guint i = 0; i < batches->len; i++) {
1766     BatchUploadAsyncData *data_dup = batch_upload_async_data_dup (data);
1767 
1768     if (i == batches->len - 1)
1769       data_dup->batch_is_last = TRUE;
1770 
1771     ephy_sync_service_queue_storage_request (data->service, endpoint, SOUP_METHOD_POST,
1772                                              g_ptr_array_index (batches, i), -1, -1,
1773                                              upload_batch_cb, data_dup);
1774   }
1775 
1776 out:
1777   g_free (endpoint);
1778   if (node)
1779     json_node_unref (node);
1780   if (batches)
1781     g_ptr_array_unref (batches);
1782   batch_upload_async_data_free (data);
1783 }
1784 
1785 static void
merge_collection_finished_cb(GPtrArray * to_upload,gpointer user_data)1786 merge_collection_finished_cb (GPtrArray *to_upload,
1787                               gpointer   user_data)
1788 {
1789   SyncCollectionAsyncData *data = user_data;
1790   BatchUploadAsyncData *bdata;
1791   guint step = EPHY_SYNC_MAX_BATCHES * EPHY_SYNC_BATCH_SIZE;
1792   const char *collection;
1793   char *endpoint = NULL;
1794 
1795   if (!to_upload || to_upload->len == 0) {
1796     if (data->is_last)
1797       g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
1798     goto out;
1799   }
1800 
1801   collection = ephy_synchronizable_manager_get_collection_name (data->manager);
1802   endpoint = g_strdup_printf ("storage/%s?batch=true", collection);
1803 
1804   for (guint i = 0; i < to_upload->len; i += step) {
1805     bdata = batch_upload_async_data_new (data->service, data->manager,
1806                                          to_upload, i,
1807                                          MIN (i + step, to_upload->len),
1808                                          NULL, FALSE,
1809                                          data->is_last && i + step >= to_upload->len);
1810     ephy_sync_service_queue_storage_request (data->service, endpoint,
1811                                              SOUP_METHOD_POST, "[]", -1, -1,
1812                                              start_batch_upload_cb, bdata);
1813   }
1814 
1815 out:
1816   g_free (endpoint);
1817   sync_collection_async_data_free (data);
1818 }
1819 
1820 static void
sync_collection_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)1821 sync_collection_cb (SoupSession *session,
1822                     SoupMessage *msg,
1823                     gpointer     user_data)
1824 {
1825   SyncCollectionAsyncData *data = (SyncCollectionAsyncData *)user_data;
1826   EphySynchronizable *remote;
1827   SyncCryptoKeyBundle *bundle = NULL;
1828   JsonNode *node = NULL;
1829   JsonArray *array = NULL;
1830   g_autoptr (GError) error = NULL;
1831   GType type;
1832   const char *collection;
1833   gboolean is_deleted;
1834   guint status_code;
1835   g_autoptr (GBytes) response_body = NULL;
1836 
1837   collection = ephy_synchronizable_manager_get_collection_name (data->manager);
1838 
1839 #if SOUP_CHECK_VERSION (2, 99, 4)
1840   status_code = soup_message_get_status (msg);
1841   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
1842 #else
1843   status_code = msg->status_code;
1844   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
1845 #endif
1846 
1847   if (status_code != 200) {
1848     g_warning ("Failed to get records in collection %s. Status code: %u, response: %s",
1849                collection, status_code, (const char *)g_bytes_get_data (response_body, NULL));
1850     goto out_error;
1851   }
1852   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
1853   if (error) {
1854     g_warning ("Response is not a valid JSON: %s", error->message);
1855     goto out_error;
1856   }
1857   array = json_node_get_array (node);
1858   if (!array) {
1859     g_warning ("JSON node does not hold an array");
1860     goto out_error;
1861   }
1862 
1863   type = ephy_synchronizable_manager_get_synchronizable_type (data->manager);
1864   bundle = ephy_sync_service_get_key_bundle (data->service, collection);
1865   if (!bundle)
1866     goto out_error;
1867 
1868   for (guint i = 0; i < json_array_get_length (array); i++) {
1869     remote = EPHY_SYNCHRONIZABLE (ephy_synchronizable_from_bso (json_array_get_element (array, i),
1870                                                                 type, bundle, &is_deleted));
1871     if (!remote) {
1872       g_warning ("Failed to create synchronizable object from BSO, skipping...");
1873       continue;
1874     }
1875     if (is_deleted)
1876       data->remotes_deleted = g_list_prepend (data->remotes_deleted, remote);
1877     else
1878       data->remotes_updated = g_list_prepend (data->remotes_updated, remote);
1879   }
1880 
1881   LOG ("Found %u deleted objects and %u new/updated objects in %s collection",
1882        g_list_length (data->remotes_deleted),
1883        g_list_length (data->remotes_updated),
1884        collection);
1885 
1886   ephy_synchronizable_manager_set_is_initial_sync (data->manager, FALSE);
1887   ephy_synchronizable_manager_merge (data->manager, data->is_initial,
1888                                      data->remotes_deleted, data->remotes_updated,
1889                                      merge_collection_finished_cb, data);
1890   goto out_no_error;
1891 
1892 out_error:
1893   if (data->is_last)
1894     g_signal_emit (data->service, signals[SYNC_FINISHED], 0);
1895   sync_collection_async_data_free (data);
1896 out_no_error:
1897   if (bundle)
1898     ephy_sync_crypto_key_bundle_free (bundle);
1899   if (node)
1900     json_node_unref (node);
1901 }
1902 
1903 static void
ephy_sync_service_sync_collection(EphySyncService * self,EphySynchronizableManager * manager,gboolean is_last)1904 ephy_sync_service_sync_collection (EphySyncService           *self,
1905                                    EphySynchronizableManager *manager,
1906                                    gboolean                   is_last)
1907 {
1908   SyncCollectionAsyncData *data;
1909   const char *collection;
1910   char *endpoint;
1911   gboolean is_initial;
1912 
1913   g_assert (EPHY_IS_SYNC_SERVICE (self));
1914   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
1915   g_assert (ephy_sync_utils_user_is_signed_in ());
1916 
1917   collection = ephy_synchronizable_manager_get_collection_name (manager);
1918   is_initial = ephy_synchronizable_manager_is_initial_sync (manager);
1919 
1920   if (is_initial) {
1921     endpoint = g_strdup_printf ("storage/%s?full=true", collection);
1922   } else {
1923     endpoint = g_strdup_printf ("storage/%s?newer=%" PRId64 "&full=true", collection,
1924                                 ephy_synchronizable_manager_get_sync_time (manager));
1925   }
1926 
1927   LOG ("Syncing %s collection %s...", collection, is_initial ? "initial" : "regular");
1928   data = sync_collection_async_data_new (self, manager, is_initial, is_last);
1929   ephy_sync_service_queue_storage_request (self, endpoint, SOUP_METHOD_GET,
1930                                            NULL, -1, -1,
1931                                            sync_collection_cb, data);
1932 
1933   g_free (endpoint);
1934 }
1935 
1936 static gboolean
ephy_sync_service_sync_internal(EphySyncService * self)1937 ephy_sync_service_sync_internal (EphySyncService *self)
1938 {
1939   GNetworkMonitor *monitor;
1940   guint index = 0;
1941   guint num_managers;
1942 
1943   g_assert (ephy_sync_utils_user_is_signed_in ());
1944 
1945   monitor = g_network_monitor_get_default ();
1946   if (g_network_monitor_get_connectivity (monitor) != G_NETWORK_CONNECTIVITY_FULL) {
1947     g_signal_emit (self, signals[SYNC_FINISHED], 0);
1948     return G_SOURCE_CONTINUE;
1949   }
1950 
1951   if (!self->managers) {
1952     g_signal_emit (self, signals[SYNC_FINISHED], 0);
1953     return G_SOURCE_CONTINUE;
1954   }
1955 
1956   num_managers = g_slist_length (self->managers);
1957   for (GSList *l = self->managers; l && l->data; l = l->next)
1958     ephy_sync_service_sync_collection (self, l->data, ++index == num_managers);
1959 
1960   ephy_sync_utils_set_sync_time (g_get_real_time () / 1000000);
1961 
1962   return G_SOURCE_CONTINUE;
1963 }
1964 
1965 static void
ephy_sync_service_schedule_periodical_sync(EphySyncService * self)1966 ephy_sync_service_schedule_periodical_sync (EphySyncService *self)
1967 {
1968   guint seconds;
1969 
1970   g_assert (EPHY_IS_SYNC_SERVICE (self));
1971 
1972   seconds = ephy_sync_utils_get_sync_frequency () * 60;
1973   self->source_id = g_timeout_add_seconds (seconds,
1974                                            (GSourceFunc)ephy_sync_service_sync_internal,
1975                                            self);
1976   g_source_set_name_by_id (self->source_id, "[epiphany] sync_service_sync");
1977 
1978   LOG ("Scheduled new sync with frequency %u minutes", seconds / 60);
1979 }
1980 
1981 static void
ephy_sync_service_stop_periodical_sync(EphySyncService * self)1982 ephy_sync_service_stop_periodical_sync (EphySyncService *self)
1983 {
1984   g_assert (EPHY_IS_SYNC_SERVICE (self));
1985 
1986   g_clear_handle_id (&self->source_id, g_source_remove);
1987 }
1988 
1989 static void
sync_frequency_changed_cb(GSettings * settings,char * key,EphySyncService * self)1990 sync_frequency_changed_cb (GSettings       *settings,
1991                            char            *key,
1992                            EphySyncService *self)
1993 {
1994   g_assert (EPHY_IS_SYNC_SERVICE (self));
1995 
1996   ephy_sync_service_stop_periodical_sync (self);
1997   ephy_sync_service_schedule_periodical_sync (self);
1998 }
1999 
2000 static void
load_secrets_cb(GObject * source_object,GAsyncResult * result,EphySyncService * self)2001 load_secrets_cb (GObject         *source_object,
2002                  GAsyncResult    *result,
2003                  EphySyncService *self)
2004 {
2005   SecretValue *value = NULL;
2006   JsonNode *node = NULL;
2007   JsonObject *object;
2008   GList *res = NULL;
2009   GError *error = NULL;
2010   const char *message;
2011   const char *suggestion;
2012 
2013   res = secret_password_search_finish (result, &error);
2014   if (error) {
2015     if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2016       g_error_free (error);
2017       goto out_no_error;
2018     }
2019     g_warning ("Failed to search for sync secrets: %s", error->message);
2020     g_error_free (error);
2021     message = _("Could not find the sync secrets for the current sync user.");
2022     goto out_error;
2023   }
2024 
2025   if (!(res && res->data)) {
2026     message = _("Could not find the sync secrets for the current sync user.");
2027     goto out_error;
2028   }
2029 
2030   value = secret_item_get_secret ((SecretItem *)res->data);
2031   if (!value) {
2032     g_warning ("Failed to retrieve the value of the sync secrets");
2033     message = _("The sync secrets for the current sync user are invalid.");
2034     goto out_error;
2035   }
2036 
2037   node = json_from_string (secret_value_get_text (value), &error);
2038   if (error) {
2039     g_warning ("Sync secrets are not a valid JSON: %s", error->message);
2040     g_error_free (error);
2041     message = _("The sync secrets for the current sync user are invalid.");
2042     goto out_error;
2043   }
2044 
2045   /* Set secrets and start periodical sync. */
2046   object = json_node_get_object (node);
2047   for (GList *l = json_object_get_members (object); l && l->data; l = l->next)
2048     ephy_sync_service_set_secret (self, l->data,
2049                                   json_object_get_string_member (object, l->data));
2050 
2051   g_signal_emit (self, signals[LOAD_FINISHED], 0);
2052   goto out_no_error;
2053 
2054 out_error:
2055   suggestion = _("Please visit Firefox Sync and sign in again to continue syncing.");
2056   ephy_notification_show (ephy_notification_new (message, suggestion));
2057   /* Reset the sync user so that it will be considered signed-out
2058    * when the preferences dialog is opened. */
2059   ephy_sync_utils_set_sync_user (NULL);
2060   ephy_sync_utils_set_sync_time (0);
2061   ephy_sync_utils_set_bookmarks_sync_is_initial (TRUE);
2062   ephy_sync_utils_set_passwords_sync_is_initial (TRUE);
2063   ephy_sync_utils_set_history_sync_is_initial (TRUE);
2064 out_no_error:
2065   if (value)
2066     secret_value_unref (value);
2067   if (res)
2068     g_list_free_full (res, g_object_unref);
2069   if (node)
2070     json_node_unref (node);
2071 }
2072 
2073 static void
ephy_sync_service_load_secrets(EphySyncService * self)2074 ephy_sync_service_load_secrets (EphySyncService *self)
2075 {
2076   GHashTable *attributes;
2077   char *user;
2078 
2079   g_assert (EPHY_IS_SYNC_SERVICE (self));
2080   g_assert (self->secrets);
2081 
2082   user = ephy_sync_utils_get_sync_user ();
2083   attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
2084                                         EPHY_SYNC_SECRET_ACCOUNT_KEY, user,
2085                                         NULL);
2086   secret_password_searchv (EPHY_SYNC_SECRET_SCHEMA, attributes,
2087                            SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
2088                            self->cancellable, (GAsyncReadyCallback)load_secrets_cb, self);
2089 
2090   g_hash_table_unref (attributes);
2091   g_free (user);
2092 }
2093 
2094 static void
store_secrets_cb(GObject * source_object,GAsyncResult * result,EphySyncService * self)2095 store_secrets_cb (GObject         *source_object,
2096                   GAsyncResult    *result,
2097                   EphySyncService *self)
2098 {
2099   GError *error = NULL;
2100 
2101   secret_password_store_finish (result, &error);
2102   if (error) {
2103     if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2104       return;
2105     g_warning ("Failed to store sync secrets: %s", error->message);
2106     ephy_sync_service_destroy_session (self, NULL);
2107     g_hash_table_remove_all (self->secrets);
2108   } else {
2109     LOG ("Successfully stored sync secrets");
2110     ephy_sync_utils_set_sync_user (self->user);
2111   }
2112 
2113   g_signal_emit (self, signals[STORE_FINISHED], 0, error);
2114   self->is_signing_in = FALSE;
2115 
2116   g_clear_pointer (&self->user, g_free);
2117   if (error)
2118     g_error_free (error);
2119 }
2120 
2121 static void
ephy_sync_service_store_secrets(EphySyncService * self)2122 ephy_sync_service_store_secrets (EphySyncService *self)
2123 {
2124   JsonNode *node;
2125   JsonObject *object;
2126   GHashTable *attributes;
2127   GHashTableIter iter;
2128   gpointer key;
2129   gpointer value;
2130   char *json_string;
2131   char *label;
2132 
2133   g_assert (EPHY_IS_SYNC_SERVICE (self));
2134   g_assert (self->secrets);
2135   g_assert (self->user);
2136 
2137   node = json_node_new (JSON_NODE_OBJECT);
2138   object = json_object_new ();
2139   g_hash_table_iter_init (&iter, self->secrets);
2140   while (g_hash_table_iter_next (&iter, &key, &value))
2141     json_object_set_string_member (object, key, value);
2142   json_node_set_object (node, object);
2143   json_string = json_to_string (node, FALSE);
2144 
2145   attributes = secret_attributes_build (EPHY_SYNC_SECRET_SCHEMA,
2146                                         EPHY_SYNC_SECRET_ACCOUNT_KEY, self->user,
2147                                         NULL);
2148   /* Translators: %s is the email of the user. */
2149   label = g_strdup_printf (_("The sync secrets of %s"), self->user);
2150 
2151   LOG ("Storing sync secrets...");
2152   secret_password_storev (EPHY_SYNC_SECRET_SCHEMA,
2153                           attributes, NULL, label, json_string, NULL,
2154                           (GAsyncReadyCallback)store_secrets_cb, self);
2155 
2156   g_free (label);
2157   g_free (json_string);
2158   g_hash_table_unref (attributes);
2159   json_object_unref (object);
2160   json_node_unref (node);
2161 }
2162 
2163 static void
upload_client_record_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2164 upload_client_record_cb (SoupSession *session,
2165                          SoupMessage *msg,
2166                          gpointer     user_data)
2167 {
2168   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
2169   guint status_code;
2170   g_autoptr (GBytes) response_body = NULL;
2171 
2172 #if SOUP_CHECK_VERSION (2, 99, 4)
2173   status_code = soup_message_get_status (msg);
2174   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2175 #else
2176   status_code = msg->status_code;
2177   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2178 #endif
2179 
2180   if (status_code != 200) {
2181     g_warning ("Failed to upload client record. Status code: %u, response: %s",
2182                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2183     if (self->is_signing_in)
2184       ephy_sync_service_report_sign_in_error (self, _("Failed to upload client record."), NULL, TRUE);
2185   } else {
2186     LOG ("Successfully uploaded client record");
2187     if (self->is_signing_in)
2188       ephy_sync_service_store_secrets (self);
2189   }
2190 }
2191 
2192 static void
ephy_sync_service_upload_client_record(EphySyncService * self)2193 ephy_sync_service_upload_client_record (EphySyncService *self)
2194 {
2195   SyncCryptoKeyBundle *bundle;
2196   JsonNode *node;
2197   JsonObject *bso;
2198   char *device_bso_id;
2199   char *device_id;
2200   char *device_name;
2201   char *record;
2202   char *encrypted;
2203   char *body;
2204   char *endpoint;
2205 
2206   g_assert (EPHY_IS_SYNC_SERVICE (self));
2207 
2208   bundle = ephy_sync_service_get_key_bundle (self, "clients");
2209   if (!bundle)
2210     return;
2211 
2212   /* Make device ID and name. */
2213   device_bso_id = ephy_sync_utils_get_device_bso_id ();
2214   device_id = ephy_sync_utils_get_device_id ();
2215   device_name = ephy_sync_utils_get_device_name ();
2216 
2217   /* Make BSO as string. */
2218   record = ephy_sync_utils_make_client_record (device_bso_id, device_id, device_name);
2219   encrypted = ephy_sync_crypto_encrypt_record (record, bundle);
2220 
2221   bso = json_object_new ();
2222   json_object_set_string_member (bso, "id", device_bso_id);
2223   json_object_set_string_member (bso, "payload", encrypted);
2224 
2225   node = json_node_new (JSON_NODE_OBJECT);
2226   json_node_set_object (node, bso);
2227   body = json_to_string (node, FALSE);
2228 
2229   /* Upload BSO and store the new device ID and name. */
2230   LOG ("Uploading client record, device_bso_id=%s, device_id=%s, device_name=%s",
2231        device_bso_id, device_id, device_name);
2232   endpoint = g_strdup_printf ("storage/clients/%s", device_bso_id);
2233   ephy_sync_service_queue_storage_request (self, endpoint,
2234                                            SOUP_METHOD_PUT, body, -1, -1,
2235                                            upload_client_record_cb, self);
2236 
2237   g_free (device_bso_id);
2238   g_free (device_id);
2239   g_free (device_name);
2240   g_free (record);
2241   g_free (encrypted);
2242   g_free (endpoint);
2243   g_free (body);
2244   json_object_unref (bso);
2245   json_node_unref (node);
2246   ephy_sync_crypto_key_bundle_free (bundle);
2247 }
2248 
2249 static void
ephy_sync_service_finalize(GObject * object)2250 ephy_sync_service_finalize (GObject *object)
2251 {
2252   EphySyncService *self = EPHY_SYNC_SERVICE (object);
2253 
2254   if (ephy_sync_utils_user_is_signed_in ())
2255     ephy_sync_service_stop_periodical_sync (self);
2256 
2257   if (self->key_pair)
2258     ephy_sync_crypto_rsa_key_pair_free (self->key_pair);
2259 
2260   g_free (self->crypto_keys);
2261   g_slist_free (self->managers);
2262   g_queue_free_full (self->storage_queue, (GDestroyNotify)storage_request_async_data_free);
2263   ephy_sync_service_clear_storage_credentials (self);
2264 
2265   G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
2266 }
2267 
2268 static void
ephy_sync_service_dispose(GObject * object)2269 ephy_sync_service_dispose (GObject *object)
2270 {
2271   EphySyncService *self = EPHY_SYNC_SERVICE (object);
2272 
2273   g_clear_object (&self->session);
2274   g_clear_pointer (&self->secrets, g_hash_table_unref);
2275 
2276   if (self->cancellable) {
2277     g_cancellable_cancel (self->cancellable);
2278     g_clear_object (&self->cancellable);
2279   }
2280 
2281   G_OBJECT_CLASS (ephy_sync_service_parent_class)->dispose (object);
2282 }
2283 
2284 static void
ephy_sync_service_constructed(GObject * object)2285 ephy_sync_service_constructed (GObject *object)
2286 {
2287   EphySyncService *self = EPHY_SYNC_SERVICE (object);
2288 
2289   G_OBJECT_CLASS (ephy_sync_service_parent_class)->constructed (object);
2290 
2291   if (self->sync_periodically) {
2292     g_object_set (self->session,
2293                   "user-agent", ephy_user_agent_get (),
2294                   NULL);
2295 
2296     g_signal_connect (EPHY_SETTINGS_SYNC, "changed::"EPHY_PREFS_SYNC_FREQUENCY,
2297                       G_CALLBACK (sync_frequency_changed_cb), self);
2298   }
2299 }
2300 
2301 static void
ephy_sync_service_init(EphySyncService * self)2302 ephy_sync_service_init (EphySyncService *self)
2303 {
2304   self->session = soup_session_new ();
2305   self->storage_queue = g_queue_new ();
2306   self->secrets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
2307   self->cancellable = g_cancellable_new ();
2308 
2309   if (ephy_sync_utils_user_is_signed_in ())
2310     ephy_sync_service_load_secrets (self);
2311 }
2312 
2313 static void
ephy_sync_service_class_init(EphySyncServiceClass * klass)2314 ephy_sync_service_class_init (EphySyncServiceClass *klass)
2315 {
2316   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2317 
2318   object_class->set_property = ephy_sync_service_set_property;
2319   object_class->get_property = ephy_sync_service_get_property;
2320   object_class->constructed = ephy_sync_service_constructed;
2321   object_class->dispose = ephy_sync_service_dispose;
2322   object_class->finalize = ephy_sync_service_finalize;
2323 
2324   obj_properties[PROP_SYNC_PERIODICALLY] =
2325     g_param_spec_boolean ("sync-periodically",
2326                           "Sync periodically",
2327                           "Whether should periodically sync data",
2328                           FALSE,
2329                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
2330 
2331   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
2332 
2333   signals[STORE_FINISHED] =
2334     g_signal_new ("sync-secrets-store-finished",
2335                   EPHY_TYPE_SYNC_SERVICE,
2336                   G_SIGNAL_RUN_LAST,
2337                   0, NULL, NULL, NULL,
2338                   G_TYPE_NONE, 1,
2339                   G_TYPE_ERROR);
2340 
2341   signals[LOAD_FINISHED] =
2342     g_signal_new ("sync-secrets-load-finished",
2343                   EPHY_TYPE_SYNC_SERVICE,
2344                   G_SIGNAL_RUN_LAST,
2345                   0, NULL, NULL, NULL,
2346                   G_TYPE_NONE, 0);
2347 
2348   signals[SIGN_IN_ERROR] =
2349     g_signal_new ("sync-sign-in-error",
2350                   EPHY_TYPE_SYNC_SERVICE,
2351                   G_SIGNAL_RUN_LAST,
2352                   0, NULL, NULL, NULL,
2353                   G_TYPE_NONE, 1,
2354                   G_TYPE_STRING);
2355 
2356   signals[SYNC_FINISHED] =
2357     g_signal_new ("sync-finished",
2358                   EPHY_TYPE_SYNC_SERVICE,
2359                   G_SIGNAL_RUN_LAST,
2360                   0, NULL, NULL, NULL,
2361                   G_TYPE_NONE, 0);
2362 }
2363 
2364 EphySyncService *
ephy_sync_service_new(gboolean sync_periodically)2365 ephy_sync_service_new (gboolean sync_periodically)
2366 {
2367   return EPHY_SYNC_SERVICE (g_object_new (EPHY_TYPE_SYNC_SERVICE,
2368                                           "sync-periodically", sync_periodically,
2369                                           NULL));
2370 }
2371 
2372 static void
upload_crypto_keys_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2373 upload_crypto_keys_cb (SoupSession *session,
2374                        SoupMessage *msg,
2375                        gpointer     user_data)
2376 {
2377   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
2378   guint status_code;
2379   g_autoptr (GBytes) response_body = NULL;
2380 
2381 #if SOUP_CHECK_VERSION (2, 99, 4)
2382   status_code = soup_message_get_status (msg);
2383   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2384 #else
2385   status_code = msg->status_code;
2386   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2387 #endif
2388 
2389   if (status_code != 200) {
2390     g_warning ("Failed to upload crypto/keys record. Status code: %u, response: %s",
2391                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2392     ephy_sync_service_report_sign_in_error (self,
2393                                             _("Failed to upload crypto/keys record."),
2394                                             NULL, TRUE);
2395   } else {
2396     LOG ("Successfully uploaded crypto/keys record");
2397     ephy_sync_service_set_secret (self, secrets[CRYPTO_KEYS], self->crypto_keys);
2398     ephy_sync_service_upload_client_record (self);
2399   }
2400 
2401   g_clear_pointer (&self->crypto_keys, g_free);
2402 }
2403 
2404 static void
ephy_sync_service_upload_crypto_keys(EphySyncService * self)2405 ephy_sync_service_upload_crypto_keys (EphySyncService *self)
2406 {
2407   SyncCryptoKeyBundle *bundle;
2408   JsonNode *node;
2409   JsonObject *record;
2410   char *payload;
2411   char *body;
2412   const char *kb_hex;
2413   guint8 *kb;
2414 
2415   g_assert (EPHY_IS_SYNC_SERVICE (self));
2416   kb_hex = ephy_sync_service_get_secret (self, secrets[MASTER_KEY]);
2417   g_assert (kb_hex);
2418 
2419   node = json_node_new (JSON_NODE_OBJECT);
2420   record = json_object_new ();
2421   self->crypto_keys = ephy_sync_crypto_generate_crypto_keys ();
2422   kb = ephy_sync_utils_decode_hex (kb_hex);
2423   bundle = ephy_sync_crypto_derive_master_bundle (kb);
2424   payload = ephy_sync_crypto_encrypt_record (self->crypto_keys, bundle);
2425   json_object_set_string_member (record, "payload", payload);
2426   json_object_set_string_member (record, "id", "keys");
2427   json_node_set_object (node, record);
2428   body = json_to_string (node, FALSE);
2429 
2430   ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
2431                                            SOUP_METHOD_PUT, body, -1, -1,
2432                                            upload_crypto_keys_cb, self);
2433 
2434   g_free (body);
2435   g_free (payload);
2436   g_free (kb);
2437   json_object_unref (record);
2438   json_node_unref (node);
2439   ephy_sync_crypto_key_bundle_free (bundle);
2440 }
2441 
2442 static void
get_crypto_keys_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2443 get_crypto_keys_cb (SoupSession *session,
2444                     SoupMessage *msg,
2445                     gpointer     user_data)
2446 {
2447   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
2448   SyncCryptoKeyBundle *bundle = NULL;
2449   JsonNode *node = NULL;
2450   JsonObject *json = NULL;
2451   g_autoptr (GError) error = NULL;
2452   const char *payload;
2453   char *crypto_keys = NULL;
2454   guint8 *kb = NULL;
2455   guint status_code;
2456   g_autoptr (GBytes) response_body = NULL;
2457 
2458 #if SOUP_CHECK_VERSION (2, 99, 4)
2459   status_code = soup_message_get_status (msg);
2460   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2461 #else
2462   status_code = msg->status_code;
2463   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2464 #endif
2465 
2466   if (status_code == 404) {
2467     LOG ("crypto/keys record not found, uploading new one...");
2468     ephy_sync_service_upload_crypto_keys (self);
2469     return;
2470   }
2471 
2472   if (status_code != 200) {
2473     g_warning ("Failed to get crypto/keys record. Status code: %u, response: %s",
2474                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2475     goto out_error;
2476   }
2477 
2478   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
2479   if (error) {
2480     g_warning ("Response is not a valid JSON: %s", error->message);
2481     goto out_error;
2482   }
2483   json = json_node_get_object (node);
2484   if (!json) {
2485     g_warning ("JSON node does not hold an object");
2486     goto out_error;
2487   }
2488   payload = json_object_get_string_member (json, "payload");
2489   if (!payload) {
2490     g_warning ("JSON object has missing or invalid 'payload' member");
2491     goto out_error;
2492   }
2493   /* Derive the Sync Key bundle from kB. The bundle consists of two 32 bytes keys:
2494    * the first one used as a symmetric encryption key (AES) and the second one
2495    * used as a HMAC key.
2496    */
2497   kb = ephy_sync_utils_decode_hex (ephy_sync_service_get_secret (self, secrets[MASTER_KEY]));
2498   bundle = ephy_sync_crypto_derive_master_bundle (kb);
2499   crypto_keys = ephy_sync_crypto_decrypt_record (payload, bundle);
2500   if (!crypto_keys) {
2501     g_warning ("Failed to decrypt crypto/keys record");
2502     goto out_error;
2503   }
2504 
2505   ephy_sync_service_set_secret (self, secrets[CRYPTO_KEYS], crypto_keys);
2506   ephy_sync_service_upload_client_record (self);
2507   goto out_no_error;
2508 
2509 out_error:
2510   ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve crypto keys."),
2511                                           NULL, TRUE);
2512 out_no_error:
2513   if (bundle)
2514     ephy_sync_crypto_key_bundle_free (bundle);
2515   if (node)
2516     json_node_unref (node);
2517   g_free (crypto_keys);
2518   g_free (kb);
2519 }
2520 
2521 static void
ephy_sync_service_get_crypto_keys(EphySyncService * self)2522 ephy_sync_service_get_crypto_keys (EphySyncService *self)
2523 {
2524   g_assert (EPHY_IS_SYNC_SERVICE (self));
2525 
2526   LOG ("Getting account's crypto keys...");
2527   ephy_sync_service_queue_storage_request (self, "storage/crypto/keys",
2528                                            SOUP_METHOD_GET, NULL, -1, -1,
2529                                            get_crypto_keys_cb, self);
2530 }
2531 
2532 static JsonObject *
make_engine_object(int version)2533 make_engine_object (int version)
2534 {
2535   JsonObject *object;
2536   char *sync_id;
2537 
2538   object = json_object_new ();
2539   sync_id = ephy_sync_utils_get_random_sync_id ();
2540   json_object_set_int_member (object, "version", version);
2541   json_object_set_string_member (object, "syncID", sync_id);
2542 
2543   g_free (sync_id);
2544 
2545   return object;
2546 }
2547 
2548 static void
upload_meta_global_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2549 upload_meta_global_cb (SoupSession *session,
2550                        SoupMessage *msg,
2551                        gpointer     user_data)
2552 {
2553   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
2554   guint status_code;
2555   g_autoptr (GBytes) response_body = NULL;
2556 
2557 #if SOUP_CHECK_VERSION (2, 99, 4)
2558   status_code = soup_message_get_status (msg);
2559   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2560 #else
2561   status_code = msg->status_code;
2562   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2563 #endif
2564 
2565   if (status_code != 200) {
2566     g_warning ("Failed to upload meta/global record. Status code: %u, response: %s",
2567                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2568     ephy_sync_service_report_sign_in_error (self,
2569                                             _("Failed to upload meta/global record."),
2570                                             NULL, TRUE);
2571   } else {
2572     LOG ("Successfully uploaded meta/global record");
2573     ephy_sync_service_get_crypto_keys (self);
2574   }
2575 }
2576 
2577 static void
ephy_sync_service_upload_meta_global(EphySyncService * self)2578 ephy_sync_service_upload_meta_global (EphySyncService *self)
2579 {
2580   JsonNode *node;
2581   JsonObject *record;
2582   JsonObject *payload;
2583   JsonObject *engines;
2584   JsonArray *declined;
2585   char *sync_id;
2586   char *payload_str;
2587   char *body;
2588 
2589   g_assert (EPHY_IS_SYNC_SERVICE (self));
2590 
2591   node = json_node_new (JSON_NODE_OBJECT);
2592   record = json_object_new ();
2593   payload = json_object_new ();
2594   engines = json_object_new ();
2595   declined = json_array_new ();
2596   json_array_add_string_element (declined, "addons");
2597   json_array_add_string_element (declined, "prefs");
2598   json_object_set_array_member (payload, "declined", declined);
2599   json_object_set_object_member (engines, "clients", make_engine_object (1));
2600   json_object_set_object_member (engines, "bookmarks", make_engine_object (2));
2601   json_object_set_object_member (engines, "history", make_engine_object (1));
2602   json_object_set_object_member (engines, "passwords", make_engine_object (1));
2603   json_object_set_object_member (engines, "tabs", make_engine_object (1));
2604   json_object_set_object_member (engines, "forms", make_engine_object (1));
2605   json_object_set_object_member (payload, "engines", engines);
2606   json_object_set_int_member (payload, "storageVersion", EPHY_SYNC_STORAGE_VERSION);
2607   sync_id = ephy_sync_utils_get_random_sync_id ();
2608   json_object_set_string_member (payload, "syncID", sync_id);
2609   json_node_set_object (node, payload);
2610   payload_str = json_to_string (node, FALSE);
2611   json_object_set_string_member (record, "payload", payload_str);
2612   json_object_set_string_member (record, "id", "global");
2613   json_node_set_object (node, record);
2614   body = json_to_string (node, FALSE);
2615 
2616   ephy_sync_service_queue_storage_request (self, "storage/meta/global",
2617                                            SOUP_METHOD_PUT, body, -1, -1,
2618                                            upload_meta_global_cb, self);
2619 
2620   g_free (body);
2621   g_free (payload_str);
2622   g_free (sync_id);
2623   json_object_unref (payload);
2624   json_object_unref (record);
2625   json_node_unref (node);
2626 }
2627 
2628 static void
verify_storage_version_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2629 verify_storage_version_cb (SoupSession *session,
2630                            SoupMessage *msg,
2631                            gpointer     user_data)
2632 {
2633   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
2634   JsonParser *parser = NULL;
2635   JsonObject *json = NULL;
2636   g_autoptr (GError) error = NULL;
2637   char *payload = NULL;
2638   char *message = NULL;
2639   int storage_version;
2640   guint status_code;
2641   g_autoptr (GBytes) response_body = NULL;
2642 
2643 #if SOUP_CHECK_VERSION (2, 99, 4)
2644   status_code = soup_message_get_status (msg);
2645   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2646 #else
2647   status_code = msg->status_code;
2648   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2649 #endif
2650 
2651   if (status_code == 404) {
2652     LOG ("meta/global record not found, uploading new one...");
2653     ephy_sync_service_upload_meta_global (self);
2654     return;
2655   }
2656 
2657   if (status_code != 200) {
2658     g_warning ("Failed to get meta/global record. Status code: %u, response: %s",
2659                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2660     goto out_error;
2661   }
2662 
2663   parser = json_parser_new ();
2664   json_parser_load_from_data (parser, g_bytes_get_data (response_body, NULL), -1, &error);
2665   if (error) {
2666     g_warning ("Response is not a valid JSON: %s", error->message);
2667     goto out_error;
2668   }
2669   json = json_node_get_object (json_parser_get_root (parser));
2670   if (!json) {
2671     g_warning ("JSON node does not hold a JSON object");
2672     goto out_error;
2673   }
2674   if (!json_object_get_string_member (json, "payload")) {
2675     g_warning ("JSON object has missing or invalid 'payload' member");
2676     goto out_error;
2677   }
2678   payload = g_strdup (json_object_get_string_member (json, "payload"));
2679   json_parser_load_from_data (parser, payload, -1, &error);
2680   if (error) {
2681     g_warning ("Payload is not a valid JSON: %s", error->message);
2682     goto out_error;
2683   }
2684   json = json_node_get_object (json_parser_get_root (parser));
2685   if (!json) {
2686     g_warning ("JSON node does not hold a JSON object");
2687     goto out_error;
2688   }
2689   if (!json_object_get_int_member (json, "storageVersion")) {
2690     g_warning ("JSON object has missing or invalid 'storageVersion' member");
2691     goto out_error;
2692   }
2693   storage_version = json_object_get_int_member (json, "storageVersion");
2694   if (storage_version != EPHY_SYNC_STORAGE_VERSION) {
2695     /* Translators: the %d is the storage version, the \n is a newline character. */
2696     message = g_strdup_printf (_("Your Firefox Account uses storage version %d. "
2697                                  "Web only supports version %d."),
2698                                EPHY_SYNC_STORAGE_VERSION,
2699                                storage_version);
2700     goto out_error;
2701   }
2702 
2703   ephy_sync_service_get_crypto_keys (self);
2704   goto out_no_error;
2705 
2706 out_error:
2707   message = message ? message : _("Failed to verify storage version.");
2708   ephy_sync_service_report_sign_in_error (self, message, NULL, TRUE);
2709 out_no_error:
2710   if (parser)
2711     g_object_unref (parser);
2712   g_free (payload);
2713   g_free (message);
2714 }
2715 
2716 static void
ephy_sync_service_verify_storage_version(EphySyncService * self)2717 ephy_sync_service_verify_storage_version (EphySyncService *self)
2718 {
2719   g_assert (EPHY_IS_SYNC_SERVICE (self));
2720 
2721   LOG ("Verifying account's storage version...");
2722   ephy_sync_service_queue_storage_request (self, "storage/meta/global",
2723                                            SOUP_METHOD_GET, NULL, -1, -1,
2724                                            verify_storage_version_cb, self);
2725 }
2726 
2727 static void
upload_fxa_device_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2728 upload_fxa_device_cb (SoupSession *session,
2729                       SoupMessage *msg,
2730                       gpointer     user_data)
2731 {
2732   EphySyncService *self = user_data;
2733   JsonNode *node;
2734   JsonObject *object;
2735   g_autoptr (GError) error = NULL;
2736   guint status_code;
2737   g_autoptr (GBytes) response_body = NULL;
2738 
2739 #if SOUP_CHECK_VERSION (2, 99, 4)
2740   status_code = soup_message_get_status (msg);
2741   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2742 #else
2743   status_code = msg->status_code;
2744   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2745 #endif
2746 
2747   if (status_code != 200) {
2748     g_warning ("Failed to upload device info on FxA Server. Status code: %u, response: %s",
2749                status_code, (const char *)g_bytes_get_data (response_body, NULL));
2750     goto out_error;
2751   }
2752 
2753   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
2754   if (error) {
2755     g_warning ("Response is not a valid JSON: %s", error->message);
2756     goto out_error;
2757   }
2758 
2759   object = json_node_get_object (node);
2760   ephy_sync_utils_set_device_id (json_object_get_string_member (object, "id"));
2761   json_node_unref (node);
2762 
2763   LOG ("Successfully uploaded device info on FxA Server");
2764   if (self->is_signing_in)
2765     ephy_sync_service_verify_storage_version (self);
2766   return;
2767 
2768 out_error:
2769   if (self->is_signing_in)
2770     ephy_sync_service_report_sign_in_error (self, _("Failed to upload device info"), NULL, TRUE);
2771 }
2772 
2773 static void
ephy_sync_service_upload_fxa_device(EphySyncService * self)2774 ephy_sync_service_upload_fxa_device (EphySyncService *self)
2775 {
2776   JsonNode *node;
2777   JsonObject *object;
2778   const char *session_token;
2779   char *body;
2780   char *device_name;
2781   char *token_id_hex;
2782   guint8 *token_id;
2783   guint8 *req_hmac_key;
2784   guint8 *tmp;
2785 
2786   g_assert (EPHY_IS_SYNC_SERVICE (self));
2787 
2788   session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
2789   if (!session_token)
2790     return;
2791 
2792   object = json_object_new ();
2793   device_name = ephy_sync_utils_get_device_name ();
2794   json_object_set_string_member (object, "name", device_name);
2795   json_object_set_string_member (object, "type", "desktop");
2796 
2797   /* If we are signing in, the ID for the newly registered device will be returned
2798    * by the FxA server in the response. Otherwise, we are updating the current
2799    * device (i.e. setting its name), so we use the previously obtained ID. */
2800   if (!self->is_signing_in) {
2801     char *device_id = ephy_sync_utils_get_device_id ();
2802     json_object_set_string_member (object, "id", device_id);
2803     g_free (device_id);
2804   }
2805 
2806   node = json_node_new (JSON_NODE_OBJECT);
2807   json_node_take_object (node, object);
2808   body = json_to_string (node, FALSE);
2809 
2810   ephy_sync_crypto_derive_session_token (session_token, &token_id, &req_hmac_key, &tmp);
2811   token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
2812 
2813   LOG ("Uploading device info on FxA Server...");
2814   ephy_sync_service_fxa_hawk_post (self, "account/device", token_id_hex,
2815                                    req_hmac_key, 32, body,
2816                                    upload_fxa_device_cb, self);
2817 
2818   g_free (body);
2819   g_free (device_name);
2820   g_free (token_id_hex);
2821   g_free (token_id);
2822   g_free (req_hmac_key);
2823   g_free (tmp);
2824   json_node_unref (node);
2825 }
2826 
2827 static void
ephy_sync_service_sign_in_finish(EphySyncService * self,SignInAsyncData * data,const char * bundle)2828 ephy_sync_service_sign_in_finish (EphySyncService *self,
2829                                   SignInAsyncData *data,
2830                                   const char      *bundle)
2831 {
2832   guint8 *unwrap_kb;
2833   guint8 *ka;
2834   guint8 *kb;
2835   char *kb_hex;
2836 
2837   g_assert (EPHY_IS_SYNC_SERVICE (self));
2838   g_assert (data);
2839   g_assert (bundle);
2840 
2841   /* Derive the master sync keys form the key bundle. */
2842   unwrap_kb = ephy_sync_utils_decode_hex (data->unwrap_kb);
2843   if (!ephy_sync_crypto_derive_master_keys (bundle, data->resp_hmac_key,
2844                                             data->resp_xor_key, unwrap_kb,
2845                                             &ka, &kb)) {
2846     ephy_sync_service_report_sign_in_error (self, _("Failed to retrieve the Sync Key"),
2847                                             data->session_token, FALSE);
2848     goto out;
2849   }
2850 
2851   /* Cache the user email until the secrets are stored. We cannot use
2852    * ephy_sync_utils_set_sync_user() here because that will trigger the
2853    * 'changed' signal of EPHY_PREFS_SYNC_USER which in turn will cause
2854    * the web extension to destroy its own sync service and create a new
2855    * one. That new sync service will fail to load the sync secrets from
2856    * disk because the secrets are not yet stored at this point, thus it
2857    * will be unable to operate.
2858    */
2859   self->user = g_strdup (data->email);
2860   ephy_sync_service_set_secret (self, secrets[UID], data->uid);
2861   ephy_sync_service_set_secret (self, secrets[SESSION_TOKEN], data->session_token);
2862   kb_hex = ephy_sync_utils_encode_hex (kb, 32);
2863   ephy_sync_service_set_secret (self, secrets[MASTER_KEY], kb_hex);
2864 
2865   ephy_sync_service_upload_fxa_device (self);
2866 
2867   g_free (kb_hex);
2868   g_free (kb);
2869   g_free (ka);
2870 out:
2871   g_free (unwrap_kb);
2872   sign_in_async_data_free (data);
2873 }
2874 
2875 static void
get_account_keys_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)2876 get_account_keys_cb (SoupSession *session,
2877                      SoupMessage *msg,
2878                      gpointer     user_data)
2879 {
2880   SignInAsyncData *data = (SignInAsyncData *)user_data;
2881   JsonNode *node = NULL;
2882   JsonObject *json = NULL;
2883   g_autoptr (GError) error = NULL;
2884   const char *bundle;
2885   guint status_code;
2886   g_autoptr (GBytes) response_body = NULL;
2887 
2888 #if SOUP_CHECK_VERSION (2, 99, 4)
2889   status_code = soup_message_get_status (msg);
2890   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
2891 #else
2892   status_code = msg->status_code;
2893   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
2894 #endif
2895 
2896   node = json_from_string (g_bytes_get_data (response_body, NULL), &error);
2897   if (error) {
2898     g_warning ("Response is not a valid JSON: %s", error->message);
2899     goto out_error;
2900   }
2901   json = json_node_get_object (node);
2902   if (!json) {
2903     g_warning ("JSON node does not hold a JSON object");
2904     goto out_error;
2905   }
2906 
2907   if (status_code == 200) {
2908     bundle = json_object_get_string_member (json, "bundle");
2909     if (!bundle) {
2910       g_warning ("JSON object has invalid or missing 'bundle' member");
2911       goto out_error;
2912     }
2913     /* Extract the master sync keys from the bundle and save tokens. */
2914     ephy_sync_service_sign_in_finish (data->service, data, bundle);
2915     goto out_no_error;
2916   }
2917 
2918   /* If account is not verified, poll the Firefox Accounts Server
2919    * until the verification has completed.
2920    */
2921   if (json_object_get_int_member (json, "errno") == 104) {
2922     LOG ("Account not verified, retrying...");
2923     ephy_sync_service_fxa_hawk_get (data->service, "account/keys",
2924                                     data->token_id_hex, data->req_hmac_key, 32,
2925                                     get_account_keys_cb, data);
2926     goto out_no_error;
2927   }
2928 
2929   g_warning ("Failed to get /account/keys. Status code: %u, response: %s",
2930              status_code, (const char *)g_bytes_get_data (response_body, NULL));
2931 
2932 out_error:
2933   ephy_sync_service_report_sign_in_error (data->service,
2934                                           _("Failed to retrieve the Sync Key"),
2935                                           data->session_token, FALSE);
2936   sign_in_async_data_free (data);
2937 out_no_error:
2938   if (node)
2939     json_node_unref (node);
2940 }
2941 
2942 void
ephy_sync_service_sign_in(EphySyncService * self,const char * email,const char * uid,const char * session_token,const char * key_fetch_token,const char * unwrap_kb)2943 ephy_sync_service_sign_in (EphySyncService *self,
2944                            const char      *email,
2945                            const char      *uid,
2946                            const char      *session_token,
2947                            const char      *key_fetch_token,
2948                            const char      *unwrap_kb)
2949 {
2950   SignInAsyncData *data;
2951   guint8 *token_id;
2952   guint8 *req_hmac_key;
2953   guint8 *resp_hmac_key;
2954   guint8 *resp_xor_key;
2955   char *token_id_hex;
2956 
2957   g_assert (EPHY_IS_SYNC_SERVICE (self));
2958   g_assert (email);
2959   g_assert (uid);
2960   g_assert (session_token);
2961   g_assert (key_fetch_token);
2962   g_assert (unwrap_kb);
2963 
2964   self->is_signing_in = TRUE;
2965 
2966   /* Derive tokenID, reqHMACkey, respHMACkey and respXORkey from keyFetchToken.
2967    * tokenID and reqHMACkey are used to sign HAWK GET requests to /account/keys
2968    * endpoint. The server looks up the stored table entry with tokenID, checks
2969    * the request HMAC for validity, then returns the pre-encrypted response.
2970    * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#fetching-sync-keys
2971    */
2972   ephy_sync_crypto_derive_key_fetch_token (key_fetch_token,
2973                                            &token_id, &req_hmac_key,
2974                                            &resp_hmac_key, &resp_xor_key);
2975   token_id_hex = ephy_sync_utils_encode_hex (token_id, 32);
2976 
2977   /* Get the master sync key bundle from the /account/keys endpoint. */
2978   data = sign_in_async_data_new (self, email, uid,
2979                                  session_token, unwrap_kb,
2980                                  token_id_hex, req_hmac_key,
2981                                  resp_hmac_key, resp_xor_key);
2982   LOG ("Getting account's Sync Key...");
2983   ephy_sync_service_fxa_hawk_get (self, "account/keys",
2984                                   token_id_hex, req_hmac_key, 32,
2985                                   get_account_keys_cb, data);
2986 
2987   g_free (token_id_hex);
2988   g_free (token_id);
2989   g_free (req_hmac_key);
2990   g_free (resp_hmac_key);
2991   g_free (resp_xor_key);
2992 }
2993 
2994 static void
synchronizable_deleted_cb(EphySynchronizableManager * manager,EphySynchronizable * synchronizable,EphySyncService * self)2995 synchronizable_deleted_cb (EphySynchronizableManager *manager,
2996                            EphySynchronizable        *synchronizable,
2997                            EphySyncService           *self)
2998 {
2999   GNetworkMonitor *monitor;
3000 
3001   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
3002   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
3003   g_assert (EPHY_IS_SYNC_SERVICE (self));
3004 
3005   monitor = g_network_monitor_get_default ();
3006   if (g_network_monitor_get_connectivity (monitor) != G_NETWORK_CONNECTIVITY_FULL)
3007     return;
3008 
3009   if (!ephy_sync_utils_user_is_signed_in ())
3010     return;
3011 
3012   ephy_sync_service_delete_synchronizable (self, manager, synchronizable);
3013 }
3014 
3015 static void
synchronizable_modified_cb(EphySynchronizableManager * manager,EphySynchronizable * synchronizable,gboolean should_force,EphySyncService * self)3016 synchronizable_modified_cb (EphySynchronizableManager *manager,
3017                             EphySynchronizable        *synchronizable,
3018                             gboolean                   should_force,
3019                             EphySyncService           *self)
3020 {
3021   GNetworkMonitor *monitor;
3022 
3023   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
3024   g_assert (EPHY_IS_SYNCHRONIZABLE (synchronizable));
3025   g_assert (EPHY_IS_SYNC_SERVICE (self));
3026 
3027   monitor = g_network_monitor_get_default ();
3028   if (g_network_monitor_get_connectivity (monitor) != G_NETWORK_CONNECTIVITY_FULL)
3029     return;
3030 
3031   if (!ephy_sync_utils_user_is_signed_in ())
3032     return;
3033 
3034   ephy_sync_service_upload_synchronizable (self, manager, synchronizable, should_force);
3035 }
3036 
3037 void
ephy_sync_service_register_manager(EphySyncService * self,EphySynchronizableManager * manager)3038 ephy_sync_service_register_manager (EphySyncService           *self,
3039                                     EphySynchronizableManager *manager)
3040 {
3041   g_assert (EPHY_IS_SYNC_SERVICE (self));
3042   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
3043 
3044   if (!g_slist_find (self->managers, manager)) {
3045     self->managers = g_slist_prepend (self->managers, manager);
3046 
3047     g_signal_connect (manager, "synchronizable-deleted",
3048                       G_CALLBACK (synchronizable_deleted_cb), self);
3049     g_signal_connect (manager, "synchronizable-modified",
3050                       G_CALLBACK (synchronizable_modified_cb), self);
3051   }
3052 }
3053 
3054 void
ephy_sync_service_unregister_manager(EphySyncService * self,EphySynchronizableManager * manager)3055 ephy_sync_service_unregister_manager (EphySyncService           *self,
3056                                       EphySynchronizableManager *manager)
3057 {
3058   g_assert (EPHY_IS_SYNC_SERVICE (self));
3059   g_assert (EPHY_IS_SYNCHRONIZABLE_MANAGER (manager));
3060 
3061   self->managers = g_slist_remove (self->managers, manager);
3062 
3063   g_signal_handlers_disconnect_by_func (manager, synchronizable_deleted_cb, self);
3064   g_signal_handlers_disconnect_by_func (manager, synchronizable_modified_cb, self);
3065 }
3066 
3067 void
ephy_sync_service_update_device_name(EphySyncService * self,const char * name)3068 ephy_sync_service_update_device_name (EphySyncService *self,
3069                                       const char      *name)
3070 {
3071   g_assert (EPHY_IS_SYNC_SERVICE (self));
3072   g_assert (name);
3073 
3074   ephy_sync_utils_set_device_name (name);
3075   ephy_sync_service_upload_fxa_device (self);
3076   ephy_sync_service_upload_client_record (self);
3077 }
3078 
3079 static void
delete_open_tabs_record_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)3080 delete_open_tabs_record_cb (SoupSession *session,
3081                             SoupMessage *msg,
3082                             gpointer     user_data)
3083 {
3084   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
3085   const char *session_token;
3086   guint status_code;
3087   g_autoptr (GBytes) response_body = NULL;
3088 
3089 #if SOUP_CHECK_VERSION (2, 99, 4)
3090   status_code = soup_message_get_status (msg);
3091   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
3092 #else
3093   status_code = msg->status_code;
3094   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
3095 #endif
3096 
3097   if (status_code != 200) {
3098     g_warning ("Failed to delete open tabs record. Status code: %u, response: %s",
3099                status_code, (const char *)g_bytes_get_data (response_body, NULL));
3100   } else {
3101     LOG ("Successfully deleted open tabs record");
3102   }
3103 
3104   ephy_sync_service_clear_storage_queue (self);
3105   ephy_sync_service_clear_storage_credentials (self);
3106 
3107   session_token = ephy_sync_service_get_secret (self, secrets[SESSION_TOKEN]);
3108   ephy_sync_service_destroy_session (self, session_token);
3109 
3110   ephy_sync_service_forget_secrets (self);
3111   ephy_sync_utils_set_device_id (NULL);
3112   ephy_sync_utils_set_sync_user (NULL);
3113 }
3114 
3115 static void
delete_client_record_cb(SoupSession * session,SoupMessage * msg,gpointer user_data)3116 delete_client_record_cb (SoupSession *session,
3117                          SoupMessage *msg,
3118                          gpointer     user_data)
3119 {
3120   EphySyncService *self = EPHY_SYNC_SERVICE (user_data);
3121   char *endpoint;
3122   char *device_bso_id;
3123   guint status_code;
3124   g_autoptr (GBytes) response_body = NULL;
3125 
3126 #if SOUP_CHECK_VERSION (2, 99, 4)
3127   status_code = soup_message_get_status (msg);
3128   response_body = g_bytes_ref (g_object_get_data (G_OBJECT (msg), "ephy-request-body"));
3129 #else
3130   status_code = msg->status_code;
3131   response_body = g_bytes_new_static (msg->response_body->data, msg->response_body->length);
3132 #endif
3133 
3134   if (status_code != 200) {
3135     g_warning ("Failed to delete client record. Status code: %u, response: %s",
3136                status_code, (const char *)g_bytes_get_data (response_body, NULL));
3137   } else {
3138     LOG ("Successfully deleted client record");
3139   }
3140 
3141   device_bso_id = ephy_sync_utils_get_device_bso_id ();
3142   /* Delete the open tabs record associated to this device. */
3143   endpoint = g_strdup_printf ("storage/tabs/%s", device_bso_id);
3144   ephy_sync_service_queue_storage_request (self, endpoint,
3145                                            SOUP_METHOD_DELETE,
3146                                            NULL, -1, -1,
3147                                            delete_open_tabs_record_cb, self);
3148   g_free (endpoint);
3149   g_free (device_bso_id);
3150 }
3151 
3152 static void
ephy_sync_service_delete_client_record(EphySyncService * self)3153 ephy_sync_service_delete_client_record (EphySyncService *self)
3154 {
3155   char *endpoint;
3156   char *device_bso_id;
3157 
3158   g_assert (EPHY_IS_SYNC_SERVICE (self));
3159 
3160   device_bso_id = ephy_sync_utils_get_device_bso_id ();
3161   /* Delete the client record associated to this device. */
3162   endpoint = g_strdup_printf ("storage/clients/%s", device_bso_id);
3163   ephy_sync_service_queue_storage_request (self, endpoint,
3164                                            SOUP_METHOD_DELETE,
3165                                            NULL, -1, -1,
3166                                            delete_client_record_cb, self);
3167   g_free (endpoint);
3168   g_free (device_bso_id);
3169 }
3170 
3171 void
ephy_sync_service_sign_out(EphySyncService * self)3172 ephy_sync_service_sign_out (EphySyncService *self)
3173 {
3174   g_assert (EPHY_IS_SYNC_SERVICE (self));
3175 
3176   ephy_sync_service_stop_periodical_sync (self);
3177   ephy_sync_service_delete_client_record (self);
3178 
3179   /* Clear managers. */
3180   for (GSList *l = self->managers; l && l->data; l = l->next) {
3181     g_signal_handlers_disconnect_by_func (l->data, synchronizable_deleted_cb, self);
3182     g_signal_handlers_disconnect_by_func (l->data, synchronizable_modified_cb, self);
3183   }
3184   g_clear_pointer (&self->managers, g_slist_free);
3185 
3186   ephy_sync_utils_set_bookmarks_sync_is_initial (TRUE);
3187   ephy_sync_utils_set_passwords_sync_is_initial (TRUE);
3188   ephy_sync_utils_set_history_sync_is_initial (TRUE);
3189   ephy_sync_utils_set_sync_time (0);
3190 }
3191 
3192 void
ephy_sync_service_sync(EphySyncService * self)3193 ephy_sync_service_sync (EphySyncService *self)
3194 {
3195   g_assert (EPHY_IS_SYNC_SERVICE (self));
3196   g_assert (ephy_sync_utils_user_is_signed_in ());
3197 
3198   ephy_sync_service_sync_internal (self);
3199 }
3200 
3201 void
ephy_sync_service_start_sync(EphySyncService * self)3202 ephy_sync_service_start_sync (EphySyncService *self)
3203 {
3204   g_assert (EPHY_IS_SYNC_SERVICE (self));
3205   g_assert (self->sync_periodically);
3206 
3207   if (ephy_sync_utils_user_is_signed_in ()) {
3208     ephy_sync_service_sync_internal (self);
3209     ephy_sync_service_schedule_periodical_sync (self);
3210   }
3211 }
3212