]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-auth-factory.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy / empathy-auth-factory.c
index 58b24dc4f8ed0b762891b1f2b59a11870cc793e4..cba4607a3e44af53b9df386e1e96e69d89cf3104 100644 (file)
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#include "config.h"
 #include "empathy-auth-factory.h"
 
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/simple-handler.h>
-#include <telepathy-glib/util.h>
-
-#define DEBUG_FLAG EMPATHY_DEBUG_TLS
-#include "empathy-debug.h"
+#include "empathy-keyring.h"
+#include "empathy-sasl-mechanisms.h"
 #include "empathy-server-sasl-handler.h"
 #include "empathy-server-tls-handler.h"
 #include "empathy-utils.h"
 
-#include "extensions/extensions.h"
+#ifdef HAVE_GOA
+#include "empathy-goa-auth-handler.h"
+#endif /* HAVE_GOA */
 
-G_DEFINE_TYPE (EmpathyAuthFactory, empathy_auth_factory, G_TYPE_OBJECT);
+#ifdef HAVE_UOA
+#include "empathy-uoa-auth-handler.h"
+#endif /* HAVE_UOA */
 
-typedef struct {
-  TpBaseClient *handler;
+#include "extensions.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_TLS
+#include "empathy-debug.h"
+
+G_DEFINE_TYPE (EmpathyAuthFactory, empathy_auth_factory, TP_TYPE_BASE_CLIENT);
 
+struct _EmpathyAuthFactoryPriv {
   /* Keep a ref here so the auth client doesn't have to mess with
    * refs. It will be cleared when the channel (and so the handler)
-   * gets invalidated. */
-  EmpathyServerSASLHandler *sasl_handler;
+   * gets invalidated.
+   *
+   * The channel path of the handler's channel (borrowed gchar *) ->
+   * reffed (EmpathyServerSASLHandler *)
+   * */
+  GHashTable *sasl_handlers;
+
+#ifdef HAVE_GOA
+  EmpathyGoaAuthHandler *goa_handler;
+#endif /* HAVE_GOA */
+
+#ifdef HAVE_UOA
+  EmpathyUoaAuthHandler *uoa_handler;
+#endif /* HAVE_UOA */
+
+  /* If an account failed to connect and user enters a new password to try, we
+   * store it in this hash table and will try to use it next time the account
+   * attemps to connect.
+   *
+   * reffed TpAccount -> owned password (gchar *) */
+  GHashTable *retry_passwords;
 
   gboolean dispose_run;
-} EmpathyAuthFactoryPriv;
+};
 
 enum {
   NEW_SERVER_TLS_HANDLER,
   NEW_SERVER_SASL_HANDLER,
+  AUTH_PASSWORD_FAILED,
   LAST_SIGNAL,
 };
 
@@ -79,7 +105,9 @@ handler_context_data_new (EmpathyAuthFactory *self,
 
   data = g_slice_new0 (HandlerContextData);
   data->self = g_object_ref (self);
-  data->context = g_object_ref (context);
+
+  if (context != NULL)
+    data->context = g_object_ref (context);
 
   return data;
 }
