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 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
41 #include "empathy-debug.h"
43 G_DEFINE_TYPE (EmpathyAuthFactory
, empathy_auth_factory
, TP_TYPE_BASE_CLIENT
);
45 struct _EmpathyAuthFactoryPriv
{
46 /* Keep a ref here so the auth client doesn't have to mess with
47 * refs. It will be cleared when the channel (and so the handler)
50 * The channel path of the handler's channel (borrowed gchar *) ->
51 * reffed (EmpathyServerSASLHandler *)
53 GHashTable
*sasl_handlers
;
56 EmpathyGoaAuthHandler
*goa_handler
;
60 EmpathyUoaAuthHandler
*uoa_handler
;
63 /* If an account failed to connect and user enters a new password to try, we
64 * store it in this hash table and will try to use it next time the account
67 * reffed TpAccount -> owned password (gchar *) */
68 GHashTable
*retry_passwords
;
74 NEW_SERVER_TLS_HANDLER
,
75 NEW_SERVER_SASL_HANDLER
,
80 static guint signals
[LAST_SIGNAL
] = { 0, };
82 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
84 static EmpathyAuthFactory
*auth_factory_singleton
= NULL
;
87 TpHandleChannelsContext
*context
;
88 EmpathyAuthFactory
*self
;
92 handler_context_data_free (HandlerContextData
*data
)
94 tp_clear_object (&data
->self
);
95 tp_clear_object (&data
->context
);
97 g_slice_free (HandlerContextData
, data
);
100 static HandlerContextData
*
101 handler_context_data_new (EmpathyAuthFactory
*self
,
102 TpHandleChannelsContext
*context
)
104 HandlerContextData
*data
;
106 data
= g_slice_new0 (HandlerContextData
);
107 data
->self
= g_object_ref (self
);
110 data
->context
= g_object_ref (context
);
116 server_tls_handler_ready_cb (GObject
*source
,
120 EmpathyServerTLSHandler
*handler
;
121 GError
*error
= NULL
;
122 HandlerContextData
*data
= user_data
;
124 handler
= empathy_server_tls_handler_new_finish (res
, &error
);
128 DEBUG ("Failed to create a server TLS handler; error %s",
130 tp_handle_channels_context_fail (data
->context
, error
);
132 g_error_free (error
);
136 tp_handle_channels_context_accept (data
->context
);
137 g_signal_emit (data
->self
, signals
[NEW_SERVER_TLS_HANDLER
], 0,
140 g_object_unref (handler
);
143 handler_context_data_free (data
);
147 sasl_handler_invalidated_cb (EmpathyServerSASLHandler
*handler
,
150 EmpathyAuthFactory
*self
= user_data
;
151 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (self
);
154 channel
= empathy_server_sasl_handler_get_channel (handler
);
155 g_assert (channel
!= NULL
);
157 DEBUG ("SASL handler for channel %s is invalidated, unref it",
158 tp_proxy_get_object_path (channel
));
160 g_hash_table_remove (priv
->sasl_handlers
, tp_proxy_get_object_path (channel
));
164 sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler
*handler
,
165 const gchar
*password
,
166 EmpathyAuthFactory
*self
)
170 account
= empathy_server_sasl_handler_get_account (handler
);
172 g_signal_emit (self
, signals
[AUTH_PASSWORD_FAILED
], 0, account
, password
);
176 server_sasl_handler_ready_cb (GObject
*source
,
180 EmpathyAuthFactoryPriv
*priv
;
181 GError
*error
= NULL
;
182 HandlerContextData
*data
= user_data
;
183 EmpathyServerSASLHandler
*handler
;
185 priv
= GET_PRIV (data
->self
);
186 handler
= empathy_server_sasl_handler_new_finish (res
, &error
);
190 DEBUG ("Failed to create a server SASL handler; error %s",
193 if (data
->context
!= NULL
)
194 tp_handle_channels_context_fail (data
->context
, error
);
196 g_error_free (error
);
201 const gchar
*password
;
204 if (data
->context
!= NULL
)
205 tp_handle_channels_context_accept (data
->context
);
207 channel
= empathy_server_sasl_handler_get_channel (handler
);
208 g_assert (channel
!= NULL
);
210 /* Pass the ref to the hash table */
211 g_hash_table_insert (priv
->sasl_handlers
,
212 (gpointer
) tp_proxy_get_object_path (channel
), handler
);
214 tp_g_signal_connect_object (handler
, "invalidated",
215 G_CALLBACK (sasl_handler_invalidated_cb
), data
->self
, 0);
217 tp_g_signal_connect_object (handler
, "auth-password-failed",
218 G_CALLBACK (sasl_handler_auth_password_failed_cb
), data
->self
, 0);
220 /* Is there a retry password? */
221 account
= empathy_server_sasl_handler_get_account (handler
);
223 password
= g_hash_table_lookup (data
->self
->priv
->retry_passwords
,
225 if (password
!= NULL
)
229 DEBUG ("Use retry password");
231 /* We want to save this new password only if there is another
232 * (wrong) password saved. The SASL handler will only save it if it
233 * manages to connect. */
234 save
= empathy_server_sasl_handler_has_password (handler
);
236 empathy_server_sasl_handler_provide_password (handler
,
239 /* We only want to try this password once */
240 g_hash_table_remove (data
->self
->priv
->retry_passwords
, account
);
243 g_signal_emit (data
->self
, signals
[NEW_SERVER_SASL_HANDLER
], 0,
247 handler_context_data_free (data
);
251 common_checks (EmpathyAuthFactory
*self
,
256 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (self
);
258 const GError
*dbus_error
;
259 EmpathyServerSASLHandler
*handler
;
261 /* there can't be more than one ServerTLSConnection or
262 * ServerAuthentication channels at the same time, for the same
263 * connection/account.
265 if (g_list_length (channels
) != 1)
267 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
268 "Can't %s more than one ServerTLSConnection or ServerAuthentication "
269 "channel for the same connection.", observe
? "observe" : "handle");
274 channel
= channels
->data
;
276 if (tp_channel_get_channel_type_id (channel
) !=
277 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
)
279 /* If we are observing we care only about ServerAuthentication channels.
280 * If we are handling we care about ServerAuthentication and
281 * ServerTLSConnection channels. */
283 || tp_channel_get_channel_type_id (channel
) !=
284 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION
)
286 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
287 "Can only %s ServerTLSConnection or ServerAuthentication channels, "
288 "this was a %s channel", observe
? "observe" : "handle",
289 tp_channel_get_channel_type (channel
));
295 handler
= g_hash_table_lookup (priv
->sasl_handlers
,
296 tp_proxy_get_object_path (channel
));
298 if (tp_channel_get_channel_type_id (channel
) ==
299 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
300 && handler
!= NULL
&&
303 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
304 "We are already handling this channel: %s",
305 tp_proxy_get_object_path (channel
));
310 dbus_error
= tp_proxy_get_invalidated (channel
);
311 if (dbus_error
!= NULL
)
313 *error
= g_error_copy (dbus_error
);
321 handle_channels (TpBaseClient
*handler
,
323 TpConnection
*connection
,
325 GList
*requests_satisfied
,
326 gint64 user_action_time
,
327 TpHandleChannelsContext
*context
)
330 GError
*error
= NULL
;
331 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (handler
);
332 HandlerContextData
*data
;
334 DEBUG ("Handle TLS or SASL carrier channels.");
336 if (!common_checks (self
, channels
, FALSE
, &error
))
338 DEBUG ("Failed checks: %s", error
->message
);
339 tp_handle_channels_context_fail (context
, error
);
340 g_clear_error (&error
);
344 /* The common checks above have checked this is fine. */
345 channel
= channels
->data
;
347 /* Only password authentication is supported from here */
348 if (tp_channel_get_channel_type_id (channel
) ==
349 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
&&
350 !empathy_sasl_channel_supports_mechanism (channel
,
351 "X-TELEPATHY-PASSWORD"))
353 g_set_error_literal (&error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
354 "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
355 DEBUG ("%s", error
->message
);
356 tp_handle_channels_context_fail (context
, error
);
357 g_clear_error (&error
);
361 data
= handler_context_data_new (self
, context
);
362 tp_handle_channels_context_delay (context
);
364 /* create a handler */
365 if (tp_channel_get_channel_type_id (channel
) ==
366 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION
)
368 empathy_server_tls_handler_new_async (channel
, server_tls_handler_ready_cb
,
371 else if (tp_channel_get_channel_type_id (channel
) ==
372 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
)
374 empathy_server_sasl_handler_new_async (account
, channel
,
375 server_sasl_handler_ready_cb
, data
);
381 EmpathyAuthFactory
*self
;
382 TpObserveChannelsContext
*context
;
383 TpChannelDispatchOperation
*dispatch_operation
;
386 } ObserveChannelsData
;
389 observe_channels_data_free (ObserveChannelsData
*data
)
391 g_object_unref (data
->context
);
392 g_object_unref (data
->account
);
393 g_object_unref (data
->channel
);
394 g_object_unref (data
->dispatch_operation
);
395 g_slice_free (ObserveChannelsData
, data
);
399 password_claim_cb (GObject
*source
,
400 GAsyncResult
*result
,
403 ObserveChannelsData
*data
= user_data
;
404 GError
*error
= NULL
;
406 if (!tp_channel_dispatch_operation_claim_with_finish (
407 TP_CHANNEL_DISPATCH_OPERATION (source
), result
, &error
))
409 DEBUG ("Failed to call Claim: %s", error
->message
);
410 g_clear_error (&error
);
414 HandlerContextData
*h_data
;
416 DEBUG ("Claim called successfully");
418 h_data
= handler_context_data_new (data
->self
, NULL
);
420 empathy_server_sasl_handler_new_async (TP_ACCOUNT (data
->account
),
421 data
->channel
, server_sasl_handler_ready_cb
, h_data
);
424 observe_channels_data_free (data
);
428 get_password_cb (GObject
*source
,
429 GAsyncResult
*result
,
432 ObserveChannelsData
*data
= user_data
;
434 if (tpaw_keyring_get_account_password_finish (TP_ACCOUNT (source
), result
, NULL
) == NULL
)
436 /* We don't actually mind if this fails, just let the approver
437 * go ahead and take the channel. */
439 DEBUG ("We don't have a password for account %s, letting the event "
440 "manager approver take it", tp_proxy_get_object_path (source
));
442 tp_observe_channels_context_accept (data
->context
);
443 observe_channels_data_free (data
);
447 DEBUG ("We have a password for account %s, calling Claim",
448 tp_proxy_get_object_path (source
));
450 tp_channel_dispatch_operation_claim_with_async (data
->dispatch_operation
,
451 TP_BASE_CLIENT (data
->self
), password_claim_cb
, data
);
453 tp_observe_channels_context_accept (data
->context
);
459 goa_claim_cb (GObject
*source
,
460 GAsyncResult
*result
,
463 ObserveChannelsData
*data
= user_data
;
464 EmpathyAuthFactory
*self
= data
->self
;
465 GError
*error
= NULL
;
467 if (!tp_channel_dispatch_operation_claim_with_finish (data
->dispatch_operation
,
470 DEBUG ("Failed to claim: %s", error
->message
);
471 g_clear_error (&error
);
475 empathy_goa_auth_handler_start (self
->priv
->goa_handler
,
476 data
->channel
, data
->account
);
479 observe_channels_data_free (data
);
481 #endif /* HAVE_GOA */
485 uoa_claim_cb (GObject
*source
,
486 GAsyncResult
*result
,
489 ObserveChannelsData
*data
= user_data
;
490 EmpathyAuthFactory
*self
= data
->self
;
491 GError
*error
= NULL
;
493 if (!tp_channel_dispatch_operation_claim_with_finish (data
->dispatch_operation
,
496 DEBUG ("Failed to claim: %s", error
->message
);
497 g_clear_error (&error
);
501 empathy_uoa_auth_handler_start (self
->priv
->uoa_handler
,
502 data
->channel
, data
->account
);
505 observe_channels_data_free (data
);
507 #endif /* HAVE_UOA */
510 observe_channels (TpBaseClient
*client
,
512 TpConnection
*connection
,
514 TpChannelDispatchOperation
*dispatch_operation
,
516 TpObserveChannelsContext
*context
)
518 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (client
);
520 GError
*error
= NULL
;
521 ObserveChannelsData
*data
;
523 DEBUG ("New auth channel to observe");
525 if (!common_checks (self
, channels
, TRUE
, &error
))
527 DEBUG ("Failed checks: %s", error
->message
);
528 tp_observe_channels_context_fail (context
, error
);
529 g_clear_error (&error
);
533 /* The common checks above have checked this is fine. */
534 channel
= channels
->data
;
536 data
= g_slice_new0 (ObserveChannelsData
);
538 data
->context
= g_object_ref (context
);
539 data
->dispatch_operation
= g_object_ref (dispatch_operation
);
540 data
->account
= g_object_ref (account
);
541 data
->channel
= g_object_ref (channel
);
545 if (empathy_goa_auth_handler_supports (self
->priv
->goa_handler
, channel
, account
))
547 DEBUG ("Supported GOA account (%s), claim SASL channel",
548 tp_proxy_get_object_path (account
));
550 tp_channel_dispatch_operation_claim_with_async (dispatch_operation
,
551 client
, goa_claim_cb
, data
);
552 tp_observe_channels_context_accept (context
);
555 #endif /* HAVE_GOA */
559 if (empathy_uoa_auth_handler_supports (self
->priv
->uoa_handler
, channel
, account
))
561 DEBUG ("Supported UOA account (%s), claim SASL channel",
562 tp_proxy_get_object_path (account
));
564 tp_channel_dispatch_operation_claim_with_async (dispatch_operation
,
565 client
, uoa_claim_cb
, data
);
566 tp_observe_channels_context_accept (context
);
569 #endif /* HAVE_UOA */
572 if (empathy_sasl_channel_supports_mechanism (data
->channel
,
573 "X-TELEPATHY-PASSWORD"))
575 if (g_hash_table_lookup (self
->priv
->retry_passwords
, account
) != NULL
)
577 DEBUG ("We have a retry password for account %s, calling Claim",
578 tp_account_get_path_suffix (account
));
580 tp_channel_dispatch_operation_claim_with_async (dispatch_operation
,
581 client
, password_claim_cb
, data
);
583 tp_observe_channels_context_accept (context
);
587 tpaw_keyring_get_account_password_async (data
->account
,
588 get_password_cb
, data
);
589 tp_observe_channels_context_delay (context
);
594 error
= g_error_new_literal (TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
595 "Unknown auth mechanism");
596 tp_observe_channels_context_fail (context
, error
);
597 g_clear_error (&error
);
599 observe_channels_data_free (data
);
603 empathy_auth_factory_constructor (GType type
,
605 GObjectConstructParam
*params
)
609 if (auth_factory_singleton
!= NULL
)
611 retval
= g_object_ref (auth_factory_singleton
);
615 retval
= G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->constructor
616 (type
, n_params
, params
);
618 auth_factory_singleton
= EMPATHY_AUTH_FACTORY (retval
);
619 g_object_add_weak_pointer (retval
, (gpointer
*) &auth_factory_singleton
);
626 empathy_auth_factory_init (EmpathyAuthFactory
*self
)
628 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
629 EMPATHY_TYPE_AUTH_FACTORY
, EmpathyAuthFactoryPriv
);
631 self
->priv
->sasl_handlers
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
632 NULL
, g_object_unref
);
635 self
->priv
->goa_handler
= empathy_goa_auth_handler_new ();
636 #endif /* HAVE_GOA */
639 self
->priv
->uoa_handler
= empathy_uoa_auth_handler_new ();
640 #endif /* HAVE_UOA */
642 self
->priv
->retry_passwords
= g_hash_table_new_full (NULL
, NULL
,
643 g_object_unref
, g_free
);
647 empathy_auth_factory_constructed (GObject
*obj
)
649 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (obj
);
650 TpBaseClient
*client
= TP_BASE_CLIENT (self
);
652 /* chain up to TpBaseClient first */
653 G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->constructed (obj
);
655 tp_base_client_set_handler_bypass_approval (client
, FALSE
);
657 /* Handle ServerTLSConnection and ServerAuthentication channels */
658 tp_base_client_take_handler_filter (client
, tp_asv_new (
660 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
661 TP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION
,
662 /* AuthenticationMethod */
663 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE
, G_TYPE_UINT
,
664 TP_HANDLE_TYPE_NONE
, NULL
));
666 tp_base_client_take_handler_filter (client
, tp_asv_new (
668 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
669 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION
,
670 /* AuthenticationMethod */
671 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD
,
672 G_TYPE_STRING
, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION
,
675 /* We are also an observer so that we can see new auth channels
676 * popping up and if we have the password already saved to one
677 * account where an auth channel has just appeared we can call
678 * Claim() on the CDO so the approver won't get it, which makes
681 /* Observe ServerAuthentication channels */
682 tp_base_client_take_observer_filter (client
, tp_asv_new (
684 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
685 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION
,
686 /* AuthenticationMethod */
687 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD
,
688 G_TYPE_STRING
, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION
,
691 tp_base_client_set_observer_delay_approvers (client
, TRUE
);
695 empathy_auth_factory_dispose (GObject
*object
)
697 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (object
);
699 if (priv
->dispose_run
)
702 priv
->dispose_run
= TRUE
;
704 g_hash_table_unref (priv
->sasl_handlers
);
707 g_object_unref (priv
->goa_handler
);
708 #endif /* HAVE_GOA */
711 g_object_unref (priv
->uoa_handler
);
712 #endif /* HAVE_UOA */
714 g_hash_table_unref (priv
->retry_passwords
);
716 G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->dispose (object
);
720 empathy_auth_factory_class_init (EmpathyAuthFactoryClass
*klass
)
722 GObjectClass
*oclass
= G_OBJECT_CLASS (klass
);
723 TpBaseClientClass
*base_client_cls
= TP_BASE_CLIENT_CLASS (klass
);
725 oclass
->constructor
= empathy_auth_factory_constructor
;
726 oclass
->constructed
= empathy_auth_factory_constructed
;
727 oclass
->dispose
= empathy_auth_factory_dispose
;
729 base_client_cls
->handle_channels
= handle_channels
;
730 base_client_cls
->observe_channels
= observe_channels
;
732 g_type_class_add_private (klass
, sizeof (EmpathyAuthFactoryPriv
));
734 signals
[NEW_SERVER_TLS_HANDLER
] =
735 g_signal_new ("new-server-tls-handler",
736 G_TYPE_FROM_CLASS (klass
),
737 G_SIGNAL_RUN_LAST
, 0,
739 g_cclosure_marshal_generic
,
741 1, EMPATHY_TYPE_SERVER_TLS_HANDLER
);
743 signals
[NEW_SERVER_SASL_HANDLER
] =
744 g_signal_new ("new-server-sasl-handler",
745 G_TYPE_FROM_CLASS (klass
),
746 G_SIGNAL_RUN_LAST
, 0,
748 g_cclosure_marshal_generic
,
750 1, EMPATHY_TYPE_SERVER_SASL_HANDLER
);
752 signals
[AUTH_PASSWORD_FAILED
] =
753 g_signal_new ("auth-password-failed",
754 G_TYPE_FROM_CLASS (klass
),
755 G_SIGNAL_RUN_LAST
, 0,
757 g_cclosure_marshal_generic
,
759 2, TP_TYPE_ACCOUNT
, G_TYPE_STRING
);
763 empathy_auth_factory_new (TpSimpleClientFactory
*factory
)
765 return g_object_new (EMPATHY_TYPE_AUTH_FACTORY
,
767 "name", "Empathy.Auth",
772 empathy_auth_factory_register (EmpathyAuthFactory
*self
,
775 return tp_base_client_register (TP_BASE_CLIENT (self
), error
);
779 empathy_auth_factory_save_retry_password (EmpathyAuthFactory
*self
,
781 const gchar
*password
)
783 g_hash_table_insert (self
->priv
->retry_passwords
,
784 g_object_ref (account
), g_strdup (password
));