2 * empathy-auth-factory.c - Source for EmpathyAuthFactory
3 * Copyright (C) 2010 Collabora Ltd.
4 * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "empathy-auth-factory.h"
24 #include <telepathy-glib/telepathy-glib-dbus.h>
25 #include <tp-account-widgets/tpaw-keyring.h>
27 #include "empathy-sasl-mechanisms.h"
28 #include "empathy-server-sasl-handler.h"
29 #include "empathy-server-tls-handler.h"
30 #include "empathy-utils.h"
33 #include "empathy-goa-auth-handler.h"
37 #include "empathy-uoa-auth-handler.h"
40 #include "extensions.h"
42 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
43 #include "empathy-debug.h"
45 G_DEFINE_TYPE (EmpathyAuthFactory, empathy_auth_factory, TP_TYPE_BASE_CLIENT);
47 struct _EmpathyAuthFactoryPriv {
48 /* Keep a ref here so the auth client doesn't have to mess with
49 * refs. It will be cleared when the channel (and so the handler)
52 * The channel path of the handler's channel (borrowed gchar *) ->
53 * reffed (EmpathyServerSASLHandler *)
55 GHashTable *sasl_handlers;
58 EmpathyGoaAuthHandler *goa_handler;
62 EmpathyUoaAuthHandler *uoa_handler;
65 /* If an account failed to connect and user enters a new password to try, we
66 * store it in this hash table and will try to use it next time the account
69 * reffed TpAccount -> owned password (gchar *) */
70 GHashTable *retry_passwords;
76 NEW_SERVER_TLS_HANDLER,
77 NEW_SERVER_SASL_HANDLER,
82 static guint signals[LAST_SIGNAL] = { 0, };
84 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
86 static EmpathyAuthFactory *auth_factory_singleton = NULL;
89 TpHandleChannelsContext *context;
90 EmpathyAuthFactory *self;
94 handler_context_data_free (HandlerContextData *data)
96 tp_clear_object (&data->self);
97 tp_clear_object (&data->context);
99 g_slice_free (HandlerContextData, data);
102 static HandlerContextData *
103 handler_context_data_new (EmpathyAuthFactory *self,
104 TpHandleChannelsContext *context)
106 HandlerContextData *data;
108 data = g_slice_new0 (HandlerContextData);
109 data->self = g_object_ref (self);
112 data->context = g_object_ref (context);
118 server_tls_handler_ready_cb (GObject *source,
122 EmpathyServerTLSHandler *handler;
123 GError *error = NULL;
124 HandlerContextData *data = user_data;
126 handler = empathy_server_tls_handler_new_finish (res, &error);
130 DEBUG ("Failed to create a server TLS handler; error %s",
132 tp_handle_channels_context_fail (data->context, error);
134 g_error_free (error);
138 tp_handle_channels_context_accept (data->context);
139 g_signal_emit (data->self, signals[NEW_SERVER_TLS_HANDLER], 0,
142 g_object_unref (handler);
145 handler_context_data_free (data);
149 sasl_handler_invalidated_cb (EmpathyServerSASLHandler *handler,
152 EmpathyAuthFactory *self = user_data;
153 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
156 channel = empathy_server_sasl_handler_get_channel (handler);
157 g_assert (channel != NULL);
159 DEBUG ("SASL handler for channel %s is invalidated, unref it",
160 tp_proxy_get_object_path (channel));
162 g_hash_table_remove (priv->sasl_handlers, tp_proxy_get_object_path (channel));
166 sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler *handler,
167 const gchar *password,
168 EmpathyAuthFactory *self)
172 account = empathy_server_sasl_handler_get_account (handler);
174 g_signal_emit (self, signals[AUTH_PASSWORD_FAILED], 0, account, password);
178 server_sasl_handler_ready_cb (GObject *source,
182 EmpathyAuthFactoryPriv *priv;
183 GError *error = NULL;
184 HandlerContextData *data = user_data;
185 EmpathyServerSASLHandler *handler;
187 priv = GET_PRIV (data->self);
188 handler = empathy_server_sasl_handler_new_finish (res, &error);
192 DEBUG ("Failed to create a server SASL handler; error %s",
195 if (data->context != NULL)
196 tp_handle_channels_context_fail (data->context, error);
198 g_error_free (error);
203 const gchar *password;
206 if (data->context != NULL)
207 tp_handle_channels_context_accept (data->context);
209 channel = empathy_server_sasl_handler_get_channel (handler);
210 g_assert (channel != NULL);
212 /* Pass the ref to the hash table */
213 g_hash_table_insert (priv->sasl_handlers,
214 (gpointer) tp_proxy_get_object_path (channel), handler);
216 tp_g_signal_connect_object (handler, "invalidated",
217 G_CALLBACK (sasl_handler_invalidated_cb), data->self, 0);
219 tp_g_signal_connect_object (handler, "auth-password-failed",
220 G_CALLBACK (sasl_handler_auth_password_failed_cb), data->self, 0);
222 /* Is there a retry password? */
223 account = empathy_server_sasl_handler_get_account (handler);
225 password = g_hash_table_lookup (data->self->priv->retry_passwords,
227 if (password != NULL)
231 DEBUG ("Use retry password");
233 /* We want to save this new password only if there is another
234 * (wrong) password saved. The SASL handler will only save it if it
235 * manages to connect. */
236 save = empathy_server_sasl_handler_has_password (handler);
238 empathy_server_sasl_handler_provide_password (handler,
241 /* We only want to try this password once */
242 g_hash_table_remove (data->self->priv->retry_passwords, account);
245 g_signal_emit (data->self, signals[NEW_SERVER_SASL_HANDLER], 0,
249 handler_context_data_free (data);
253 common_checks (EmpathyAuthFactory *self,
258 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
260 const GError *dbus_error;
261 EmpathyServerSASLHandler *handler;
263 /* there can't be more than one ServerTLSConnection or
264 * ServerAuthentication channels at the same time, for the same
265 * connection/account.
267 if (g_list_length (channels) != 1)
269 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
270 "Can't %s more than one ServerTLSConnection or ServerAuthentication "
271 "channel for the same connection.", observe ? "observe" : "handle");
276 channel = channels->data;
278 if (tp_channel_get_channel_type_id (channel) !=
279 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
281 /* If we are observing we care only about ServerAuthentication channels.
282 * If we are handling we care about ServerAuthentication and
283 * ServerTLSConnection channels. */
285 || tp_channel_get_channel_type_id (channel) !=
286 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
288 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
289 "Can only %s ServerTLSConnection or ServerAuthentication channels, "
290 "this was a %s channel", observe ? "observe" : "handle",
291 tp_channel_get_channel_type (channel));
297 handler = g_hash_table_lookup (priv->sasl_handlers,
298 tp_proxy_get_object_path (channel));
300 if (tp_channel_get_channel_type_id (channel) ==
301 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
302 && handler != NULL &&
305 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
306 "We are already handling this channel: %s",
307 tp_proxy_get_object_path (channel));
312 dbus_error = tp_proxy_get_invalidated (channel);
313 if (dbus_error != NULL)
315 *error = g_error_copy (dbus_error);
323 handle_channels (TpBaseClient *handler,
325 TpConnection *connection,
327 GList *requests_satisfied,
328 gint64 user_action_time,
329 TpHandleChannelsContext *context)
332 GError *error = NULL;
333 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (handler);
334 HandlerContextData *data;
336 DEBUG ("Handle TLS or SASL carrier channels.");
338 if (!common_checks (self, channels, FALSE, &error))
340 DEBUG ("Failed checks: %s", error->message);
341 tp_handle_channels_context_fail (context, error);
342 g_clear_error (&error);
346 /* The common checks above have checked this is fine. */
347 channel = channels->data;
349 /* Only password authentication is supported from here */
350 if (tp_channel_get_channel_type_id (channel) ==
351 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION &&
352 !empathy_sasl_channel_supports_mechanism (channel,
353 "X-TELEPATHY-PASSWORD"))
355 g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
356 "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
357 DEBUG ("%s", error->message);
358 tp_handle_channels_context_fail (context, error);
359 g_clear_error (&error);
363 data = handler_context_data_new (self, context);
364 tp_handle_channels_context_delay (context);
366 /* create a handler */
367 if (tp_channel_get_channel_type_id (channel) ==
368 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
370 empathy_server_tls_handler_new_async (channel, server_tls_handler_ready_cb,
373 else if (tp_channel_get_channel_type_id (channel) ==
374 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
376 empathy_server_sasl_handler_new_async (account, channel,
377 server_sasl_handler_ready_cb, data);
383 EmpathyAuthFactory *self;
384 TpObserveChannelsContext *context;
385 TpChannelDispatchOperation *dispatch_operation;
388 } ObserveChannelsData;
391 observe_channels_data_free (ObserveChannelsData *data)
393 g_object_unref (data->context);
394 g_object_unref (data->account);
395 g_object_unref (data->channel);
396 g_object_unref (data->dispatch_operation);
397 g_slice_free (ObserveChannelsData, data);
401 password_claim_cb (GObject *source,
402 GAsyncResult *result,
405 ObserveChannelsData *data = user_data;
406 GError *error = NULL;
408 if (!tp_channel_dispatch_operation_claim_with_finish (
409 TP_CHANNEL_DISPATCH_OPERATION (source), result, &error))
411 DEBUG ("Failed to call Claim: %s", error->message);
412 g_clear_error (&error);
416 HandlerContextData *h_data;
418 DEBUG ("Claim called successfully");
420 h_data = handler_context_data_new (data->self, NULL);
422 empathy_server_sasl_handler_new_async (TP_ACCOUNT (data->account),
423 data->channel, server_sasl_handler_ready_cb, h_data);
426 observe_channels_data_free (data);
430 get_password_cb (GObject *source,
431 GAsyncResult *result,
434 ObserveChannelsData *data = user_data;
436 if (tpaw_keyring_get_account_password_finish (TP_ACCOUNT (source), result, NULL) == NULL)
438 /* We don't actually mind if this fails, just let the approver
439 * go ahead and take the channel. */
441 DEBUG ("We don't have a password for account %s, letting the event "
442 "manager approver take it", tp_proxy_get_object_path (source));
444 tp_observe_channels_context_accept (data->context);
445 observe_channels_data_free (data);
449 DEBUG ("We have a password for account %s, calling Claim",
450 tp_proxy_get_object_path (source));
452 tp_channel_dispatch_operation_claim_with_async (data->dispatch_operation,
453 TP_BASE_CLIENT (data->self), password_claim_cb, data);
455 tp_observe_channels_context_accept (data->context);
461 goa_claim_cb (GObject *source,
462 GAsyncResult *result,
465 ObserveChannelsData *data = user_data;
466 EmpathyAuthFactory *self = data->self;
467 GError *error = NULL;
469 if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
472 DEBUG ("Failed to claim: %s", error->message);
473 g_clear_error (&error);
477 empathy_goa_auth_handler_start (self->priv->goa_handler,
478 data->channel, data->account);
481 observe_channels_data_free (data);
483 #endif /* HAVE_GOA */
487 uoa_claim_cb (GObject *source,
488 GAsyncResult *result,
491 ObserveChannelsData *data = user_data;
492 EmpathyAuthFactory *self = data->self;
493 GError *error = NULL;
495 if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
498 DEBUG ("Failed to claim: %s", error->message);
499 g_clear_error (&error);
503 empathy_uoa_auth_handler_start (self->priv->uoa_handler,
504 data->channel, data->account);
507 observe_channels_data_free (data);
509 #endif /* HAVE_UOA */
512 observe_channels (TpBaseClient *client,
514 TpConnection *connection,
516 TpChannelDispatchOperation *dispatch_operation,
518 TpObserveChannelsContext *context)
520 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (client);
522 GError *error = NULL;
523 ObserveChannelsData *data;
525 DEBUG ("New auth channel to observe");
527 if (!common_checks (self, channels, TRUE, &error))
529 DEBUG ("Failed checks: %s", error->message);
530 tp_observe_channels_context_fail (context, error);
531 g_clear_error (&error);
535 /* The common checks above have checked this is fine. */
536 channel = channels->data;
538 data = g_slice_new0 (ObserveChannelsData);
540 data->context = g_object_ref (context);
541 data->dispatch_operation = g_object_ref (dispatch_operation);
542 data->account = g_object_ref (account);
543 data->channel = g_object_ref (channel);
547 if (empathy_goa_auth_handler_supports (self->priv->goa_handler, channel, account))
549 DEBUG ("Supported GOA account (%s), claim SASL channel",
550 tp_proxy_get_object_path (account));
552 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
553 client, goa_claim_cb, data);
554 tp_observe_channels_context_accept (context);
557 #endif /* HAVE_GOA */
561 if (empathy_uoa_auth_handler_supports (self->priv->uoa_handler, channel, account))
563 DEBUG ("Supported UOA account (%s), claim SASL channel",
564 tp_proxy_get_object_path (account));
566 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
567 client, uoa_claim_cb, data);
568 tp_observe_channels_context_accept (context);
571 #endif /* HAVE_UOA */
574 if (empathy_sasl_channel_supports_mechanism (data->channel,
575 "X-TELEPATHY-PASSWORD"))
577 if (g_hash_table_lookup (self->priv->retry_passwords, account) != NULL)
579 DEBUG ("We have a retry password for account %s, calling Claim",
580 tp_account_get_path_suffix (account));
582 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
583 client, password_claim_cb, data);
585 tp_observe_channels_context_accept (context);
589 tpaw_keyring_get_account_password_async (data->account,
590 get_password_cb, data);
591 tp_observe_channels_context_delay (context);
596 error = g_error_new_literal (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
597 "Unknown auth mechanism");
598 tp_observe_channels_context_fail (context, error);
599 g_clear_error (&error);
601 observe_channels_data_free (data);
605 empathy_auth_factory_constructor (GType type,
607 GObjectConstructParam *params)
611 if (auth_factory_singleton != NULL)
613 retval = g_object_ref (auth_factory_singleton);
617 retval = G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructor
618 (type, n_params, params);
620 auth_factory_singleton = EMPATHY_AUTH_FACTORY (retval);
621 g_object_add_weak_pointer (retval, (gpointer *) &auth_factory_singleton);
628 empathy_auth_factory_init (EmpathyAuthFactory *self)
630 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
631 EMPATHY_TYPE_AUTH_FACTORY, EmpathyAuthFactoryPriv);
633 self->priv->sasl_handlers = g_hash_table_new_full (g_str_hash, g_str_equal,
634 NULL, g_object_unref);
637 self->priv->goa_handler = empathy_goa_auth_handler_new ();
638 #endif /* HAVE_GOA */
641 self->priv->uoa_handler = empathy_uoa_auth_handler_new ();
642 #endif /* HAVE_UOA */
644 self->priv->retry_passwords = g_hash_table_new_full (NULL, NULL,
645 g_object_unref, g_free);
649 empathy_auth_factory_constructed (GObject *obj)
651 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (obj);
652 TpBaseClient *client = TP_BASE_CLIENT (self);
654 /* chain up to TpBaseClient first */
655 G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructed (obj);
657 tp_base_client_set_handler_bypass_approval (client, FALSE);
659 /* Handle ServerTLSConnection and ServerAuthentication channels */
660 tp_base_client_take_handler_filter (client, tp_asv_new (
662 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
663 EMP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION,
664 /* AuthenticationMethod */
665 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
666 TP_HANDLE_TYPE_NONE, NULL));
668 tp_base_client_take_handler_filter (client, tp_asv_new (
670 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
671 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
672 /* AuthenticationMethod */
673 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
674 G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
677 /* We are also an observer so that we can see new auth channels
678 * popping up and if we have the password already saved to one
679 * account where an auth channel has just appeared we can call
680 * Claim() on the CDO so the approver won't get it, which makes
683 /* Observe ServerAuthentication channels */
684 tp_base_client_take_observer_filter (client, tp_asv_new (
686 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
687 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
688 /* AuthenticationMethod */
689 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
690 G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
693 tp_base_client_set_observer_delay_approvers (client, TRUE);
697 empathy_auth_factory_dispose (GObject *object)
699 EmpathyAuthFactoryPriv *priv = GET_PRIV (object);
701 if (priv->dispose_run)
704 priv->dispose_run = TRUE;
706 g_hash_table_unref (priv->sasl_handlers);
709 g_object_unref (priv->goa_handler);
710 #endif /* HAVE_GOA */
713 g_object_unref (priv->uoa_handler);
714 #endif /* HAVE_UOA */
716 g_hash_table_unref (priv->retry_passwords);
718 G_OBJECT_CLASS (empathy_auth_factory_parent_class)->dispose (object);
722 empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
724 GObjectClass *oclass = G_OBJECT_CLASS (klass);
725 TpBaseClientClass *base_client_cls = TP_BASE_CLIENT_CLASS (klass);
727 oclass->constructor = empathy_auth_factory_constructor;
728 oclass->constructed = empathy_auth_factory_constructed;
729 oclass->dispose = empathy_auth_factory_dispose;
731 base_client_cls->handle_channels = handle_channels;
732 base_client_cls->observe_channels = observe_channels;
734 g_type_class_add_private (klass, sizeof (EmpathyAuthFactoryPriv));
736 signals[NEW_SERVER_TLS_HANDLER] =
737 g_signal_new ("new-server-tls-handler",
738 G_TYPE_FROM_CLASS (klass),
739 G_SIGNAL_RUN_LAST, 0,
741 g_cclosure_marshal_generic,
743 1, EMPATHY_TYPE_SERVER_TLS_HANDLER);
745 signals[NEW_SERVER_SASL_HANDLER] =
746 g_signal_new ("new-server-sasl-handler",
747 G_TYPE_FROM_CLASS (klass),
748 G_SIGNAL_RUN_LAST, 0,
750 g_cclosure_marshal_generic,
752 1, EMPATHY_TYPE_SERVER_SASL_HANDLER);
754 signals[AUTH_PASSWORD_FAILED] =
755 g_signal_new ("auth-password-failed",
756 G_TYPE_FROM_CLASS (klass),
757 G_SIGNAL_RUN_LAST, 0,
759 g_cclosure_marshal_generic,
761 2, TP_TYPE_ACCOUNT, G_TYPE_STRING);
765 empathy_auth_factory_new (TpSimpleClientFactory *factory)
767 return g_object_new (EMPATHY_TYPE_AUTH_FACTORY,
769 "name", "Empathy.Auth",
774 empathy_auth_factory_register (EmpathyAuthFactory *self,
777 return tp_base_client_register (TP_BASE_CLIENT (self), error);
781 empathy_auth_factory_save_retry_password (EmpathyAuthFactory *self,
783 const gchar *password)
785 g_hash_table_insert (self->priv->retry_passwords,
786 g_object_ref (account), g_strdup (password));