[gnome-online-accounts/exchange: 2/2] Add Micrsoft Exchange using EWS



commit 653343cda4cb0dd6b4c5469cdfa955a69f3e5719
Author: Debarshi Ray <debarshir gnome org>
Date:   Fri Mar 30 18:06:00 2012 +0200

    Add Micrsoft Exchange using EWS
    
    Known deficiencies:
      * goa_provider_ensure_credentials is not implemented
      * goa_provider_refresh_account is not implemented
      * AsUrl and OabUrl values are not exposed over DBus
    
    New dependency:
      * libxml-2.0
    
    Fixes: https://bugzilla.gnome.org/667889

 configure.ac                         |   15 +
 po/POTFILES.in                       |    2 +
 src/goabackend/Makefile.am           |    5 +
 src/goabackend/goabackendtypes.h     |    8 +-
 src/goabackend/goaewsclient.c        |  546 +++++++++++++++++++++++++++++
 src/goabackend/goaewsclient.h        |   71 ++++
 src/goabackend/goaexchangeprovider.c |  625 ++++++++++++++++++++++++++++++++++
 src/goabackend/goaexchangeprovider.h |   42 +++
 src/goabackend/goaprovider.c         |    4 +
 9 files changed, 1317 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6d8756a..3783d97 100644
--- a/configure.ac
+++ b/configure.ac
@@ -69,11 +69,25 @@ PKG_CHECK_MODULES(REST, [rest-0.7])
 AC_SUBST(REST_CFLAGS)
 AC_SUBST(REST_LIBS)
 
+PKG_CHECK_MODULES(LIBXML, [libxml-2.0])
+AC_SUBST(LIBXML_CFLAGS)
+AC_SUBST(LIBXML_LIBS)
+
 GOBJECT_INTROSPECTION_CHECK([0.6.2])
 
 # service providers
 #
 
