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