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 <tp-account-widgets/tpaw-keyring.h>
26 #include "empathy-sasl-mechanisms.h"
27 #include "empathy-server-sasl-handler.h"
28 #include "empathy-server-tls-handler.h"
29 #include "empathy-utils.h"
32 #include "empathy-goa-auth-handler.h"
36 #include "empathy-uoa-auth-handler.h"
39 #include "extensions.h"
41 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
42 #include "empathy-debug.h"
44 G_DEFINE_TYPE (EmpathyAuthFactory, empathy_auth_factory, TP_TYPE_BASE_CLIENT);
46 struct _EmpathyAuthFactoryPriv {
47 /* Keep a ref here so the auth client doesn't have to mess with
48 * refs. It will be cleared when the channel (and so the handler)
51 * The channel path of the handler's channel (borrowed gchar *) ->
52 * reffed (EmpathyServerSASLHandler *)
54 GHashTable *sasl_handlers;
57 EmpathyGoaAuthHandler *goa_handler;
61 EmpathyUoaAuthHandler *uoa_handler;
64 /* If an account failed to connect and user enters a new password to try, we
65 * store it in this hash table and will try to use it next time the account
68 * reffed TpAccount -> owned password (gchar *) */
69 GHashTable *retry_passwords;
75 NEW_SERVER_TLS_HANDLER,
76 NEW_SERVER_SASL_HANDLER,
81 static guint signals[LAST_SIGNAL] = { 0, };
83 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
85 static EmpathyAuthFactory *auth_factory_singleton = NULL;
88 TpHandleChannelsContext *context;
89 EmpathyAuthFactory *self;
93 handler_context_data_free (HandlerContextData *data)
95 tp_clear_object (&data->self);
96 tp_clear_object (&data->context);
98 g_slice_free (HandlerContextData, data);
101 static HandlerContextData *
102 handler_context_data_new (EmpathyAuthFactory *self,
103 TpHandleChannelsContext *context)
105 HandlerContextData *data;
107 data = g_slice_new0 (HandlerContextData);
108 data->self = g_object_ref (self);
111 data->context = g_object_ref (context);
117 server_tls_handler_ready_cb (GObject *source,
121 EmpathyServerTLSHandler *handler;
122 GError *error = NULL;
123 HandlerContextData *data = user_data;
125 handler = empathy_server_tls_handler_new_finish (res, &error);
129 DEBUG ("Failed to create a server TLS handler; error %s",
131 tp_handle_channels_context_fail (data->context, error);
133 g_error_free (error);
137 tp_handle_channels_context_accept (data->context);
138 g_signal_emit (data->self, signals[NEW_SERVER_TLS_HANDLER], 0,
141 g_object_unref (handler);
144 handler_context_data_free (data);
148 sasl_handler_invalidated_cb (EmpathyServerSASLHandler *handler,
151 EmpathyAuthFactory *self = user_data;
152 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
155 channel = empathy_server_sasl_handler_get_channel (handler);
156 g_assert (channel != NULL);
158 DEBUG ("SASL handler for channel %s is invalidated, unref it",
159 tp_proxy_get_object_path (channel));
161 g_hash_table_remove (priv->sasl_handlers, tp_proxy_get_object_path (channel));
165 sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler *handler,
166 const gchar *password,
167 EmpathyAuthFactory *self)
171 account = empathy_server_sasl_handler_get_account (handler);
173 g_signal_emit (self, signals[AUTH_PASSWORD_FAILED], 0, account, password);
177 server_sasl_handler_ready_cb (GObject *source,
181 EmpathyAuthFactoryPriv *priv;
182 GError *error = NULL;
183 HandlerContextData *data = user_data;
184 EmpathyServerSASLHandler *handler;
186 priv = GET_PRIV (data->self);
187 handler = empathy_server_sasl_handler_new_finish (res, &error);
191 DEBUG ("Failed to create a server SASL handler; error %s",
194 if (data->context != NULL)
195 tp_handle_channels_context_fail (data->context, error);
197 g_error_free (error);
202 const gchar *password;
205 if (data->context != NULL)
206 tp_handle_channels_context_accept (data->context);
208 channel = empathy_server_sasl_handler_get_channel (handler);
209 g_assert (channel != NULL);
211 /* Pass the ref to the hash table */
212 g_hash_table_insert (priv->sasl_handlers,
213 (gpointer) tp_proxy_get_object_path (channel), handler);
215 tp_g_signal_connect_object (handler, "invalidated",
216 G_CALLBACK (sasl_handler_invalidated_cb), data->self, 0);
218 tp_g_signal_connect_object (handler, "auth-password-failed",
219 G_CALLBACK (sasl_handler_auth_password_failed_cb), data->self, 0);
221 /* Is there a retry password? */
222 account = empathy_server_sasl_handler_get_account (handler);
224 password = g_hash_table_lookup (data->self->priv->retry_passwords,
226 if (password != NULL)
230 DEBUG ("Use retry password");
232 /* We want to save this new password only if there is another
233 * (wrong) password saved. The SASL handler will only save it if it
234 * manages to connect. */
235 save = empathy_server_sasl_handler_has_password (handler);
237 empathy_server_sasl_handler_provide_password (handler,
240 /* We only want to try this password once */
241 g_hash_table_remove (data->self->priv->retry_passwords, account);
244 g_signal_emit (data->self, signals[NEW_SERVER_SASL_HANDLER], 0,
248 handler_context_data_free (data);
252 common_checks (EmpathyAuthFactory *self,
257 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
259 const GError *dbus_error;
260 EmpathyServerSASLHandler *handler;
262 /* there can't be more than one ServerTLSConnection or
263 * ServerAuthentication channels at the same time, for the same
264 * connection/account.
266 if (g_list_length (channels) != 1)
268 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
269 "Can't %s more than one ServerTLSConnection or ServerAuthentication "
270 "channel for the same connection.", observe ? "observe" : "handle");
275 channel = channels->data;
277 if (tp_channel_get_channel_type_id (channel) !=
278 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
280 /* If we are observing we care only about ServerAuthentication channels.
281 * If we are handling we care about ServerAuthentication and
282 * ServerTLSConnection channels. */
284 || tp_channel_get_channel_type_id (channel) !=
285 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
287 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
288 "Can only %s ServerTLSConnection or ServerAuthentication channels, "
289 "this was a %s channel", observe ? "observe" : "handle",
290 tp_channel_get_channel_type (channel));
296 handler = g_hash_table_lookup (priv->sasl_handlers,
297 tp_proxy_get_object_path (channel));
299 if (tp_channel_get_channel_type_id (channel) ==
300 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
301 && handler != NULL &&
304 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
305 "We are already handling this channel: %s",
306 tp_proxy_get_object_path (channel));
311 dbus_error = tp_proxy_get_invalidated (channel);
312 if (dbus_error != NULL)
314 *error = g_error_copy (dbus_error);
322 handle_channels (TpBaseClient *handler,
324 TpConnection *connection,
326 GList *requests_satisfied,
327 gint64 user_action_time,
328 TpHandleChannelsContext *context)
331 GError *error = NULL;
332 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (handler);
333 HandlerContextData *data;
335 DEBUG ("Handle TLS or SASL carrier channels.");
337 if (!common_checks (self, channels, FALSE, &error))
339 DEBUG ("Failed checks: %s", error->message);
340 tp_handle_channels_context_fail (context, error);
341 g_clear_error (&error);
345 /* The common checks above have checked this is fine. */
346 channel = channels->data;
348 /* Only password authentication is supported from here */
349 if (tp_channel_get_channel_type_id (channel) ==
350 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION &&
351 !empathy_sasl_channel_supports_mechanism (channel,
352 "X-TELEPATHY-PASSWORD"))
354 g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
355 "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
356 DEBUG ("%s", error->message);
357 tp_handle_channels_context_fail (context, error);
358 g_clear_error (&error);
362 data = handler_context_data_new (self, context);
363 tp_handle_channels_context_delay (context);
365 /* create a handler */
366 if (tp_channel_get_channel_type_id (channel) ==
367 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
369 empathy_server_tls_handler_new_async (channel, server_tls_handler_ready_cb,
372 else if (tp_channel_get_channel_type_id (channel) ==
373 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
375 empathy_server_sasl_handler_new_async (account, channel,
376 server_sasl_handler_ready_cb, data);
382 EmpathyAuthFactory *self;
383 TpObserveChannelsContext *context;
384 TpChannelDispatchOperation *dispatch_operation;
387 } ObserveChannelsData;
390 observe_channels_data_free (ObserveChannelsData *data)
392 g_object_unref (data->context);
393 g_object_unref (data->account);
394 g_object_unref (data->channel);
395 g_object_unref (data->dispatch_operation);
396 g_slice_free (ObserveChannelsData, data);
400 password_claim_cb (GObject *source,
401 GAsyncResult *result,
404 ObserveChannelsData *data = user_data;
405 GError *error = NULL;
407 if (!tp_channel_dispatch_operation_claim_with_finish (
408 TP_CHANNEL_DISPATCH_OPERATION (source), result, &error))
410 DEBUG ("Failed to call Claim: %s", error->message);
411 g_clear_error (&error);
415 HandlerContextData *h_data;
417 DEBUG ("Claim called successfully");
419 h_data = handler_context_data_new (data->self, NULL);
421 empathy_server_sasl_handler_new_async (TP_ACCOUNT (data->account),
422 data->channel, server_sasl_handler_ready_cb, h_data);
425 observe_channels_data_free (data);
429 get_password_cb (GObject *source,
430 GAsyncResult *result,
433 ObserveChannelsData *data = user_data;
435 if (tpaw_keyring_get_account_password_finish (TP_ACCOUNT (source), result, NULL) == NULL)
437 /* We don't actually mind if this fails, just let the approver
438 * go ahead and take the channel. */
440 DEBUG ("We don't have a password for account %s, letting the event "
441 "manager approver take it", tp_proxy_get_object_path (source));
443 tp_observe_channels_context_accept (data->context);
444 observe_channels_data_free (data);
448 DEBUG ("We have a password for account %s, calling Claim",
449 tp_proxy_get_object_path (source));
451 tp_channel_dispatch_operation_claim_with_async (data->dispatch_operation,
452 TP_BASE_CLIENT (data->self), password_claim_cb, data);
454 tp_observe_channels_context_accept (data->context);
460 goa_claim_cb (GObject *source,
461 GAsyncResult *result,
464 ObserveChannelsData *data = user_data;
465 EmpathyAuthFactory *self = data->self;
466 GError *error = NULL;
468 if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
471 DEBUG ("Failed to claim: %s", error->message);
472 g_clear_error (&error);
476 empathy_goa_auth_handler_start (self->priv->goa_handler,
477 data->channel, data->account);
480 observe_channels_data_free (data);
482 #endif /* HAVE_GOA */
486 uoa_claim_cb (GObject *source,
487 GAsyncResult *result,
490 ObserveChannelsData *data = user_data;
491 EmpathyAuthFactory *self = data->self;
492 GError *error = NULL;
494 if (!tp_channel_dispatch_operation_claim_with_finish (data->dispatch_operation,
497 DEBUG ("Failed to claim: %s", error->message);
498 g_clear_error (&error);
502 empathy_uoa_auth_handler_start (self->priv->uoa_handler,
503 data->channel, data->account);
506 observe_channels_data_free (data);
508 #endif /* HAVE_UOA */
511 observe_channels (TpBaseClient *client,
513 TpConnection *connection,
515 TpChannelDispatchOperation *dispatch_operation,
517 TpObserveChannelsContext *context)
519 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (client);
521 GError *error = NULL;
522 ObserveChannelsData *data;
524 DEBUG ("New auth channel to observe");
526 if (!common_checks (self, channels, TRUE, &error))
528 DEBUG ("Failed checks: %s", error->message);
529 tp_observe_channels_context_fail (context, error);
530 g_clear_error (&error);
534 /* The common checks above have checked this is fine. */
535 channel = channels->data;
537 data = g_slice_new0 (ObserveChannelsData);
539 data->context = g_object_ref (context);
540 data->dispatch_operation = g_object_ref (dispatch_operation);
541 data->account = g_object_ref (account);
542 data->channel = g_object_ref (channel);
546 if (empathy_goa_auth_handler_supports (self->priv->goa_handler, channel, account))
548 DEBUG ("Supported GOA account (%s), claim SASL channel",
549 tp_proxy_get_object_path (account));
551 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
552 client, goa_claim_cb, data);
553 tp_observe_channels_context_accept (context);
556 #endif /* HAVE_GOA */
560 if (empathy_uoa_auth_handler_supports (self->priv->uoa_handler, channel, account))
562 DEBUG ("Supported UOA account (%s), claim SASL channel",
563 tp_proxy_get_object_path (account));
565 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
566 client, uoa_claim_cb, data);
567 tp_observe_channels_context_accept (context);
570 #endif /* HAVE_UOA */
573 if (empathy_sasl_channel_supports_mechanism (data->channel,
574 "X-TELEPATHY-PASSWORD"))
576 if (g_hash_table_lookup (self->priv->retry_passwords, account) != NULL)
578 DEBUG ("We have a retry password for account %s, calling Claim",
579 tp_account_get_path_suffix (account));
581 tp_channel_dispatch_operation_claim_with_async (dispatch_operation,
582 client, password_claim_cb, data);
584 tp_observe_channels_context_accept (context);
588 tpaw_keyring_get_account_password_async (data->account,
589 get_password_cb, data);
590 tp_observe_channels_context_delay (context);
595 error = g_error_new_literal (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
596 "Unknown auth mechanism");
597 tp_observe_channels_context_fail (context, error);
598 g_clear_error (&error);
600 observe_channels_data_free (data);
604 empathy_auth_factory_constructor (GType type,
606 GObjectConstructParam *params)
610 if (auth_factory_singleton != NULL)
612 retval = g_object_ref (auth_factory_singleton);
616 retval = G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructor
617 (type, n_params, params);
619 auth_factory_singleton = EMPATHY_AUTH_FACTORY (retval);
620 g_object_add_weak_pointer (retval, (gpointer *) &auth_factory_singleton);
627 empathy_auth_factory_init (EmpathyAuthFactory *self)
629 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
630 EMPATHY_TYPE_AUTH_FACTORY, EmpathyAuthFactoryPriv);
632 self->priv->sasl_handlers = g_hash_table_new_full (g_str_hash, g_str_equal,
633 NULL, g_object_unref);
636 self->priv->goa_handler = empathy_goa_auth_handler_new ();
637 #endif /* HAVE_GOA */
640 self->priv->uoa_handler = empathy_uoa_auth_handler_new ();
641 #endif /* HAVE_UOA */
643 self->priv->retry_passwords = g_hash_table_new_full (NULL, NULL,
644 g_object_unref, g_free);
648 empathy_auth_factory_constructed (GObject *obj)
650 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (obj);
651 TpBaseClient *client = TP_BASE_CLIENT (self);
653 /* chain up to TpBaseClient first */
654 G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructed (obj);
656 tp_base_client_set_handler_bypass_approval (client, FALSE);
658 /* Handle ServerTLSConnection and ServerAuthentication channels */
659 tp_base_client_take_handler_filter (client, tp_asv_new (
661 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
662 EMP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION,
663 /* AuthenticationMethod */
664 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
665 TP_HANDLE_TYPE_NONE, NULL));
667 tp_base_client_take_handler_filter (client, tp_asv_new (
669 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
670 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
671 /* AuthenticationMethod */
672 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
673 G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
676 /* We are also an observer so that we can see new auth channels
677 * popping up and if we have the password already saved to one
678 * account where an auth channel has just appeared we can call
679 * Claim() on the CDO so the approver won't get it, which makes
682 /* Observe ServerAuthentication channels */
683 tp_base_client_take_observer_filter (client, tp_asv_new (
685 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
686 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
687 /* AuthenticationMethod */
688 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
689 G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
692 tp_base_client_set_observer_delay_approvers (client, TRUE);
696 empathy_auth_factory_dispose (GObject *object)
698 EmpathyAuthFactoryPriv *priv = GET_PRIV (object);
700 if (priv->dispose_run)
703 priv->dispose_run = TRUE;
705 g_hash_table_unref (priv->sasl_handlers);
708 g_object_unref (priv->goa_handler);
709 #endif /* HAVE_GOA */
712 g_object_unref (priv->uoa_handler);
713 #endif /* HAVE_UOA */
715 g_hash_table_unref (priv->retry_passwords);
717 G_OBJECT_CLASS (empathy_auth_factory_parent_class)->dispose (object);
721 empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
723 GObjectClass *oclass = G_OBJECT_CLASS (klass);
724 TpBaseClientClass *base_client_cls = TP_BASE_CLIENT_CLASS (klass);
726 oclass->constructor = empathy_auth_factory_constructor;
727 oclass->constructed = empathy_auth_factory_constructed;
728 oclass->dispose = empathy_auth_factory_dispose;
730 base_client_cls->handle_channels = handle_channels;
731 base_client_cls->observe_channels = observe_channels;
733 g_type_class_add_private (klass, sizeof (EmpathyAuthFactoryPriv));
735 signals[NEW_SERVER_TLS_HANDLER] =
736 g_signal_new ("new-server-tls-handler",
737 G_TYPE_FROM_CLASS (klass),
738 G_SIGNAL_RUN_LAST, 0,
740 g_cclosure_marshal_generic,
742 1, EMPATHY_TYPE_SERVER_TLS_HANDLER);
744 signals[NEW_SERVER_SASL_HANDLER] =
745 g_signal_new ("new-server-sasl-handler",
746 G_TYPE_FROM_CLASS (klass),
747 G_SIGNAL_RUN_LAST, 0,
749 g_cclosure_marshal_generic,
751 1, EMPATHY_TYPE_SERVER_SASL_HANDLER);
753 signals[AUTH_PASSWORD_FAILED] =
754 g_signal_new ("auth-password-failed",
755 G_TYPE_FROM_CLASS (klass),
756 G_SIGNAL_RUN_LAST, 0,
758 g_cclosure_marshal_generic,
760 2, TP_TYPE_ACCOUNT, G_TYPE_STRING);
764 empathy_auth_factory_new (TpSimpleClientFactory *factory)
766 return g_object_new (EMPATHY_TYPE_AUTH_FACTORY,
768 "name", "Empathy.Auth",
773 empathy_auth_factory_register (EmpathyAuthFactory *self,
776 return tp_base_client_register (TP_BASE_CLIENT (self), error);
780 empathy_auth_factory_save_retry_password (EmpathyAuthFactory *self,
782 const gchar *password)
784 g_hash_table_insert (self->priv->retry_passwords,
785 g_object_ref (account), g_strdup (password));