+# Microsoft Exchange
+AC_ARG_ENABLE([exchange],
+              [AS_HELP_STRING([--enable-exchange],
+              [Enable Microsoft Exchange provider])],
+              [],
+              [enable_exchange=no])
+if test "$enable_exchange" != "no"; then
+  AC_DEFINE(GOA_EXCHANGE_ENABLED, 1, [Enable Microsoft Exchange data provider])
+fi
+
 # Google
 AC_ARG_ENABLE([google], [AS_HELP_STRING([--enable-google], [Enable Google provider])], [], [enable_google=yes])
 AC_ARG_WITH(google-consumer-key,
@@ -240,6 +254,7 @@ echo "
 	introspection:		    ${found_introspection}
 
 	Google provider:            ${enable_google} (OAuth 1.0, key:${with_google_consumer_key} secret:${with_google_consumer_secret})
+	Microsoft Exchange provider:${enable_exchange}
 	Twitter provider:           ${enable_twitter} (OAuth 1.0, key:${with_twitter_consumer_key} secret:${with_twitter_consumer_secret})
 	Yahoo provider:             ${enable_yahoo} (OAuth 1.0, key:${with_yahoo_consumer_key} secret:${with_yahoo_consumer_secret})
 	Facebook provider:          ${enable_facebook} (OAuth 2.0, id:${with_facebook_client_id})
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c25275d..9a54202 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,8 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 src/daemon/goadaemon.c
+src/goabackend/goaewsclient.c
+src/goabackend/goaexchangeprovider.c
 src/goabackend/goafacebookprovider.c
 src/goabackend/goagoogleprovider.c
 src/goabackend/goawindowsliveprovider.c
diff --git a/src/goabackend/Makefile.am b/src/goabackend/Makefile.am
index f175d38..2a5fc57 100644
--- a/src/goabackend/Makefile.am
+++ b/src/goabackend/Makefile.am
@@ -49,6 +49,7 @@ libgoa_backend_1_0_la_HEADERS =						\
 	goalogging.h							\
 	goaeditablelabel.h						\
 	goaprovider.h							\
+	goaexchangeprovider.h						\
 	goaoauthprovider.h						\
 	goaoauth2provider.h						\
 	goagoogleprovider.h						\
@@ -64,7 +65,9 @@ libgoa_backend_1_0_la_SOURCES =						\
 	goabackendenums.h						\
 	goabackendenumtypes.h		goabackendenumtypes.c		\
 	goaeditablelabel.h		goaeditablelabel.c		\
+	goaewsclient.h			goaewsclient.c			\
 	goaprovider.h			goaprovider.c			\
+	goaexchangeprovider.h		goaexchangeprovider.c		\
 	goalogging.h			goalogging.c			\
 	goaoauthprovider.h		goaoauthprovider.c		\
 	goaoauth2provider.h		goaoauth2provider.c		\
@@ -89,6 +92,7 @@ libgoa_backend_1_0_la_CFLAGS =					\
 	$(GNOME_KEYRING_CFLAGS)					\
 	$(REST_CFLAGS)						\
 	$(LIBSOUP_CFLAGS)					\
+	$(LIBXML_CFLAGS)					\
 	$(NULL)
 
 libgoa_backend_1_0_la_LIBADD = 					\
@@ -99,6 +103,7 @@ libgoa_backend_1_0_la_LIBADD = 					\
 	$(GNOME_KEYRING_LIBS)					\
 	$(REST_LIBS)						\
 	$(LIBSOUP_LIBS)						\
+	$(LIBXML_LIBS)						\
 	$(NULL)
 
 # ----------------------------------------------------------------------------------------------------
diff --git a/src/goabackend/goabackendtypes.h b/src/goabackend/goabackendtypes.h
index b69c7c8..db340c9 100644
--- a/src/goabackend/goabackendtypes.h
+++ b/src/goabackend/goabackendtypes.h
@@ -1,6 +1,6 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /*
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011, 2012 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -33,9 +33,15 @@
 
 G_BEGIN_DECLS
 
+struct _GoaEwsClient;
+typedef struct _GoaEwsClient GoaEwsClient;
+
 struct _GoaProvider;
 typedef struct _GoaProvider GoaProvider;
 
+struct _GoaExchangeProvider;
+typedef struct _GoaExchangeProvider GoaExchangeProvider;
+
 struct _GoaOAuthProvider;
 typedef struct _GoaOAuthProvider GoaOAuthProvider;
 
diff --git a/src/goabackend/goaewsclient.c b/src/goabackend/goaewsclient.c
new file mode 100644
index 0000000..34a9944
--- /dev/null
+++ b/src/goabackend/goaewsclient.c
@@ -0,0 +1,546 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+/* Based on code by the Evolution team.
+ *
+ * This was originally written as a part of evolution-ews:
+ * evolution-ews/src/server/e-ews-connection.c
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libsoup/soup.h>
+#include <libxml/xmlIO.h>
+
+#include "goalogging.h"
+#include "goaewsclient.h"
+
+struct _GoaEwsClient
+{
+  GObject parent_instance;
+};
+
+typedef struct _GoaEwsClientClass GoaEwsClientClass;
+
+struct _GoaEwsClientClass
+{
+  GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (GoaEwsClient, goa_ews_client, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_ews_client_init (GoaEwsClient *client)
+{
+}
+
+static void
+goa_ews_client_class_init (GoaEwsClientClass *klass)
+{
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GoaEwsClient *
+goa_ews_client_new (void)
+{
+  return GOA_EWS_CLIENT (g_object_new (GOA_TYPE_EWS_CLIENT, NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GCancellable *cancellable;
+  GSimpleAsyncResult *res;
+  SoupMessage *msgs[2];
+  SoupSession *session;
+  gulong cancellable_id;
+  xmlOutputBuffer *buf;
+} AutodiscoverData;
+
+typedef struct
+{
+  gchar *password;
+  gchar *username;
+} AutodiscoverAuthData;
+
+static void
+ews_client_autodiscover_data_free (AutodiscoverData *data)
+{
+  if (data->cancellable_id > 0)
+    {
+      g_cancellable_disconnect (data->cancellable, data->cancellable_id);
+      g_object_unref (data->cancellable);
+    }
+
+  /* soup_session_queue_message stole the references to data->msgs */
+  xmlOutputBufferClose (data->buf);
+  g_object_unref (data->res);
+  g_object_unref (data->session);
+  g_slice_free (AutodiscoverData, data);
+}
+
+static void
+ews_client_autodiscover_auth_data_free (gpointer data, GClosure *closure)
+{
+  AutodiscoverAuthData *auth = data;
+
+  g_free (auth->password);
+  g_free (auth->username);
+  g_slice_free (AutodiscoverAuthData, auth);
+}
+
+static gboolean
+ews_client_check_node (const xmlNode *node, const gchar *name)
+{
+  g_return_val_if_fail (node != NULL, FALSE);
+  return node->type == XML_ELEMENT_NODE && !g_strcmp0 ((gchar *) node->name, name);
+}
+
+static void
+ews_client_authenticate (SoupSession *session,
+                         SoupMessage *msg,
+                         SoupAuth *auth,
+                         gboolean retrying,
+                         gpointer user_data)
+{
+  AutodiscoverAuthData *data = user_data;
+
+  if (retrying)
+    return;
+
+  soup_auth_authenticate (auth, data->username, data->password);
+}
+
+static void
+ews_client_autodiscover_cancelled_cb (GCancellable *cancellable, gpointer user_data)
+{
+  AutodiscoverData *data = user_data;
+  soup_session_abort (data->session);
+}
+
+static GoaEwsUrls *
+ews_client_autodiscover_parse_protocol (xmlNode *node)
+{
+  GoaEwsUrls *urls;
+  gchar *as_url;
+  gchar *oab_url;
+
+  urls = NULL;
+  as_url = NULL;
+  oab_url = NULL;
+
+  for (node = node->children; node; node = node->next)
+    {
+      if (ews_client_check_node (node, "ASUrl"))
+        as_url = (gchar *) xmlNodeGetContent (node);
+      else if (ews_client_check_node (node, "OABUrl"))
+        oab_url = (gchar *) xmlNodeGetContent (node);
+
+      if (as_url != NULL && oab_url !=NULL)
+        {
+          urls = g_slice_new0 (GoaEwsUrls);
+          urls->as_url = as_url;
+          urls->oab_url = oab_url;
+          goto out;
+        }
+    }
+
+  g_free (oab_url);
+  g_free (as_url);
+ out:
+  return urls;
+}
+
+static void
+ews_client_autodiscover_response_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+  GError *error;
+  AutodiscoverData *data = user_data;
+  GoaEwsUrls *urls;
+  guint status;
+  gint idx;
+  gsize size;
+  xmlDoc *doc;
+  xmlNode *node;
+
+  status = msg->status_code;
+  if (status == SOUP_STATUS_CANCELLED)
+    return;
+
+  urls = NULL;
+  error = NULL;
+  size = sizeof (data->msgs) / sizeof (data->msgs[0]);
+
+  for (idx = 0; idx < size; idx++)
+    {
+      if (data->msgs[idx] == msg)
+        break;
+    }
+  if (idx == size)
+    return;
+
+  data->msgs[idx] = NULL;
+
+  if (status != SOUP_STATUS_OK)
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific */
+                   _("Code: %u - Unexpected response from server"),
+                   status);
+      goto out;
+    }
+
+  soup_buffer_free (soup_message_body_flatten (SOUP_MESSAGE (msg)->response_body));
+  g_debug ("The response headers");
+  g_debug ("===================");
+  g_debug ("%s", SOUP_MESSAGE (msg)->response_body->data);
+
+  doc = xmlReadMemory (msg->response_body->data, msg->response_body->length, "autodiscover.xml", NULL, 0);
+  if (doc == NULL)
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific */
+                   _("Failed to parse autodiscover response XML"));
+      goto out;
+    }
+
+  node = xmlDocGetRootElement (doc);
+  if (g_strcmp0 ((gchar *) node->name, "Autodiscover"))
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific */
+                   _("Failed to find Autodiscover element"));
+      goto out;
+    }
+
+  for (node = node->children; node; node = node->next)
+    {
+      if (ews_client_check_node (node, "Response"))
+        break;
+    }
+  if (node == NULL)
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific */
+                   _("Failed to find Response element"));
+      goto out;
+    }
+
+  for (node = node->children; node; node = node->next)
+    {
+      if (ews_client_check_node (node, "Account"))
+        break;
+    }
+  if (node == NULL)
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific */
+                   _("Failed to find Account element"));
+      goto out;
+    }
+
+  for (node = node->children; node; node = node->next)
+    {
+      if (ews_client_check_node (node, "Protocol"))
+        {
+          urls = ews_client_autodiscover_parse_protocol (node);
+          break;
+        }
+    }
+  if (urls == NULL)
+    {
+      g_set_error (&error,
+                   GOA_ERROR,
+                   GOA_ERROR_FAILED, /* TODO: more specific*/
+                   _("Failed to find ASUrl and OABUrl in autodiscover response"));
+      goto out;
+    }
+
+  for (idx = 0; idx < size; idx++)
+    {
+      if (data->msgs[idx] != NULL)
+        {
+          /* Since we are cancelling from the same thread that we queued the
+           * message, the callback (ie. this function) will be invoked before
+           * soup_session_cancel_message returns.
+           */
+          soup_session_cancel_message (data->session, data->msgs[idx], SOUP_STATUS_CANCELLED);
+          data->msgs[idx] = NULL;
+        }
+    }
+
+ out:
+  if (error != NULL)
+    {
+      for (idx = 0; idx < size; idx++)
+        {
+          if (data->msgs[idx] != NULL)
+            {
+              /* There's another request outstanding.
+               * Hope that it has better luck.
+               */
+              g_clear_error (&error);
+              return;
+            }
+        }
+      g_simple_async_result_set_from_error (data->res, error);
+    }
+  else
+    g_simple_async_result_set_op_res_gpointer (data->res, urls, NULL);
+
+  g_simple_async_result_complete_in_idle (data->res);
+  ews_client_autodiscover_data_free (data);
+}
+
+static xmlDoc *
+ews_client_create_autodiscover_xml (const gchar *email)
+{
+  xmlDoc *doc;
+  xmlNode *node;
+  xmlNs *ns;
+
+  doc = xmlNewDoc ((xmlChar *) "1.0");
+
+  node = xmlNewDocNode (doc, NULL, (xmlChar *) "Autodiscover", NULL);
+  xmlDocSetRootElement (doc, node);
+  ns = xmlNewNs (node,
+                 (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006";,
+                 NULL);
+
+  node = xmlNewChild (node, ns, (xmlChar *) "Request", NULL);
+  xmlNewChild (node, ns, (xmlChar *) "EMailAddress", (xmlChar *) email);
+  xmlNewChild (node,
+               ns,
+               (xmlChar *) "AcceptableResponseSchema",
+               (xmlChar *) "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";);
+
+  return doc;
+}
+
+static void
+ews_client_post_restarted_cb (SoupMessage *msg, gpointer data)
+{
+  xmlOutputBuffer *buf = data;
+
+  /* In violation of RFC2616, libsoup will change a POST request to
+   * a GET on receiving a 302 redirect.
+   */
+  g_debug ("Working around libsoup bug with redirect");
+  g_object_set (msg, SOUP_MESSAGE_METHOD, "POST", NULL);
+
+  soup_message_set_request(msg,
+                           "text/xml; charset=utf-8",
+                           SOUP_MEMORY_COPY,
+                           (gchar *) buf->buffer->content,
+                           buf->buffer->use);
+}
+
+static SoupMessage *
+ews_client_create_msg_for_url (const gchar *url, xmlOutputBuffer *buf)
+{
+  SoupMessage *msg;
+
+  msg = soup_message_new (buf != NULL ? "POST" : "GET", url);
+  soup_message_headers_append (msg->request_headers, "User-Agent", "libews/0.1");
+
+  if (buf != NULL)
+    {
+      soup_message_set_request (msg,
+                                "text/xml; charset=utf-8",
+                                SOUP_MEMORY_COPY,
+                                (gchar *) buf->buffer->content,
+                                buf->buffer->use);
+      g_signal_connect (msg, "restarted", G_CALLBACK (ews_client_post_restarted_cb), buf);
+    }
+
+  soup_buffer_free (soup_message_body_flatten (SOUP_MESSAGE (msg)->request_body));
+  g_debug ("The request headers");
+  g_debug ("===================");
+  g_debug ("%s", SOUP_MESSAGE (msg)->request_body->data);
+
+  return msg;
+}
+
+void
+goa_ews_client_autodiscover (GoaEwsClient        *client,
+                             const gchar         *email,
+                             const gchar         *password,
+                             const gchar         *username,
+                             const gchar         *server,
+                             GCancellable        *cancellable,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
+{
+  AutodiscoverData *data;
+  AutodiscoverAuthData *auth;
+  gchar *url1;
+  gchar *url2;
+  xmlDoc *doc;
+  xmlOutputBuffer *buf;
+
+  g_return_if_fail (GOA_IS_EWS_CLIENT (client));
+  g_return_if_fail (email != NULL || email[0] != '\0');
+  g_return_if_fail (password != NULL || password[0] != '\0');
+  g_return_if_fail (username != NULL || username[0] != '\0');
+  g_return_if_fail (server != NULL || server[0] != '\0');
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  doc = ews_client_create_autodiscover_xml (email);
+  buf = xmlAllocOutputBuffer (NULL);
+  xmlNodeDumpOutput (buf, doc, xmlDocGetRootElement (doc), 0, 1, NULL);
+  xmlOutputBufferFlush (buf);
+
+  url1 = g_strdup_printf ("https://%s/autodiscover/autodiscover.xml";, server);
+  url2 = g_strdup_printf ("https://autodiscover.%s/autodiscover/autodiscover.xml";, server);
+
+  /* http://msdn.microsoft.com/en-us/library/ee332364.aspx says we are
+   * supposed to try $domain and then autodiscover.$domain. But some
+   * people have broken firewalls on the former which drop packets
+   * instead of rejecting connections, and make the request take ages
+   * to time out. So run both queries in parallel and let the fastest
+   * (successful) one win.
+   */
+  data = g_slice_new0 (AutodiscoverData);
+  data->buf = buf;
+  data->res = g_simple_async_result_new (G_OBJECT (client), callback, user_data, goa_ews_client_autodiscover);
+  data->msgs[0] = ews_client_create_msg_for_url (url1, buf);
+  data->msgs[1] = ews_client_create_msg_for_url (url2, buf);
+  data->session = soup_session_async_new_with_options (SOUP_SESSION_USE_NTLM, TRUE,
+                                                       SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                                       NULL);
+  if (cancellable != NULL)
+    {
+      data->cancellable = g_object_ref (cancellable);
+      data->cancellable_id = g_cancellable_connect (data->cancellable,
+                                                    G_CALLBACK (ews_client_autodiscover_cancelled_cb),
+                                                    data,
+                                                    NULL);
+    }
+
+  auth = g_slice_new0 (AutodiscoverAuthData);
+  auth->username = g_strdup (username);
+  auth->password = g_strdup (password);
+  g_signal_connect_data (data->session,
+                         "authenticate",
+                         G_CALLBACK (ews_client_authenticate),
+                         auth,
+                         ews_client_autodiscover_auth_data_free,
+                         0);
+
+  soup_session_queue_message (data->session, data->msgs[0], ews_client_autodiscover_response_cb, data);
+  soup_session_queue_message (data->session, data->msgs[1], ews_client_autodiscover_response_cb, data);
+
+  g_free (url2);
+  g_free (url1);
+  xmlFreeDoc (doc);
+}
+
+GoaEwsUrls *
+goa_ews_client_autodiscover_finish (GoaEwsClient *client, GAsyncResult *res, GError **error)
+{
+  GSimpleAsyncResult *simple;
+
+  g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (client), goa_ews_client_autodiscover), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  simple = G_SIMPLE_ASYNC_RESULT (res);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    return NULL;
+
+  return g_simple_async_result_get_op_res_gpointer (simple);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GError **error;
+  GMainLoop *loop;
+  GoaEwsUrls *urls;
+} AutodiscoverSyncData;
+
+static void
+ews_client_autodiscover_sync_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  AutodiscoverSyncData *data = user_data;
+
+  data->urls = goa_ews_client_autodiscover_finish (GOA_EWS_CLIENT (source_object), res, data->error);
+  g_main_loop_quit (data->loop);
+}
+
+GoaEwsUrls *
+goa_ews_client_autodiscover_sync (GoaEwsClient        *client,
+                                  const gchar         *email,
+                                  const gchar         *password,
+                                  const gchar         *username,
+                                  const gchar         *server,
+                                  GCancellable        *cancellable,
+                                  GError             **error)
+{
+  AutodiscoverSyncData data;
+  GMainContext *context = NULL;
+
+  data.error = error;
+  data.urls = NULL;
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  data.loop = g_main_loop_new (context, FALSE);
+
+  goa_ews_client_autodiscover (client,
+                               email,
+                               password,
+                               username,
+                               server,
+                               cancellable,
+                               ews_client_autodiscover_sync_cb,
+                               &data);
+  g_main_loop_run (data.loop);
+  g_main_loop_unref (data.loop);
+
+  g_main_context_pop_thread_default (context);
+  g_main_context_unref (context);
+
+  return data.urls;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+goa_ews_urls_free (GoaEwsUrls *urls)
+{
+  g_free (urls->as_url);
+  g_free (urls->oab_url);
+  g_slice_free (GoaEwsUrls, urls);
+}
diff --git a/src/goabackend/goaewsclient.h b/src/goabackend/goaewsclient.h
new file mode 100644
index 0000000..0390506
--- /dev/null
+++ b/src/goabackend/goaewsclient.h
@@ -0,0 +1,71 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_EWS_CLIENT_H__
+#define __GOA_EWS_CLIENT_H__
+
+#include <goabackend/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_EWS_CLIENT         (goa_ews_client_get_type ())
+#define GOA_EWS_CLIENT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_EWS_CLIENT, GoaEwsClient))
+#define GOA_IS_EWS_CLIENT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_EWS_CLIENT))
+
+typedef struct _GoaEwsUrls GoaEwsUrls;
+
+struct _GoaEwsUrls
+{
+  gchar *as_url;
+  gchar *oab_url;
+};
+
+GType           goa_ews_client_get_type            (void) G_GNUC_CONST;
+GoaEwsClient   *goa_ews_client_new                 (void);
+void            goa_ews_client_autodiscover        (GoaEwsClient        *client,
+                                                    const gchar         *email,
+                                                    const gchar         *password,
+                                                    const gchar         *username,
+                                                    const gchar         *server,
+                                                    GCancellable        *cancellable,
+                                                    GAsyncReadyCallback  callback,
+                                                    gpointer             gpointer);
+GoaEwsUrls     *goa_ews_client_autodiscover_finish (GoaEwsClient        *client,
+                                                    GAsyncResult        *res,
+                                                    GError             **error);
+GoaEwsUrls     *goa_ews_client_autodiscover_sync   (GoaEwsClient        *client,
+                                                    const gchar         *email,
+                                                    const gchar         *password,
+                                                    const gchar         *username,
+                                                    const gchar         *server,
+                                                    GCancellable        *cancellable,
+                                                    GError             **error);
+
+void            goa_ews_urls_free                  (GoaEwsUrls *urls);
+
+G_END_DECLS
+
+#endif /* __GOA_EWS_CLIENT_H__ */
diff --git a/src/goabackend/goaexchangeprovider.c b/src/goabackend/goaexchangeprovider.c
new file mode 100644
index 0000000..b046104
--- /dev/null
+++ b/src/goabackend/goaexchangeprovider.c
@@ -0,0 +1,625 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "goaewsclient.h"
+#include "goalogging.h"
+#include "goaprovider.h"
+#include "goaexchangeprovider.h"
+#include "goaeditablelabel.h"
+
+/**
+ * GoaExchangeProvider:
+ *
+ * The #GoaExchangeProvider structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GoaExchangeProvider
+{
+  /*< private >*/
+  GoaProvider parent_instance;
+};
+
+typedef struct _GoaExchangeProviderClass GoaExchangeProviderClass;
+
+struct _GoaExchangeProviderClass
+{
+  GoaProviderClass parent_class;
+};
+
+/**
+ * SECTION:goaexchangeprovider
+ * @title: GoaExchangeProvider
+ * @short_description: A provider for Microsoft Exchange servers
+ *
+ * #GoaExchangeProvider is used to access Microsoft Exchange servers.
+ */
+
+G_DEFINE_TYPE_WITH_CODE (GoaExchangeProvider, goa_exchange_provider, GOA_TYPE_PROVIDER,
+                         g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+							 g_define_type_id,
+							 "exchange",
+							 0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_provider_type (GoaProvider *provider)
+{
+  return "exchange";
+}
+
+static gchar *
+get_provider_name (GoaProvider *provider, GoaObject *object)
+{
+  return g_strdup(_("Microsoft Exchange"));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean on_handle_get_password (GoaPasswordBased      *interface,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar           *id,
+                                        gpointer               user_data);
+
+static gboolean
+build_object (GoaProvider         *provider,
+              GoaObjectSkeleton   *object,
+              GKeyFile            *key_file,
+              const gchar         *group,
+              GError             **error)
+{
+  GoaAccount *account;
+  GoaCalendar *calendar;
+  GoaContacts *contacts;
+  GoaMail *mail;
+  GoaPasswordBased *password_based;
+  gboolean calendar_enabled;
+  gboolean contacts_enabled;
+  gboolean mail_enabled;
+  gboolean ret;
+
+  account = NULL;
+  calendar = NULL;
+  contacts = NULL;
+  mail = NULL;
+  password_based = NULL;
+  ret = FALSE;
+
+  /* Chain up */
+  if (!GOA_PROVIDER_CLASS (goa_exchange_provider_parent_class)->build_object (provider,
+                                                                              object,
+                                                                              key_file,
+                                                                              group,
+                                                                              error))
+    goto out;
+
+  password_based = goa_object_get_password_based (GOA_OBJECT (object));
+  if (password_based == NULL)
+    {
+      password_based = goa_password_based_skeleton_new ();
+      /* Ensure D-Bus method invocations run in their own thread */
+      g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (password_based),
+                                           G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+      goa_object_skeleton_set_password_based (object, password_based);
+      g_signal_connect (password_based,
+                        "handle-get-password",
+                        G_CALLBACK (on_handle_get_password),
+                        NULL);
+    }
+
+  account = goa_object_get_account (GOA_OBJECT (object));
+
+  /* Email */
+  mail = goa_object_get_mail (GOA_OBJECT (object));
+  mail_enabled = g_key_file_get_boolean (key_file, group, "MailEnabled", NULL);
+  if (mail_enabled)
+    {
+      if (mail == NULL)
+        {
+          const gchar *email_address;
+
+          email_address = goa_account_get_presentation_identity (account);
+          mail = goa_mail_skeleton_new ();
+          g_object_set (G_OBJECT (mail), "email-address", email_address, NULL);
+          goa_object_skeleton_set_mail (object, mail);
+        }
+    }
+  else
+    {
+      if (mail != NULL)
+        goa_object_skeleton_set_mail (object, NULL);
+    }
+
+  /* Calendar */
+  calendar = goa_object_get_calendar (GOA_OBJECT (object));
+  calendar_enabled = g_key_file_get_boolean (key_file, group, "CalendarEnabled", NULL);
+  if (calendar_enabled)
+    {
+      if (calendar == NULL)
+        {
+          calendar = goa_calendar_skeleton_new ();
+          goa_object_skeleton_set_calendar (object, calendar);
+        }
+    }
+  else
+    {
+      if (calendar != NULL)
+        goa_object_skeleton_set_calendar (object, NULL);
+    }
+
+  /* Contacts */
+  contacts = goa_object_get_contacts (GOA_OBJECT (object));
+  contacts_enabled = g_key_file_get_boolean (key_file, group, "ContactsEnabled", NULL);
+  if (contacts_enabled)
+    {
+      if (contacts == NULL)
+        {
+          contacts = goa_contacts_skeleton_new ();
+          goa_object_skeleton_set_contacts (object, contacts);
+        }
+    }
+  else
+    {
+      if (contacts != NULL)
+        goa_object_skeleton_set_contacts (object, NULL);
+    }
+
+  ret = TRUE;
+
+ out:
+  if (contacts != NULL)
+    g_object_unref (contacts);
+  if (calendar != NULL)
+    g_object_unref (calendar);
+  if (mail != NULL)
+    g_object_unref (mail);
+  if (password_based != NULL)
+    g_object_unref (password_based);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_credentials_sync (GoaProvider         *provider,
+                         GoaObject           *object,
+                         gint                *out_expires_in,
+                         GCancellable        *cancellable,
+                         GError             **error)
+{
+  if (out_expires_in != NULL)
+    *out_expires_in = 0;
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget     *table,
+           guint          row,
+           const gchar   *text,
+           GtkWidget    **out_entry)
+{
+  GtkWidget *label;
+  GtkWidget *entry;
+
+  label = gtk_label_new_with_mnemonic (text);
+  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+  gtk_table_attach (GTK_TABLE (table), label,
+                    0, 1,
+                    row, row + 1,
+                    GTK_FILL, GTK_FILL, 0, 0);
+  entry = gtk_entry_new ();
+  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+  gtk_entry_set_max_length (GTK_ENTRY (entry), 132);
+  gtk_table_attach (GTK_TABLE (table), entry,
+                    1, 2,
+                    row, row + 1,
+                    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+  if (out_entry != NULL)
+    *out_entry = entry;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GtkDialog *dialog;
+  GMainLoop *loop;
+
+  GtkWidget *cluebar;
+  GtkWidget *cluebar_label;
+
+  GtkWidget *email_address;
+  GtkWidget *password;
+
+  GtkWidget *username;
+  GtkWidget *server;
+
+  gchar *account_object_path;
+
+  GError *error;
+} AddAccountData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_valid_email_address (const gchar *email, gchar **out_username, gchar **out_domain)
+{
+  gchar *at;
+
+  if (email == NULL || email[0] == '\0')
+    return FALSE;
+
+  at = strchr (email, '@');
+  if (at == NULL || *(at + 1) == '\0')
+    return FALSE;
+
+  if (out_username != NULL)
+    {
+      *out_username = g_strdup (email);
+      (*out_username)[at - email] = '\0';
+    }
+
+  if (out_domain != NULL)
+    *out_domain = g_strdup (at + 1);
+
+  return TRUE;
+}
+
+static void
+on_email_address_or_password_changed (GtkEditable *editable, gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  gboolean can_add;
+  const gchar *email;
+  gchar *domain;
+  gchar *url;
+  gchar *username;
+
+  can_add = FALSE;
+  domain = NULL;
+  url = NULL;
+  username = NULL;
+
+  email = gtk_entry_get_text (GTK_ENTRY (data->email_address));
+  if (!is_valid_email_address (email, &username, &domain))
+    goto out;
+
+  gtk_entry_set_text (GTK_ENTRY (data->username), username);
+  gtk_entry_set_text (GTK_ENTRY (data->server), domain);
+
+  can_add = gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
+
+ out:
+  gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+  g_free (url);
+  g_free (domain);
+  g_free (username);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  goa_manager_call_add_account_finish (manager,
+                                       &data->account_object_path,
+                                       res,
+                                       &data->error);
+  g_main_loop_quit (data->loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaObject *
+add_account (GoaProvider    *provider,
+             GoaClient      *client,
+             GtkDialog      *dialog,
+             GtkBox         *vbox,
+             GError        **error)
+{
+  AddAccountData data;
+  GError *local_error;
+  GVariantBuilder builder;
+  GoaEwsClient *ews_client;
+  GoaEwsUrls *urls;
+  GoaObject *ret;
+  GtkWidget *alignment;
+  GtkWidget *label;
+  GtkWidget *table;
+
+  const gchar *email_address;
+  const gchar *server;
+  const gchar *password;
+  const gchar *username;
+  gchar *markup;
+  gint response;
+  guint row;
+
+  ews_client = NULL;
+  ret = NULL;
+
+  memset (&data, 0, sizeof (AddAccountData));
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.dialog = dialog;
+  data.error = NULL;
+
+  data.cluebar = gtk_info_bar_new ();
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (data.cluebar), GTK_MESSAGE_ERROR);
+  gtk_widget_set_no_show_all (data.cluebar, TRUE);
+  gtk_box_pack_start (GTK_BOX (vbox), data.cluebar, FALSE, FALSE, 0);
+
+  data.cluebar_label = gtk_label_new ("");
+  gtk_label_set_line_wrap (GTK_LABEL (data.cluebar_label), TRUE);
+  gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data.cluebar))),
+                     data.cluebar_label);
+
+  label = gtk_label_new (NULL);
+  markup = g_strconcat ("<b>", _("New Microsoft Exchange Account"), "</b>", NULL);
+  gtk_label_set_markup (GTK_LABEL (label), markup);
+  g_free (markup);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+  gtk_box_pack_start (vbox, label, FALSE, FALSE, 0);
+
+  table = gtk_table_new (4, 2, FALSE);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 12);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 12);
+
+  alignment = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
+  gtk_container_add (GTK_CONTAINER (alignment), table);
+  gtk_box_pack_start (vbox, alignment, FALSE, TRUE, 0);
+
+  row = 0;
+  add_entry (table, row++, _("Email _Address"), &data.email_address);
+  add_entry (table, row++, _("_Password"), &data.password);
+  add_entry (table, row++, _("User_name"), &data.username);
+  add_entry (table, row++, _("_Server"), &data.server);
+
+  gtk_entry_set_visibility (GTK_ENTRY (data.password), FALSE);
+  gtk_widget_grab_focus (data.email_address);
+
+  g_signal_connect (data.email_address, "changed", G_CALLBACK (on_email_address_or_password_changed), &data);
+  g_signal_connect (data.password, "changed", G_CALLBACK (on_email_address_or_password_changed), &data);
+
+  gtk_widget_show_all (GTK_WIDGET (vbox));
+  gtk_dialog_add_button (dialog, GTK_STOCK_OK, GTK_RESPONSE_OK);
+  gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
+  gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, FALSE);
+
+  ews_client = goa_ews_client_new ();
+
+ ews_again:
+  response = gtk_dialog_run (dialog);
+  if (response != GTK_RESPONSE_OK)
+    {
+      g_set_error (error,
+                   GOA_ERROR,
+                   GOA_ERROR_DIALOG_DISMISSED,
+                   _("Dialog was dismissed"));
+      goto out;
+    }
+
+  email_address = gtk_entry_get_text (GTK_ENTRY (data.email_address));
+  password = gtk_entry_get_text (GTK_ENTRY (data.password));
+  username = gtk_entry_get_text (GTK_ENTRY (data.username));
+  server = gtk_entry_get_text (GTK_ENTRY (data.server));
+
+  local_error = NULL;
+  urls = goa_ews_client_autodiscover_sync (ews_client,
+                                           email_address,
+                                           password,
+                                           username,
+                                           server,
+                                           NULL,
+                                           &local_error);
+  if (urls == NULL)
+    {
+      markup = g_strdup_printf ("<b>%s:</b> %s",
+                                _("Error connecting to Microsoft Exchange server"),
+                                local_error->message);
+      g_error_free (local_error);
+
+      gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+      g_free (markup);
+
+      gtk_widget_set_no_show_all (data.cluebar, FALSE);
+      gtk_widget_show_all (data.cluebar);
+      goto ews_again;
+    }
+
+  gtk_widget_hide (GTK_WIDGET (dialog));
+
+  /* OK, everything is dandy, add the account */
+  /* we want the GoaClient to update before this method returns (so it
+   * can create a proxy for the new object) so run the mainloop while
+   * waiting for this to complete
+   */
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+  g_variant_builder_add (&builder, "{ss}", "MailEnabled", "true");
+  g_variant_builder_add (&builder, "{ss}", "CalendarEnabled", "true");
+  g_variant_builder_add (&builder, "{ss}", "ContactsEnabled", "true");
+  g_variant_builder_add (&builder, "{ss}", "Server", server);
+  g_variant_builder_add (&builder, "{ss}", "AsUrl", urls->as_url);
+  g_variant_builder_add (&builder, "{ss}", "OabUrl", urls->oab_url);
+  goa_ews_urls_free (urls);
+
+  goa_manager_call_add_account (goa_client_get_manager (client),
+                                goa_provider_get_provider_type (provider),
+                                username,
+                                email_address,
+                                g_variant_builder_end (&builder),
+                                NULL, /* GCancellable* */
+                                (GAsyncReadyCallback) add_account_cb,
+                                &data);
+  g_main_loop_run (data.loop);
+  if (data.error != NULL)
+    {
+      g_propagate_error (error, data.error);
+      goto out;
+    }
+
+  ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+                                                      data.account_object_path));
+  /* TODO: run in worker thread */
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
+
+  if (!goa_provider_store_credentials_sync (provider,
+                                            ret,
+                                            g_variant_builder_end (&builder),
+                                            NULL, /* GCancellable */
+                                            error))
+    goto out;
+
+ out:
+  g_free (data.account_object_path);
+  if (data.loop != NULL)
+    g_main_loop_unref (data.loop);
+  if (ews_client != NULL)
+    g_object_unref (ews_client);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider    *provider,
+                 GoaClient      *client,
+                 GoaObject      *object,
+                 GtkWindow      *parent,
+                 GError        **error)
+{
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+show_account (GoaProvider         *provider,
+              GoaClient           *client,
+              GoaObject           *object,
+              GtkBox              *vbox,
+              GtkTable            *table)
+{
+  /* Chain up */
+  GOA_PROVIDER_CLASS (goa_exchange_provider_parent_class)->show_account (provider, client, object, vbox, table);
+
+  goa_util_add_account_info (table, object);
+
+  goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+                                                   _("Use for"),
+                                                   "MailEnabled",
+                                                   _("Mail"));
+
+  goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+                                                   NULL,
+                                                   "CalendarEnabled",
+                                                   _("Calendar"));
+
+  goa_util_add_row_switch_from_keyfile_with_blurb (GTK_TABLE (table), object,
+                                                   NULL,
+                                                   "ContactsEnabled",
+                                                   _("Contacts"));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_exchange_provider_init (GoaExchangeProvider *provider)
+{
+}
+
+static void
+goa_exchange_provider_class_init (GoaExchangeProviderClass *klass)
+{
+  GoaProviderClass *provider_class;
+
+  provider_class = GOA_PROVIDER_CLASS (klass);
+  provider_class->get_provider_type          = get_provider_type;
+  provider_class->get_provider_name          = get_provider_name;
+  provider_class->add_account                = add_account;
+  provider_class->refresh_account            = refresh_account;
+  provider_class->build_object               = build_object;
+  provider_class->show_account               = show_account;
+  provider_class->ensure_credentials_sync    = ensure_credentials_sync;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_handle_get_password (GoaPasswordBased      *interface,
+                        GDBusMethodInvocation *invocation,
+                        const gchar           *id,
+                        gpointer               user_data)
+{
+  GoaObject *object;
+  GoaAccount *account;
+  GoaProvider *provider;
+  GError *error;
+  GVariant *credentials;
+  gchar *password;
+
+  /* TODO: maybe log what app is requesting access */
+
+  password = NULL;
+  credentials = NULL;
+
+  object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+  account = goa_object_peek_account (object);
+  provider = goa_provider_get_for_provider_type (goa_account_get_provider_type (account));
+
+  error = NULL;
+  credentials = goa_provider_lookup_credentials_sync (provider,
+                                                      object,
+                                                      NULL, /* GCancellable* */
+                                                      &error);
+  if (credentials == NULL)
+    {
+      g_dbus_method_invocation_take_error (invocation, error);
+      goto out;
+    }
+
+  if (!g_variant_lookup (credentials, "password", "s", &password))
+    {
+      g_dbus_method_invocation_return_error (invocation,
+                                             GOA_ERROR,
+                                             GOA_ERROR_FAILED, /* TODO: more specific */
+                                             _("Did not find password with username `%s' in credentials"),
+                                             id);
+      goto out;
+    }
+
+  goa_password_based_complete_get_password (interface, invocation, password);
+
+ out:
+  g_free (password);
+  if (credentials != NULL)
+    g_variant_unref (credentials);
+  g_object_unref (provider);
+  return TRUE; /* invocation was handled */
+}
diff --git a/src/goabackend/goaexchangeprovider.h b/src/goabackend/goaexchangeprovider.h
new file mode 100644
index 0000000..76c6911
--- /dev/null
+++ b/src/goabackend/goaexchangeprovider.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_EXCHANGE_PROVIDER_H__
+#define __GOA_EXCHANGE_PROVIDER_H__
+
+#include <goabackend/goabackendtypes.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_EXCHANGE_PROVIDER   (goa_exchange_provider_get_type ())
+#define GOA_EXCHANGE_PROVIDER(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_EXCHANGE_PROVIDER, GoaExchangeProvider))
+#define GOA_IS_EXCHANGE_PROVIDER(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_EXCHANGE_PROVIDER))
+
+GType goa_exchange_provider_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GOA_EXCHANGE_PROVIDER_H__ */
diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
index a88cbd7..fc0d99b 100644
--- a/src/goabackend/goaprovider.c
+++ b/src/goabackend/goaprovider.c
@@ -27,6 +27,7 @@
 
 #include "goalogging.h"
 #include "goaprovider.h"
+#include "goaexchangeprovider.h"
 #include "goagoogleprovider.h"
 #include "goafacebookprovider.h"
 #include "goayahooprovider.h"
@@ -586,6 +587,9 @@ ensure_ep_and_builtins (void)
       extension_point = g_io_extension_point_register (GOA_PROVIDER_EXTENSION_POINT_NAME);
       g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER);
 
+#ifdef GOA_EXCHANGE_ENABLED
+      type = GOA_TYPE_EXCHANGE_PROVIDER;
+#endif
 #ifdef GOA_GOOGLE_ENABLED
       type = GOA_TYPE_GOOGLE_PROVIDER;
 #endif



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