Fix few memory leaks in evolution-source-registry
[platform/upstream/evolution-data-server.git] / modules / gnome-online-accounts / module-gnome-online-accounts.c
1 /*
2  * module-gnome-online-accounts.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 /* XXX Yeah, yeah... */
20 #define GOA_API_IS_SUBJECT_TO_CHANGE
21
22 #include <config.h>
23 #include <goa/goa.h>
24 #include <glib/gi18n-lib.h>
25 #include <libsecret/secret.h>
26 #include <libsoup/soup.h>
27
28 #include <libebackend/libebackend.h>
29
30 #include "goaewsclient.h"
31
32 /* Standard GObject macros */
33 #define E_TYPE_GNOME_ONLINE_ACCOUNTS \
34         (e_gnome_online_accounts_get_type ())
35 #define E_GNOME_ONLINE_ACCOUNTS(obj) \
36         (G_TYPE_CHECK_INSTANCE_CAST \
37         ((obj), E_TYPE_GNOME_ONLINE_ACCOUNTS, EGnomeOnlineAccounts))
38
39 #define CAMEL_OAUTH_MECHANISM_NAME  "XOAUTH"
40 #define CAMEL_OAUTH2_MECHANISM_NAME "XOAUTH2"
41
42 typedef struct _EGnomeOnlineAccounts EGnomeOnlineAccounts;
43 typedef struct _EGnomeOnlineAccountsClass EGnomeOnlineAccountsClass;
44
45 struct _EGnomeOnlineAccounts {
46         EExtension parent;
47
48         GoaClient *goa_client;
49         GCancellable *create_client;
50
51         /* GoaAccount ID -> ESource UID */
52         GHashTable *goa_to_eds;
53 };
54
55 struct _EGnomeOnlineAccountsClass {
56         EExtensionClass parent_class;
57 };
58
59 /* The keyring definintions are copied from e-authentication-session.c */
60
61 #define KEYRING_ITEM_ATTRIBUTE_NAME     "e-source-uid"
62 #define KEYRING_ITEM_DISPLAY_FORMAT     "Evolution Data Source %s"
63
64 #ifdef HAVE_GOA_PASSWORD_BASED
65 /* XXX Probably want to share this with
66  *     evolution-source-registry-migrate-sources.c */
67 static SecretSchema schema = {
68         "org.gnome.Evolution.DataSource",
69         SECRET_SCHEMA_DONT_MATCH_NAME,
70         {
71                 { KEYRING_ITEM_ATTRIBUTE_NAME,
72                   SECRET_SCHEMA_ATTRIBUTE_STRING },
73                 { NULL, 0 }
74         }
75 };
76 #endif /* HAVE_GOA_PASSWORD_BASED */
77
78 /* Module Entry Points */
79 void e_module_load (GTypeModule *type_module);
80 void e_module_unload (GTypeModule *type_module);
81
82 /* Forward Declarations */
83 GType e_gnome_online_accounts_get_type (void);
84 static void e_gnome_online_accounts_oauth2_support_init
85                                         (EOAuth2SupportInterface *interface);
86
87 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
88         EGnomeOnlineAccounts,
89         e_gnome_online_accounts,
90         E_TYPE_EXTENSION,
91         0,
92         G_IMPLEMENT_INTERFACE_DYNAMIC (
93                 E_TYPE_OAUTH2_SUPPORT,
94                 e_gnome_online_accounts_oauth2_support_init))
95
96 static const gchar *
97 gnome_online_accounts_get_backend_name (const gchar *goa_provider_type)
98 {
99         const gchar *eds_backend_name = NULL;
100
101         g_return_val_if_fail (goa_provider_type != NULL, NULL);
102
103         /* This is a mapping between GoaAccount provider types and
104          * ESourceCollection backend names.  It requires knowledge
105          * of other registry modules, possibly even from 3rd party
106          * packages.  No way around it. */
107
108         if (g_str_equal (goa_provider_type, "exchange"))
109                 eds_backend_name = "ews";
110
111         if (g_str_equal (goa_provider_type, "google"))
112                 eds_backend_name = "google";
113
114         if (g_str_equal (goa_provider_type, "imap_smtp"))
115                 eds_backend_name = "none";
116
117         if (g_str_equal (goa_provider_type, "yahoo"))
118                 eds_backend_name = "yahoo";
119
120         if (g_str_equal (goa_provider_type, "owncloud"))
121                 eds_backend_name = "owncloud";
122
123         return eds_backend_name;
124 }
125
126 static ESourceRegistryServer *
127 gnome_online_accounts_get_server (EGnomeOnlineAccounts *extension)
128 {
129         EExtensible *extensible;
130
131         extensible = e_extension_get_extensible (E_EXTENSION (extension));
132
133         return E_SOURCE_REGISTRY_SERVER (extensible);
134 }
135
136 static gboolean
137 gnome_online_accounts_provider_type_to_backend_name (GBinding *binding,
138                                                      const GValue *source_value,
139                                                      GValue *target_value,
140                                                      gpointer unused)
141 {
142         const gchar *provider_type;
143         const gchar *backend_name;
144
145         provider_type = g_value_get_string (source_value);
146         backend_name = gnome_online_accounts_get_backend_name (provider_type);
147         g_return_val_if_fail (backend_name != NULL, FALSE);
148         g_value_set_string (target_value, backend_name);
149
150         return TRUE;
151 }
152
153 static gboolean
154 gnome_online_accounts_object_is_non_null (GBinding *binding,
155                                           const GValue *source_value,
156                                           GValue *target_value,
157                                           gpointer unused)
158 {
159         gpointer v_object;
160
161         v_object = g_value_get_object (source_value);
162         g_value_set_boolean (target_value, v_object != NULL);
163
164         return TRUE;
165 }
166
167 static GoaObject *
168 gnome_online_accounts_ref_account (EGnomeOnlineAccounts *extension,
169                                    ESource *source)
170 {
171         ESourceRegistryServer *server;
172         GoaObject *match = NULL;
173         GList *list, *iter;
174         const gchar *extension_name;
175         gchar *account_id = NULL;
176
177         extension_name = E_SOURCE_EXTENSION_GOA;
178         server = gnome_online_accounts_get_server (extension);
179
180         source = e_source_registry_server_find_extension (
181                 server, source, extension_name);
182
183         if (source != NULL) {
184                 ESourceGoa *goa_ext;
185
186                 goa_ext = e_source_get_extension (source, extension_name);
187                 account_id = e_source_goa_dup_account_id (goa_ext);
188
189                 g_object_unref (source);
190         }
191
192         if (account_id == NULL)
193                 return NULL;
194
195         /* FIXME Use goa_client_lookup_by_id() once we require GOA 3.6. */
196
197         list = goa_client_get_accounts (extension->goa_client);
198
199         for (iter = list; iter != NULL; iter = g_list_next (iter)) {
200                 GoaObject *goa_object;
201                 GoaAccount *goa_account;
202                 const gchar *candidate_id;
203
204                 goa_object = GOA_OBJECT (iter->data);
205                 goa_account = goa_object_get_account (goa_object);
206                 candidate_id = goa_account_get_id (goa_account);
207
208                 if (g_strcmp0 (account_id, candidate_id) == 0)
209                         match = g_object_ref (goa_object);
210
211                 g_object_unref (goa_account);
212
213                 if (match != NULL)
214                         break;
215         }
216
217         g_list_free_full (list, (GDestroyNotify) g_object_unref);
218
219         g_free (account_id);
220
221         return match;
222 }
223
224 static ESource *
225 gnome_online_accounts_new_source (EGnomeOnlineAccounts *extension)
226 {
227         ESourceRegistryServer *server;
228         ESource *source;
229         GFile *file;
230         GError *error = NULL;
231
232         /* This being a brand new data source, creating the instance
233          * should never fail but we'll check for errors just the same. */
234         server = gnome_online_accounts_get_server (extension);
235         file = e_server_side_source_new_user_file (NULL);
236         source = e_server_side_source_new (server, file, &error);
237         g_object_unref (file);
238
239         if (error != NULL) {
240                 g_warn_if_fail (source == NULL);
241                 g_warning ("%s: %s", G_STRFUNC, error->message);
242                 g_error_free (error);
243         }
244
245         return source;
246 }
247
248 #ifdef HAVE_GOA_PASSWORD_BASED
249 static void
250 replace_host (gchar **url,
251               const gchar *host)
252 {
253         SoupURI *uri;
254
255         uri = soup_uri_new (*url);
256         if (!uri)
257                 return;
258
259         soup_uri_set_host (uri, host);
260
261         g_free (*url);
262         *url = soup_uri_to_string (uri, FALSE);
263
264         soup_uri_free (uri);
265 }
266 #endif /* HAVE_GOA_PASSWORD_BASED */
267
268 static void
269 gnome_online_accounts_config_exchange (EGnomeOnlineAccounts *extension,
270                                        ESource *source,
271                                        GoaObject *goa_object)
272 {
273 #ifdef HAVE_GOA_PASSWORD_BASED
274         GoaExchange *goa_exchange;
275         ESourceExtension *source_extension;
276         const gchar *extension_name;
277         gchar *as_url = NULL;
278         gchar *oab_url = NULL;
279         gpointer class;
280         GError *error = NULL;
281
282         goa_exchange = goa_object_peek_exchange (goa_object);
283         if (goa_exchange == NULL)
284                 return;
285
286         /* This should force the ESourceCamelEws type to be registered.
287          * It will also tell us if Evolution-EWS is even installed. */
288         class = g_type_class_ref (g_type_from_name ("EEwsBackend"));
289         if (class != NULL) {
290                 g_type_class_unref (class);
291         } else {
292                 g_critical (
293                         "%s: Could not locate EEwsBackendClass. "
294                         "Is Evolution-EWS installed?", G_STRFUNC);
295                 return;
296         }
297
298         /* XXX GNOME Online Accounts already runs autodiscover to test
299          *     the user-entered values but doesn't share the discovered
300          *     URLs.  It only provides us a host name and expects us to
301          *     re-run autodiscover for ourselves.
302          *
303          *     So I've copied a slab of code from GOA which was in turn
304          *     copied from Evolution-EWS which does the autodiscovery.
305          *
306          *     I've already complained to Debarshi Ray about the lack
307          *     of useful info in GOA's Exchange interface so hopefully
308          *     it will someday publish discovered URLs and then we can
309          *     remove this hack. */
310
311         goa_ews_autodiscover_sync (
312                 goa_object, &as_url, &oab_url, NULL, &error);
313
314         if (error != NULL) {
315                 g_warning ("%s: %s", G_STRFUNC, error->message);
316                 g_error_free (error);
317                 return;
318         }
319
320         g_return_if_fail (as_url != NULL);
321         g_return_if_fail (oab_url != NULL);
322
323         /* XXX We don't have direct access to CamelEwsSettings from here
324          *     since it's defined in Evolution-EWS.  But we can find out
325          *     its extension name and set properties by name. */
326
327         extension_name = e_source_camel_get_extension_name ("ews");
328         source_extension = e_source_get_extension (source, extension_name);
329
330         /* This will be NULL if Evolution-EWS is not installed. */
331         if (source_extension != NULL) {
332                 GoaAccount *goa_account;
333                 CamelSettings *settings;
334                 gchar *host, *user, *email;
335
336                 goa_account = goa_object_peek_account (goa_object);
337                 host = goa_exchange_dup_host (goa_exchange);
338                 user = goa_account_dup_identity (goa_account);
339                 email = goa_account_dup_presentation_identity (goa_account);
340
341                 if (host && *host) {
342                         replace_host (&as_url, host);
343                         replace_host (&oab_url, host);
344                 }
345
346                 g_object_set (
347                         source_extension,
348                         "hosturl", as_url,
349                         "oaburl", oab_url,
350                         "email", email,
351                         NULL);
352
353                 settings = e_source_camel_get_settings (
354                         E_SOURCE_CAMEL (source_extension));
355
356                 g_object_set (
357                         settings,
358                         "host", host,
359                         "user", user,
360                         "email", email,
361                         NULL);
362
363                 g_free (host);
364                 g_free (user);
365                 g_free (email);
366         } else {
367                 g_critical (
368                         "%s: Failed to create [%s] extension",
369                         G_STRFUNC, extension_name);
370         }
371
372         g_free (as_url);
373         g_free (oab_url);
374 #endif /* HAVE_GOA_PASSWORD_BASED */
375 }
376
377 static void
378 gnome_online_accounts_config_oauth (EGnomeOnlineAccounts *extension,
379                                     ESource *source,
380                                     GoaObject *goa_object)
381 {
382         ESourceExtension *source_extension;
383         const gchar *extension_name;
384
385         if (goa_object_peek_oauth_based (goa_object) == NULL)
386                 return;
387
388         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
389         source_extension = e_source_get_extension (source, extension_name);
390
391         e_source_authentication_set_method (
392                 E_SOURCE_AUTHENTICATION (source_extension),
393                 CAMEL_OAUTH_MECHANISM_NAME);
394 }
395
396 static void
397 gnome_online_accounts_config_oauth2 (EGnomeOnlineAccounts *extension,
398                                      ESource *source,
399                                      GoaObject *goa_object)
400 {
401         ESourceExtension *source_extension;
402         const gchar *extension_name;
403
404         if (goa_object_peek_oauth2_based (goa_object) == NULL)
405                 return;
406
407         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
408         source_extension = e_source_get_extension (source, extension_name);
409
410         e_source_authentication_set_method (
411                 E_SOURCE_AUTHENTICATION (source_extension),
412                 CAMEL_OAUTH2_MECHANISM_NAME);
413 }
414
415 static void
416 gnome_online_accounts_config_password (EGnomeOnlineAccounts *extension,
417                                        ESource *source,
418                                        GoaObject *goa_object)
419 {
420 #ifdef HAVE_GOA_PASSWORD_BASED
421         GoaAccount *goa_account;
422         GoaPasswordBased *goa_password_based;
423         EAsyncClosure *closure;
424         GAsyncResult *result;
425         const gchar *uid;
426         gchar *arg_id;
427         gchar *display_name;
428         gchar *password = NULL;
429         GError *error = NULL;
430
431         /* If the GNOME Online Account is password-based, we use its
432          * password to seed our own keyring entry for the collection
433          * source which avoids having to special-case authentication
434          * like we do for OAuth.  Plus, if the stored password is no
435          * good we'll prompt for a new one instead of just giving up. */
436
437         goa_password_based = goa_object_get_password_based (goa_object);
438
439         if (goa_password_based == NULL)
440                 return;
441
442         closure = e_async_closure_new ();
443
444         /* XXX The GOA documentation doesn't explain the string
445          *     argument in goa_password_based_get_password() so
446          *     we'll pass in the identity and hope for the best. */
447         goa_account = goa_object_get_account (goa_object);
448         arg_id = goa_account_dup_identity (goa_account);
449         g_object_unref (goa_account);
450
451         goa_password_based_call_get_password (
452                 goa_password_based, arg_id, NULL,
453                 e_async_closure_callback, closure);
454
455         g_free (arg_id);
456
457         result = e_async_closure_wait (closure);
458
459         goa_password_based_call_get_password_finish (
460                 goa_password_based, &password, result, &error);
461
462         if (error != NULL) {
463                 g_warning ("%s: %s", G_STRFUNC, error->message);
464                 g_error_free (error);
465                 goto exit;
466         }
467
468         uid = e_source_get_uid (source);
469         display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
470
471         secret_password_store (
472                 &schema, SECRET_COLLECTION_DEFAULT,
473                 display_name, password, NULL,
474                 e_async_closure_callback, closure,
475                 KEYRING_ITEM_ATTRIBUTE_NAME, uid,
476                 NULL);
477
478         result = e_async_closure_wait (closure);
479
480         secret_password_store_finish (result, &error);
481
482         g_free (display_name);
483         g_free (password);
484
485         /* If we fail to store the password, we'll just end up prompting
486          * for a password like normal.  Annoying, maybe, but not the end
487          * of the world.  Still leave a breadcrumb for debugging though. */
488         if (error != NULL) {
489                 g_warning ("%s: %s", G_STRFUNC, error->message);
490                 g_error_free (error);
491         }
492
493 exit:
494         e_async_closure_free (closure);
495         g_object_unref (goa_password_based);
496 #endif /* HAVE_GOA_PASSWORD_BASED */
497 }
498
499 static void
500 gnome_online_accounts_config_collection (EGnomeOnlineAccounts *extension,
501                                          ESource *source,
502                                          GoaObject *goa_object)
503 {
504         GoaAccount *goa_account;
505         ESourceExtension *source_extension;
506         const gchar *extension_name;
507         const gchar *provider_type;
508         const gchar *backend_name;
509
510         goa_account = goa_object_get_account (goa_object);
511         provider_type = goa_account_get_provider_type (goa_account);
512         backend_name = gnome_online_accounts_get_backend_name (provider_type);
513
514         g_object_bind_property (
515                 goa_account, "presentation-identity",
516                 source, "display-name",
517                 G_BINDING_SYNC_CREATE);
518
519         extension_name = E_SOURCE_EXTENSION_GOA;
520         source_extension = e_source_get_extension (source, extension_name);
521
522         g_object_bind_property (
523                 goa_account, "id",
524                 source_extension, "account-id",
525                 G_BINDING_SYNC_CREATE);
526
527         /* requires more properties from ownCould, but these are not
528            available before ownCloud was introduced, thus workaround
529            it with the backend_name check
530         */
531         if (g_strcmp0 (backend_name, "owncloud") == 0) {
532                 GoaCalendar *goa_calendar;
533                 GoaContacts *goa_contacts;
534
535                 goa_calendar = goa_object_get_calendar (goa_object);
536                 if (goa_calendar) {
537                         g_object_bind_property (
538                                 goa_calendar, "uri",
539                                 source_extension, "calendar-url",
540                                 G_BINDING_SYNC_CREATE);
541                         g_object_unref (goa_calendar);
542                 }
543
544                 goa_contacts = goa_object_get_contacts (goa_object);
545                 if (goa_contacts) {
546                         g_object_bind_property (
547                                 goa_contacts, "uri",
548                                 source_extension, "contacts-url",
549                                 G_BINDING_SYNC_CREATE);
550                         g_object_unref (goa_contacts);
551                 }
552         }
553
554         extension_name = E_SOURCE_EXTENSION_COLLECTION;
555         source_extension = e_source_get_extension (source, extension_name);
556
557         g_object_bind_property_full (
558                 goa_account, "provider-type",
559                 source_extension, "backend-name",
560                 G_BINDING_SYNC_CREATE,
561                 gnome_online_accounts_provider_type_to_backend_name,
562                 NULL,
563                 NULL, (GDestroyNotify) NULL);
564
565         g_object_bind_property (
566                 goa_account, "identity",
567                 source_extension, "identity",
568                 G_BINDING_SYNC_CREATE);
569
570         g_object_bind_property_full (
571                 goa_object, "calendar",
572                 source_extension, "calendar-enabled",
573                 G_BINDING_SYNC_CREATE,
574                 gnome_online_accounts_object_is_non_null,
575                 NULL,
576                 NULL, (GDestroyNotify) NULL);
577
578         g_object_bind_property_full (
579                 goa_object, "contacts",
580                 source_extension, "contacts-enabled",
581                 G_BINDING_SYNC_CREATE,
582                 gnome_online_accounts_object_is_non_null,
583                 NULL,
584                 NULL, (GDestroyNotify) NULL);
585
586         g_object_bind_property_full (
587                 goa_object, "mail",
588                 source_extension, "mail-enabled",
589                 G_BINDING_SYNC_CREATE,
590                 gnome_online_accounts_object_is_non_null,
591                 NULL,
592                 NULL, (GDestroyNotify) NULL);
593
594         g_object_unref (goa_account);
595
596         /* Handle optional GOA interfaces. */
597         gnome_online_accounts_config_exchange (extension, source, goa_object);
598         gnome_online_accounts_config_password (extension, source, goa_object);
599
600         /* The data source should not be removable by clients. */
601         e_server_side_source_set_removable (
602                 E_SERVER_SIDE_SOURCE (source), FALSE);
603
604         if (goa_object_peek_oauth2_based (goa_object) != NULL) {
605                 /* This module provides OAuth 2.0 support to the collection.
606                  * Note, children of the collection source will automatically
607                  * inherit our EOAuth2Support through the property binding in
608                  * collection_backend_child_added(). */
609                 e_server_side_source_set_oauth2_support (
610                         E_SERVER_SIDE_SOURCE (source),
611                         E_OAUTH2_SUPPORT (extension));
612         }
613 }
614
615 static void
616 gnome_online_accounts_config_mail_account (EGnomeOnlineAccounts *extension,
617                                            ESource *source,
618                                            GoaObject *goa_object)
619 {
620         EServerSideSource *server_side_source;
621
622         /* Only one or the other should be present, not both. */
623         gnome_online_accounts_config_oauth (extension, source, goa_object);
624         gnome_online_accounts_config_oauth2 (extension, source, goa_object);
625
626         /* XXX Need to defer the network security settings to the
627          *     provider-specific module since "imap-use-tls" tells
628          *     us neither the port number, nor whether to use IMAP
629          *     over SSL versus STARTTLS.  The module will know. */
630
631         /* Clients may change the source by may not remove it. */
632         server_side_source = E_SERVER_SIDE_SOURCE (source);
633         e_server_side_source_set_writable (server_side_source, TRUE);
634         e_server_side_source_set_removable (server_side_source, FALSE);
635 }
636
637 static void
638 gnome_online_accounts_config_mail_identity (EGnomeOnlineAccounts *extension,
639                                             ESource *source,
640                                             GoaObject *goa_object)
641 {
642         GoaMail *goa_mail;
643         ESourceExtension *source_extension;
644         EServerSideSource *server_side_source;
645         const gchar *extension_name;
646
647         goa_mail = goa_object_get_mail (goa_object);
648         g_return_if_fail (goa_mail != NULL);
649
650         extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
651         source_extension = e_source_get_extension (source, extension_name);
652
653         g_object_bind_property (
654                 goa_mail, "email-address",
655                 source_extension, "address",
656                 G_BINDING_SYNC_CREATE);
657
658         g_object_unref (goa_mail);
659
660         /* Clients may change the source by may not remove it. */
661         server_side_source = E_SERVER_SIDE_SOURCE (source);
662         e_server_side_source_set_writable (server_side_source, TRUE);
663         e_server_side_source_set_removable (server_side_source, FALSE);
664 }
665
666 static void
667 gnome_online_accounts_config_mail_transport (EGnomeOnlineAccounts *extension,
668                                              ESource *source,
669                                              GoaObject *goa_object)
670 {
671         EServerSideSource *server_side_source;
672
673         /* Only one or the other should be present, not both. */
674         gnome_online_accounts_config_oauth (extension, source, goa_object);
675         gnome_online_accounts_config_oauth2 (extension, source, goa_object);
676
677         /* XXX Need to defer the network security settings to the
678          *     provider-specific module since "smtp-use-tls" tells
679          *     us neither the port number, nor whether to use SMTP
680          *     over SSL versus STARTTLS.  The module will know. */
681
682         /* Clients may change the source by may not remove it. */
683         server_side_source = E_SERVER_SIDE_SOURCE (source);
684         e_server_side_source_set_writable (server_side_source, TRUE);
685         e_server_side_source_set_removable (server_side_source, FALSE);
686 }
687
688 static void
689 gnome_online_accounts_config_sources (EGnomeOnlineAccounts *extension,
690                                       ESource *source,
691                                       GoaObject *goa_object)
692 {
693         ESourceRegistryServer *server;
694         ECollectionBackend *backend;
695         GList *list, *link;
696
697         /* XXX This function was primarily intended to smooth the
698          *     transition of mail accounts from XOAUTH to XOAUTH2,
699          *     but it may be useful for other types of migration. */
700
701         gnome_online_accounts_config_collection (extension, source, goa_object);
702
703         server = gnome_online_accounts_get_server (extension);
704         backend = e_source_registry_server_ref_backend (server, source);
705         g_return_if_fail (backend != NULL);
706
707         list = e_collection_backend_list_mail_sources (backend);
708
709         for (link = list; link != NULL; link = g_list_next (link)) {
710                 const gchar *extension_name;
711
712                 source = E_SOURCE (link->data);
713
714                 extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
715                 if (e_source_has_extension (source, extension_name))
716                         gnome_online_accounts_config_mail_account (
717                                 extension, source, goa_object);
718
719                 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
720                 if (e_source_has_extension (source, extension_name))
721                         gnome_online_accounts_config_mail_identity (
722                                 extension, source, goa_object);
723
724                 extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
725                 if (e_source_has_extension (source, extension_name))
726                         gnome_online_accounts_config_mail_transport (
727                                 extension, source, goa_object);
728         }
729
730         g_list_free_full (list, (GDestroyNotify) g_object_unref);
731
732         g_object_unref (backend);
733 }
734
735 static void
736 gnome_online_accounts_create_collection (EGnomeOnlineAccounts *extension,
737                                          EBackendFactory *backend_factory,
738                                          GoaObject *goa_object)
739 {
740         GoaAccount *goa_account;
741         ESourceRegistryServer *server;
742         ESource *collection_source;
743         ESource *mail_account_source = NULL;
744         ESource *mail_identity_source = NULL;
745         ESource *mail_transport_source = NULL;
746         const gchar *account_id;
747         const gchar *parent_uid;
748
749         server = gnome_online_accounts_get_server (extension);
750
751         collection_source = gnome_online_accounts_new_source (extension);
752         g_return_if_fail (E_IS_SOURCE (collection_source));
753
754         gnome_online_accounts_config_collection (extension, collection_source, goa_object);
755         parent_uid = e_source_get_uid (collection_source);
756
757         if (goa_object_peek_mail (goa_object)) {
758                 mail_account_source = gnome_online_accounts_new_source (extension);
759                 g_return_if_fail (E_IS_SOURCE (mail_account_source));
760
761                 mail_identity_source = gnome_online_accounts_new_source (extension);
762                 g_return_if_fail (E_IS_SOURCE (mail_identity_source));
763
764                 mail_transport_source = gnome_online_accounts_new_source (extension);
765                 g_return_if_fail (E_IS_SOURCE (mail_transport_source));
766
767                 /* Configure parent/child relationships. */
768                 e_source_set_parent (mail_account_source, parent_uid);
769                 e_source_set_parent (mail_identity_source, parent_uid);
770                 e_source_set_parent (mail_transport_source, parent_uid);
771
772                 /* Give the factory first crack at mail configuration. */
773                 e_collection_backend_factory_prepare_mail (
774                         E_COLLECTION_BACKEND_FACTORY (backend_factory),
775                         mail_account_source,
776                         mail_identity_source,
777                         mail_transport_source);
778
779                 gnome_online_accounts_config_mail_account (extension, mail_account_source, goa_object);
780                 gnome_online_accounts_config_mail_identity (extension, mail_identity_source, goa_object);
781                 gnome_online_accounts_config_mail_transport (extension, mail_transport_source, goa_object);
782         }
783
784         /* Export the new source collection. */
785         e_source_registry_server_add_source (server, collection_source);
786
787         if (mail_account_source) {
788                 e_source_registry_server_add_source (server, mail_account_source);
789                 g_object_unref (mail_account_source);
790         }
791
792         if (mail_identity_source) {
793                 e_source_registry_server_add_source (server, mail_identity_source);
794                 g_object_unref (mail_identity_source);
795         }
796
797         if (mail_transport_source) {
798                 e_source_registry_server_add_source (server, mail_transport_source);
799                 g_object_unref (mail_transport_source);
800         }
801
802         goa_account = goa_object_get_account (goa_object);
803         account_id = goa_account_get_id (goa_account);
804
805         g_hash_table_insert (
806                 extension->goa_to_eds,
807                 g_strdup (account_id),
808                 g_strdup (parent_uid));
809
810         g_object_unref (goa_account);
811         g_object_unref (collection_source);
812 }
813
814 static void
815 gnome_online_accounts_remove_collection (EGnomeOnlineAccounts *extension,
816                                          ESource *source)
817 {
818         GError *error = NULL;
819
820         /* This removes the entire subtree rooted at source.
821          * Deletes the corresponding on-disk key files too. */
822         e_source_remove_sync (source, NULL, &error);
823
824         if (error != NULL) {
825                 g_warning ("%s: %s", G_STRFUNC, error->message);
826                 g_error_free (error);
827         }
828 }
829
830 static void
831 gnome_online_accounts_account_added_cb (GoaClient *goa_client,
832                                         GoaObject *goa_object,
833                                         EGnomeOnlineAccounts *extension)
834 {
835         GoaAccount *goa_account;
836         ESourceRegistryServer *server;
837         EBackendFactory *backend_factory = NULL;
838         const gchar *provider_type;
839         const gchar *backend_name;
840         const gchar *account_id;
841         const gchar *source_uid;
842
843         server = gnome_online_accounts_get_server (extension);
844
845         goa_account = goa_object_get_account (goa_object);
846         provider_type = goa_account_get_provider_type (goa_account);
847         backend_name = gnome_online_accounts_get_backend_name (provider_type);
848
849         account_id = goa_account_get_id (goa_account);
850         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
851
852         if (source_uid == NULL && backend_name != NULL)
853                 backend_factory = e_data_factory_ref_backend_factory (
854                         E_DATA_FACTORY (server), backend_name);
855
856         if (backend_factory != NULL) {
857                 gnome_online_accounts_create_collection (
858                         extension, backend_factory, goa_object);
859                 g_object_unref (backend_factory);
860         }
861
862         g_object_unref (goa_account);
863 }
864
865 static void
866 gnome_online_accounts_account_removed_cb (GoaClient *goa_client,
867                                           GoaObject *goa_object,
868                                           EGnomeOnlineAccounts *extension)
869 {
870         ESource *source = NULL;
871         ESourceRegistryServer *server;
872         GoaAccount *goa_account;
873         const gchar *account_id;
874         const gchar *source_uid;
875
876         server = gnome_online_accounts_get_server (extension);
877
878         goa_account = goa_object_get_account (goa_object);
879
880         account_id = goa_account_get_id (goa_account);
881         source_uid = g_hash_table_lookup (extension->goa_to_eds, account_id);
882
883         if (source_uid != NULL)
884                 source = e_source_registry_server_ref_source (
885                         server, source_uid);
886
887         if (source != NULL) {
888                 gnome_online_accounts_remove_collection (extension, source);
889                 g_object_unref (source);
890         }
891
892         g_object_unref (goa_account);
893 }
894
895 static gint
896 gnome_online_accounts_compare_id (GoaObject *goa_object,
897                                   const gchar *target_id)
898 {
899         GoaAccount *goa_account;
900         const gchar *account_id;
901         gint result;
902
903         goa_account = goa_object_get_account (goa_object);
904         account_id = goa_account_get_id (goa_account);
905         result = g_strcmp0 (account_id, target_id);
906         g_object_unref (goa_account);
907
908         return result;
909 }
910
911 static void
912 gnome_online_accounts_populate_accounts_table (EGnomeOnlineAccounts *extension,
913                                                GList *goa_objects)
914 {
915         ESourceRegistryServer *server;
916         GQueue trash = G_QUEUE_INIT;
917         GList *list, *link;
918         const gchar *extension_name;
919
920         server = gnome_online_accounts_get_server (extension);
921
922         extension_name = E_SOURCE_EXTENSION_GOA;
923         list = e_source_registry_server_list_sources (server, extension_name);
924
925         for (link = list; link != NULL; link = g_list_next (link)) {
926                 ESource *source;
927                 ESourceGoa *goa_ext;
928                 const gchar *account_id;
929                 const gchar *source_uid;
930                 GList *match;
931
932                 source = E_SOURCE (link->data);
933                 source_uid = e_source_get_uid (source);
934
935                 extension_name = E_SOURCE_EXTENSION_GOA;
936                 goa_ext = e_source_get_extension (source, extension_name);
937                 account_id = e_source_goa_get_account_id (goa_ext);
938
939                 if (account_id == NULL)
940                         continue;
941
942                 /* Verify the GOA account still exists. */
943                 match = g_list_find_custom (
944                         goa_objects, account_id,
945                         (GCompareFunc) gnome_online_accounts_compare_id);
946
947                 /* If a matching GoaObject was found, add its ID
948                  * to our accounts hash table.  Otherwise remove
949                  * the ESource after we finish looping. */
950                 if (match != NULL) {
951                         GoaObject *goa_object;
952
953                         g_hash_table_insert (
954                                 extension->goa_to_eds,
955                                 g_strdup (account_id),
956                                 g_strdup (source_uid));
957
958                         goa_object = GOA_OBJECT (match->data);
959                         gnome_online_accounts_config_sources (
960                                 extension, source, goa_object);
961                 } else {
962                         g_queue_push_tail (&trash, source);
963                 }
964         }
965
966         /* Empty the trash. */
967         while (!g_queue_is_empty (&trash)) {
968                 ESource *source = g_queue_pop_head (&trash);
969                 gnome_online_accounts_remove_collection (extension, source);
970         }
971
972         g_list_free_full (list, (GDestroyNotify) g_object_unref);
973 }
974
975 static void
976 gnome_online_accounts_create_client_cb (GObject *source_object,
977                                         GAsyncResult *result,
978                                         gpointer user_data)
979 {
980         EGnomeOnlineAccounts *extension;
981         GoaClient *goa_client;
982         GList *list, *link;
983         GError *error = NULL;
984
985         /* If we get back a G_IO_ERROR_CANCELLED then it means the
986          * EGnomeOnlineAccounts is already finalized, so be careful
987          * not to touch it until after we have a valid GoaClient. */
988
989         goa_client = goa_client_new_finish (result, &error);
990
991         if (error != NULL) {
992                 g_warn_if_fail (goa_client == NULL);
993                 g_warning (
994                         "Unable to connect to the GNOME Online "
995                         "Accounts service: %s", error->message);
996                 g_error_free (error);
997                 return;
998         }
999
1000         g_return_if_fail (GOA_IS_CLIENT (goa_client));
1001
1002         /* Should be safe to dereference the EGnomeOnlineAccounts now. */
1003
1004         extension = E_GNOME_ONLINE_ACCOUNTS (user_data);
1005         extension->goa_client = goa_client;  /* takes ownership */
1006
1007         /* Don't need the GCancellable anymore. */
1008         g_object_unref (extension->create_client);
1009         extension->create_client = NULL;
1010
1011         list = goa_client_get_accounts (extension->goa_client);
1012
1013         /* This populates a hash table of GOA ID -> ESource UID strings by
1014          * searching through available data sources for ones with a "GNOME
1015          * Online Accounts" extension.  If such an extension is found, but
1016          * no corresponding GoaAccount (presumably meaning the GOA account
1017          * was somehow deleted between E-D-S sessions) then the ESource in
1018          * which the extension was found gets deleted. */
1019         gnome_online_accounts_populate_accounts_table (extension, list);
1020
1021         for (link = list; link != NULL; link = g_list_next (link))
1022                 gnome_online_accounts_account_added_cb (
1023                         extension->goa_client,
1024                         GOA_OBJECT (link->data),
1025                         extension);
1026
1027         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1028
1029         /* Listen for Online Account changes. */
1030         g_signal_connect (
1031                 extension->goa_client, "account-added",
1032                 G_CALLBACK (gnome_online_accounts_account_added_cb),
1033                 extension);
1034         g_signal_connect (
1035                 extension->goa_client, "account-removed",
1036                 G_CALLBACK (gnome_online_accounts_account_removed_cb),
1037                 extension);
1038 }
1039
1040 static void
1041 gnome_online_accounts_bus_acquired_cb (EDBusServer *server,
1042                                        GDBusConnection *connection,
1043                                        EGnomeOnlineAccounts *extension)
1044 {
1045         /* Connect to the GNOME Online Accounts service. */
1046
1047         /* Note we don't reference the extension.  If the
1048          * extension gets destroyed before this completes
1049          * we cancel the operation from dispose(). */
1050         goa_client_new (
1051                 extension->create_client,
1052                 gnome_online_accounts_create_client_cb,
1053                 extension);
1054 }
1055
1056 static void
1057 gnome_online_accounts_dispose (GObject *object)
1058 {
1059         EGnomeOnlineAccounts *extension;
1060
1061         extension = E_GNOME_ONLINE_ACCOUNTS (object);
1062
1063         if (extension->goa_client != NULL) {
1064                 g_signal_handlers_disconnect_matched (
1065                         extension->goa_client,
1066                         G_SIGNAL_MATCH_DATA,
1067                         0, 0, NULL, NULL, object);
1068                 g_object_unref (extension->goa_client);
1069                 extension->goa_client = NULL;
1070         }
1071
1072         /* This cancels goa_client_new() in case it still
1073          * hasn't completed.  We're no longer interested. */
1074         if (extension->create_client != NULL) {
1075                 g_cancellable_cancel (extension->create_client);
1076                 g_object_unref (extension->create_client);
1077                 extension->create_client = NULL;
1078         }
1079
1080         /* Chain up to parent's dispose() method. */
1081         G_OBJECT_CLASS (e_gnome_online_accounts_parent_class)->
1082                 dispose (object);
1083 }
1084
1085 static void
1086 gnome_online_accounts_finalize (GObject *object)
1087 {
1088         EGnomeOnlineAccounts *extension;
1089
1090         extension = E_GNOME_ONLINE_ACCOUNTS (object);
1091
1092         g_hash_table_destroy (extension->goa_to_eds);
1093
1094         /* Chain up to parent's finalize() method. */
1095         G_OBJECT_CLASS (e_gnome_online_accounts_parent_class)->
1096                 finalize (object);
1097 }
1098
1099 static void
1100 gnome_online_accounts_constructed (GObject *object)
1101 {
1102         EExtension *extension;
1103         EExtensible *extensible;
1104
1105         extension = E_EXTENSION (object);
1106         extensible = e_extension_get_extensible (extension);
1107
1108         /* Wait for the registry service to acquire its well-known
1109          * bus name so we don't do anything destructive beforehand. */
1110
1111         g_signal_connect (
1112                 extensible, "bus-acquired",
1113                 G_CALLBACK (gnome_online_accounts_bus_acquired_cb),
1114                 extension);
1115
1116         /* Chain up to parent's constructed() method. */
1117         G_OBJECT_CLASS (e_gnome_online_accounts_parent_class)->
1118                 constructed (object);
1119 }
1120
1121 static gboolean
1122 gnome_online_accounts_get_access_token_sync (EOAuth2Support *support,
1123                                              ESource *source,
1124                                              GCancellable *cancellable,
1125                                              gchar **out_access_token,
1126                                              gint *out_expires_in,
1127                                              GError **error)
1128 {
1129         GoaObject *goa_object;
1130         GoaAccount *goa_account;
1131         GoaOAuth2Based *goa_oauth2_based;
1132         gboolean success;
1133
1134         goa_object = gnome_online_accounts_ref_account (
1135                 E_GNOME_ONLINE_ACCOUNTS (support), source);
1136
1137         if (goa_object == NULL) {
1138                 g_set_error (
1139                         error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1140                         _("Cannot find a corresponding account in "
1141                         "the org.gnome.OnlineAccounts service from "
1142                         "which to obtain an access token for '%s'"),
1143                         e_source_get_display_name (source));
1144                 return FALSE;
1145         }
1146
1147         goa_account = goa_object_get_account (goa_object);
1148         g_return_val_if_fail (goa_account != NULL, FALSE);
1149
1150         goa_oauth2_based = goa_object_get_oauth2_based (goa_object);
1151         g_return_val_if_fail (goa_oauth2_based != NULL, FALSE);
1152
1153         success = goa_account_call_ensure_credentials_sync (
1154                 goa_account, NULL, cancellable, error);
1155
1156         if (success)
1157                 success = goa_oauth2_based_call_get_access_token_sync (
1158                         goa_oauth2_based, out_access_token,
1159                         out_expires_in, cancellable, error);
1160
1161         g_object_unref (goa_oauth2_based);
1162         g_object_unref (goa_account);
1163         g_object_unref (goa_object);
1164
1165         g_prefix_error (
1166                 error,
1167                 _("Failed to obtain an access token for '%s': "),
1168                 e_source_get_display_name (source));
1169
1170         return success;
1171 }
1172
1173 static void
1174 e_gnome_online_accounts_class_init (EGnomeOnlineAccountsClass *class)
1175 {
1176         GObjectClass *object_class;
1177         EExtensionClass *extension_class;
1178
1179         object_class = G_OBJECT_CLASS (class);
1180         object_class->dispose = gnome_online_accounts_dispose;
1181         object_class->finalize = gnome_online_accounts_finalize;
1182         object_class->constructed = gnome_online_accounts_constructed;
1183
1184         extension_class = E_EXTENSION_CLASS (class);
1185         extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
1186 }
1187
1188 static void
1189 e_gnome_online_accounts_class_finalize (EGnomeOnlineAccountsClass *class)
1190 {
1191 }
1192
1193 static void
1194 e_gnome_online_accounts_oauth2_support_init (EOAuth2SupportInterface *interface)
1195 {
1196         interface->get_access_token_sync = gnome_online_accounts_get_access_token_sync;
1197 }
1198
1199 static void
1200 e_gnome_online_accounts_init (EGnomeOnlineAccounts *extension)
1201 {
1202         /* Used to cancel unfinished goa_client_new(). */
1203         extension->create_client = g_cancellable_new ();
1204
1205         extension->goa_to_eds = g_hash_table_new_full (
1206                 (GHashFunc) g_str_hash,
1207                 (GEqualFunc) g_str_equal,
1208                 (GDestroyNotify) g_free,
1209                 (GDestroyNotify) g_free);
1210 }
1211
1212 G_MODULE_EXPORT void
1213 e_module_load (GTypeModule *type_module)
1214 {
1215         e_gnome_online_accounts_register_type (type_module);
1216 }
1217
1218 G_MODULE_EXPORT void
1219 e_module_unload (GTypeModule *type_module)
1220 {
1221 }
1222