[evolution-data-server/uoa: 3/3] Add an "ubuntu-online-accounts" module.



commit e02baa883c6b922601486ec6ab375e0fa00dbf4d
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sun Oct 7 10:34:48 2012 -0400

    Add an "ubuntu-online-accounts" module.
    
    Initially supports Google and Yahoo! services.

 configure.ac                                       |   10 +
 modules/Makefile.am                                |    5 +
 modules/ubuntu-online-accounts/Makefile.am         |   84 ++
 .../calendar.service-type.in.in                    |    7 +
 .../contacts.service-type.in.in                    |    8 +
 .../evolution-data-server.application.in.in        |   10 +
 .../google-calendar.service.in.in                  |   18 +
 .../google-contacts.service.in.in                  |   18 +
 .../google-gmail.service.in.in                     |   18 +
 .../ubuntu-online-accounts/mail.service-type.in.in |    7 +
 .../module-ubuntu-online-accounts.c                | 1137 ++++++++++++++++++++
 modules/ubuntu-online-accounts/uoa-utils.c         |  324 ++++++
 modules/ubuntu-online-accounts/uoa-utils.h         |   39 +
 .../yahoo-calendar.service.in.in                   |   15 +
 .../yahoo-mail.service.in.in                       |   15 +
 15 files changed, 1715 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e048a6b..7a4fe10 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1658,6 +1658,16 @@ modules/Makefile
 modules/cache-reaper/Makefile
 modules/gnome-online-accounts/Makefile
 modules/google-backend/Makefile
+modules/ubuntu-online-accounts/Makefile
+modules/ubuntu-online-accounts/calendar.service-type.in
+modules/ubuntu-online-accounts/contacts.service-type.in
+modules/ubuntu-online-accounts/evolution-data-server.application.in
+modules/ubuntu-online-accounts/google-calendar.service.in
+modules/ubuntu-online-accounts/google-contacts.service.in
+modules/ubuntu-online-accounts/google-gmail.service.in
+modules/ubuntu-online-accounts/mail.service-type.in
+modules/ubuntu-online-accounts/yahoo-calendar.service.in
+modules/ubuntu-online-accounts/yahoo-mail.service.in
 modules/trust-prompt/Makefile
 modules/yahoo-backend/Makefile
 private/Makefile
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 21e7252..42ebdfa 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -4,12 +4,17 @@ if HAVE_GOA
 GNOME_ONLINE_ACCOUNTS_DIR = gnome-online-accounts
 endif
 
+if HAVE_UOA
+UBUNTU_ONLINE_ACCOUNTS_DIR = ubuntu-online-accounts
+endif
+
 SUBDIRS = \
 	cache-reaper \
 	google-backend \
 	trust-prompt \
 	yahoo-backend \
 	$(GNOME_ONLINE_ACCOUNTS_DIR) \