@@ -91,10 +119,8 @@ server_tls_handler_ready_cb (GObject *source,
 {
   EmpathyServerTLSHandler *handler;
   GError *error = NULL;
-  EmpathyAuthFactoryPriv *priv;
   HandlerContextData *data = user_data;
 
-  priv = GET_PRIV (data->self);
   handler = empathy_server_tls_handler_new_finish (res, &error);
 
   if (error != NULL)
@@ -123,32 +149,114 @@ sasl_handler_invalidated_cb (EmpathyServerSASLHandler *handler,
 {
   EmpathyAuthFactory *self = user_data;
   EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
+  TpChannel * channel;
+
+  channel = empathy_server_sasl_handler_get_channel (handler);
+  g_assert (channel != NULL);
 
-  DEBUG ("SASL handler is invalidated, unref it");
+  DEBUG ("SASL handler for channel %s is invalidated, unref it",
+      tp_proxy_get_object_path (channel));
 
-  tp_clear_object (&priv->sasl_handler);
+  g_hash_table_remove (priv->sasl_handlers, tp_proxy_get_object_path (channel));
 }
 
 static void
-handle_channels_cb (TpSimpleHandler *handler,
-    TpAccount *account,
-    TpConnection *connection,
-    GList *channels,
-    GList *requests_satisfied,
-    gint64 user_action_time,
-    TpHandleChannelsContext *context,
+sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler *handler,
+    const gchar *password,
+    EmpathyAuthFactory *self)
+{
+  TpAccount *account;
+
+  account = empathy_server_sasl_handler_get_account (handler);
+
+  g_signal_emit (self, signals[AUTH_PASSWORD_FAILED], 0, account, password);
+}
+
+static void
+server_sasl_handler_ready_cb (GObject *source,
+    GAsyncResult *res,
     gpointer user_data)
 {
-  TpChannel *channel;
-  const GError *dbus_error;
+  EmpathyAuthFactoryPriv *priv;
   GError *error = NULL;
-  EmpathyAuthFactory *self = user_data;
-  EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
-  HandlerContextData *data;
-  GHashTable *props;
-  const gchar * const *available_mechanisms;
+  HandlerContextData *data = user_data;
+  EmpathyServerSASLHandler *handler;
 
-  DEBUG ("Handle TLS or SASL carrier channels.");
+  priv = GET_PRIV (data->self);
+  handler = empathy_server_sasl_handler_new_finish (res, &error);
+
+  if (error != NULL)
+    {
+      DEBUG ("Failed to create a server SASL handler; error %s",
+          error->message);
+
+      if (data->context != NULL)
+        tp_handle_channels_context_fail (data->context, error);
+
+      g_error_free (error);
+    }
+  else
+    {
+      TpChannel *channel;
+      const gchar *password;
+      TpAccount *account;
+
+      if (data->context != NULL)
+        tp_handle_channels_context_accept (data->context);
+
+      channel = empathy_server_sasl_handler_get_channel (handler);
+      g_assert (channel != NULL);
+
+      /* Pass the ref to the hash table */
+      g_hash_table_insert (priv->sasl_handlers,
+          (gpointer) tp_proxy_get_object_path (channel), handler);
+
+      tp_g_signal_connect_object (handler, "invalidated",
+          G_CALLBACK (sasl_handler_invalidated_cb), data->self, 0);
+
+      tp_g_signal_connect_object (handler, "auth-password-failed",
+          G_CALLBACK (sasl_handler_auth_password_failed_cb), data->self, 0);
+
+      /* Is there a retry password? */
+      account = empathy_server_sasl_handler_get_account (handler);
+
+      password = g_hash_table_lookup (data->self->priv->retry_passwords,
+          account);
+      if (password != NULL)
+        {
+          gboolean save;
+
+          DEBUG ("Use retry password");
+
+          /* We want to save this new password only if there is another
+           * (wrong) password saved. The SASL handler will only save it if it
+           * manages to connect. */
+          save = empathy_server_sasl_handler_has_password (handler);
+
+          empathy_server_sasl_handler_provide_password (handler,
+              password, save);
+
+          /* We only want to try this password once */
+          g_hash_table_remove (data->self->priv->retry_passwords, account);
+        }
+
+      g_signal_emit (data->self, signals[NEW_SERVER_SASL_HANDLER], 0,
+          handler);
+    }
+
+  handler_context_data_free (data);
+}
+
+static gboolean
+common_checks (EmpathyAuthFactory *self,
+    GList *channels,
+    gboolean observe,
+    GError **error)
+{
+  EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
+  TpChannel *channel;
+  const GError *dbus_error;
+  EmpathyServerSASLHandler *handler;
 
   /* there can't be more than one ServerTLSConnection or
    * ServerAuthentication channels at the same time, for the same
@@ -156,88 +264,339 @@ handle_channels_cb (TpSimpleHandler *handler,
    */
   if (g_list_length (channels) != 1)
     {
-      g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
-          "Can't handle more than one ServerTLSConnection or ServerAuthentication "
-          "channel for the same connection.");
+      g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+          "Can't %s more than one ServerTLSConnection or ServerAuthentication "
+          "channel for the same connection.", observe ? "observe" : "handle");
 
-      goto error;
+      return FALSE;
     }
 
   channel = channels->data;
 
   if (tp_channel_get_channel_type_id (channel) !=
-      EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION
-      && tp_channel_get_channel_type_id (channel) !=
       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
     {
-      g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
-          "Can only handle ServerTLSConnection or ServerAuthentication channels, "
-          "this was a %s channel", tp_channel_get_channel_type (channel));
-
-      goto error;
+      /* If we are observing we care only about ServerAuthentication channels.
+       * If we are handling we care about ServerAuthentication and
+       * ServerTLSConnection channels. */
+      if (observe
+          || tp_channel_get_channel_type_id (channel) !=
+          EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
+        {
+          g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+              "Can only %s ServerTLSConnection or ServerAuthentication channels, "
+              "this was a %s channel", observe ? "observe" : "handle",
+              tp_channel_get_channel_type (channel));
+
+          return FALSE;
+        }
     }
 
+  handler = g_hash_table_lookup (priv->sasl_handlers,
+          tp_proxy_get_object_path (channel));
+
   if (tp_channel_get_channel_type_id (channel) ==
       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
-      && priv->sasl_handler != NULL)
+      && handler != NULL &&
+      !observe)
     {
-      g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
-          "Can't handle more than one ServerAuthentication channel at one time");
+      g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+          "We are already handling this channel: %s",
+          tp_proxy_get_object_path (channel));
 
