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