uoa: Remove a debug message.
[platform/upstream/evolution-data-server.git] / modules / ubuntu-online-accounts / module-ubuntu-online-accounts.c
1 /*
2  * module-ubuntu-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 #include <config.h>
20 #include <glib/gi18n-lib.h>
21 #include <libsignon-glib/signon-glib.h>
22 #include <libaccounts-glib/accounts-glib.h>
23
24 /* XXX accounts-glib.h should include this */
25 #include <libaccounts-glib/ag-auth-data.h>
26
27 #include <libebackend/libebackend.h>
28
29 #include "uoa-utils.h"
30
31 /* Standard GObject macros */
32 #define E_TYPE_UBUNTU_ONLINE_ACCOUNTS \
33         (e_ubuntu_online_accounts_get_type ())
34 #define E_UBUNTU_ONLINE_ACCOUNTS(obj) \
35         (G_TYPE_CHECK_INSTANCE_CAST \
36         ((obj), E_TYPE_UBUNTU_ONLINE_ACCOUNTS, EUbuntuOnlineAccounts))
37
38 /* Service types we support. */
39 #define SERVICE_TYPE_MAIL     "mail"
40 #define SERVICE_TYPE_CALENDAR "calendar"
41 #define SERVICE_TYPE_CONTACTS "contacts"
42
43 #define CAMEL_OAUTH2_MECHANISM_NAME "XOAUTH2"
44
45 typedef struct _EUbuntuOnlineAccounts EUbuntuOnlineAccounts;
46 typedef struct _EUbuntuOnlineAccountsClass EUbuntuOnlineAccountsClass;
47 typedef struct _AsyncContext AsyncContext;
48
49 struct _EUbuntuOnlineAccounts {
50         EExtension parent;
51
52         AgManager *ag_manager;
53
54         /* AgAccountId -> ESource UID */
55         GHashTable *uoa_to_eds;
56 };
57
58 struct _EUbuntuOnlineAccountsClass {
59         EExtensionClass parent_class;
60 };
61
62 struct _AsyncContext {
63         EUbuntuOnlineAccounts *extension;
64         EBackendFactory *backend_factory;
65         gchar *access_token;
66         gint expires_in;
67 };
68
69 /* Module Entry Points */
70 void e_module_load (GTypeModule *type_module);
71 void e_module_unload (GTypeModule *type_module);
72
73 /* Forward Declarations */
74 GType e_ubuntu_online_accounts_get_type (void);
75 static void e_ubuntu_online_accounts_oauth2_support_init
76                                         (EOAuth2SupportInterface *interface);
77
78 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
79         EUbuntuOnlineAccounts,
80         e_ubuntu_online_accounts,
81         E_TYPE_EXTENSION,
82         0,
83         G_IMPLEMENT_INTERFACE_DYNAMIC (
84                 E_TYPE_OAUTH2_SUPPORT,
85                 e_ubuntu_online_accounts_oauth2_support_init))
86
87 static void
88 async_context_free (AsyncContext *async_context)
89 {
90         if (async_context->extension != NULL)
91                 g_object_unref (async_context->extension);
92
93         if (async_context->backend_factory != NULL)
94                 g_object_unref (async_context->backend_factory);
95
96         g_free (async_context->access_token);
97
98         g_slice_free (AsyncContext, async_context);
99 }
100
101 static const gchar *
102 ubuntu_online_accounts_get_backend_name (const gchar *uoa_provider_name)
103 {
104         const gchar *eds_backend_name = NULL;
105
106         /* This is a mapping between AgAccount provider names and
107          * ESourceCollection backend names.  It requires knowledge
108          * of other registry modules, possibly even from 3rd party
109          * packages.
110          *
111          * FIXME Put the EDS backend name in the .service config
112          *       files so we're not hard-coding the providers we
113          *       support.  This isn't GNOME Online Accounts. */
114
115         if (g_strcmp0 (uoa_provider_name, "google") == 0)
116                 eds_backend_name = "google";
117
118         if (g_strcmp0 (uoa_provider_name, "yahoo") == 0)
119                 eds_backend_name = "yahoo";
120
121         return eds_backend_name;
122 }
123
124 static ESourceRegistryServer *
125 ubuntu_online_accounts_get_server (EUbuntuOnlineAccounts *extension)
126 {
127         EExtensible *extensible;
128
129         extensible = e_extension_get_extensible (E_EXTENSION (extension));
130
131         return E_SOURCE_REGISTRY_SERVER (extensible);
132 }
133
134 static gboolean
135 ubuntu_online_accounts_provider_name_to_backend_name (GBinding *binding,
136                                                       const GValue *source_value,
137                                                       GValue *target_value,
138                                                       gpointer unused)
139 {
140         const gchar *provider_name;
141         const gchar *backend_name;
142
143         provider_name = g_value_get_string (source_value);
144         backend_name = ubuntu_online_accounts_get_backend_name (provider_name);
145         g_return_val_if_fail (backend_name != NULL, FALSE);
146         g_value_set_string (target_value, backend_name);
147
148         return TRUE;
149 }
150
151 static AgAccountService *
152 ubuntu_online_accounts_ref_account_service (EUbuntuOnlineAccounts *extension,
153                                             ESource *source)
154 {
155         GHashTable *account_services;
156         ESourceRegistryServer *server;
157         AgAccountService *ag_account_service = NULL;
158         const gchar *extension_name;
159         const gchar *service_name = NULL;
160
161         /* Figure out which AgAccountService to use based
162          * on which extensions are present in the ESource. */
163
164         extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
165         if (e_source_has_extension (source, extension_name))
166                 service_name = SERVICE_TYPE_CONTACTS;
167
168         extension_name = E_SOURCE_EXTENSION_CALENDAR;
169         if (e_source_has_extension (source, extension_name))
170                 service_name = SERVICE_TYPE_CALENDAR;
171
172         extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
173         if (e_source_has_extension (source, extension_name))
174                 service_name = SERVICE_TYPE_CALENDAR;
175
176         extension_name = E_SOURCE_EXTENSION_TASK_LIST;
177         if (e_source_has_extension (source, extension_name))
178                 service_name = SERVICE_TYPE_CALENDAR;
179
180         extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
181         if (e_source_has_extension (source, extension_name))
182                 service_name = SERVICE_TYPE_MAIL;
183
184         extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
185         if (e_source_has_extension (source, extension_name))
186                 service_name = SERVICE_TYPE_MAIL;
187
188         g_return_val_if_fail (service_name != NULL, NULL);
189
190         extension_name = E_SOURCE_EXTENSION_UOA;
191         server = ubuntu_online_accounts_get_server (extension);
192
193         source = e_source_registry_server_find_extension (
194                 server, source, extension_name);
195
196         if (source != NULL) {
197                 account_services = g_object_get_data (
198                         G_OBJECT (source), "ag-account-services");
199                 g_warn_if_fail (account_services != NULL);
200
201                 if (account_services != NULL) {
202                         ag_account_service = g_hash_table_lookup (
203                                 account_services, service_name);
204                         if (ag_account_service != NULL)
205                                 g_object_ref (ag_account_service);
206                 }
207
208                 g_object_unref (source);
209         }
210
211         return ag_account_service;
212 }
213
214 static gboolean
215 ubuntu_online_accounts_supports_oauth2 (AgAccountService *ag_account_service)
216 {
217         AgAuthData *ag_auth_data;
218         gboolean supports_oauth2 = FALSE;
219         const gchar *method;
220
221         ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
222         method = ag_auth_data_get_method (ag_auth_data);
223         supports_oauth2 = (g_strcmp0 (method, "oauth2") == 0);
224         ag_auth_data_unref (ag_auth_data);
225
226         return supports_oauth2;
227 }
228
229 static ESource *
230 ubuntu_online_accounts_new_source (EUbuntuOnlineAccounts *extension)
231 {
232         ESourceRegistryServer *server;
233         ESource *source;
234         GFile *file;
235         GError *error = NULL;
236
237         /* This being a brand new data source, creating the instance
238          * should never fail but we'll check for errors just the same. */
239         server = ubuntu_online_accounts_get_server (extension);
240         file = e_server_side_source_new_user_file (NULL);
241         source = e_server_side_source_new (server, file, &error);
242         g_object_unref (file);
243
244         if (error != NULL) {
245                 g_warn_if_fail (source == NULL);
246                 g_warning ("%s: %s", G_STRFUNC, error->message);
247                 g_error_free (error);
248         }
249
250         return source;
251 }
252
253 static GHashTable *
254 ubuntu_online_accounts_new_account_services (EUbuntuOnlineAccounts *extension,
255                                              AgAccount *ag_account)
256 {
257         GHashTable *account_services;
258         GList *list, *link;
259
260         account_services = g_hash_table_new_full (
261                 (GHashFunc) g_str_hash,
262                 (GEqualFunc) g_str_equal,
263                 (GDestroyNotify) g_free,
264                 (GDestroyNotify) g_object_unref);
265
266         /* Populate the hash table with AgAccountService instances by
267          * service type.  There should only be one AgService per type.
268          *
269          * XXX We really should not have to create AgAccountService
270          *     instances ourselves.  The AgAccount itself should own
271          *     them and provide functions for listing them.  Instead
272          *     it only provides functions for listing its AgServices,
273          *     which is decidedly less useful. */
274         list = ag_account_list_services (ag_account);
275         for (link = list; link != NULL; link = g_list_next (link)) {
276                 AgService *ag_service = link->data;
277                 const gchar *service_type;
278
279                 service_type = ag_service_get_service_type (ag_service);
280
281                 g_hash_table_insert (
282                         account_services,
283                         g_strdup (service_type),
284                         ag_account_service_new (ag_account, ag_service));
285         }
286         ag_service_list_free (list);
287
288         return account_services;
289 }
290
291 static void
292 ubuntu_online_accounts_config_oauth2 (EUbuntuOnlineAccounts *extension,
293                                       ESource *source,
294                                       GHashTable *account_services)
295 {
296         AgAccountService *ag_account_service;
297         ESourceExtension *source_extension;
298         const gchar *extension_name;
299
300         ag_account_service = g_hash_table_lookup (
301                 account_services, SERVICE_TYPE_MAIL);
302         if (ag_account_service == NULL)
303                 return;
304
305         if (!ubuntu_online_accounts_supports_oauth2 (ag_account_service))
306                 return;
307
308         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
309         source_extension = e_source_get_extension (source, extension_name);
310
311         e_source_authentication_set_method (
312                 E_SOURCE_AUTHENTICATION (source_extension),
313                 CAMEL_OAUTH2_MECHANISM_NAME);
314 }
315
316 static void
317 ubuntu_online_accounts_config_collection (EUbuntuOnlineAccounts *extension,
318                                           ESource *source,
319                                           AgAccount *ag_account,
320                                           GHashTable *account_services,
321                                           const gchar *user_identity)
322 {
323         AgAccountService *ag_account_service;
324         ESourceExtension *source_extension;
325         gboolean supports_oauth2 = FALSE;
326         const gchar *extension_name;
327
328         g_object_bind_property (
329                 ag_account, "display-name",
330                 source, "display-name",
331                 G_BINDING_SYNC_CREATE);
332
333         g_object_bind_property (
334                 ag_account, "enabled",
335                 source, "enabled",
336                 G_BINDING_SYNC_CREATE);
337
338         extension_name = E_SOURCE_EXTENSION_UOA;
339         source_extension = e_source_get_extension (source, extension_name);
340
341         g_object_bind_property (
342                 ag_account, "id",
343                 source_extension, "account-id",
344                 G_BINDING_SYNC_CREATE);
345
346         extension_name = E_SOURCE_EXTENSION_COLLECTION;
347         source_extension = e_source_get_extension (source, extension_name);
348
349         g_object_bind_property_full (
350                 ag_account, "provider",
351                 source_extension, "backend-name",
352                 G_BINDING_SYNC_CREATE,
353                 ubuntu_online_accounts_provider_name_to_backend_name,
354                 NULL,
355                 NULL, (GDestroyNotify) NULL);
356
357         if (user_identity != NULL)
358                 e_source_collection_set_identity (
359                         E_SOURCE_COLLECTION (source_extension),
360                         user_identity);
361
362         ag_account_service = g_hash_table_lookup (
363                 account_services, SERVICE_TYPE_MAIL);
364         if (ag_account_service != NULL) {
365                 g_object_bind_property (
366                         ag_account_service , "enabled",
367                         source_extension, "mail-enabled",
368                         G_BINDING_SYNC_CREATE);
369                 supports_oauth2 |=
370                         ubuntu_online_accounts_supports_oauth2 (
371                         ag_account_service);
372         }
373
374         ag_account_service = g_hash_table_lookup (
375                 account_services, SERVICE_TYPE_CALENDAR);
376         if (ag_account_service != NULL) {
377                 g_object_bind_property (
378                         ag_account_service, "enabled",
379                         source_extension, "calendar-enabled",
380                         G_BINDING_SYNC_CREATE);
381                 supports_oauth2 |=
382                         ubuntu_online_accounts_supports_oauth2 (
383                         ag_account_service);
384         }
385
386         ag_account_service = g_hash_table_lookup (
387                 account_services, SERVICE_TYPE_CONTACTS);
388         if (ag_account_service != NULL) {
389                 g_object_bind_property (
390                         ag_account_service, "enabled",
391                         source_extension, "contacts-enabled",
392                         G_BINDING_SYNC_CREATE);
393                 supports_oauth2 |=
394                         ubuntu_online_accounts_supports_oauth2 (
395                         ag_account_service);
396         }
397
398         /* Stash the AgAccountService hash table in the ESource
399          * to keep the property bindings alive.  The hash table
400          * will be destroyed along with the ESource. */
401         g_object_set_data_full (
402                 G_OBJECT (source),
403                 "ag-account-services",
404                 g_hash_table_ref (account_services),
405                 (GDestroyNotify) g_hash_table_unref);
406
407         /* The data source should not be removable by clients. */
408         e_server_side_source_set_removable (
409                 E_SERVER_SIDE_SOURCE (source), FALSE);
410
411         if (supports_oauth2) {
412                 /* This module provides OAuth 2.0 support to the collection.
413                  * Note, children of the collection source will automatically
414                  * inherit our EOAuth2Support through the property binding in
415                  * collection_backend_child_added(). */
416                 e_server_side_source_set_oauth2_support (
417                         E_SERVER_SIDE_SOURCE (source),
418                         E_OAUTH2_SUPPORT (extension));
419         }
420 }
421
422 static void
423 ubuntu_online_accounts_config_mail_account (EUbuntuOnlineAccounts *extension,
424                                             ESource *source,
425                                             GHashTable *account_services)
426 {
427         EServerSideSource *server_side_source;
428
429         ubuntu_online_accounts_config_oauth2 (
430                 extension, source, account_services);
431
432         /* Clients may change the source but may not remove it. */
433         server_side_source = E_SERVER_SIDE_SOURCE (source);
434         e_server_side_source_set_writable (server_side_source, TRUE);
435         e_server_side_source_set_removable (server_side_source, FALSE);
436 }
437
438 static void
439 ubuntu_online_accounts_config_mail_identity (EUbuntuOnlineAccounts *extension,
440                                              ESource *source,
441                                              GHashTable *account_services,
442                                              const gchar *email_address)
443 {
444         EServerSideSource *server_side_source;
445
446         if (email_address != NULL) {
447                 ESourceMailIdentity *source_extension;
448
449                 source_extension = e_source_get_extension (
450                         source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
451                 e_source_mail_identity_set_address (
452                         source_extension, email_address);
453         }
454
455         /* Clients may change the source but may not remove it. */
456         server_side_source = E_SERVER_SIDE_SOURCE (source);
457         e_server_side_source_set_writable (server_side_source, TRUE);
458         e_server_side_source_set_removable (server_side_source, FALSE);
459 }
460
461 static void
462 ubuntu_online_accounts_config_mail_transport (EUbuntuOnlineAccounts *extension,
463                                               ESource *source,
464                                               GHashTable *account_services)
465 {
466         EServerSideSource *server_side_source;
467
468         ubuntu_online_accounts_config_oauth2 (
469                 extension, source, account_services);
470
471         /* Clients may change the source but may not remove it. */
472         server_side_source = E_SERVER_SIDE_SOURCE (source);
473         e_server_side_source_set_writable (server_side_source, TRUE);
474         e_server_side_source_set_removable (server_side_source, FALSE);
475 }
476
477 static void
478 ubuntu_online_accounts_config_sources (EUbuntuOnlineAccounts *extension,
479                                        ESource *source,
480                                        AgAccount *ag_account)
481 {
482         ESourceRegistryServer *server;
483         ECollectionBackend *backend;
484         GHashTable *account_services;
485         GList *list, *link;
486
487         account_services = ubuntu_online_accounts_new_account_services (
488                 extension, ag_account);
489
490         ubuntu_online_accounts_config_collection (
491                 extension, source, ag_account, account_services, NULL);
492
493         server = ubuntu_online_accounts_get_server (extension);
494         backend = e_source_registry_server_ref_backend (server, source);
495         g_return_if_fail (backend != NULL);
496
497         list = e_collection_backend_list_mail_sources (backend);
498
499         for (link = list; link != NULL; link = g_list_next (link)) {
500                 const gchar *extension_name;
501
502                 source = E_SOURCE (link->data);
503
504                 extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
505                 if (e_source_has_extension (source, extension_name))
506                         ubuntu_online_accounts_config_mail_account (
507                                 extension, source, account_services);
508
509                 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
510                 if (e_source_has_extension (source, extension_name))
511                         ubuntu_online_accounts_config_mail_identity (
512                                 extension, source, account_services, NULL);
513
514                 extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
515                 if (e_source_has_extension (source, extension_name))
516                         ubuntu_online_accounts_config_mail_transport (
517                                 extension, source, account_services);
518         }
519
520         g_list_free_full (list, (GDestroyNotify) g_object_unref);
521
522         g_object_unref (backend);
523
524         g_hash_table_unref (account_services);
525 }
526
527 static void
528 ubuntu_online_accounts_create_collection (EUbuntuOnlineAccounts *extension,
529                                           EBackendFactory *backend_factory,
530                                           AgAccount *ag_account,
531                                           const gchar *user_identity,
532                                           const gchar *email_address)
533 {
534         ESourceRegistryServer *server;
535         ESource *collection_source;
536         ESource *mail_account_source;
537         ESource *mail_identity_source;
538         ESource *mail_transport_source;
539         GHashTable *account_services;
540         const gchar *parent_uid;
541
542         server = ubuntu_online_accounts_get_server (extension);
543
544         collection_source = ubuntu_online_accounts_new_source (extension);
545         g_return_if_fail (E_IS_SOURCE (collection_source));
546
547         mail_account_source = ubuntu_online_accounts_new_source (extension);
548         g_return_if_fail (E_IS_SOURCE (mail_account_source));
549
550         mail_identity_source = ubuntu_online_accounts_new_source (extension);
551         g_return_if_fail (E_IS_SOURCE (mail_identity_source));
552
553         mail_transport_source = ubuntu_online_accounts_new_source (extension);
554         g_return_if_fail (E_IS_SOURCE (mail_transport_source));
555
556         /* Configure parent/child relationships. */
557         parent_uid = e_source_get_uid (collection_source);
558         e_source_set_parent (mail_account_source, parent_uid);
559         e_source_set_parent (mail_identity_source, parent_uid);
560         e_source_set_parent (mail_transport_source, parent_uid);
561
562         /* Give the factory first crack at mail configuration. */
563         e_collection_backend_factory_prepare_mail (
564                 E_COLLECTION_BACKEND_FACTORY (backend_factory),
565                 mail_account_source,
566                 mail_identity_source,
567                 mail_transport_source);
568
569         /* Now it's our turn. */
570         account_services = ubuntu_online_accounts_new_account_services (
571                 extension, ag_account);
572         ubuntu_online_accounts_config_collection (
573                 extension, collection_source, ag_account,
574                 account_services, user_identity);
575         ubuntu_online_accounts_config_mail_account (
576                 extension, mail_account_source, account_services);
577         ubuntu_online_accounts_config_mail_identity (
578                 extension, mail_identity_source,
579                 account_services, email_address);
580         ubuntu_online_accounts_config_mail_transport (
581                 extension, mail_transport_source, account_services);
582         g_hash_table_unref (account_services);
583
584         /* Export the new source collection. */
585         e_source_registry_server_add_source (server, collection_source);
586         e_source_registry_server_add_source (server, mail_account_source);
587         e_source_registry_server_add_source (server, mail_identity_source);
588         e_source_registry_server_add_source (server, mail_transport_source);
589
590         g_hash_table_insert (
591                 extension->uoa_to_eds,
592                 GUINT_TO_POINTER (ag_account->id),
593                 g_strdup (parent_uid));
594
595         g_object_unref (collection_source);
596         g_object_unref (mail_account_source);
597         g_object_unref (mail_identity_source);
598         g_object_unref (mail_transport_source);
599 }
600
601 static void
602 ubuntu_online_accounts_got_userinfo_cb (GObject *source_object,
603                                         GAsyncResult *result,
604                                         gpointer user_data)
605 {
606         AgAccount *ag_account;
607         AsyncContext *async_context = user_data;
608         gchar *user_identity = NULL;
609         gchar *email_address = NULL;
610         GError *error = NULL;
611
612         ag_account = AG_ACCOUNT (source_object);
613
614         e_ag_account_collect_userinfo_finish (
615                 ag_account, result, &user_identity, &email_address, &error);
616
617         if (error == NULL) {
618                 ubuntu_online_accounts_create_collection (
619                         async_context->extension,
620                         async_context->backend_factory,
621                         ag_account,
622                         user_identity,
623                         email_address);
624         } else {
625                 g_warning (
626                         "%s: Failed to create ESource "
627                         "collection for AgAccount '%s': %s",
628                         G_STRFUNC,
629                         ag_account_get_display_name (ag_account),
630                         error->message);
631                 g_error_free (error);
632         }
633
634         g_free (user_identity);
635         g_free (email_address);
636
637         async_context_free (async_context);
638 }
639
640 static void
641 ubuntu_online_accounts_collect_userinfo (EUbuntuOnlineAccounts *extension,
642                                          EBackendFactory *backend_factory,
643                                          AgAccount *ag_account)
644 {
645         AsyncContext *async_context;
646
647         /* Before we create a collection we need to collect user info from
648          * the online service.  GNOME Online Accounts does this for us, but
649          * no such luck with libaccounts-glib or libsignon-glib. */
650
651         async_context = g_slice_new0 (AsyncContext);
652         async_context->extension = g_object_ref (extension);
653         async_context->backend_factory = g_object_ref (backend_factory);
654
655         e_ag_account_collect_userinfo (
656                 ag_account, NULL,
657                 ubuntu_online_accounts_got_userinfo_cb,
658                 async_context);
659 }
660
661 static void
662 ubuntu_online_accounts_remove_collection (EUbuntuOnlineAccounts *extension,
663                                           ESource *source)
664 {
665         GError *error = NULL;
666
667         /* This removes the entire subtree rooted at source.
668          * Deletes the corresponding on-disk key files too. */
669         e_source_remove_sync (source, NULL, &error);
670
671         if (error != NULL) {
672                 g_warning ("%s: %s", G_STRFUNC, error->message);
673                 g_error_free (error);
674         }
675 }
676
677 static void
678 ubuntu_online_accounts_account_created_cb (AgManager *ag_manager,
679                                            AgAccountId ag_account_id,
680                                            EUbuntuOnlineAccounts *extension)
681 {
682         AgAccount *ag_account;
683         ESourceRegistryServer *server;
684         EBackendFactory *backend_factory = NULL;
685         const gchar *provider_name;
686         const gchar *backend_name;
687         const gchar *source_uid;
688
689         server = ubuntu_online_accounts_get_server (extension);
690
691         ag_account = ag_manager_get_account (ag_manager, ag_account_id);
692         g_return_if_fail (ag_account != NULL);
693
694         provider_name = ag_account_get_provider_name (ag_account);
695         backend_name = ubuntu_online_accounts_get_backend_name (provider_name);
696
697         source_uid = g_hash_table_lookup (
698                 extension->uoa_to_eds,
699                 GUINT_TO_POINTER (ag_account_id));
700
701         if (source_uid == NULL && backend_name != NULL)
702                 backend_factory = e_data_factory_ref_backend_factory (
703                         E_DATA_FACTORY (server), backend_name);
704
705         if (backend_factory != NULL) {
706                 ubuntu_online_accounts_collect_userinfo (
707                         extension, backend_factory, ag_account);
708                 g_object_unref (backend_factory);
709         }
710
711         g_object_unref (ag_account);
712 }
713
714 static void
715 ubuntu_online_accounts_account_deleted_cb (AgManager *ag_manager,
716                                            AgAccountId ag_account_id,
717                                            EUbuntuOnlineAccounts *extension)
718 {
719         ESource *source = NULL;
720         ESourceRegistryServer *server;
721         const gchar *source_uid;
722
723         server = ubuntu_online_accounts_get_server (extension);
724
725         source_uid = g_hash_table_lookup (
726                 extension->uoa_to_eds,
727                 GUINT_TO_POINTER (ag_account_id));
728
729         if (source_uid != NULL)
730                 source = e_source_registry_server_ref_source (
731                         server, source_uid);
732
733         if (source != NULL) {
734                 ubuntu_online_accounts_remove_collection (extension, source);
735                 g_object_unref (source);
736         }
737 }
738
739 static void
740 ubuntu_online_accounts_populate_accounts_table (EUbuntuOnlineAccounts *extension,
741                                                 GList *ag_account_ids)
742 {
743         ESourceRegistryServer *server;
744         GQueue trash = G_QUEUE_INIT;
745         GList *list, *link;
746         const gchar *extension_name;
747
748         server = ubuntu_online_accounts_get_server (extension);
749
750         extension_name = E_SOURCE_EXTENSION_UOA;
751         list = e_source_registry_server_list_sources (server, extension_name);
752
753         for (link = list; link != NULL; link = g_list_next (link)) {
754                 ESource *source;
755                 ESourceUoa *uoa_ext;
756                 AgAccount *ag_account = NULL;
757                 AgAccountId ag_account_id;
758                 const gchar *source_uid;
759                 GList *match;
760
761                 source = E_SOURCE (link->data);
762                 source_uid = e_source_get_uid (source);
763
764                 extension_name = E_SOURCE_EXTENSION_UOA;
765                 uoa_ext = e_source_get_extension (source, extension_name);
766                 ag_account_id = e_source_uoa_get_account_id (uoa_ext);
767
768                 if (ag_account_id == 0)
769                         continue;
770
771                 /* Verify the UOA account still exists. */
772                 match = g_list_find (
773                         ag_account_ids,
774                         GUINT_TO_POINTER (ag_account_id));
775                 if (match != NULL)
776                         ag_account = ag_manager_get_account (
777                                 extension->ag_manager, ag_account_id);
778
779                 /* If a matching AgAccountId was found, add it
780                  * to our accounts hash table.  Otherwise remove
781                  * the ESource after we finish looping. */
782                 if (ag_account != NULL) {
783                         g_hash_table_insert (
784                                 extension->uoa_to_eds,
785                                 GUINT_TO_POINTER (ag_account_id),
786                                 g_strdup (source_uid));
787
788                         ubuntu_online_accounts_config_sources (
789                                 extension, source, ag_account);
790                 } else {
791                         g_queue_push_tail (&trash, source);
792                 }
793         }
794
795         /* Empty the trash. */
796         while (!g_queue_is_empty (&trash)) {
797                 ESource *source = g_queue_pop_head (&trash);
798                 ubuntu_online_accounts_remove_collection (extension, source);
799         }
800
801         g_list_free_full (list, (GDestroyNotify) g_object_unref);
802 }
803
804 static void
805 ubuntu_online_accounts_bus_acquired_cb (EDBusServer *server,
806                                         GDBusConnection *connection,
807                                         EUbuntuOnlineAccounts *extension)
808 {
809         GList *list, *link;
810
811         extension->ag_manager = ag_manager_new ();
812
813         list = ag_manager_list (extension->ag_manager);
814
815         /* This populates a hash table of UOA ID -> ESource UID strings by
816          * searching through available data sources for ones with a "Ubuntu
817          * Online Accounts" extension.  If such an extension is found, but
818          * no corresponding AgAccount (presumably meaning the UOA account
819          * was somehow deleted between E-D-S sessions) then the ESource in
820          * which the extension was found gets deleted. */
821         ubuntu_online_accounts_populate_accounts_table (extension, list);
822
823         for (link = list; link != NULL; link = g_list_next (link))
824                 ubuntu_online_accounts_account_created_cb (
825                         extension->ag_manager,
826                         GPOINTER_TO_UINT (link->data),
827                         extension);
828
829         ag_manager_list_free (list);
830
831         /* Listen for Online Account changes. */
832         g_signal_connect (
833                 extension->ag_manager, "account-created",
834                 G_CALLBACK (ubuntu_online_accounts_account_created_cb),
835                 extension);
836         g_signal_connect (
837                 extension->ag_manager, "account-deleted",
838                 G_CALLBACK (ubuntu_online_accounts_account_deleted_cb),
839                 extension);
840 }
841
842 static void
843 ubuntu_online_accounts_dispose (GObject *object)
844 {
845         EUbuntuOnlineAccounts *extension;
846
847         extension = E_UBUNTU_ONLINE_ACCOUNTS (object);
848
849         if (extension->ag_manager != NULL) {
850                 g_signal_handlers_disconnect_matched (
851                         extension->ag_manager,
852                         G_SIGNAL_MATCH_DATA,
853                         0, 0, NULL, NULL, object);
854                 g_object_unref (extension->ag_manager);
855                 extension->ag_manager = NULL;
856         }
857
858         /* Chain up to parent's dispose() method. */
859         G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
860                 dispose (object);
861 }
862
863 static void
864 ubuntu_online_accounts_finalize (GObject *object)
865 {
866         EUbuntuOnlineAccounts *extension;
867
868         extension = E_UBUNTU_ONLINE_ACCOUNTS (object);
869
870         g_hash_table_destroy (extension->uoa_to_eds);
871
872         /* Chain up to parent's finalize() method. */
873         G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
874                 finalize (object);
875 }
876
877 static void
878 ubuntu_online_accounts_constructed (GObject *object)
879 {
880         EExtension *extension;
881         EExtensible *extensible;
882
883         extension = E_EXTENSION (object);
884         extensible = e_extension_get_extensible (extension);
885
886         /* Wait for the registry service to acquire its well-known
887          * bus name so we don't do anything destructive beforehand. */
888
889         g_signal_connect (
890                 extensible, "bus-acquired",
891                 G_CALLBACK (ubuntu_online_accounts_bus_acquired_cb),
892                 extension);
893
894         /* Chain up to parent's constructed() method. */
895         G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
896                 constructed (object);
897 }
898
899 static gboolean
900 ubuntu_online_accounts_get_access_token_sync (EOAuth2Support *support,
901                                               ESource *source,
902                                               GCancellable *cancellable,
903                                               gchar **out_access_token,
904                                               gint *out_expires_in,
905                                               GError **error)
906 {
907         EAsyncClosure *closure;
908         GAsyncResult *result;
909         gboolean success;
910
911         closure = e_async_closure_new ();
912
913         e_oauth2_support_get_access_token (
914                 support, source, cancellable,
915                 e_async_closure_callback, closure);
916
917         result = e_async_closure_wait (closure);
918
919         success = e_oauth2_support_get_access_token_finish (
920                 support, result, out_access_token, out_expires_in, error);
921
922         e_async_closure_free (closure);
923
924         return success;
925 }
926
927 /* Helper for ubuntu_online_accounts_get_access_token() */
928 static void
929 ubuntu_online_accounts_session_process_cb (GObject *source_object,
930                                            GAsyncResult *result,
931                                            gpointer user_data)
932 {
933         GSimpleAsyncResult *simple;
934         AsyncContext *async_context;
935         GVariant *session_data;
936         GError *error = NULL;
937
938         simple = G_SIMPLE_ASYNC_RESULT (user_data);
939         async_context = g_simple_async_result_get_op_res_gpointer (simple);
940
941         session_data = signon_auth_session_process_finish (
942                 SIGNON_AUTH_SESSION (source_object), result, &error);
943
944         /* Sanity check. */
945         g_return_if_fail (
946                 ((session_data != NULL) && (error == NULL)) ||
947                 ((session_data == NULL) && (error != NULL)));
948
949         if (session_data != NULL) {
950                 g_variant_lookup (
951                         session_data, "AccessToken", "s",
952                         &async_context->access_token);
953
954                 g_variant_lookup (
955                         session_data, "ExpiresIn", "i",
956                         &async_context->expires_in);
957
958                 g_warn_if_fail (async_context->access_token != NULL);
959                 g_variant_unref (session_data);
960         }
961
962         if (error != NULL)
963                 g_simple_async_result_take_error (simple, error);
964
965         g_simple_async_result_complete (simple);
966
967         g_object_unref (simple);
968 }
969
970 static void
971 ubuntu_online_accounts_get_access_token (EOAuth2Support *support,
972                                          ESource *source,
973                                          GCancellable *cancellable,
974                                          GAsyncReadyCallback callback,
975                                          gpointer user_data)
976 {
977         GSimpleAsyncResult *simple;
978         AsyncContext *async_context;
979         SignonAuthSession *session;
980         AgAccountService *ag_account_service;
981         AgAuthData *ag_auth_data;
982         GError *error = NULL;
983
984         async_context = g_slice_new0 (AsyncContext);
985
986         simple = g_simple_async_result_new (
987                 G_OBJECT (support), callback, user_data,
988                 ubuntu_online_accounts_get_access_token);
989
990         g_simple_async_result_set_check_cancellable (simple, cancellable);
991
992         g_simple_async_result_set_op_res_gpointer (
993                 simple, async_context, (GDestroyNotify) async_context_free);
994
995         ag_account_service = ubuntu_online_accounts_ref_account_service (
996                 E_UBUNTU_ONLINE_ACCOUNTS (support), source);
997
998         if (ag_account_service == NULL) {
999                 g_simple_async_result_set_error (
1000                         simple, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1001                         _("Cannot find a corresponding account "
1002                         "service in the accounts database from "
1003                         "which to obtain an access token for '%s'"),
1004                         e_source_get_display_name (source));
1005                 g_simple_async_result_complete_in_idle (simple);
1006                 g_object_unref (simple);
1007                 return;
1008         }
1009
1010         /* XXX This should never happen.  But because libaccounts-glib
1011          *     splits authentication method by service-type instead of
1012          *     by provider, and because we broadcast OAuth 2.0 support
1013          *     across the entire collection (spanning multiple service
1014          *     types), it's conceivable that not all service-types for
1015          *     a provider use OAuth 2.0, and an ESource for one of the
1016          *     ones that DOESN'T could mistakenly request the token. */
1017         if (!ubuntu_online_accounts_supports_oauth2 (ag_account_service)) {
1018                 g_simple_async_result_set_error (
1019                         simple, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
1020                         _("Data source '%s' does not "
1021                         "support OAuth 2.0 authentication"),
1022                         e_source_get_display_name (source));
1023                 g_simple_async_result_complete_in_idle (simple);
1024                 g_object_unref (simple);
1025                 return;
1026         }
1027
1028         ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
1029
1030         session = signon_auth_session_new (
1031                 ag_auth_data_get_credentials_id (ag_auth_data),
1032                 ag_auth_data_get_method (ag_auth_data), &error);
1033
1034         /* Sanity check. */
1035         g_return_if_fail (
1036                 ((session != NULL) && (error == NULL)) ||
1037                 ((session == NULL) && (error != NULL)));
1038
1039         if (session != NULL) {
1040                 signon_auth_session_process_async (
1041                         session,
1042                         ag_auth_data_get_login_parameters (ag_auth_data, NULL),
1043                         ag_auth_data_get_mechanism (ag_auth_data),
1044                         cancellable,
1045                         ubuntu_online_accounts_session_process_cb,
1046                         g_object_ref (simple));
1047                 g_object_unref (session);
1048         } else {
1049                 g_simple_async_result_take_error (simple, error);
1050                 g_simple_async_result_complete_in_idle (simple);
1051         }
1052
1053         ag_auth_data_unref (ag_auth_data);
1054
1055         g_object_unref (ag_account_service);
1056         g_object_unref (simple);
1057 }
1058
1059 static gboolean
1060 ubuntu_online_accounts_get_access_token_finish (EOAuth2Support *support,
1061                                                 GAsyncResult *result,
1062                                                 gchar **out_access_token,
1063                                                 gint *out_expires_in,
1064                                                 GError **error)
1065 {
1066         GSimpleAsyncResult *simple;
1067         AsyncContext *async_context;
1068
1069         g_return_val_if_fail (
1070                 g_simple_async_result_is_valid (
1071                 result, G_OBJECT (support),
1072                 ubuntu_online_accounts_get_access_token), FALSE);
1073
1074         simple = G_SIMPLE_ASYNC_RESULT (result);
1075         async_context = g_simple_async_result_get_op_res_gpointer (simple);
1076
1077         if (g_simple_async_result_propagate_error (simple, error))
1078                 return FALSE;
1079
1080         g_return_val_if_fail (async_context->access_token != NULL, FALSE);
1081
1082         if (out_access_token != NULL) {
1083                 *out_access_token = async_context->access_token;
1084                 async_context->access_token = NULL;
1085         }
1086
1087         if (out_expires_in != NULL)
1088                 *out_expires_in = async_context->expires_in;
1089
1090         return TRUE;
1091 }
1092
1093 static void
1094 e_ubuntu_online_accounts_class_init (EUbuntuOnlineAccountsClass *class)
1095 {
1096         GObjectClass *object_class;
1097         EExtensionClass *extension_class;
1098
1099         object_class = G_OBJECT_CLASS (class);
1100         object_class->dispose = ubuntu_online_accounts_dispose;
1101         object_class->finalize = ubuntu_online_accounts_finalize;
1102         object_class->constructed = ubuntu_online_accounts_constructed;
1103
1104         extension_class = E_EXTENSION_CLASS (class);
1105         extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
1106 }
1107
1108 static void
1109 e_ubuntu_online_accounts_class_finalize (EUbuntuOnlineAccountsClass *class)
1110 {
1111 }
1112
1113 static void
1114 e_ubuntu_online_accounts_oauth2_support_init (EOAuth2SupportInterface *interface)
1115 {
1116         interface->get_access_token_sync = ubuntu_online_accounts_get_access_token_sync;
1117         interface->get_access_token = ubuntu_online_accounts_get_access_token;
1118         interface->get_access_token_finish = ubuntu_online_accounts_get_access_token_finish;
1119 }
1120
1121 static void
1122 e_ubuntu_online_accounts_init (EUbuntuOnlineAccounts *extension)
1123 {
1124         extension->uoa_to_eds = g_hash_table_new_full (
1125                 (GHashFunc) g_direct_hash,
1126                 (GEqualFunc) g_direct_equal,
1127                 (GDestroyNotify) NULL,
1128                 (GDestroyNotify) g_free);
1129 }
1130
1131 G_MODULE_EXPORT void
1132 e_module_load (GTypeModule *type_module)
1133 {
1134         e_ubuntu_online_accounts_register_type (type_module);
1135 }
1136
1137 G_MODULE_EXPORT void
1138 e_module_unload (GTypeModule *type_module)
1139 {
1140 }
1141