-      goto error;
+      return FALSE;
     }
 
-  props = tp_channel_borrow_immutable_properties (channel);
-  available_mechanisms = tp_asv_get_boxed (props,
-      TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_AVAILABLE_MECHANISMS,
-      G_TYPE_STRV);
-
-  if (tp_channel_get_channel_type_id (channel) ==
-      TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
-      && !tp_strv_contains (available_mechanisms, "X-TELEPATHY-PASSWORD"))
+  dbus_error = tp_proxy_get_invalidated (channel);
+  if (dbus_error != NULL)
     {
-      g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
-          "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
+      *error = g_error_copy (dbus_error);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+handle_channels (TpBaseClient *handler,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    GList *requests_satisfied,
+    gint64 user_action_time,
+    TpHandleChannelsContext *context)
+{
+  TpChannel *channel;
+  GError *error = NULL;
+  EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (handler);
+  HandlerContextData *data;
 
-      goto error;
+  DEBUG ("Handle TLS or SASL carrier channels.");
+
+  if (!common_checks (self, channels, FALSE, &error))
+    {
+      DEBUG ("Failed checks: %s", error->message);
+      tp_handle_channels_context_fail (context, error);
+      g_clear_error (&error);
+      return;
     }
 
-  dbus_error = tp_proxy_get_invalidated (channel);
+  /* The common checks above have checked this is fine. */
+  channel = channels->data;
 
-  if (dbus_error != NULL)
+  /* Only password authentication is supported from here */
+  if (tp_channel_get_channel_type_id (channel) ==
+      TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION &&
+      !empathy_sasl_channel_supports_mechanism (channel,
+          "X-TELEPATHY-PASSWORD"))
     {
-      error = g_error_copy (dbus_error);
-      goto error;
+      g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+          "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
+      DEBUG ("%s", error->message);
+      tp_handle_channels_context_fail (context, error);
+      g_clear_error (&error);
+      return;
     }
 
+  data = handler_context_data_new (self, context);
+  tp_handle_channels_context_delay (context);
+
   /* create a handler */
   if (tp_channel_get_channel_type_id (channel) ==
       EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
     {
-      data = handler_context_data_new (self, context);
-      tp_handle_channels_context_delay (context);
-
       empathy_server_tls_handler_new_async (channel, server_tls_handler_ready_cb,
           data);
     }
   else if (tp_channel_get_channel_type_id (channel) ==
       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
     {
-      priv->sasl_handler = empathy_server_sasl_handler_new (
-          account, channel);
+      empathy_server_sasl_handler_new_async (account, channel,
+          server_sasl_handler_ready_cb, data);
+    }
+}
+
+typedef struct
+{
+  EmpathyAuthFactory *self;
+  TpObserveChannelsContext *context;
+  TpChannelDispatchOperation *dispatch_operation;
+  TpAccount *account;
+  TpChannel *channel;
+} ObserveChannelsData;
+
+static void
+observe_channels_data_free (ObserveChannelsData *data)
+{
+  g_object_unref (data->context);
+  g_object_unref (data->account);
+  g_object_unref (data->channel);
+  g_object_unref (data->dispatch_operation);
+  g_slice_free (ObserveChannelsData, data);
+}
+
+static void
+password_claim_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  ObserveChannelsData *data = user_data;
+  GError *error = NULL;
+
+  if (!tp_channel_dispatch_operation_claim_with_finish (
+          TP_CHANNEL_DISPATCH_OPERATION (source), result, &error))
+    {
+      DEBUG ("Failed to call Claim: %s", error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      HandlerContextData *h_data;
+
+      DEBUG ("Claim called successfully");
 
-      g_signal_connect (priv->sasl_handler, "invalidated",
-          G_CALLBACK (sasl_handler_invalidated_cb), self);
+      h_data = handler_context_data_new (data->self, NULL);
 
-      tp_handle_channels_context_accept (context);
-      g_signal_emit (self, signals[NEW_SERVER_SASL_HANDLER], 0,
-          priv->sasl_handler);
+      empathy_server_sasl_handler_new_async (TP_ACCOUNT (data->account),
+          data->channel, server_sasl_handler_ready_cb, h_data);
     }
-  return;
 
- error:
-  tp_handle_channels_context_fail (context, error);
+  observe_channels_data_free (data);
+}
+
+static void
+get_password_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  ObserveChannelsData *data = user_data;
+
+  if (empathy_keyring_get_account_password_finish (TP_ACCOUNT (source), result, NULL) == NULL)
+    {
+      /* We don't actually mind if this fails, just let the approver
+       * go ahead and take the channel. */
+
+      DEBUG ("We don't have a password for account %s, letting the event "
+          "manager approver take it", tp_proxy_get_object_path (source));
+
+      tp_observe_channels_context_accept (data->context);
+      observe_channels_data_free (data);
+    }
+  else
+    {
+      DEBUG ("We have a password for account %s, calling Claim",
+          tp_proxy_get_object_path (source));
+
+      tp_channel_dispatch_operation_claim_with_async (data->dispatch_operation,
+          TP_BASE_CLIENT (data->self), password_claim_cb, data);
+
+      tp_observe_channels_context_accept (data->context);
+    }
+}
+
+#ifdef HAVE_GOA
+static void
+goa_claim_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  ObserveChannelsData *data = user_data;
+  EmpathyAuthFactory *self = data->self;
+  GError *error = NULL;
+
+  if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
+          result, &error))
+    {
+      DEBUG ("Failed to claim: %s", error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      empathy_goa_auth_handler_start (self->priv->goa_handler,
+          data->channel, data->account);
+    }
+
+  observe_channels_data_free (data);
+}
+#endif /* HAVE_GOA */
+
+#ifdef HAVE_UOA
+static void
+uoa_claim_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  ObserveChannelsData *data = user_data;
+  EmpathyAuthFactory *self = data->self;
+  GError *error = NULL;
+
+  if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
+          result, &error))
+    {
+      DEBUG ("Failed to claim: %s", error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      empathy_uoa_auth_handler_start (self->priv->uoa_handler,
+          data->channel, data->account);
+    }
+
+  observe_channels_data_free (data);
+}
+#endif /* HAVE_UOA */
+
+static void
+observe_channels (TpBaseClient *client,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    TpChannelDispatchOperation *dispatch_operation,
+    GList *requests,
+    TpObserveChannelsContext *context)
+{
+  EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (client);
+  TpChannel *channel;
+  GError *error = NULL;
+  ObserveChannelsData *data;
+
+  DEBUG ("New auth channel to observe");
+
+  if (!common_checks (self, channels, TRUE, &error))
+    {
+      DEBUG ("Failed checks: %s", error->message);
+      tp_observe_channels_context_fail (context, error);
+      g_clear_error (&error);
+      return;
+    }
+
+  /* The common checks above have checked this is fine. */
+  channel = channels->data;
+
+  data = g_slice_new0 (ObserveChannelsData);
+  data->self = self;
+  data->context = g_object_ref (context);
+  data->dispatch_operation = g_object_ref (dispatch_operation);
+  data->account = g_object_ref (account);
+  data->channel = g_object_ref (channel);
+
+#ifdef HAVE_GOA
+  /* GOA auth? */
+  if (empathy_goa_auth_handler_supports (self->priv->goa_handler, channel, account))
+    {
+      DEBUG ("Supported GOA account (%s), claim SASL channel",
+          tp_proxy_get_object_path (account));
+
+      tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
+          client, goa_claim_cb, data);
+      tp_observe_channels_context_accept (context);
+      return;
+    }
+#endif /* HAVE_GOA */
+
+#ifdef HAVE_UOA
+  /* UOA auth? */
+  if (empathy_uoa_auth_handler_supports (self->priv->uoa_handler, channel, account))
+    {
+      DEBUG ("Supported UOA account (%s), claim SASL channel",
+          tp_proxy_get_object_path (account));
+
+      tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
+          client, uoa_claim_cb, data);
+      tp_observe_channels_context_accept (context);
+      return;
+    }
+#endif /* HAVE_UOA */
+
+  /* Password auth? */
+  if (empathy_sasl_channel_supports_mechanism (data->channel,
+          "X-TELEPATHY-PASSWORD"))
+    {
+      if (g_hash_table_lookup (self->priv->retry_passwords, account) != NULL)
+        {
+          DEBUG ("We have a retry password for account %s, calling Claim",
+              tp_account_get_path_suffix (account));
+
+          tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
+              client, password_claim_cb, data);
+
+          tp_observe_channels_context_accept (context);
+          return;
+        }
+
+      empathy_keyring_get_account_password_async (data->account,
+          get_password_cb, data);
+      tp_observe_channels_context_delay (context);
+      return;
+    }
+
+  /* Unknown auth */
+  error = g_error_new_literal (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+      "Unknown auth mechanism");
+  tp_observe_channels_context_fail (context, error);
   g_clear_error (&error);