+	$(UBUNTU_ONLINE_ACCOUNTS_DIR) \
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/modules/ubuntu-online-accounts/Makefile.am b/modules/ubuntu-online-accounts/Makefile.am
new file mode 100644
index 0000000..2dc77a0
--- /dev/null
+++ b/modules/ubuntu-online-accounts/Makefile.am
@@ -0,0 +1,84 @@
+NULL =
+
+module_LTLIBRARIES = module-ubuntu-online-accounts.la
+
+module_ubuntu_online_accounts_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-DG_LOG_DOMAIN=\"module-ubuntu-online-accounts\" \
+	$(LIBACCOUNTS_GLIB_CFLAGS) \
+	$(LIBSIGNON_GLIB_CFLAGS) \
+	$(E_BACKEND_CFLAGS) \
+	$(JSON_GLIB_CFLAGS) \
+	$(CAMEL_CFLAGS) \
+	$(REST_CFLAGS) \
+	$(NULL)
+
+module_ubuntu_online_accounts_la_SOURCES = \
+	module-ubuntu-online-accounts.c \
+	uoa-utils.c \
+	uoa-utils.h \
+	$(NULL)
+
+module_ubuntu_online_accounts_la_LIBADD = \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(LIBACCOUNTS_GLIB_LIBS) \
+	$(LIBSIGNON_GLIB_LIBS) \
+	$(E_BACKEND_LIBS) \
+	$(JSON_GLIB_LIBS) \
+	$(CAMEL_LIBS) \
+	$(REST_LIBS) \
+	$(NULL)
+
+module_ubuntu_online_accounts_la_LDFLAGS = \
+	-module -avoid-version $(NO_UNDEFINED) \
+	$(NULL)
+
+%.application: %.application.in
+	$(AM_V_GEN) $(INTLTOOL_MERGE) --no-translations -x -u $< $@
+
+%.service-type: %.service-type.in
+	$(AM_V_GEN) $(INTLTOOL_MERGE) --no-translations -x -u $< $@
+
+%.service: %.service.in
+	$(AM_V_GEN) $(INTLTOOL_MERGE) --no-translations -x -u $< $@
+
+applicationdir = `$(PKG_CONFIG) --variable=applicationfilesdir libaccounts-glib`
+application_DATA = evolution-data-server.application
+
+servicetypedir = `$(PKG_CONFIG) --variable=servicetypefilesdir libaccounts-glib`
+servicetype_DATA = \
+	mail.service-type \
+	calendar.service-type \
+	contacts.service-type \
+	$(NULL)
+
+servicedir = `$(PKG_CONFIG) --variable=servicefilesdir libaccounts-glib`
+service_DATA = \
+	google-gmail.service \
+	google-calendar.service \
+	google-contacts.service \
+	yahoo-mail.service \
+	yahoo-calendar.service \
+	$(NULL)
+
+EXTRA_DIST = \
+	evolution-data-server.application.in.in \
+	mail.service-type.in.in \
+	calendar.service-type.in.in \
+	contacts.service-type.in.in \
+	google-gmail.service.in.in \
+	google-calendar.service.in.in \
+	google-contacts.service.in.in \
+	yahoo-mail.service.in.in \
+	yahoo-calendar.service.in.in \
+	$(NULL)
+
+DISTCLEANFILES = \
+	$(application_DATA) \
+	$(servicetype_DATA) \
+	$(service_DATA) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/ubuntu-online-accounts/calendar.service-type.in.in b/modules/ubuntu-online-accounts/calendar.service-type.in.in
new file mode 100644
index 0000000..ac03f14
--- /dev/null
+++ b/modules/ubuntu-online-accounts/calendar.service-type.in.in
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service-type id="calendar">
+  <_name>Calendar</_name>
+  <_description>Integrate your calendars</_description>
+  <icon>x-office-calendar</icon>
+  <translations>@GETTEXT_PACKAGE@</translations>
+</service-type>
diff --git a/modules/ubuntu-online-accounts/contacts.service-type.in.in b/modules/ubuntu-online-accounts/contacts.service-type.in.in
new file mode 100644
index 0000000..3ced915
--- /dev/null
+++ b/modules/ubuntu-online-accounts/contacts.service-type.in.in
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service-type id="contacts">
+  <_name>Contacts</_name>
+  <_description>Integrate your contacts</_description>
+  <icon>x-office-address-book</icon>
+  <translations>@GETTEXT_PACKAGE@</translations>
+</service-type>
+
diff --git a/modules/ubuntu-online-accounts/evolution-data-server.application.in.in b/modules/ubuntu-online-accounts/evolution-data-server.application.in.in
new file mode 100644
index 0000000..8d9277e
--- /dev/null
+++ b/modules/ubuntu-online-accounts/evolution-data-server.application.in.in
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<application id="evolution-data-server">
+  <description>Evolution-Data-Server</description>
+  <translations>@GETTEXT_PACKAGE@</translations>
+  <service-types>
+    <service-type id="mail"/>
+    <service-type id="calendar"/>
+    <service-type id="contacts"/>
+  </service-types>
+</application>
diff --git a/modules/ubuntu-online-accounts/google-calendar.service.in.in b/modules/ubuntu-online-accounts/google-calendar.service.in.in
new file mode 100644
index 0000000..5810ff1
--- /dev/null
+++ b/modules/ubuntu-online-accounts/google-calendar.service.in.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service id="google-calendar">
+  <type>calendar</type>
+  <_name>Google Calendar</_name>
+  <provider>google</provider>
+  <translations>@GETTEXT_PACKAGE@</translations>
+
+  <!-- default settings (account settings have precedence over these) -->
+  <template>
+    <group name="auth">
+      <group name="oauth2">
+        <group name="user_agent">
+          <setting type="as" name="Scope">["https://www.googleapis.com/auth/calendar";]</setting>
+        </group>
+      </group>
+    </group>
+  </template>
+</service>
diff --git a/modules/ubuntu-online-accounts/google-contacts.service.in.in b/modules/ubuntu-online-accounts/google-contacts.service.in.in
new file mode 100644
index 0000000..47ad7d8
--- /dev/null
+++ b/modules/ubuntu-online-accounts/google-contacts.service.in.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service id="google-contacts">
+  <type>contacts</type>
+  <_name>Google Contacts</_name>
+  <provider>google</provider>
+  <translations>@GETTEXT_PACKAGE@</translations>
+
+  <!-- default settings (account settings have precedence over these) -->
+  <template>
+    <group name="auth">
+      <group name="oauth2">
+        <group name="user_agent">
+          <setting type="as" name="Scope">["https://www.google.com/m8/feeds/";]</setting>
+        </group>
+      </group>
+    </group>
+  </template>
+</service>
diff --git a/modules/ubuntu-online-accounts/google-gmail.service.in.in b/modules/ubuntu-online-accounts/google-gmail.service.in.in
new file mode 100644
index 0000000..972799e
--- /dev/null
+++ b/modules/ubuntu-online-accounts/google-gmail.service.in.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service id="google-gmail">
+  <type>mail</type>
+  <_name>GMail</_name>
+  <provider>google</provider>
+  <translations>@GETTEXT_PACKAGE@</translations>
+
+  <!-- default settings (account settings have precedence over these) -->
+  <template>
+    <group name="auth">
+      <group name="oauth2">
+        <group name="user_agent">
+          <setting type="as" name="Scope">["https://mail.google.com/";]</setting>
+        </group>
+      </group>
+    </group>
+  </template>
+</service>
diff --git a/modules/ubuntu-online-accounts/mail.service-type.in.in b/modules/ubuntu-online-accounts/mail.service-type.in.in
new file mode 100644
index 0000000..6917f3e
--- /dev/null
+++ b/modules/ubuntu-online-accounts/mail.service-type.in.in
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service-type id="mail">
+  <_name>Mail</_name>
+  <_description>Integrate your mailboxes</_description>
+  <icon>emblem-mail</icon>
+  <translations>@GETTEXT_PACKAGE@</translations>
+</service-type>
diff --git a/modules/ubuntu-online-accounts/module-ubuntu-online-accounts.c b/modules/ubuntu-online-accounts/module-ubuntu-online-accounts.c
new file mode 100644
index 0000000..c61baa3
--- /dev/null
+++ b/modules/ubuntu-online-accounts/module-ubuntu-online-accounts.c
@@ -0,0 +1,1137 @@
+/*
+ * module-ubuntu-online-accounts.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <libsignon-glib/signon-glib.h>
+#include <libaccounts-glib/accounts-glib.h>
+
+/* XXX accounts-glib.h should include this */
+#include <libaccounts-glib/ag-auth-data.h>
+
+#include <libebackend/libebackend.h>
+
+#include "uoa-utils.h"
+
+/* Standard GObject macros */
+#define E_TYPE_UBUNTU_ONLINE_ACCOUNTS \
+	(e_ubuntu_online_accounts_get_type ())
+#define E_UBUNTU_ONLINE_ACCOUNTS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_UBUNTU_ONLINE_ACCOUNTS, EUbuntuOnlineAccounts))
+
+/* Service types we support. */
+#define SERVICE_TYPE_MAIL     "mail"
+#define SERVICE_TYPE_CALENDAR "calendar"
+#define SERVICE_TYPE_CONTACTS "contacts"
+
+#define CAMEL_OAUTH2_MECHANISM_NAME "XOAUTH2"
+
+typedef struct _EUbuntuOnlineAccounts EUbuntuOnlineAccounts;
+typedef struct _EUbuntuOnlineAccountsClass EUbuntuOnlineAccountsClass;
+typedef struct _AsyncContext AsyncContext;
+
+struct _EUbuntuOnlineAccounts {
+	EExtension parent;
+
+	AgManager *ag_manager;
+
+	/* AgAccountId -> ESource UID */
+	GHashTable *uoa_to_eds;
+};
+
+struct _EUbuntuOnlineAccountsClass {
+	EExtensionClass parent_class;
+};
+
+struct _AsyncContext {
+	EUbuntuOnlineAccounts *extension;
+	EBackendFactory *backend_factory;
+	gchar *access_token;
+	gint expires_in;
+};
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_ubuntu_online_accounts_get_type (void);
+static void e_ubuntu_online_accounts_oauth2_support_init
+					(EOAuth2SupportInterface *interface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (
+	EUbuntuOnlineAccounts,
+	e_ubuntu_online_accounts,
+	E_TYPE_EXTENSION,
+	0,
+	G_IMPLEMENT_INTERFACE_DYNAMIC (
+		E_TYPE_OAUTH2_SUPPORT,
+		e_ubuntu_online_accounts_oauth2_support_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->extension != NULL)
+		g_object_unref (async_context->extension);
+
+	if (async_context->backend_factory != NULL)
+		g_object_unref (async_context->backend_factory);
+
+	g_free (async_context->access_token);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static const gchar *
+ubuntu_online_accounts_get_backend_name (const gchar *uoa_provider_name)
+{
+	const gchar *eds_backend_name = NULL;
+
+	/* This is a mapping between AgAccount provider names and
+	 * ESourceCollection backend names.  It requires knowledge
+	 * of other registry modules, possibly even from 3rd party
+	 * packages.  No way around it. */
+
+	if (g_strcmp0 (uoa_provider_name, "google") == 0)
+		eds_backend_name = "google";
+
+	if (g_strcmp0 (uoa_provider_name, "yahoo") == 0)
+		eds_backend_name = "yahoo";
+
+	return eds_backend_name;
+}
+
+static ESourceRegistryServer *
+ubuntu_online_accounts_get_server (EUbuntuOnlineAccounts *extension)
+{
+	EExtensible *extensible;
+
+	extensible = e_extension_get_extensible (E_EXTENSION (extension));
+
+	return E_SOURCE_REGISTRY_SERVER (extensible);
+}
+
+static gboolean
+ubuntu_online_accounts_provider_name_to_backend_name (GBinding *binding,
+                                                      const GValue *source_value,
+                                                      GValue *target_value,
+                                                      gpointer unused)
+{
+	const gchar *provider_name;
+	const gchar *backend_name;
+
+	provider_name = g_value_get_string (source_value);
+	backend_name = ubuntu_online_accounts_get_backend_name (provider_name);
+	g_return_val_if_fail (backend_name != NULL, FALSE);
+	g_value_set_string (target_value, backend_name);
+
+	return TRUE;
+}
+
+static AgAccountService *
+ubuntu_online_accounts_ref_account_service (EUbuntuOnlineAccounts *extension,
+                                            ESource *source)
+{
+	GHashTable *account_services;
+	ESourceRegistryServer *server;
+	AgAccountService *ag_account_service = NULL;
+	const gchar *extension_name;
+	const gchar *service_name = NULL;
+
+	/* Figure out which AgAccountService to use based
+	 * on which extensions are present in the ESource. */
+
+	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_CONTACTS;
+
+	extension_name = E_SOURCE_EXTENSION_CALENDAR;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_CALENDAR;
+
+	extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_CALENDAR;
+
+	extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_CALENDAR;
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_MAIL;
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
+	if (e_source_has_extension (source, extension_name))
+		service_name = SERVICE_TYPE_MAIL;
+
+	g_return_val_if_fail (service_name != NULL, NULL);
+
+	extension_name = E_SOURCE_EXTENSION_UOA;
+	server = ubuntu_online_accounts_get_server (extension);
+
+	source = e_source_registry_server_find_extension (
+		server, source, extension_name);
+
+	if (source != NULL) {
+		account_services = g_object_get_data (
+			G_OBJECT (source), "ag-account-services");
+		g_warn_if_fail (account_services != NULL);
+
+		if (account_services != NULL) {
+			ag_account_service = g_hash_table_lookup (
+				account_services, service_name);
+			if (ag_account_service != NULL)
+				g_object_ref (ag_account_service);
+		}
+
+		g_object_unref (source);
+	}
+
+	return ag_account_service;
+}
+
+static gboolean
+ubuntu_online_accounts_supports_oauth2 (AgAccountService *ag_account_service)
+{
+	AgAuthData *ag_auth_data;
+	gboolean supports_oauth2 = FALSE;
+	const gchar *method;
+
+	ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
+	method = ag_auth_data_get_method (ag_auth_data);
+	supports_oauth2 = (g_strcmp0 (method, "oauth2") == 0);
+	ag_auth_data_unref (ag_auth_data);
+
+	return supports_oauth2;
+}
+
+static ESource *
+ubuntu_online_accounts_new_source (EUbuntuOnlineAccounts *extension)
+{
+	ESourceRegistryServer *server;
+	ESource *source;
+	GFile *file;
+	GError *error = NULL;
+
+	/* This being a brand new data source, creating the instance
+	 * should never fail but we'll check for errors just the same. */
+	server = ubuntu_online_accounts_get_server (extension);
+	file = e_server_side_source_new_user_file (NULL);
+	source = e_server_side_source_new (server, file, &error);
+	g_object_unref (file);
+
+	if (error != NULL) {
+		g_warn_if_fail (source == NULL);
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	return source;
+}
+
+static GHashTable *
+ubuntu_online_accounts_new_account_services (EUbuntuOnlineAccounts *extension,
+                                             AgAccount *ag_account)
+{
+	GHashTable *account_services;
+	GList *list, *link;
+
+	account_services = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	/* Populate the hash table with AgAccountService instances by
+	 * service type.  There should only be one AgService per type.
+	 *
+	 * XXX We really should not have to create AgAccountService
+	 *     instances ourselves.  The AgAccount itself should own
+	 *     them and provide functions for listing them.  Instead
+	 *     it only provides functions for listing its AgServices,
+	 *     which is decidedly less useful. */
+	list = ag_account_list_services (ag_account);
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		AgService *ag_service = link->data;
+		const gchar *service_type;
+
+		service_type = ag_service_get_service_type (ag_service);
+
+		g_hash_table_insert (
+			account_services,
+			g_strdup (service_type),
+			ag_account_service_new (ag_account, ag_service));
+	}
+	ag_service_list_free (list);
+
+	return account_services;
+}
+
+static void
+ubuntu_online_accounts_config_oauth2 (EUbuntuOnlineAccounts *extension,
+                                      ESource *source,
+                                      GHashTable *account_services)
+{
+	AgAccountService *ag_account_service;
+	ESourceExtension *source_extension;
+	const gchar *extension_name;
+
+	ag_account_service = g_hash_table_lookup (
+		account_services, SERVICE_TYPE_MAIL);
+	if (ag_account_service == NULL)
+		return;
+
+	if (!ubuntu_online_accounts_supports_oauth2 (ag_account_service))
+		return;
+
+	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	e_source_authentication_set_method (
+		E_SOURCE_AUTHENTICATION (source_extension),
+		CAMEL_OAUTH2_MECHANISM_NAME);
+}
+
+static void
+ubuntu_online_accounts_config_collection (EUbuntuOnlineAccounts *extension,
+                                          ESource *source,
+                                          AgAccount *ag_account,
+                                          GHashTable *account_services,
+                                          const gchar *user_identity)
+{
+	AgAccountService *ag_account_service;
+	ESourceExtension *source_extension;
+	gboolean supports_oauth2 = FALSE;
+	const gchar *extension_name;
+
+	g_object_bind_property (
+		ag_account, "display-name",
+		source, "display-name",
+		G_BINDING_SYNC_CREATE);
+
+	g_object_bind_property (
+		ag_account, "enabled",
+		source, "enabled",
+		G_BINDING_SYNC_CREATE);
+
+	extension_name = E_SOURCE_EXTENSION_UOA;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	g_object_bind_property (
+		ag_account, "id",
+		source_extension, "account-id",
+		G_BINDING_SYNC_CREATE);
+
+	extension_name = E_SOURCE_EXTENSION_COLLECTION;
+	source_extension = e_source_get_extension (source, extension_name);
+
+	g_object_bind_property_full (
+		ag_account, "provider",
+		source_extension, "backend-name",
+		G_BINDING_SYNC_CREATE,
+		ubuntu_online_accounts_provider_name_to_backend_name,
+		NULL,
+		NULL, (GDestroyNotify) NULL);
+
+	if (user_identity != NULL)
+		e_source_collection_set_identity (
+			E_SOURCE_COLLECTION (source_extension),
+			user_identity);
+
+	ag_account_service = g_hash_table_lookup (
+		account_services, SERVICE_TYPE_MAIL);
+	if (ag_account_service != NULL) {
+		g_object_bind_property (
+			ag_account_service , "enabled",
+			source_extension, "mail-enabled",
+			G_BINDING_SYNC_CREATE);
+		supports_oauth2 |=
+			ubuntu_online_accounts_supports_oauth2 (
+			ag_account_service);
+	}
+
+	ag_account_service = g_hash_table_lookup (
+		account_services, SERVICE_TYPE_CALENDAR);
+	if (ag_account_service != NULL) {
+		g_object_bind_property (
+			ag_account_service, "enabled",
+			source_extension, "calendar-enabled",
+			G_BINDING_SYNC_CREATE);
+		supports_oauth2 |=
+			ubuntu_online_accounts_supports_oauth2 (
+			ag_account_service);
+	}
+
+	ag_account_service = g_hash_table_lookup (
+		account_services, SERVICE_TYPE_CONTACTS);
+	if (ag_account_service != NULL) {
+		g_object_bind_property (
+			ag_account_service, "enabled",
+			source_extension, "contacts-enabled",
+			G_BINDING_SYNC_CREATE);
+		supports_oauth2 |=
+			ubuntu_online_accounts_supports_oauth2 (
+			ag_account_service);
+	}
+
+	/* Stash the AgAccountService hash table in the ESource
+	 * to keep the property bindings alive.  The hash table
+	 * will be destroyed along with the ESource. */
+	g_object_set_data_full (
+		G_OBJECT (source),
+		"ag-account-services",
+		g_hash_table_ref (account_services),
+		(GDestroyNotify) g_hash_table_unref);
+
+	/* The data source should not be removable by clients. */
+	e_server_side_source_set_removable (
+		E_SERVER_SIDE_SOURCE (source), FALSE);
+
+	if (supports_oauth2) {
+		/* This module provides OAuth 2.0 support to the collection.
+		 * Note, children of the collection source will automatically
+		 * inherit our EOAuth2Support through the property binding in
+		 * collection_backend_child_added(). */
+		e_server_side_source_set_oauth2_support (
+			E_SERVER_SIDE_SOURCE (source),
+			E_OAUTH2_SUPPORT (extension));
+	}
+}
+
+static void
+ubuntu_online_accounts_config_mail_account (EUbuntuOnlineAccounts *extension,
+                                            ESource *source,
+                                            GHashTable *account_services)
+{
+	EServerSideSource *server_side_source;
+
+	ubuntu_online_accounts_config_oauth2 (
+		extension, source, account_services);
+
+	/* Clients may change the source but may not remove it. */
+	server_side_source = E_SERVER_SIDE_SOURCE (source);
+	e_server_side_source_set_writable (server_side_source, TRUE);
+	e_server_side_source_set_removable (server_side_source, FALSE);
+}
+
+static void
+ubuntu_online_accounts_config_mail_identity (EUbuntuOnlineAccounts *extension,
+                                             ESource *source,
+                                             GHashTable *account_services,
+                                             const gchar *email_address)
+{
+	EServerSideSource *server_side_source;
+
+	if (email_address != NULL) {
+		ESourceMailIdentity *source_extension;
+
+		source_extension = e_source_get_extension (
+			source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
+		e_source_mail_identity_set_address (
+			source_extension, email_address);
+	}
+
+	/* Clients may change the source but may not remove it. */
+	server_side_source = E_SERVER_SIDE_SOURCE (source);
+	e_server_side_source_set_writable (server_side_source, TRUE);
+	e_server_side_source_set_removable (server_side_source, FALSE);
+}
+
+static void
+ubuntu_online_accounts_config_mail_transport (EUbuntuOnlineAccounts *extension,
+                                              ESource *source,
+                                              GHashTable *account_services)
+{
+	EServerSideSource *server_side_source;
+
+	ubuntu_online_accounts_config_oauth2 (
+		extension, source, account_services);
+
+	/* Clients may change the source but may not remove it. */
+	server_side_source = E_SERVER_SIDE_SOURCE (source);
+	e_server_side_source_set_writable (server_side_source, TRUE);
+	e_server_side_source_set_removable (server_side_source, FALSE);
+}
+
+static void
+ubuntu_online_accounts_config_sources (EUbuntuOnlineAccounts *extension,
+                                       ESource *source,
+                                       AgAccount *ag_account)
+{
+	ESourceRegistryServer *server;
+	ECollectionBackend *backend;
+	GHashTable *account_services;
+	GList *list, *link;
+
+	account_services = ubuntu_online_accounts_new_account_services (
+		extension, ag_account);
+
+	ubuntu_online_accounts_config_collection (
+		extension, source, ag_account, account_services, NULL);
+
+	server = ubuntu_online_accounts_get_server (extension);
+	backend = e_source_registry_server_ref_backend (server, source);
+	g_return_if_fail (backend != NULL);
+
+	list = e_collection_backend_list_mail_sources (backend);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		const gchar *extension_name;
+
+		source = E_SOURCE (link->data);
+
+		extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
+		if (e_source_has_extension (source, extension_name))
+			ubuntu_online_accounts_config_mail_account (
+				extension, source, account_services);
+
+		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+		if (e_source_has_extension (source, extension_name))
+			ubuntu_online_accounts_config_mail_identity (
+				extension, source, account_services, NULL);
+
+		extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
+		if (e_source_has_extension (source, extension_name))
+			ubuntu_online_accounts_config_mail_transport (
+				extension, source, account_services);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	g_object_unref (backend);
+
+	g_hash_table_unref (account_services);
+}
+
+static void
+ubuntu_online_accounts_create_collection (EUbuntuOnlineAccounts *extension,
+                                          EBackendFactory *backend_factory,
+                                          AgAccount *ag_account,
+                                          const gchar *user_identity,
+                                          const gchar *email_address)
+{
+	ESourceRegistryServer *server;
+	ESource *collection_source;
+	ESource *mail_account_source;
+	ESource *mail_identity_source;
+	ESource *mail_transport_source;
+	GHashTable *account_services;
+	const gchar *parent_uid;
+
+	server = ubuntu_online_accounts_get_server (extension);
+
+	collection_source = ubuntu_online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (collection_source));
+
+	mail_account_source = ubuntu_online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_account_source));
+
+	mail_identity_source = ubuntu_online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_identity_source));
+
+	mail_transport_source = ubuntu_online_accounts_new_source (extension);
+	g_return_if_fail (E_IS_SOURCE (mail_transport_source));
+
+	/* Configure parent/child relationships. */
+	parent_uid = e_source_get_uid (collection_source);
+	e_source_set_parent (mail_account_source, parent_uid);
+	e_source_set_parent (mail_identity_source, parent_uid);
+	e_source_set_parent (mail_transport_source, parent_uid);
+
+	/* Give the factory first crack at mail configuration. */
+	e_collection_backend_factory_prepare_mail (
+		E_COLLECTION_BACKEND_FACTORY (backend_factory),
+		mail_account_source,
+		mail_identity_source,
+		mail_transport_source);
+
+	/* Now it's our turn. */
+	account_services = ubuntu_online_accounts_new_account_services (
+		extension, ag_account);
+	ubuntu_online_accounts_config_collection (
+		extension, collection_source, ag_account,
+		account_services, user_identity);
+	ubuntu_online_accounts_config_mail_account (
+		extension, mail_account_source, account_services);
+	ubuntu_online_accounts_config_mail_identity (
+		extension, mail_identity_source,
+		account_services, email_address);
+	ubuntu_online_accounts_config_mail_transport (
+		extension, mail_transport_source, account_services);
+	g_hash_table_unref (account_services);
+
+	/* Export the new source collection. */
+	e_source_registry_server_add_source (server, collection_source);
+	e_source_registry_server_add_source (server, mail_account_source);
+	e_source_registry_server_add_source (server, mail_identity_source);
+	e_source_registry_server_add_source (server, mail_transport_source);
+
+	g_hash_table_insert (
+		extension->uoa_to_eds,
+		GUINT_TO_POINTER (ag_account->id),
+		g_strdup (parent_uid));
+
+	g_object_unref (collection_source);
+	g_object_unref (mail_account_source);
+	g_object_unref (mail_identity_source);
+	g_object_unref (mail_transport_source);
+}
+
+static void
+ubuntu_online_accounts_got_userinfo_cb (GObject *source_object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+	AgAccount *ag_account;
+	AsyncContext *async_context = user_data;
+	gchar *user_identity = NULL;
+	gchar *email_address = NULL;
+	GError *error = NULL;
+
+	ag_account = AG_ACCOUNT (source_object);
+
+	e_ag_account_collect_userinfo_finish (
+		ag_account, result, &user_identity, &email_address, &error);
+
+	if (error == NULL) {
+		ubuntu_online_accounts_create_collection (
+			async_context->extension,
+			async_context->backend_factory,
+			ag_account,
+			user_identity,
+			email_address);
+	} else {
+		g_warning (
+			"%s: Failed to create ESource "
+			"collection for AgAccount '%s': %s",
+			G_STRFUNC,
+			ag_account_get_display_name (ag_account),
+			error->message);
+		g_error_free (error);
+	}
+
+	g_free (user_identity);
+	g_free (email_address);
+
+	async_context_free (async_context);
+}
+
+static void
+ubuntu_online_accounts_collect_userinfo (EUbuntuOnlineAccounts *extension,
+                                         EBackendFactory *backend_factory,
+                                         AgAccount *ag_account)
+{
+	AsyncContext *async_context;
+
+	/* Before we create a collection we need to collect user info from
+	 * the online service.  GNOME Online Accounts does this for us, but
+	 * no such luck with libaccounts-glib or libsignon-glib. */
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->extension = g_object_ref (extension);
+	async_context->backend_factory = g_object_ref (backend_factory);
+
+	e_ag_account_collect_userinfo (
+		ag_account, NULL,
+		ubuntu_online_accounts_got_userinfo_cb,
+		async_context);
+}
+
+static void
+ubuntu_online_accounts_remove_collection (EUbuntuOnlineAccounts *extension,
+                                          ESource *source)
+{
+	GError *error = NULL;
+
+	/* This removes the entire subtree rooted at source.
+	 * Deletes the corresponding on-disk key files too. */
+	e_source_remove_sync (source, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+ubuntu_online_accounts_account_created_cb (AgManager *ag_manager,
+                                           AgAccountId ag_account_id,
+                                           EUbuntuOnlineAccounts *extension)
+{
+	AgAccount *ag_account;
+	ESourceRegistryServer *server;
+	EBackendFactory *backend_factory = NULL;
+	const gchar *provider_name;
+	const gchar *backend_name;
+	const gchar *source_uid;
+
+	server = ubuntu_online_accounts_get_server (extension);
+
+	ag_account = ag_manager_get_account (ag_manager, ag_account_id);
+	g_return_if_fail (ag_account != NULL);
+
+	provider_name = ag_account_get_provider_name (ag_account);
+	backend_name = ubuntu_online_accounts_get_backend_name (provider_name);
+
+	source_uid = g_hash_table_lookup (
+		extension->uoa_to_eds,
+		GUINT_TO_POINTER (ag_account_id));
+
+	if (source_uid == NULL && backend_name != NULL)
+		backend_factory = e_data_factory_ref_backend_factory (
+			E_DATA_FACTORY (server), backend_name);
+
+	if (backend_factory != NULL) {
+		ubuntu_online_accounts_collect_userinfo (
+			extension, backend_factory, ag_account);
+		g_object_unref (backend_factory);
+	}
+
+	g_object_unref (ag_account);
+}
+
+static void
+ubuntu_online_accounts_account_deleted_cb (AgManager *ag_manager,
+                                           AgAccountId ag_account_id,
+                                           EUbuntuOnlineAccounts *extension)
+{
+	ESource *source = NULL;
+	ESourceRegistryServer *server;
+	const gchar *source_uid;
+
+	server = ubuntu_online_accounts_get_server (extension);
+
+	source_uid = g_hash_table_lookup (
+		extension->uoa_to_eds,
+		GUINT_TO_POINTER (ag_account_id));
+
+	if (source_uid != NULL)
+		source = e_source_registry_server_ref_source (
+			server, source_uid);
+
+	if (source != NULL) {
+		ubuntu_online_accounts_remove_collection (extension, source);
+		g_object_unref (source);
+	}
+}
+
+static void
+ubuntu_online_accounts_populate_accounts_table (EUbuntuOnlineAccounts *extension,
+                                                GList *ag_account_ids)
+{
+	ESourceRegistryServer *server;
+	GQueue trash = G_QUEUE_INIT;
+	GList *list, *link;
+	const gchar *extension_name;
+
+	server = ubuntu_online_accounts_get_server (extension);
+
+	extension_name = E_SOURCE_EXTENSION_UOA;
+	list = e_source_registry_server_list_sources (server, extension_name);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source;
+		ESourceUoa *uoa_ext;
+		AgAccount *ag_account = NULL;
+		AgAccountId ag_account_id;
+		const gchar *source_uid;
+		GList *match;
+
+		source = E_SOURCE (link->data);
+		source_uid = e_source_get_uid (source);
+
+		extension_name = E_SOURCE_EXTENSION_UOA;
+		uoa_ext = e_source_get_extension (source, extension_name);
+		ag_account_id = e_source_uoa_get_account_id (uoa_ext);
+
+		if (ag_account_id == 0)
+			continue;
+
+		/* Verify the UOA account still exists. */
+		match = g_list_find (
+			ag_account_ids,
+			GUINT_TO_POINTER (ag_account_id));
+		if (match != NULL)
+			ag_account = ag_manager_get_account (
+				extension->ag_manager, ag_account_id);
+
+		/* If a matching AgAccountId was found, add it
+		 * to our accounts hash table.  Otherwise remove
+		 * the ESource after we finish looping. */
+		if (ag_account != NULL) {
+			g_hash_table_insert (
+				extension->uoa_to_eds,
+				GUINT_TO_POINTER (ag_account_id),
+				g_strdup (source_uid));
+
+			ubuntu_online_accounts_config_sources (
+				extension, source, ag_account);
+		} else {
+			g_queue_push_tail (&trash, source);
+		}
+	}
+
+	/* Empty the trash. */
+	while (!g_queue_is_empty (&trash)) {
+		ESource *source = g_queue_pop_head (&trash);
+		ubuntu_online_accounts_remove_collection (extension, source);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+ubuntu_online_accounts_bus_acquired_cb (EDBusServer *server,
+                                        GDBusConnection *connection,
+                                        EUbuntuOnlineAccounts *extension)
+{
+	GList *list, *link;
+
+	extension->ag_manager = ag_manager_new ();
+
+	list = ag_manager_list (extension->ag_manager);
+
+	/* This populates a hash table of UOA ID -> ESource UID strings by
+	 * searching through available data sources for ones with a "Ubuntu
+	 * Online Accounts" extension.  If such an extension is found, but
+	 * no corresponding AgAccount (presumably meaning the UOA account
+	 * was somehow deleted between E-D-S sessions) then the ESource in
+	 * which the extension was found gets deleted. */
+	ubuntu_online_accounts_populate_accounts_table (extension, list);
+
+	for (link = list; link != NULL; link = g_list_next (link))
+		ubuntu_online_accounts_account_created_cb (
+			extension->ag_manager,
+			GPOINTER_TO_UINT (link->data),
+			extension);
+
+	ag_manager_list_free (list);
+
+	/* Listen for Online Account changes. */
+	g_signal_connect (
+		extension->ag_manager, "account-created",
+		G_CALLBACK (ubuntu_online_accounts_account_created_cb),
+		extension);
+	g_signal_connect (
+		extension->ag_manager, "account-deleted",
+		G_CALLBACK (ubuntu_online_accounts_account_deleted_cb),
+		extension);
+}
+
+static void
+ubuntu_online_accounts_dispose (GObject *object)
+{
+	EUbuntuOnlineAccounts *extension;
+
+	extension = E_UBUNTU_ONLINE_ACCOUNTS (object);
+
+	if (extension->ag_manager != NULL) {
+		g_signal_handlers_disconnect_matched (
+			extension->ag_manager,
+			G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (extension->ag_manager);
+		extension->ag_manager = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
+		dispose (object);
+}
+
+static void
+ubuntu_online_accounts_finalize (GObject *object)
+{
+	EUbuntuOnlineAccounts *extension;
+
+	extension = E_UBUNTU_ONLINE_ACCOUNTS (object);
+
+	g_hash_table_destroy (extension->uoa_to_eds);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
+		finalize (object);
+}
+
+static void
+ubuntu_online_accounts_constructed (GObject *object)
+{
+	EExtension *extension;
+	EExtensible *extensible;
+
+	extension = E_EXTENSION (object);
+	extensible = e_extension_get_extensible (extension);
+
+	/* Wait for the registry service to acquire its well-known
+	 * bus name so we don't do anything destructive beforehand. */
+
+	g_signal_connect (
+		extensible, "bus-acquired",
+		G_CALLBACK (ubuntu_online_accounts_bus_acquired_cb),
+		extension);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_ubuntu_online_accounts_parent_class)->
+		constructed (object);
+}
+
+static gboolean
+ubuntu_online_accounts_get_access_token_sync (EOAuth2Support *support,
+                                              ESource *source,
+                                              GCancellable *cancellable,
+                                              gchar **out_access_token,
+                                              gint *out_expires_in,
+                                              GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	closure = e_async_closure_new ();
+
+	e_oauth2_support_get_access_token (
+		support, source, cancellable,
+		e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	success = e_oauth2_support_get_access_token_finish (
+		support, result, out_access_token, out_expires_in, error);
+
+	e_async_closure_free (closure);
+
+	return success;
+}
+
+/* Helper for ubuntu_online_accounts_get_access_token() */
+static void
+ubuntu_online_accounts_session_process_cb (GObject *source_object,
+                                           GAsyncResult *result,
+                                           gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	GVariant *session_data;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	session_data = signon_auth_session_process_finish (
+		SIGNON_AUTH_SESSION (source_object), result, &error);
+
+	/* Sanity check. */
+	g_return_if_fail (
+		((session_data != NULL) && (error == NULL)) ||
+		((session_data == NULL) && (error != NULL)));
+
+	if (session_data != NULL) {
+		g_variant_lookup (
+			session_data, "AccessToken", "s",
+			&async_context->access_token);
+
+		g_variant_lookup (
+			session_data, "ExpiresIn", "i",
+			&async_context->expires_in);
+
+		g_warn_if_fail (async_context->access_token != NULL);
+		g_variant_unref (session_data);
+	}
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+static void
+ubuntu_online_accounts_get_access_token (EOAuth2Support *support,
+                                         ESource *source,
+                                         GCancellable *cancellable,
+                                         GAsyncReadyCallback callback,
+                                         gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	SignonAuthSession *session;
+	AgAccountService *ag_account_service;
+	AgAuthData *ag_auth_data;
+	GError *error = NULL;
+
+	async_context = g_slice_new0 (AsyncContext);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (support), callback, user_data,
+		ubuntu_online_accounts_get_access_token);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	ag_account_service = ubuntu_online_accounts_ref_account_service (
+		E_UBUNTU_ONLINE_ACCOUNTS (support), source);
+
+	if (ag_account_service == NULL) {
+		g_simple_async_result_set_error (
+			simple, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+			_("Cannot find a corresponding account "
+			"service in the accounts database from "
+			"which to obtain an access token for '%s'"),
+			e_source_get_display_name (source));
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	/* XXX This should never happen.  But because libaccounts-glib
+	 *     splits authentication method by service-type instead of
+	 *     by provider, and because we broadcast OAuth 2.0 support
+	 *     across the entire collection (spanning multiple service
+	 *     types), it's conceivable that not all service-types for
+	 *     a provider use OAuth 2.0, and an ESource for one of the
+	 *     ones that DOESN'T could mistakenly request the token. */
+	if (!ubuntu_online_accounts_supports_oauth2 (ag_account_service)) {
+		g_simple_async_result_set_error (
+			simple, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+			_("Data source '%s' does not "
+			"support OAuth 2.0 authentication"),
+			e_source_get_display_name (source));
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
+
+	session = signon_auth_session_new (
+		ag_auth_data_get_credentials_id (ag_auth_data),
+		ag_auth_data_get_method (ag_auth_data), &error);
+
+	/* Sanity check. */
+	g_return_if_fail (
+		((session != NULL) && (error == NULL)) ||
+		((session == NULL) && (error != NULL)));
+
+	if (session != NULL) {
+		signon_auth_session_process_async (
+			session,
+			ag_auth_data_get_login_parameters (ag_auth_data, NULL),
+			ag_auth_data_get_mechanism (ag_auth_data),
+			cancellable,
+			ubuntu_online_accounts_session_process_cb,
+			g_object_ref (simple));
+		g_object_unref (session);
+	} else {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete_in_idle (simple);
+	}
+
+	ag_auth_data_unref (ag_auth_data);
+
+	g_object_unref (ag_account_service);
+	g_object_unref (simple);
+}
+
+static gboolean
+ubuntu_online_accounts_get_access_token_finish (EOAuth2Support *support,
+                                                GAsyncResult *result,
+                                                gchar **out_access_token,
+                                                gint *out_expires_in,
+                                                GError **error)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (support),
+		ubuntu_online_accounts_get_access_token), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	g_return_val_if_fail (async_context->access_token != NULL, FALSE);
+
+	if (out_access_token != NULL) {
+		*out_access_token = async_context->access_token;
+		async_context->access_token = NULL;
+	}
+
+	if (out_expires_in != NULL)
+		*out_expires_in = async_context->expires_in;
+
+	return TRUE;
+}
+
+static void
+e_ubuntu_online_accounts_class_init (EUbuntuOnlineAccountsClass *class)
+{
+	GObjectClass *object_class;
+	EExtensionClass *extension_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = ubuntu_online_accounts_dispose;
+	object_class->finalize = ubuntu_online_accounts_finalize;
+	object_class->constructed = ubuntu_online_accounts_constructed;
+
+	extension_class = E_EXTENSION_CLASS (class);
+	extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER;
+}
+
+static void
+e_ubuntu_online_accounts_class_finalize (EUbuntuOnlineAccountsClass *class)
+{
+}
+
+static void
+e_ubuntu_online_accounts_oauth2_support_init (EOAuth2SupportInterface *interface)
+{
+	interface->get_access_token_sync = ubuntu_online_accounts_get_access_token_sync;
+	interface->get_access_token = ubuntu_online_accounts_get_access_token;
+	interface->get_access_token_finish = ubuntu_online_accounts_get_access_token_finish;
+}
+
+static void
+e_ubuntu_online_accounts_init (EUbuntuOnlineAccounts *extension)
+{
+	extension->uoa_to_eds = g_hash_table_new_full (
+		(GHashFunc) g_direct_hash,
+		(GEqualFunc) g_direct_equal,
+		(GDestroyNotify) NULL,
+		(GDestroyNotify) g_free);
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+	e_ubuntu_online_accounts_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+
diff --git a/modules/ubuntu-online-accounts/uoa-utils.c b/modules/ubuntu-online-accounts/uoa-utils.c
new file mode 100644
index 0000000..dc65f98
--- /dev/null
+++ b/modules/ubuntu-online-accounts/uoa-utils.c
@@ -0,0 +1,324 @@
+/*
+ * uoa-utils.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "uoa-utils.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <rest/rest-proxy.h>
+#include <json-glib/json-glib.h>
+#include <libsignon-glib/signon-glib.h>
+
+/* XXX accounts-glib.h should include this */
+#include <libaccounts-glib/ag-auth-data.h>
+
+#define GOOGLE_USERINFO_URI \
+	"https://www.googleapis.com/oauth2/v2/userinfo";
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _AsyncContext {
+	GCancellable *cancellable;
+	gchar *user_identity;
+	gchar *email_address;
+};
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->cancellable != NULL)
+		g_object_unref (async_context->cancellable);
+
+	g_free (async_context->user_identity);
+	g_free (async_context->email_address);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+/****************************** Google Provider ******************************/
+
+static void
+e_ag_account_google_got_userinfo_cb (RestProxyCall *call,
+                                     const GError *error,
+                                     GObject *weak_object,
+                                     gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	JsonParser *json_parser;
+	JsonObject *json_object;
+	JsonNode *json_node;
+	const gchar *email;
+	GError *local_error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (error != NULL) {
+		g_simple_async_result_set_from_error (simple, error);
+		goto exit;
+	}
+
+	/* This is shamelessly stolen from goagoogleprovider.c */
+
+	if (rest_proxy_call_get_status_code (call) != 200) {
+		g_simple_async_result_set_error (
+			simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Expected status 200 when requesting guid, "
+			"instead got status %d (%s)"),
+			rest_proxy_call_get_status_code (call),
+			rest_proxy_call_get_status_message (call));
+		goto exit;
+	}
+
+	json_parser = json_parser_new ();
+	json_parser_load_from_data (
+		json_parser,
+		rest_proxy_call_get_payload (call),
+		rest_proxy_call_get_payload_length (call),
+		&local_error);
+
+	if (local_error != NULL) {
+		g_prefix_error (
+			&local_error,
+			_("Error parsing response as JSON: "));
+		g_simple_async_result_take_error (simple, local_error);
+		g_object_unref (json_parser);
+		goto exit;
+	}
+
+	json_node = json_parser_get_root (json_parser);
+	json_object = json_node_get_object (json_node);
+	email = json_object_get_string_member (json_object, "email");
+
+	if (email != NULL) {
+		async_context->user_identity = g_strdup (email);
+		async_context->email_address = g_strdup (email);
+	} else {
+		g_simple_async_result_set_error (
+			simple, G_IO_ERROR, G_IO_ERROR_FAILED,
+			_("Didn't find email member in JSON data"));
+	}
+
+	g_object_unref (json_parser);
+
+exit:
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+static void
+e_ag_account_google_session_process_cb (GObject *source_object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	GVariant *session_data;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+	session_data = signon_auth_session_process_finish (
+		SIGNON_AUTH_SESSION (source_object), result, &error);
+
+	/* Sanity check. */
+	g_return_if_fail (
+		((session_data != NULL) && (error == NULL)) ||
+		((session_data == NULL) && (error != NULL)));
+
+	/* Use the access token to obtain the user's email address. */
+
+	if (session_data != NULL) {
+		RestProxy *proxy;
+		RestProxyCall *call;
+		gchar *access_token = NULL;
+
+		g_variant_lookup (
+			session_data, "AccessToken", "s", &access_token);
+
+		g_variant_unref (session_data);
+
+		proxy = rest_proxy_new (GOOGLE_USERINFO_URI, FALSE);
+		call = rest_proxy_new_call (proxy);
+		rest_proxy_call_set_method (call, "GET");
+
+		/* XXX This should never be NULL, but if it is just let
+		 *     the call fail and pick up the resulting GError. */
+		if (access_token != NULL) {
+			rest_proxy_call_add_param (
+				call, "access_token", access_token);
+			g_free (access_token);
+		}
+
+		/* XXX The 3rd argument is supposed to be a GObject
+		 *     that RestProxyCall weakly references such that
+		 *     its disposal cancels the call.  This obviously
+		 *     predates GCancellable.  Too bizarre to bother. */
+		rest_proxy_call_async (
+			call, e_ag_account_google_got_userinfo_cb,
+			NULL, g_object_ref (simple), &error);
+
+		if (error != NULL) {
+			/* Undo the reference added to the async call. */
+			g_object_unref (simple);
+		}
+
+		g_object_unref (proxy);
+		g_object_unref (call);
+	}
+
+	if (error != NULL) {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+	}
+
+	g_object_unref (simple);
+}
+
+static void
+e_ag_account_collect_google_userinfo (GSimpleAsyncResult *simple,
+                                      AgAccount *ag_account,
+                                      GCancellable *cancellable)
+{
+	AgAccountService *ag_account_service = NULL;
+	SignonAuthSession *session;
+	AgAuthData *ag_auth_data;
+	GList *list;
+	GError *error = NULL;
+
+	/* First obtain an OAuth 2.0 access token. */
+
+	list = ag_account_list_services_by_type (ag_account, "mail");
+	if (list != NULL) {
+		ag_account_service = ag_account_service_new (
+			ag_account, (AgService *) list->data);
+		ag_service_list_free (list);
+	}
+
+	g_return_if_fail (ag_account_service != NULL);
+
+	ag_auth_data = ag_account_service_get_auth_data (ag_account_service);
+
+	session = signon_auth_session_new (
+		ag_auth_data_get_credentials_id (ag_auth_data),
+		ag_auth_data_get_method (ag_auth_data), &error);
+
+	/* Sanity check. */
+	g_return_if_fail (
+		((session != NULL) && (error == NULL)) ||
+		((session == NULL) && (error != NULL)));
+
+	if (session != NULL) {
+		signon_auth_session_process_async (
+			session,
+			ag_auth_data_get_login_parameters (ag_auth_data, NULL),
+			ag_auth_data_get_mechanism (ag_auth_data),
+			cancellable,
+			e_ag_account_google_session_process_cb,
+			g_object_ref (simple));
+	} else {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete_in_idle (simple);
+	}
+
+	ag_auth_data_unref (ag_auth_data);
+
+	g_object_unref (ag_account_service);
+	g_object_unref (simple);
+}
+
+/************************ End Provider-Specific Code *************************/
+
+void
+e_ag_account_collect_userinfo (AgAccount *ag_account,
+                               GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	const gchar *provider_name;
+
+	g_return_if_fail (AG_IS_ACCOUNT (ag_account));
+
+	async_context = g_slice_new0 (AsyncContext);
+
+	if (G_IS_CANCELLABLE (cancellable))
+		async_context->cancellable = g_object_ref (cancellable);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (ag_account), callback,
+		user_data, e_ag_account_collect_userinfo);
+
+	g_simple_async_result_set_check_cancellable (simple, cancellable);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	/* XXX This has to be done differently for each provider. */
+
+	provider_name = ag_account_get_provider_name (ag_account);
+
+	if (g_strcmp0 (provider_name, "google") == 0) {
+		e_ag_account_collect_google_userinfo (
+			g_object_ref (simple), ag_account, cancellable);
+	} else {
+		g_warn_if_reached ();
+		g_simple_async_result_complete_in_idle (simple);
+	}
+
+	g_object_unref (simple);
+}
+
+gboolean
+e_ag_account_collect_userinfo_finish (AgAccount *ag_account,
+                                      GAsyncResult *result,
+                                      gchar **out_user_identity,
+                                      gchar **out_email_address,
+                                      GError **error)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (ag_account),
+		e_ag_account_collect_userinfo), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	/* The result strings may be NULL without an error. */
+
+	if (out_user_identity != NULL) {
+		*out_user_identity = async_context->user_identity;
+		async_context->user_identity = NULL;
+	}
+
+	if (out_email_address != NULL) {
+		*out_email_address = async_context->email_address;
+		async_context->email_address = NULL;
+	}
+
+	return TRUE;
+}
+
diff --git a/modules/ubuntu-online-accounts/uoa-utils.h b/modules/ubuntu-online-accounts/uoa-utils.h
new file mode 100644
index 0000000..b4582c0
--- /dev/null
+++ b/modules/ubuntu-online-accounts/uoa-utils.h
@@ -0,0 +1,39 @@
+/*
+ * uoa-utils.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef UOA_UTILS_H
+#define UOA_UTILS_H
+
+#include <libaccounts-glib/accounts-glib.h>
+
+G_BEGIN_DECLS
+
+void		e_ag_account_collect_userinfo	(AgAccount *ag_account,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_ag_account_collect_userinfo_finish
+						(AgAccount *ag_account,
+						 GAsyncResult *result,
+						 gchar **out_user_identity,
+						 gchar **out_email_address,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* UOA_UTILS_H */
diff --git a/modules/ubuntu-online-accounts/yahoo-calendar.service.in.in b/modules/ubuntu-online-accounts/yahoo-calendar.service.in.in
new file mode 100644
index 0000000..b91f1c0
--- /dev/null
+++ b/modules/ubuntu-online-accounts/yahoo-calendar.service.in.in
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service id="yahoo-calendar">
+  <type>calendar</type>
+  <_name>Yahoo! Calendar</_name>
+  <provider>yahoo</provider>
+  <translations>@GETTEXT_PACKAGE@</translations>
+
+  <!-- default settings (account settings have precedence over these) -->
+  <template>
+    <group name="auth">
+      <setting name="method">password</setting>
+      <setting name="mechanism">password</setting>
+    </group>
+  </template>
+</service>
diff --git a/modules/ubuntu-online-accounts/yahoo-mail.service.in.in b/modules/ubuntu-online-accounts/yahoo-mail.service.in.in
new file mode 100644
index 0000000..e75556f
--- /dev/null
+++ b/modules/ubuntu-online-accounts/yahoo-mail.service.in.in
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<service id="yahoo-mail">
+  <type>mail</type>
+  <_name>Yahoo! Mail</_name>
+  <provider>yahoo</provider>
+  <translations>@GETTEXT_PACKAGE@</translations>
+
+  <!-- default settings (account settings have precedence over these) -->
+  <template>
+    <group name="auth">
+      <setting name="method">password</setting>
+      <setting name="mechanism">password</setting>
+    </group>
+  </template>
+</service>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]