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