+
+  observe_channels_data_free (data);
 }
 
 static GObject *
@@ -266,25 +625,37 @@ empathy_auth_factory_constructor (GType type,
 static void
 empathy_auth_factory_init (EmpathyAuthFactory *self)
 {
-  EmpathyAuthFactoryPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
       EMPATHY_TYPE_AUTH_FACTORY, EmpathyAuthFactoryPriv);
-  TpDBusDaemon *bus;
-  GError *error = NULL;
 
-  self->priv = priv;
+  self->priv->sasl_handlers = g_hash_table_new_full (g_str_hash, g_str_equal,
+      NULL, g_object_unref);
 
-  bus = tp_dbus_daemon_dup (&error);
-  if (error != NULL)
-    {
-      g_critical ("Failed to get TpDBusDaemon: %s", error->message);
-      g_error_free (error);
-      return;
-    }
+#ifdef HAVE_GOA
+  self->priv->goa_handler = empathy_goa_auth_handler_new ();
+#endif /* HAVE_GOA */
+
+#ifdef HAVE_UOA
+  self->priv->uoa_handler = empathy_uoa_auth_handler_new ();
+#endif /* HAVE_UOA */
+
+  self->priv->retry_passwords = g_hash_table_new_full (NULL, NULL,
+      g_object_unref, g_free);
+}
+
+static void
+empathy_auth_factory_constructed (GObject *obj)
+{
+  EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (obj);
+  TpBaseClient *client = TP_BASE_CLIENT (self);
+
+  /* chain up to TpBaseClient first */
+  G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructed (obj);
 
-  priv->handler = tp_simple_handler_new (bus, FALSE, FALSE, "Empathy.Auth",
-      FALSE, handle_channels_cb, self, NULL);
+  tp_base_client_set_handler_bypass_approval (client, FALSE);
 
-  tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
+  /* Handle ServerTLSConnection and ServerAuthentication channels */
+  tp_base_client_take_handler_filter (client, tp_asv_new (
           /* ChannelType */
           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
           EMP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION,
@@ -292,7 +663,23 @@ empathy_auth_factory_init (EmpathyAuthFactory *self)
           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
           TP_HANDLE_TYPE_NONE, NULL));
 
-  tp_base_client_take_handler_filter (priv->handler, tp_asv_new (
+  tp_base_client_take_handler_filter (client, tp_asv_new (
+          /* ChannelType */
+          TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
+          /* AuthenticationMethod */
+          TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
+          G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
+          NULL));
+
+  /* We are also an observer so that we can see new auth channels
+   * popping up and if we have the password already saved to one
+   * account where an auth channel has just appeared we can call
+   * Claim() on the CDO so the approver won't get it, which makes
+   * sense. */
+
+  /* Observe ServerAuthentication channels */
+  tp_base_client_take_observer_filter (client, tp_asv_new (
           /* ChannelType */
           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
           TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
@@ -301,7 +688,7 @@ empathy_auth_factory_init (EmpathyAuthFactory *self)
           G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
           NULL));
 
g_object_unref (bus);
 tp_base_client_set_observer_delay_approvers (client, TRUE);
 }
 
 static void
@@ -314,8 +701,17 @@ empathy_auth_factory_dispose (GObject *object)
 
   priv->dispose_run = TRUE;
 
-  tp_clear_object (&priv->handler);
-  tp_clear_object (&priv->sasl_handler);
+  g_hash_table_unref (priv->sasl_handlers);
+
+#ifdef HAVE_GOA
+  g_object_unref (priv->goa_handler);
+#endif /* HAVE_GOA */
+
+#ifdef HAVE_UOA
+  g_object_unref (priv->uoa_handler);
+#endif /* HAVE_UOA */
+
+  g_hash_table_unref (priv->retry_passwords);
 
   G_OBJECT_CLASS (empathy_auth_factory_parent_class)->dispose (object);
 }
@@ -324,10 +720,15 @@ static void
 empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
 {
   GObjectClass *oclass = G_OBJECT_CLASS (klass);
+  TpBaseClientClass *base_client_cls = TP_BASE_CLIENT_CLASS (klass);
 
   oclass->constructor = empathy_auth_factory_constructor;
+  oclass->constructed = empathy_auth_factory_constructed;
   oclass->dispose = empathy_auth_factory_dispose;
 
+  base_client_cls->handle_channels = handle_channels;
+  base_client_cls->observe_channels = observe_channels;
+
   g_type_class_add_private (klass, sizeof (EmpathyAuthFactoryPriv));
 
   signals[NEW_SERVER_TLS_HANDLER] =
@@ -335,7 +736,7 @@ empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
       G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, 0,
       NULL, NULL,
-      g_cclosure_marshal_VOID__OBJECT,
+      g_cclosure_marshal_generic,
       G_TYPE_NONE,
       1, EMPATHY_TYPE_SERVER_TLS_HANDLER);
 
@@ -344,22 +745,41 @@ empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
       G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, 0,
       NULL, NULL,
-      g_cclosure_marshal_VOID__OBJECT,
+      g_cclosure_marshal_generic,
       G_TYPE_NONE,
       1, EMPATHY_TYPE_SERVER_SASL_HANDLER);
