1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2017 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-password-manager.h"
23 
24 #include "ephy-debug.h"
25 #include "ephy-settings.h"
26 #include "ephy-sync-utils.h"
27 #include "ephy-synchronizable-manager.h"
28 
29 #include <glib/gi18n.h>
30 #include <inttypes.h>
31 #include <stdio.h>
32 
33 const SecretSchema *
ephy_password_manager_get_password_schema(void)34 ephy_password_manager_get_password_schema (void)
35 {
36   static const SecretSchema schema = {
37     "org.epiphany.FormPassword", SECRET_SCHEMA_NONE,
38     {
39       { ID_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
40       { ORIGIN_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
41       { TARGET_ORIGIN_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
42       { USERNAME_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
43       { PASSWORD_FIELD_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
44       { USERNAME_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
45       { SERVER_TIME_MODIFIED_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING},
46       { "NULL", 0 },
47     }
48   };
49   return &schema;
50 }
51 
52 struct _EphyPasswordManager {
53   GObject parent_instance;
54 
55   GHashTable *cache;
56 };
57 
58 static void ephy_password_manager_forget_record (EphyPasswordManager *self,
59                                                  EphyPasswordRecord  *record,
60                                                  EphyPasswordRecord  *replacement,
61                                                  GTask               *task);
62 
63 static void ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface);
64 
65 G_DEFINE_TYPE_WITH_CODE (EphyPasswordManager, ephy_password_manager, G_TYPE_OBJECT,
66                          G_IMPLEMENT_INTERFACE (EPHY_TYPE_SYNCHRONIZABLE_MANAGER,
67                                                 ephy_synchronizable_manager_iface_init))
68 
69 typedef struct {
70   EphyPasswordManagerQueryCallback callback;
71   gpointer user_data;
72   GList *records;
73   guint n_matches;
74 } QueryAsyncData;
75 
76 typedef struct {
77   EphyPasswordManager *manager;
78   char *password;
79 } UpdatePasswordAsyncData;
80 
81 typedef struct {
82   EphyPasswordManager *manager;
83   EphyPasswordRecord *record;
84   GTask *task;
85 } ManageRecordAsyncData;
86 
87 typedef struct {
88   EphyPasswordManager *manager;
89   gboolean is_initial;
90   GList *remotes_deleted;
91   GList *remotes_updated;
92   EphySynchronizableManagerMergeCallback callback;
93   gpointer user_data;
94 } MergePasswordsAsyncData;
95 
96 static QueryAsyncData *
query_async_data_new(EphyPasswordManagerQueryCallback callback,gpointer user_data)97 query_async_data_new (EphyPasswordManagerQueryCallback callback,
98                       gpointer                         user_data)
99 {
100   QueryAsyncData *data;
101 
102   data = g_new0 (QueryAsyncData, 1);
103   data->callback = callback;
104   data->user_data = user_data;
105 
106   return data;
107 }
108 
109 static void
query_async_data_free(QueryAsyncData * data)110 query_async_data_free (QueryAsyncData *data)
111 {
112   g_assert (data);
113 
114   g_list_free_full (data->records, g_object_unref);
115   g_free (data);
116 }
117 
118 static UpdatePasswordAsyncData *
update_password_async_data_new(EphyPasswordManager * manager,const char * password)119 update_password_async_data_new (EphyPasswordManager *manager,
120                                 const char          *password)
121 {
122   UpdatePasswordAsyncData *data;
123 
124   data = g_new0 (UpdatePasswordAsyncData, 1);
125   data->manager = g_object_ref (manager);
126   data->password = g_strdup (password);
127 
128   return data;
129 }
130 
131 static void
update_password_async_data_free(UpdatePasswordAsyncData * data)132 update_password_async_data_free (UpdatePasswordAsyncData *data)
133 {
134   g_assert (data);
135 
136   g_object_unref (data->manager);
137   g_free (data->password);
138   g_free (data);
139 }
140 
141 static MergePasswordsAsyncData *
merge_passwords_async_data_new(EphyPasswordManager * manager,gboolean is_initial,GList * remotes_deleted,GList * remotes_updated,EphySynchronizableManagerMergeCallback callback,gpointer user_data)142 merge_passwords_async_data_new (EphyPasswordManager                    *manager,
143                                 gboolean                                is_initial,
144                                 GList                                  *remotes_deleted,
145                                 GList                                  *remotes_updated,
146                                 EphySynchronizableManagerMergeCallback  callback,
147                                 gpointer                                user_data)
148 {
149   MergePasswordsAsyncData *data;
150 
151   data = g_new0 (MergePasswordsAsyncData, 1);
152   data->manager = g_object_ref (manager);
153   data->is_initial = is_initial;
154   data->remotes_deleted = remotes_deleted;
155   data->remotes_updated = remotes_updated;
156   data->callback = callback;
157   data->user_data = user_data;
158 
159   return data;
160 }
161 
162 static void
merge_passwords_async_data_free(MergePasswordsAsyncData * data)163 merge_passwords_async_data_free (MergePasswordsAsyncData *data)
164 {
165   g_assert (data);
166 
167   g_object_unref (data->manager);
168   g_list_free_full (data->remotes_deleted, g_object_unref);
169   g_list_free_full (data->remotes_updated, g_object_unref);
170   g_free (data);
171 }
172 
173 static ManageRecordAsyncData *
manage_record_async_data_new(EphyPasswordManager * manager,EphyPasswordRecord * record,GTask * task)174 manage_record_async_data_new (EphyPasswordManager *manager,
175                               EphyPasswordRecord  *record,
176                               GTask               *task)
177 {
178   ManageRecordAsyncData *data;
179 
180   data = g_new0 (ManageRecordAsyncData, 1);
181 
182   if (manager)
183     data->manager = g_object_ref (manager);
184 
185   if (record)
186     data->record = g_object_ref (record);
187 
188   if (task)
189     data->task = g_object_ref (task);
190 
191   return data;
192 }
193 
194 static void
manage_record_async_data_free(ManageRecordAsyncData * data)195 manage_record_async_data_free (ManageRecordAsyncData *data)
196 {
197   g_assert (data);
198 
199   g_clear_object (&data->manager);
200   g_clear_object (&data->record);
201   g_clear_object (&data->task);
202 
203   g_free (data);
204 }
205 
206 static GHashTable *
get_attributes_table(const char * id,const char * origin,const char * target_origin,const char * username,const char * username_field,const char * password_field,gint64 server_time_modified)207 get_attributes_table (const char *id,
208                       const char *origin,
209                       const char *target_origin,
210                       const char *username,
211                       const char *username_field,
212                       const char *password_field,
213                       gint64      server_time_modified)
214 {
215   GHashTable *attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
216 
217   if (id)
218     g_hash_table_insert (attributes,
219                          g_strdup (ID_KEY),
220                          g_strdup (id));
221   if (origin)
222     g_hash_table_insert (attributes,
223                          g_strdup (ORIGIN_KEY),
224                          g_strdup (origin));
225   if (target_origin)
226     g_hash_table_insert (attributes,
227                          g_strdup (TARGET_ORIGIN_KEY),
228                          g_strdup (target_origin));
229   if (username)
230     g_hash_table_insert (attributes,
231                          g_strdup (USERNAME_KEY),
232                          g_strdup (username));
233   if (username_field)
234     g_hash_table_insert (attributes,
235                          g_strdup (USERNAME_FIELD_KEY),
236                          g_strdup (username_field));
237   if (password_field)
238     g_hash_table_insert (attributes,
239                          g_strdup (PASSWORD_FIELD_KEY),
240                          g_strdup (password_field));
241   if (server_time_modified >= 0)
242     g_hash_table_insert (attributes,
243                          g_strdup (SERVER_TIME_MODIFIED_KEY),
244                          g_strdup_printf ("%" PRId64, server_time_modified));
245 
246   return attributes;
247 }
248 
249 static void
ephy_password_manager_cache_clear(EphyPasswordManager * self)250 ephy_password_manager_cache_clear (EphyPasswordManager *self)
251 {
252   GHashTableIter iter;
253   gpointer key, value;
254 
255   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
256   g_assert (self->cache);
257 
258   g_hash_table_iter_init (&iter, self->cache);
259   while (g_hash_table_iter_next (&iter, &key, &value))
260     g_list_free_full (value, g_free);
261   g_hash_table_remove_all (self->cache);
262 }
263 
264 static void
ephy_password_manager_cache_remove(EphyPasswordManager * self,const char * origin,const char * username)265 ephy_password_manager_cache_remove (EphyPasswordManager *self,
266                                     const char          *origin,
267                                     const char          *username)
268 {
269   GList *usernames;
270   GList *new_usernames = NULL;
271 
272   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
273   g_assert (self->cache);
274 
275   if (!origin || !username)
276     return;
277 
278   usernames = g_hash_table_lookup (self->cache, origin);
279   if (usernames) {
280     for (GList *l = usernames; l && l->data; l = l->next) {
281       if (g_strcmp0 (username, l->data))
282         new_usernames = g_list_prepend (new_usernames, g_strdup (l->data));
283     }
284     g_hash_table_replace (self->cache, g_strdup (origin), new_usernames);
285     g_list_free_full (usernames, g_free);
286   }
287 }
288 
289 static void
ephy_password_manager_cache_add(EphyPasswordManager * self,const char * origin,const char * username)290 ephy_password_manager_cache_add (EphyPasswordManager *self,
291                                  const char          *origin,
292                                  const char          *username)
293 {
294   GList *usernames;
295 
296   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
297   g_assert (self->cache);
298 
299   if (!origin || !username)
300     return;
301 
302   usernames = g_hash_table_lookup (self->cache, origin);
303   for (GList *l = usernames; l && l->data; l = l->next) {
304     if (!g_strcmp0 (username, l->data))
305       return;
306   }
307   usernames = g_list_prepend (usernames, g_strdup (username));
308   g_hash_table_replace (self->cache, g_strdup (origin), usernames);
309 }
310 
311 static void
populate_cache_cb(GList * records,gpointer user_data)312 populate_cache_cb (GList    *records,
313                    gpointer  user_data)
314 {
315   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
316 
317   for (GList *l = records; l && l->data; l = l->next) {
318     EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (l->data);
319     const char *origin = ephy_password_record_get_origin (record);
320     const char *username = ephy_password_record_get_username (record);
321 
322     ephy_password_manager_cache_add (self, origin, username);
323   }
324 }
325 
326 static void
ephy_password_manager_dispose(GObject * object)327 ephy_password_manager_dispose (GObject *object)
328 {
329   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (object);
330 
331   if (self->cache) {
332     ephy_password_manager_cache_clear (self);
333     g_clear_pointer (&self->cache, g_hash_table_unref);
334   }
335 
336   G_OBJECT_CLASS (ephy_password_manager_parent_class)->dispose (object);
337 }
338 
339 static void
ephy_password_manager_class_init(EphyPasswordManagerClass * klass)340 ephy_password_manager_class_init (EphyPasswordManagerClass *klass)
341 {
342   GObjectClass *object_class = G_OBJECT_CLASS (klass);
343 
344   object_class->dispose = ephy_password_manager_dispose;
345 }
346 
347 static void
ephy_password_manager_init(EphyPasswordManager * self)348 ephy_password_manager_init (EphyPasswordManager *self)
349 {
350   LOG ("Loading usernames into internal cache...");
351   self->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
352   ephy_password_manager_query (self, NULL, NULL, NULL, NULL, NULL, NULL,
353                                populate_cache_cb, self);
354 }
355 
356 EphyPasswordManager *
ephy_password_manager_new(void)357 ephy_password_manager_new (void)
358 {
359   return EPHY_PASSWORD_MANAGER (g_object_new (EPHY_TYPE_PASSWORD_MANAGER, NULL));
360 }
361 
362 GList *
ephy_password_manager_get_usernames_for_origin(EphyPasswordManager * self,const char * origin)363 ephy_password_manager_get_usernames_for_origin (EphyPasswordManager *self,
364                                                 const char          *origin)
365 {
366   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
367   g_assert (origin);
368 
369   return g_hash_table_lookup (self->cache, origin);
370 }
371 
372 static void
secret_password_store_cb(GObject * source_object,GAsyncResult * result,ManageRecordAsyncData * data)373 secret_password_store_cb (GObject               *source_object,
374                           GAsyncResult          *result,
375                           ManageRecordAsyncData *data)
376 {
377   GError *error = NULL;
378   const char *origin;
379   const char *username;
380 
381   origin = ephy_password_record_get_origin (data->record);
382   username = ephy_password_record_get_username (data->record);
383 
384   secret_password_store_finish (result, &error);
385   if (error) {
386     g_warning ("Failed to store password record for (%s, %s, %s, %s, %s): %s",
387                origin,
388                ephy_password_record_get_target_origin (data->record),
389                username,
390                ephy_password_record_get_username_field (data->record),
391                ephy_password_record_get_password_field (data->record),
392                error->message);
393     g_error_free (error);
394   } else {
395     ephy_password_manager_cache_add (data->manager, origin, username);
396   }
397 
398   manage_record_async_data_free (data);
399 }
400 
401 static void
ephy_password_manager_store_record(EphyPasswordManager * self,EphyPasswordRecord * record)402 ephy_password_manager_store_record (EphyPasswordManager *self,
403                                     EphyPasswordRecord  *record)
404 {
405   GHashTable *attributes;
406   const char *origin;
407   const char *target_origin;
408   const char *username;
409   const char *password;
410   const char *username_field;
411   const char *password_field;
412   char *label;
413   gint64 modified;
414 
415   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
416   g_assert (EPHY_IS_PASSWORD_RECORD (record));
417 
418   origin = ephy_password_record_get_origin (record);
419   target_origin = ephy_password_record_get_target_origin (record);
420   username = ephy_password_record_get_username (record);
421   password = ephy_password_record_get_password (record);
422   username_field = ephy_password_record_get_username_field (record);
423   password_field = ephy_password_record_get_password_field (record);
424   modified = ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE (record));
425 
426   LOG ("Storing password record for (%s, %s, %s, %s, %s)",
427        origin, target_origin, username, username_field, password_field);
428 
429   if (username) {
430     /* Translators: The first %s is the username and the second one is the
431      * security origin where this is happening. Example: gnome@gmail.com and
432      * https://mail.google.com. */
433     label = g_strdup_printf (_("Password for %s in a form in %s"), username, origin);
434   } else {
435     /* Translators: The %s is the security origin where this is happening.
436      * Example: https://mail.google.com. */
437     label = g_strdup_printf (_("Password in a form in %s"), origin);
438   }
439 
440   attributes = get_attributes_table (ephy_password_record_get_id (record),
441                                      origin, target_origin, username,
442                                      username_field, password_field,
443                                      modified);
444 
445   secret_password_storev (EPHY_FORM_PASSWORD_SCHEMA,
446                           attributes, NULL, label, password, NULL,
447                           (GAsyncReadyCallback)secret_password_store_cb,
448                           manage_record_async_data_new (self, record, NULL));
449 
450   g_free (label);
451   g_hash_table_unref (attributes);
452 }
453 
454 static GList *
deduplicate_records(EphyPasswordManager * manager,GList * records)455 deduplicate_records (EphyPasswordManager *manager,
456                      GList               *records)
457 {
458   GList *newest = records;
459   guint64 newest_modified = ephy_password_record_get_time_password_changed (newest->data);
460 
461   for (GList *l = records->next; l; l = l->next) {
462     guint64 modified = ephy_password_record_get_time_password_changed (l->data);
463     if (modified > newest_modified) {
464       newest = l;
465       newest_modified = modified;
466     }
467   }
468 
469   records = g_list_remove_link (records, newest);
470 
471   for (GList *l = records; l; l = l->next)
472     ephy_password_manager_forget_record (manager, l->data, NULL, NULL);
473   g_list_free_full (records, g_object_unref);
474 
475   return newest;
476 }
477 
478 static void
update_password_cb(GList * records,gpointer user_data)479 update_password_cb (GList    *records,
480                     gpointer  user_data)
481 {
482   UpdatePasswordAsyncData *data = (UpdatePasswordAsyncData *)user_data;
483   EphyPasswordRecord *record;
484 
485   /* Since we didn't include ID in our query, there could be multiple records
486    * returned. We only want to have one saved at a time, so delete the rest.
487    */
488   if (g_list_length (records) > 1)
489     records = deduplicate_records (data->manager, records);
490 
491   if (records) {
492     record = EPHY_PASSWORD_RECORD (records->data);
493     ephy_password_record_set_password (record, data->password);
494     ephy_password_manager_store_record (data->manager, record);
495     g_signal_emit_by_name (data->manager, "synchronizable-modified", record, FALSE);
496   } else {
497     LOG ("Attempted to update password record that doesn't exist (likely Epiphany bug)");
498   }
499 
500   update_password_async_data_free (data);
501 }
502 
503 void
ephy_password_manager_save(EphyPasswordManager * self,const char * origin,const char * target_origin,const char * username,const char * password,const char * username_field,const char * password_field,gboolean is_new)504 ephy_password_manager_save (EphyPasswordManager *self,
505                             const char          *origin,
506                             const char          *target_origin,
507                             const char          *username,
508                             const char          *password,
509                             const char          *username_field,
510                             const char          *password_field,
511                             gboolean             is_new)
512 {
513   EphyPasswordRecord *record;
514   char *uuid;
515   char *id;
516   gint64 timestamp;
517 
518   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
519   g_assert (origin);
520   g_assert (target_origin);
521 
522   /* Although we don't enforce these requirements for Firefox passwords,
523    * any password saved by Epiphany must have both password and
524    * password_field, so that it can be filled in. username and
525    * username_field are optional, but if one is provided, both must be.
526    */
527   g_assert (password);
528   g_assert (password_field);
529   g_assert ((username && username_field) || (!username && !username_field));
530 
531   if (!is_new) {
532     LOG ("Updating password for (%s, %s, %s, %s, %s)",
533          origin, target_origin, username, username_field, password_field);
534     ephy_password_manager_query (self, NULL,
535                                  origin, target_origin, username,
536                                  username_field, password_field,
537                                  update_password_cb,
538                                  update_password_async_data_new (self, password));
539     return;
540   }
541 
542   uuid = g_uuid_string_random ();
543   id = g_strdup_printf ("{%s}", uuid);
544   timestamp = g_get_real_time () / 1000;
545   record = ephy_password_record_new (id, origin, target_origin,
546                                      username, password,
547                                      username_field, password_field,
548                                      timestamp, timestamp);
549   ephy_password_manager_store_record (self, record);
550   g_signal_emit_by_name (self, "synchronizable-modified", record, FALSE);
551 
552   g_free (uuid);
553   g_free (id);
554   g_object_unref (record);
555 }
556 
557 static void
retrieve_secret_cb(GObject * source_object,GAsyncResult * result,QueryAsyncData * data)558 retrieve_secret_cb (GObject        *source_object,
559                     GAsyncResult   *result,
560                     QueryAsyncData *data)
561 {
562   SecretRetrievable *retrievable = SECRET_RETRIEVABLE (source_object);
563   GHashTable *attributes = NULL;
564   const char *id;
565   const char *origin;
566   const char *target_origin;
567   const char *username;
568   const char *username_field;
569   const char *password_field;
570   const char *timestamp;
571   gint64 created;
572   gint64 modified;
573   const char *password;
574   gint64 server_time_modified;
575   EphyPasswordRecord *record;
576   SecretValue *value = NULL;
577   GError *error = NULL;
578 
579   value = secret_retrievable_retrieve_secret_finish (retrievable, result, &error);
580   if (!value) {
581     g_warning ("Failed to retrieve password: %s", error->message);
582     g_error_free (error);
583     goto out;
584   }
585 
586   attributes = secret_retrievable_get_attributes (retrievable);
587   id = g_hash_table_lookup (attributes, ID_KEY);
588   origin = g_hash_table_lookup (attributes, ORIGIN_KEY);
589   target_origin = g_hash_table_lookup (attributes, TARGET_ORIGIN_KEY);
590   username = g_hash_table_lookup (attributes, USERNAME_KEY);
591   username_field = g_hash_table_lookup (attributes, USERNAME_FIELD_KEY);
592   password_field = g_hash_table_lookup (attributes, PASSWORD_FIELD_KEY);
593   timestamp = g_hash_table_lookup (attributes, SERVER_TIME_MODIFIED_KEY);
594   created = secret_retrievable_get_created (retrievable);
595   modified = secret_retrievable_get_modified (retrievable);
596 
597   LOG ("Found password record for (%s, %s, %s, %s, %s)",
598        origin, target_origin, username, username_field, password_field);
599 
600   if (!id || !origin || !target_origin || !timestamp) {
601     LOG ("Password record is corrupted, skipping it...");
602     goto out;
603   }
604 
605   password = secret_value_get_text (value);
606 
607   record = ephy_password_record_new (id, origin, target_origin,
608                                      username, password,
609                                      username_field, password_field,
610                                      created * 1000,
611                                      modified * 1000);
612   server_time_modified = g_ascii_strtod (timestamp, NULL);
613   ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record),
614                                                 server_time_modified);
615   data->records = g_list_prepend (data->records, record);
616 
617 out:
618   if (value)
619     secret_value_unref (value);
620   if (attributes)
621     g_hash_table_unref (attributes);
622   g_object_unref (retrievable);
623 
624   if (--data->n_matches == 0) {
625     if (data->callback)
626       data->callback (data->records, data->user_data);
627     query_async_data_free (data);
628   }
629 }
630 
631 static void
secret_password_search_cb(GObject * source_object,GAsyncResult * result,QueryAsyncData * data)632 secret_password_search_cb (GObject        *source_object,
633                            GAsyncResult   *result,
634                            QueryAsyncData *data)
635 {
636   GList *matches;
637   GError *error = NULL;
638 
639   matches = secret_password_search_finish (result, &error);
640   if (!matches) {
641     if (error) {
642       g_warning ("Failed to search secrets in password schema: %s", error->message);
643       g_error_free (error);
644     }
645     if (data->callback)
646       data->callback (NULL, data->user_data);
647     query_async_data_free (data);
648     return;
649   }
650 
651   data->records = NULL;
652   data->n_matches = g_list_length (matches);
653 
654   for (GList *l = matches; l; l = l->next) {
655     SecretRetrievable *retrievable = SECRET_RETRIEVABLE (l->data);
656     secret_retrievable_retrieve_secret (g_object_ref (retrievable),
657                                         NULL,
658                                         (GAsyncReadyCallback)retrieve_secret_cb,
659                                         data);
660   }
661 
662   g_list_free_full (matches, g_object_unref);
663 }
664 
665 void
ephy_password_manager_query(EphyPasswordManager * self,const char * id,const char * origin,const char * target_origin,const char * username,const char * username_field,const char * password_field,EphyPasswordManagerQueryCallback callback,gpointer user_data)666 ephy_password_manager_query (EphyPasswordManager              *self,
667                              const char                       *id,
668                              const char                       *origin,
669                              const char                       *target_origin,
670                              const char                       *username,
671                              const char                       *username_field,
672                              const char                       *password_field,
673                              EphyPasswordManagerQueryCallback  callback,
674                              gpointer                          user_data)
675 {
676   QueryAsyncData *data;
677   GHashTable *attributes;
678 
679   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
680 
681   LOG ("Querying password records for (%s, %s, %s, %s)",
682        origin, username, username_field, password_field);
683 
684   attributes = get_attributes_table (id, origin, target_origin, username,
685                                      username_field, password_field, -1);
686   data = query_async_data_new (callback, user_data);
687 
688   secret_password_searchv (EPHY_FORM_PASSWORD_SCHEMA,
689                            attributes,
690                            SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
691                            NULL,
692                            (GAsyncReadyCallback)secret_password_search_cb,
693                            data);
694 
695   g_hash_table_unref (attributes);
696 }
697 
698 gboolean
ephy_password_manager_find(EphyPasswordManager * self,const char * origin,const char * target_origin,const char * username,const char * username_field,const char * password_field)699 ephy_password_manager_find (EphyPasswordManager *self,
700                             const char          *origin,
701                             const char          *target_origin,
702                             const char          *username,
703                             const char          *username_field,
704                             const char          *password_field)
705 {
706   GHashTable *attributes;
707   g_autoptr (GList) list = NULL;
708 
709   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
710 
711   LOG ("Querying password records for (%s, %s, %s, %s)",
712        origin, username, username_field, password_field);
713 
714   attributes = get_attributes_table (NULL, origin, target_origin, username,
715                                      username_field, password_field, -1);
716 
717   list = secret_password_searchv_sync (EPHY_FORM_PASSWORD_SCHEMA,
718                                        attributes,
719                                        SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
720                                        NULL,
721                                        NULL);
722 
723   g_hash_table_unref (attributes);
724 
725   return list != NULL;
726 }
727 
728 static void
secret_password_clear_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)729 secret_password_clear_cb (GObject      *source_object,
730                           GAsyncResult *result,
731                           gpointer      user_data)
732 {
733   g_autoptr (GError) error = NULL;
734   ManageRecordAsyncData *data = user_data;
735 
736   secret_password_clear_finish (result, &error);
737   if (error) {
738     if (data->task)
739       g_task_return_error (data->task, error);
740     else
741       g_warning ("Failed to clear secrets from password schema: %s", error->message);
742 
743     manage_record_async_data_free (data);
744     return;
745   }
746 
747   if (data->record)
748     ephy_password_manager_store_record (data->manager, data->record);
749 
750   if (data->task)
751     g_task_return_boolean (data->task, TRUE);
752 
753   manage_record_async_data_free (data);
754 }
755 
756 static void
ephy_password_manager_forget_record(EphyPasswordManager * self,EphyPasswordRecord * record,EphyPasswordRecord * replacement,GTask * task)757 ephy_password_manager_forget_record (EphyPasswordManager *self,
758                                      EphyPasswordRecord  *record,
759                                      EphyPasswordRecord  *replacement,
760                                      GTask               *task)
761 {
762   GHashTable *attributes;
763   ManageRecordAsyncData *clear_cb_data = NULL;
764 
765   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
766   g_assert (EPHY_IS_PASSWORD_RECORD (record));
767 
768   attributes = get_attributes_table (ephy_password_record_get_id (record),
769                                      ephy_password_record_get_origin (record),
770                                      ephy_password_record_get_target_origin (record),
771                                      ephy_password_record_get_username (record),
772                                      ephy_password_record_get_username_field (record),
773                                      ephy_password_record_get_password_field (record),
774                                      -1);
775 
776   clear_cb_data = manage_record_async_data_new (self, replacement, task);
777 
778   LOG ("Forgetting password record for (%s, %s, %s, %s, %s)",
779        ephy_password_record_get_origin (record),
780        ephy_password_record_get_target_origin (record),
781        ephy_password_record_get_username (record),
782        ephy_password_record_get_username_field (record),
783        ephy_password_record_get_password_field (record));
784 
785   secret_password_clearv (EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
786                           (GAsyncReadyCallback)secret_password_clear_cb,
787                           clear_cb_data);
788 
789   ephy_password_manager_cache_remove (self,
790                                       ephy_password_record_get_origin (record),
791                                       ephy_password_record_get_username (record));
792   g_hash_table_unref (attributes);
793 }
794 
795 static void
forget_cb(GList * records,gpointer data)796 forget_cb (GList    *records,
797            gpointer  data)
798 {
799   GTask *task = data;
800   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (g_task_get_source_object (task));
801   EphyPasswordRecord *record;
802 
803   /* We expect only one matching record here. */
804   if (g_list_length (records) == 1) {
805     record = EPHY_PASSWORD_RECORD (records->data);
806     g_signal_emit_by_name (self, "synchronizable-deleted", record);
807     ephy_password_manager_forget_record (self, record, NULL, task);
808   } else {
809     g_warn_if_reached ();
810   }
811 }
812 
813 gboolean
ephy_password_manager_forget_finish(EphyPasswordManager * self,GAsyncResult * result,GError ** error)814 ephy_password_manager_forget_finish (EphyPasswordManager  *self,
815                                      GAsyncResult         *result,
816                                      GError              **error)
817 {
818   g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
819 
820   return g_task_propagate_boolean (G_TASK (result), error);
821 }
822 
823 void
ephy_password_manager_forget(EphyPasswordManager * self,const char * id,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)824 ephy_password_manager_forget (EphyPasswordManager *self,
825                               const char          *id,
826                               GCancellable        *cancellable,
827                               GAsyncReadyCallback  callback,
828                               gpointer             user_data)
829 {
830   GTask *task = NULL;
831 
832   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
833   g_assert (id);
834 
835   task = g_task_new (self, cancellable, callback, user_data);
836 
837   /* synchronizable-deleted signal needs an EphySynchronizable object,
838   * therefore we need to obtain the password record first and then emit
839   * the signal before clearing the password from the secret schema. */
840   ephy_password_manager_query (self, id,
841                                NULL, NULL, NULL, NULL, NULL,
842                                forget_cb, task);
843 }
844 
845 static void
forget_all_cb(GList * records,gpointer user_data)846 forget_all_cb (GList    *records,
847                gpointer  user_data)
848 {
849   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (user_data);
850   GHashTable *attributes;
851 
852   attributes = secret_attributes_build (EPHY_FORM_PASSWORD_SCHEMA, NULL);
853   secret_password_clearv (EPHY_FORM_PASSWORD_SCHEMA, attributes, NULL,
854                           (GAsyncReadyCallback)secret_password_clear_cb, NULL);
855 
856   for (GList *l = records; l && l->data; l = l->next)
857     g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
858 
859   ephy_password_manager_cache_clear (self);
860 
861   g_hash_table_unref (attributes);
862 }
863 
864 void
ephy_password_manager_forget_all(EphyPasswordManager * self)865 ephy_password_manager_forget_all (EphyPasswordManager *self)
866 {
867   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
868 
869   /* synchronizable-deleted signal needs an EphySynchronizable object, therefore
870    * we need to obtain the password records first and emit the signal for each
871    * one before clearing the secret schema. */
872   ephy_password_manager_query (self, NULL, NULL, NULL, NULL, NULL, NULL,
873                                forget_all_cb, self);
874 }
875 
876 static const char *
synchronizable_manager_get_collection_name(EphySynchronizableManager * manager)877 synchronizable_manager_get_collection_name (EphySynchronizableManager *manager)
878 {
879   return ephy_sync_utils_sync_with_firefox () ? "passwords" : "ephy-passwords";
880 }
881 
882 static GType
synchronizable_manager_get_synchronizable_type(EphySynchronizableManager * manager)883 synchronizable_manager_get_synchronizable_type (EphySynchronizableManager *manager)
884 {
885   return EPHY_TYPE_PASSWORD_RECORD;
886 }
887 
888 static gboolean
synchronizable_manager_is_initial_sync(EphySynchronizableManager * manager)889 synchronizable_manager_is_initial_sync (EphySynchronizableManager *manager)
890 {
891   return ephy_sync_utils_get_passwords_sync_is_initial ();
892 }
893 
894 static void
synchronizable_manager_set_is_initial_sync(EphySynchronizableManager * manager,gboolean is_initial)895 synchronizable_manager_set_is_initial_sync (EphySynchronizableManager *manager,
896                                             gboolean                   is_initial)
897 {
898   ephy_sync_utils_set_passwords_sync_is_initial (is_initial);
899 }
900 
901 static gint64
synchronizable_manager_get_sync_time(EphySynchronizableManager * manager)902 synchronizable_manager_get_sync_time (EphySynchronizableManager *manager)
903 {
904   return ephy_sync_utils_get_passwords_sync_time ();
905 }
906 
907 static void
synchronizable_manager_set_sync_time(EphySynchronizableManager * manager,gint64 sync_time)908 synchronizable_manager_set_sync_time (EphySynchronizableManager *manager,
909                                       gint64                     sync_time)
910 {
911   ephy_sync_utils_set_passwords_sync_time (sync_time);
912 }
913 
914 static void
synchronizable_manager_add(EphySynchronizableManager * manager,EphySynchronizable * synchronizable)915 synchronizable_manager_add (EphySynchronizableManager *manager,
916                             EphySynchronizable        *synchronizable)
917 {
918   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
919   EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
920 
921   ephy_password_manager_store_record (self, record);
922 }
923 
924 static void
synchronizable_manager_remove(EphySynchronizableManager * manager,EphySynchronizable * synchronizable)925 synchronizable_manager_remove (EphySynchronizableManager *manager,
926                                EphySynchronizable        *synchronizable)
927 {
928   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
929   EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
930 
931   ephy_password_manager_forget_record (self, record, NULL, NULL);
932 }
933 
934 static void
replace_existing_cb(GList * records,gpointer user_data)935 replace_existing_cb (GList    *records,
936                      gpointer  user_data)
937 {
938   ManageRecordAsyncData *data = (ManageRecordAsyncData *)user_data;
939 
940   /* We expect only one matching record here. */
941   if (g_list_length (records) == 1)
942     ephy_password_manager_forget_record (data->manager, records->data, data->record, NULL);
943   else
944     g_warn_if_reached ();
945 
946   manage_record_async_data_free (data);
947 }
948 
949 static void
ephy_password_manager_replace_existing(EphyPasswordManager * self,EphyPasswordRecord * record)950 ephy_password_manager_replace_existing (EphyPasswordManager *self,
951                                         EphyPasswordRecord  *record)
952 {
953   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
954   g_assert (EPHY_IS_PASSWORD_RECORD (record));
955 
956   ephy_password_manager_query (self, ephy_password_record_get_id (record),
957                                NULL, NULL, NULL, NULL, NULL,
958                                replace_existing_cb,
959                                manage_record_async_data_new (self, record, NULL));
960 }
961 
962 static void
synchronizable_manager_save(EphySynchronizableManager * manager,EphySynchronizable * synchronizable)963 synchronizable_manager_save (EphySynchronizableManager *manager,
964                              EphySynchronizable        *synchronizable)
965 {
966   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
967   EphyPasswordRecord *record = EPHY_PASSWORD_RECORD (synchronizable);
968 
969   ephy_password_manager_replace_existing (self, record);
970 }
971 
972 static EphyPasswordRecord *
get_record_by_id(GList * records,const char * id)973 get_record_by_id (GList      *records,
974                   const char *id)
975 {
976   g_assert (id);
977 
978   for (GList *l = records; l && l->data; l = l->next) {
979     if (!g_strcmp0 (ephy_password_record_get_id (l->data), id))
980       return l->data;
981   }
982 
983   return NULL;
984 }
985 
986 static EphyPasswordRecord *
get_record_by_parameters(GList * records,const char * origin,const char * target_origin,const char * username,const char * username_field,const char * password_field)987 get_record_by_parameters (GList      *records,
988                           const char *origin,
989                           const char *target_origin,
990                           const char *username,
991                           const char *username_field,
992                           const char *password_field)
993 {
994   for (GList *l = records; l && l->data; l = l->next) {
995     if (!g_strcmp0 (ephy_password_record_get_username (l->data), username) &&
996         !g_strcmp0 (ephy_password_record_get_origin (l->data), origin) &&
997         !g_strcmp0 (ephy_password_record_get_target_origin (l->data), target_origin) &&
998         !g_strcmp0 (ephy_password_record_get_username_field (l->data), username_field) &&
999         !g_strcmp0 (ephy_password_record_get_password_field (l->data), password_field))
1000       return l->data;
1001   }
1002 
1003   return NULL;
1004 }
1005 
1006 
1007 static GList *
delete_record_by_id(GList * records,const char * id)1008 delete_record_by_id (GList      *records,
1009                      const char *id)
1010 {
1011   for (GList *l = records; l && l->data; l = l->next) {
1012     if (!g_strcmp0 (ephy_password_record_get_id (l->data), id)) {
1013       g_object_unref (l->data);
1014       return g_list_delete_link (records, l);
1015     }
1016   }
1017 
1018   return records;
1019 }
1020 
1021 static GPtrArray *
ephy_password_manager_handle_initial_merge(EphyPasswordManager * self,GList * local_records,GList * remote_records)1022 ephy_password_manager_handle_initial_merge (EphyPasswordManager *self,
1023                                             GList               *local_records,
1024                                             GList               *remote_records)
1025 {
1026   EphyPasswordRecord *record;
1027   GHashTable *dont_upload;
1028   GPtrArray *to_upload;
1029   const char *remote_id;
1030   const char *remote_origin;
1031   const char *remote_target_origin;
1032   const char *remote_username;
1033   const char *remote_password;
1034   const char *remote_username_field;
1035   const char *remote_password_field;
1036   guint64 remote_timestamp;
1037   guint64 local_timestamp;
1038   gint64 remote_server_time_modified;
1039   gint64 local_server_time_modified;
1040 
1041   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
1042 
1043   /* A saved password record is uniquely identified by its ID or by its tuple
1044    * of (origin, username, username field, password field). When importing
1045    * password records from server, we may encounter duplicates either by ID
1046    * or by mentioned tuple. We start from the assumption that same ID means
1047    * same tuple but same tuple does not necessarily mean same ID. This is what
1048    * our merge logic is based on.
1049    */
1050   to_upload = g_ptr_array_new_with_free_func (g_object_unref);
1051   dont_upload = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1052 
1053   for (GList *l = remote_records; l && l->data; l = l->next) {
1054     remote_id = ephy_password_record_get_id (l->data);
1055     remote_origin = ephy_password_record_get_origin (l->data);
1056     remote_target_origin = ephy_password_record_get_target_origin (l->data);
1057     remote_username = ephy_password_record_get_username (l->data);
1058     remote_password = ephy_password_record_get_password (l->data);
1059     remote_username_field = ephy_password_record_get_username_field (l->data);
1060     remote_password_field = ephy_password_record_get_password_field (l->data);
1061     remote_timestamp = ephy_password_record_get_time_password_changed (l->data);
1062     remote_server_time_modified = ephy_synchronizable_get_server_time_modified (l->data);
1063 
1064     record = get_record_by_id (local_records, remote_id);
1065     if (record) {
1066       if (!g_strcmp0 (ephy_password_record_get_password (record), remote_password)) {
1067         /* Same id, same password. Nothing to do. */
1068         g_hash_table_add (dont_upload, g_strdup (remote_id));
1069       } else {
1070         /* Same id, different password. Keep the most recent modified. */
1071         local_timestamp = ephy_password_record_get_time_password_changed (record);
1072         if (local_timestamp > remote_timestamp) {
1073           /* Local record is newer. Keep it and upload it to server.
1074           * Also, must keep the most recent server time modified. */
1075           local_server_time_modified = ephy_synchronizable_get_server_time_modified (EPHY_SYNCHRONIZABLE (record));
1076           if (local_server_time_modified < remote_server_time_modified) {
1077             ephy_synchronizable_set_server_time_modified (EPHY_SYNCHRONIZABLE (record),
1078                                                           remote_server_time_modified);
1079             ephy_password_manager_replace_existing (self, record);
1080           }
1081         } else {
1082           /* Remote record is newer. Forget local record and store remote record. */
1083           ephy_password_manager_forget_record (self, record, l->data, NULL);
1084           g_hash_table_add (dont_upload, g_strdup (remote_id));
1085         }
1086       }
1087     } else {
1088       record = get_record_by_parameters (local_records,
1089                                          remote_origin,
1090                                          remote_target_origin,
1091                                          remote_username,
1092                                          remote_username_field,
1093                                          remote_password_field);
1094       if (record) {
1095         /* Different id, same tuple. Keep the most recent modified. */
1096         local_timestamp = ephy_password_record_get_time_password_changed (record);
1097         if (local_timestamp > remote_timestamp) {
1098           /* Local record is newer. Keep it, upload it and delete remote record from server. */
1099           g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
1100         } else {
1101           /* Remote record is newer. Forget local record and store remote record. */
1102           ephy_password_manager_forget_record (self, record, l->data, NULL);
1103           g_hash_table_add (dont_upload, g_strdup (remote_id));
1104         }
1105       } else {
1106         record = get_record_by_parameters (local_records,
1107                                            remote_origin,
1108                                            remote_origin,
1109                                            remote_username,
1110                                            remote_username_field,
1111                                            remote_password_field);
1112         if (record) {
1113           /* A leftover from migration: the local record has incorrect target_origin
1114            * Replace it with remote record */
1115           ephy_password_manager_forget_record (self, record, l->data, NULL);
1116           g_hash_table_add (dont_upload, g_strdup (ephy_password_record_get_id (record)));
1117         } else {
1118           /* Different id, different tuple. This is a new record. */
1119           ephy_password_manager_store_record (self, l->data);
1120           g_hash_table_add (dont_upload, g_strdup (remote_id));
1121         }
1122       }
1123     }
1124   }
1125 
1126   /* Set the remaining local records to be uploaded to server. */
1127   for (GList *l = local_records; l && l->data; l = l->next) {
1128     record = EPHY_PASSWORD_RECORD (l->data);
1129     if (!g_hash_table_contains (dont_upload, ephy_password_record_get_id (record)))
1130       g_ptr_array_add (to_upload, g_object_ref (record));
1131   }
1132 
1133   g_hash_table_unref (dont_upload);
1134 
1135   return to_upload;
1136 }
1137 
1138 static GPtrArray *
ephy_password_manager_handle_regular_merge(EphyPasswordManager * self,GList ** local_records,GList * deleted_records,GList * updated_records)1139 ephy_password_manager_handle_regular_merge (EphyPasswordManager  *self,
1140                                             GList               **local_records,
1141                                             GList                *deleted_records,
1142                                             GList                *updated_records)
1143 {
1144   EphyPasswordRecord *record;
1145   GPtrArray *to_upload;
1146   const char *remote_id;
1147   const char *remote_origin;
1148   const char *remote_target_origin;
1149   const char *remote_username;
1150   const char *remote_username_field;
1151   const char *remote_password_field;
1152   guint64 remote_timestamp;
1153   guint64 local_timestamp;
1154 
1155   g_assert (EPHY_IS_PASSWORD_MANAGER (self));
1156 
1157   to_upload = g_ptr_array_new_with_free_func (g_object_unref);
1158 
1159   for (GList *l = deleted_records; l && l->data; l = l->next) {
1160     remote_id = ephy_password_record_get_id (l->data);
1161     record = get_record_by_id (*local_records, remote_id);
1162     if (record) {
1163       ephy_password_manager_forget_record (self, record, NULL, NULL);
1164       *local_records = delete_record_by_id (*local_records, remote_id);
1165     }
1166   }
1167 
1168   /* See comment in ephy_password_manager_handle_initial_merge. */
1169   for (GList *l = updated_records; l && l->data; l = l->next) {
1170     remote_id = ephy_password_record_get_id (l->data);
1171     remote_origin = ephy_password_record_get_origin (l->data);
1172     remote_target_origin = ephy_password_record_get_target_origin (l->data);
1173     remote_username = ephy_password_record_get_username (l->data);
1174     remote_username_field = ephy_password_record_get_username_field (l->data);
1175     remote_password_field = ephy_password_record_get_password_field (l->data);
1176     remote_timestamp = ephy_password_record_get_time_password_changed (l->data);
1177 
1178     record = get_record_by_id (*local_records, remote_id);
1179     if (record) {
1180       /* Same id. Overwrite local record. */
1181       ephy_password_manager_forget_record (self, record, l->data, NULL);
1182     } else {
1183       record = get_record_by_parameters (*local_records,
1184                                          remote_origin,
1185                                          remote_target_origin,
1186                                          remote_username,
1187                                          remote_username_field,
1188                                          remote_password_field);
1189       if (record) {
1190         /* Different id, same tuple. Keep the most recent modified. */
1191         local_timestamp = ephy_password_record_get_time_password_changed (record);
1192         if (local_timestamp > remote_timestamp) {
1193           /* Local record is newer. Keep it, upload it and delete remote record from server. */
1194           g_ptr_array_add (to_upload, g_object_ref (record));
1195           g_signal_emit_by_name (self, "synchronizable-deleted", l->data);
1196         } else {
1197           /* Remote record is newer. Forget local record and store remote record. */
1198           ephy_password_manager_forget_record (self, record, l->data, NULL);
1199         }
1200       } else {
1201         /* Different id, different tuple. This is a new record. */
1202         ephy_password_manager_store_record (self, l->data);
1203       }
1204     }
1205   }
1206 
1207   return to_upload;
1208 }
1209 
1210 static void
merge_cb(GList * records,gpointer user_data)1211 merge_cb (GList    *records,
1212           gpointer  user_data)
1213 {
1214   MergePasswordsAsyncData *data = (MergePasswordsAsyncData *)user_data;
1215   GPtrArray *to_upload;
1216 
1217   if (data->is_initial)
1218     to_upload = ephy_password_manager_handle_initial_merge (data->manager, records,
1219                                                             data->remotes_updated);
1220   else
1221     to_upload = ephy_password_manager_handle_regular_merge (data->manager, &records,
1222                                                             data->remotes_deleted,
1223                                                             data->remotes_updated);
1224 
1225   data->callback (to_upload, data->user_data);
1226 
1227   merge_passwords_async_data_free (data);
1228 }
1229 
1230 static void
synchronizable_manager_merge(EphySynchronizableManager * manager,gboolean is_initial,GList * remotes_deleted,GList * remotes_updated,EphySynchronizableManagerMergeCallback callback,gpointer user_data)1231 synchronizable_manager_merge (EphySynchronizableManager              *manager,
1232                               gboolean                                is_initial,
1233                               GList                                  *remotes_deleted,
1234                               GList                                  *remotes_updated,
1235                               EphySynchronizableManagerMergeCallback  callback,
1236                               gpointer                                user_data)
1237 {
1238   EphyPasswordManager *self = EPHY_PASSWORD_MANAGER (manager);
1239 
1240   ephy_password_manager_query (self, NULL, NULL, NULL, NULL, NULL, NULL,
1241                                merge_cb,
1242                                merge_passwords_async_data_new (self,
1243                                                                is_initial,
1244                                                                g_list_copy_deep (remotes_deleted, (GCopyFunc)g_object_ref, NULL),
1245                                                                g_list_copy_deep (remotes_updated, (GCopyFunc)g_object_ref, NULL),
1246                                                                callback,
1247                                                                user_data));
1248 }
1249 
1250 static void
ephy_synchronizable_manager_iface_init(EphySynchronizableManagerInterface * iface)1251 ephy_synchronizable_manager_iface_init (EphySynchronizableManagerInterface *iface)
1252 {
1253   iface->get_collection_name = synchronizable_manager_get_collection_name;
1254   iface->get_synchronizable_type = synchronizable_manager_get_synchronizable_type;
1255   iface->is_initial_sync = synchronizable_manager_is_initial_sync;
1256   iface->set_is_initial_sync = synchronizable_manager_set_is_initial_sync;
1257   iface->get_sync_time = synchronizable_manager_get_sync_time;
1258   iface->set_sync_time = synchronizable_manager_set_sync_time;
1259   iface->add = synchronizable_manager_add;
1260   iface->remove = synchronizable_manager_remove;
1261   iface->save = synchronizable_manager_save;
1262   iface->merge = synchronizable_manager_merge;
1263 }
1264