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