+
+  signals[AUTH_PASSWORD_FAILED] =
+    g_signal_new ("auth-password-failed",
+      G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, 0,
+      NULL, NULL,
+      g_cclosure_marshal_generic,
+      G_TYPE_NONE,
+      2, TP_TYPE_ACCOUNT, G_TYPE_STRING);
 }
 
 EmpathyAuthFactory *
-empathy_auth_factory_dup_singleton (void)
+empathy_auth_factory_new (TpSimpleClientFactory *factory)
 {
-  return g_object_new (EMPATHY_TYPE_AUTH_FACTORY, NULL);
+  return g_object_new (EMPATHY_TYPE_AUTH_FACTORY,
+      "factory", factory,
+      "name", "Empathy.Auth",
+      NULL);
 }
 
 gboolean
 empathy_auth_factory_register (EmpathyAuthFactory *self,
     GError **error)
 {
-  EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
+  return tp_base_client_register (TP_BASE_CLIENT (self), error);
+}
 
-  return tp_base_client_register (priv->handler, error);
+void
+empathy_auth_factory_save_retry_password (EmpathyAuthFactory *self,
+    TpAccount *account,
+    const gchar *password)
+{
+  g_hash_table_insert (self->priv->retry_passwords,
+      g_object_ref (account), g_strdup (password));
 }