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
23 #include "empathy-auth-factory.h"
25 #include <telepathy-glib/telepathy-glib.h>
27 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
28 #include "empathy-debug.h"
29 #include "empathy-keyring.h"
30 #include "empathy-server-sasl-handler.h"
31 #include "empathy-server-tls-handler.h"
32 #include "empathy-utils.h"
35 #include "empathy-goa-auth-handler.h"
38 #include "extensions/extensions.h"
40 G_DEFINE_TYPE (EmpathyAuthFactory
, empathy_auth_factory
, TP_TYPE_BASE_CLIENT
);
42 struct _EmpathyAuthFactoryPriv
{
43 /* Keep a ref here so the auth client doesn't have to mess with
44 * refs. It will be cleared when the channel (and so the handler)
47 * The channel path of the handler's channel (borrowed gchar *) ->
48 * reffed (EmpathyServerSASLHandler *)
50 GHashTable
*sasl_handlers
;
53 EmpathyGoaAuthHandler
*goa_handler
;
56 /* If an account failed to connect and user enters a new password to try, we
57 * store it in this hash table and will try to use it next time the account
60 * reffed TpAccount -> owned password (gchar *) */
61 GHashTable
*retry_passwords
;
67 NEW_SERVER_TLS_HANDLER
,
68 NEW_SERVER_SASL_HANDLER
,
73 static guint signals
[LAST_SIGNAL
] = { 0, };
75 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
77 static EmpathyAuthFactory
*auth_factory_singleton
= NULL
;
80 TpHandleChannelsContext
*context
;
81 EmpathyAuthFactory
*self
;
85 handler_context_data_free (HandlerContextData
*data
)
87 tp_clear_object (&data
->self
);
88 tp_clear_object (&data
->context
);
90 g_slice_free (HandlerContextData
, data
);
93 static HandlerContextData
*
94 handler_context_data_new (EmpathyAuthFactory
*self
,
95 TpHandleChannelsContext
*context
)
97 HandlerContextData
*data
;
99 data
= g_slice_new0 (HandlerContextData
);
100 data
->self
= g_object_ref (self
);
103 data
->context
= g_object_ref (context
);
109 server_tls_handler_ready_cb (GObject
*source
,
113 EmpathyServerTLSHandler
*handler
;
114 GError
*error
= NULL
;
115 HandlerContextData
*data
= user_data
;
117 handler
= empathy_server_tls_handler_new_finish (res
, &error
);
121 DEBUG ("Failed to create a server TLS handler; error %s",
123 tp_handle_channels_context_fail (data
->context
, error
);
125 g_error_free (error
);
129 tp_handle_channels_context_accept (data
->context
);
130 g_signal_emit (data
->self
, signals
[NEW_SERVER_TLS_HANDLER
], 0,
133 g_object_unref (handler
);
136 handler_context_data_free (data
);
140 sasl_handler_invalidated_cb (EmpathyServerSASLHandler
*handler
,
143 EmpathyAuthFactory
*self
= user_data
;
144 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (self
);
147 channel
= empathy_server_sasl_handler_get_channel (handler
);
148 g_assert (channel
!= NULL
);
150 DEBUG ("SASL handler for channel %s is invalidated, unref it",
151 tp_proxy_get_object_path (channel
));
153 g_hash_table_remove (priv
->sasl_handlers
, tp_proxy_get_object_path (channel
));
157 sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler
*handler
,
158 const gchar
*password
,
159 EmpathyAuthFactory
*self
)
163 account
= empathy_server_sasl_handler_get_account (handler
);
165 g_signal_emit (self
, signals
[AUTH_PASSWORD_FAILED
], 0, account
, password
);
169 server_sasl_handler_ready_cb (GObject
*source
,
173 EmpathyAuthFactoryPriv
*priv
;
174 GError
*error
= NULL
;
175 HandlerContextData
*data
= user_data
;
176 EmpathyServerSASLHandler
*handler
;
178 priv
= GET_PRIV (data
->self
);
179 handler
= empathy_server_sasl_handler_new_finish (res
, &error
);
183 DEBUG ("Failed to create a server SASL handler; error %s",
186 if (data
->context
!= NULL
)
187 tp_handle_channels_context_fail (data
->context
, error
);
189 g_error_free (error
);
194 const gchar
*password
;
197 if (data
->context
!= NULL
)
198 tp_handle_channels_context_accept (data
->context
);
200 channel
= empathy_server_sasl_handler_get_channel (handler
);
201 g_assert (channel
!= NULL
);
203 /* Pass the ref to the hash table */
204 g_hash_table_insert (priv
->sasl_handlers
,
205 (gpointer
) tp_proxy_get_object_path (channel
), handler
);
207 tp_g_signal_connect_object (handler
, "invalidated",
208 G_CALLBACK (sasl_handler_invalidated_cb
), data
->self
, 0);
210 tp_g_signal_connect_object (handler
, "auth-password-failed",
211 G_CALLBACK (sasl_handler_auth_password_failed_cb
), data
->self
, 0);
213 /* Is there a retry password? */
214 account
= empathy_server_sasl_handler_get_account (handler
);
216 password
= g_hash_table_lookup (data
->self
->priv
->retry_passwords
,
218 if (password
!= NULL
)
222 DEBUG ("Use retry password");
224 /* We want to save this new password only if there is another
225 * (wrong) password saved. The SASL handler will only save it if it
226 * manages to connect. */
227 save
= empathy_server_sasl_handler_has_password (handler
);
229 empathy_server_sasl_handler_provide_password (handler
,
232 /* We only want to try this password once */
233 g_hash_table_remove (data
->self
->priv
->retry_passwords
, account
);
236 g_signal_emit (data
->self
, signals
[NEW_SERVER_SASL_HANDLER
], 0,
240 handler_context_data_free (data
);
244 common_checks (EmpathyAuthFactory
*self
,
249 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (self
);
251 const GError
*dbus_error
;
252 EmpathyServerSASLHandler
*handler
;
254 /* there can't be more than one ServerTLSConnection or
255 * ServerAuthentication channels at the same time, for the same
256 * connection/account.
258 if (g_list_length (channels
) != 1)
260 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
261 "Can't %s more than one ServerTLSConnection or ServerAuthentication "
262 "channel for the same connection.", observe
? "observe" : "handle");
267 channel
= channels
->data
;
269 if (tp_channel_get_channel_type_id (channel
) !=
270 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
)
272 /* If we are observing we care only about ServerAuthentication channels.
273 * If we are handling we care about ServerAuthentication and
274 * ServerTLSConnection channels. */
276 || tp_channel_get_channel_type_id (channel
) !=
277 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION
)
279 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
280 "Can only %s ServerTLSConnection or ServerAuthentication channels, "
281 "this was a %s channel", observe
? "observe" : "handle",
282 tp_channel_get_channel_type (channel
));
288 handler
= g_hash_table_lookup (priv
->sasl_handlers
,
289 tp_proxy_get_object_path (channel
));
291 if (tp_channel_get_channel_type_id (channel
) ==
292 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
293 && handler
!= NULL
&&
296 g_set_error (error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
297 "We are already handling this channel: %s",
298 tp_proxy_get_object_path (channel
));
303 dbus_error
= tp_proxy_get_invalidated (channel
);
304 if (dbus_error
!= NULL
)
306 *error
= g_error_copy (dbus_error
);
314 handle_channels (TpBaseClient
*handler
,
316 TpConnection
*connection
,
318 GList
*requests_satisfied
,
319 gint64 user_action_time
,
320 TpHandleChannelsContext
*context
)
323 GError
*error
= NULL
;
324 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (handler
);
325 HandlerContextData
*data
;
327 DEBUG ("Handle TLS or SASL carrier channels.");
329 if (!common_checks (self
, channels
, FALSE
, &error
))
331 DEBUG ("Failed checks: %s", error
->message
);
332 tp_handle_channels_context_fail (context
, error
);
333 g_clear_error (&error
);
337 /* The common checks above have checked this is fine. */
338 channel
= channels
->data
;
340 /* Only password authentication is supported from here */
341 if (tp_channel_get_channel_type_id (channel
) ==
342 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
&&
343 !empathy_sasl_channel_supports_mechanism (channel
,
344 "X-TELEPATHY-PASSWORD"))
346 g_set_error_literal (&error
, TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
347 "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
348 DEBUG ("%s", error
->message
);
349 tp_handle_channels_context_fail (context
, error
);
350 g_clear_error (&error
);
354 data
= handler_context_data_new (self
, context
);
355 tp_handle_channels_context_delay (context
);
357 /* create a handler */
358 if (tp_channel_get_channel_type_id (channel
) ==
359 EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION
)
361 empathy_server_tls_handler_new_async (channel
, server_tls_handler_ready_cb
,
364 else if (tp_channel_get_channel_type_id (channel
) ==
365 TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
)
367 empathy_server_sasl_handler_new_async (account
, channel
,
368 server_sasl_handler_ready_cb
, data
);
374 EmpathyAuthFactory
*self
;
375 TpObserveChannelsContext
*context
;
376 TpChannelDispatchOperation
*dispatch_operation
;
379 } ObserveChannelsData
;
382 observe_channels_data_free (ObserveChannelsData
*data
)
384 g_object_unref (data
->context
);
385 g_object_unref (data
->account
);
386 g_object_unref (data
->channel
);
387 g_object_unref (data
->dispatch_operation
);
388 g_slice_free (ObserveChannelsData
, data
);
392 password_claim_cb (GObject
*source
,
393 GAsyncResult
*result
,
396 ObserveChannelsData
*data
= user_data
;
397 GError
*error
= NULL
;
399 if (!tp_channel_dispatch_operation_claim_with_finish (
400 TP_CHANNEL_DISPATCH_OPERATION (source
), result
, &error
))
402 DEBUG ("Failed to call Claim: %s", error
->message
);
403 g_clear_error (&error
);
407 HandlerContextData
*h_data
;
409 DEBUG ("Claim called successfully");
411 h_data
= handler_context_data_new (data
->self
, NULL
);
413 empathy_server_sasl_handler_new_async (TP_ACCOUNT (data
->account
),
414 data
->channel
, server_sasl_handler_ready_cb
, h_data
);
417 observe_channels_data_free (data
);
421 get_password_cb (GObject
*source
,
422 GAsyncResult
*result
,
425 ObserveChannelsData
*data
= user_data
;
427 if (empathy_keyring_get_account_password_finish (TP_ACCOUNT (source
), result
, NULL
) == NULL
)
429 /* We don't actually mind if this fails, just let the approver
430 * go ahead and take the channel. */
432 DEBUG ("We don't have a password for account %s, letting the event "
433 "manager approver take it", tp_proxy_get_object_path (source
));
435 tp_observe_channels_context_accept (data
->context
);
436 observe_channels_data_free (data
);
440 DEBUG ("We have a password for account %s, calling Claim",
441 tp_proxy_get_object_path (source
));
443 tp_channel_dispatch_operation_claim_with_async (data
->dispatch_operation
,
444 TP_BASE_CLIENT (data
->self
), password_claim_cb
, data
);
446 tp_observe_channels_context_accept (data
->context
);
452 goa_claim_cb (GObject
*source
,
453 GAsyncResult
*result
,
456 ObserveChannelsData
*data
= user_data
;
457 EmpathyAuthFactory
*self
= data
->self
;
458 GError
*error
= NULL
;
460 if (!tp_channel_dispatch_operation_claim_with_finish (data
->dispatch_operation
,
463 DEBUG ("Failed to claim: %s", error
->message
);
464 g_clear_error (&error
);
468 empathy_goa_auth_handler_start (self
->priv
->goa_handler
,
469 data
->channel
, data
->account
);
472 observe_channels_data_free (data
);
474 #endif /* HAVE_GOA */
477 observe_channels (TpBaseClient
*client
,
479 TpConnection
*connection
,
481 TpChannelDispatchOperation
*dispatch_operation
,
483 TpObserveChannelsContext
*context
)
485 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (client
);
487 GError
*error
= NULL
;
488 ObserveChannelsData
*data
;
490 DEBUG ("New auth channel to observe");
492 if (!common_checks (self
, channels
, TRUE
, &error
))
494 DEBUG ("Failed checks: %s", error
->message
);
495 tp_observe_channels_context_fail (context
, error
);
496 g_clear_error (&error
);
500 /* The common checks above have checked this is fine. */
501 channel
= channels
->data
;
503 data
= g_slice_new0 (ObserveChannelsData
);
505 data
->context
= g_object_ref (context
);
506 data
->dispatch_operation
= g_object_ref (dispatch_operation
);
507 data
->account
= g_object_ref (account
);
508 data
->channel
= g_object_ref (channel
);
512 if (empathy_goa_auth_handler_supports (self
->priv
->goa_handler
, channel
, account
))
514 DEBUG ("Supported GOA account (%s), claim SASL channel",
515 tp_proxy_get_object_path (account
));
517 tp_channel_dispatch_operation_claim_with_async (dispatch_operation
,
518 client
, goa_claim_cb
, data
);
519 tp_observe_channels_context_accept (context
);
522 #endif /* HAVE_GOA */
525 if (empathy_sasl_channel_supports_mechanism (data
->channel
,
526 "X-TELEPATHY-PASSWORD"))
528 if (g_hash_table_lookup (self
->priv
->retry_passwords
, account
) != NULL
)
530 DEBUG ("We have a retry password for account %s, calling Claim",
531 tp_account_get_path_suffix (account
));
533 tp_channel_dispatch_operation_claim_with_async (dispatch_operation
,
534 client
, password_claim_cb
, data
);
536 tp_observe_channels_context_accept (context
);
540 empathy_keyring_get_account_password_async (data
->account
,
541 get_password_cb
, data
);
542 tp_observe_channels_context_delay (context
);
547 error
= g_error_new_literal (TP_ERROR
, TP_ERROR_INVALID_ARGUMENT
,
548 "Unknown auth mechanism");
549 tp_observe_channels_context_fail (context
, error
);
550 g_clear_error (&error
);
552 observe_channels_data_free (data
);
556 empathy_auth_factory_constructor (GType type
,
558 GObjectConstructParam
*params
)
562 if (auth_factory_singleton
!= NULL
)
564 retval
= g_object_ref (auth_factory_singleton
);
568 retval
= G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->constructor
569 (type
, n_params
, params
);
571 auth_factory_singleton
= EMPATHY_AUTH_FACTORY (retval
);
572 g_object_add_weak_pointer (retval
, (gpointer
*) &auth_factory_singleton
);
579 empathy_auth_factory_init (EmpathyAuthFactory
*self
)
581 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
582 EMPATHY_TYPE_AUTH_FACTORY
, EmpathyAuthFactoryPriv
);
584 self
->priv
->sasl_handlers
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
585 NULL
, g_object_unref
);
588 self
->priv
->goa_handler
= empathy_goa_auth_handler_new ();
589 #endif /* HAVE_GOA */
591 self
->priv
->retry_passwords
= g_hash_table_new_full (NULL
, NULL
,
592 g_object_unref
, g_free
);
596 empathy_auth_factory_constructed (GObject
*obj
)
598 EmpathyAuthFactory
*self
= EMPATHY_AUTH_FACTORY (obj
);
599 TpBaseClient
*client
= TP_BASE_CLIENT (self
);
601 /* chain up to TpBaseClient first */
602 G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->constructed (obj
);
604 tp_base_client_set_handler_bypass_approval (client
, FALSE
);
606 /* Handle ServerTLSConnection and ServerAuthentication channels */
607 tp_base_client_take_handler_filter (client
, tp_asv_new (
609 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
610 EMP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION
,
611 /* AuthenticationMethod */
612 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE
, G_TYPE_UINT
,
613 TP_HANDLE_TYPE_NONE
, NULL
));
615 tp_base_client_take_handler_filter (client
, tp_asv_new (
617 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
618 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION
,
619 /* AuthenticationMethod */
620 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD
,
621 G_TYPE_STRING
, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION
,
624 /* We are also an observer so that we can see new auth channels
625 * popping up and if we have the password already saved to one
626 * account where an auth channel has just appeared we can call
627 * Claim() on the CDO so the approver won't get it, which makes
630 /* Observe ServerAuthentication channels */
631 tp_base_client_take_observer_filter (client
, tp_asv_new (
633 TP_PROP_CHANNEL_CHANNEL_TYPE
, G_TYPE_STRING
,
634 TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION
,
635 /* AuthenticationMethod */
636 TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD
,
637 G_TYPE_STRING
, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION
,
640 tp_base_client_set_observer_delay_approvers (client
, TRUE
);
644 empathy_auth_factory_dispose (GObject
*object
)
646 EmpathyAuthFactoryPriv
*priv
= GET_PRIV (object
);
648 if (priv
->dispose_run
)
651 priv
->dispose_run
= TRUE
;
653 g_hash_table_unref (priv
->sasl_handlers
);
656 g_object_unref (priv
->goa_handler
);
657 #endif /* HAVE_GOA */
659 g_hash_table_unref (priv
->retry_passwords
);
661 G_OBJECT_CLASS (empathy_auth_factory_parent_class
)->dispose (object
);
665 empathy_auth_factory_class_init (EmpathyAuthFactoryClass
*klass
)
667 GObjectClass
*oclass
= G_OBJECT_CLASS (klass
);
668 TpBaseClientClass
*base_client_cls
= TP_BASE_CLIENT_CLASS (klass
);
670 oclass
->constructor
= empathy_auth_factory_constructor
;
671 oclass
->constructed
= empathy_auth_factory_constructed
;
672 oclass
->dispose
= empathy_auth_factory_dispose
;
674 base_client_cls
->handle_channels
= handle_channels
;
675 base_client_cls
->observe_channels
= observe_channels
;
677 g_type_class_add_private (klass
, sizeof (EmpathyAuthFactoryPriv
));
679 signals
[NEW_SERVER_TLS_HANDLER
] =
680 g_signal_new ("new-server-tls-handler",
681 G_TYPE_FROM_CLASS (klass
),
682 G_SIGNAL_RUN_LAST
, 0,
684 g_cclosure_marshal_generic
,
686 1, EMPATHY_TYPE_SERVER_TLS_HANDLER
);
688 signals
[NEW_SERVER_SASL_HANDLER
] =
689 g_signal_new ("new-server-sasl-handler",
690 G_TYPE_FROM_CLASS (klass
),
691 G_SIGNAL_RUN_LAST
, 0,
693 g_cclosure_marshal_generic
,
695 1, EMPATHY_TYPE_SERVER_SASL_HANDLER
);
697 signals
[AUTH_PASSWORD_FAILED
] =
698 g_signal_new ("auth-password-failed",
699 G_TYPE_FROM_CLASS (klass
),
700 G_SIGNAL_RUN_LAST
, 0,
702 g_cclosure_marshal_generic
,
704 2, TP_TYPE_ACCOUNT
, G_TYPE_STRING
);
708 empathy_auth_factory_new (TpSimpleClientFactory
*factory
)
710 return g_object_new (EMPATHY_TYPE_AUTH_FACTORY
,
712 "name", "Empathy.Auth",
717 empathy_auth_factory_register (EmpathyAuthFactory
*self
,
720 return tp_base_client_register (TP_BASE_CLIENT (self
), error
);
724 empathy_auth_factory_save_retry_password (EmpathyAuthFactory
*self
,
726 const gchar
*password
)
728 g_hash_table_insert (self
->priv
->retry_passwords
,
729 g_object_ref (account
), g_strdup (password
));