account-settings: use TpProtocol's API to get TpConnectionManagerParam
[empathy-mirror.git] / libempathy / empathy-auth-factory.c
blobfb178216d5412c2a77c90020b5346a1d968e6c79
1 /*
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
21 #include <config.h>
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"
34 #ifdef HAVE_GOA
35 #include "empathy-goa-auth-handler.h"
36 #endif /* HAVE_GOA */
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)
45 * gets invalidated.
47 * The channel path of the handler's channel (borrowed gchar *) ->
48 * reffed (EmpathyServerSASLHandler *)
49 * */
50 GHashTable *sasl_handlers;
52 #ifdef HAVE_GOA
53 EmpathyGoaAuthHandler *goa_handler;
54 #endif /* HAVE_GOA */
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
58 * attemps to connect.
60 * reffed TpAccount -> owned password (gchar *) */
61 GHashTable *retry_passwords;
63 gboolean dispose_run;
66 enum {
67 NEW_SERVER_TLS_HANDLER,
68 NEW_SERVER_SASL_HANDLER,
69 AUTH_PASSWORD_FAILED,
70 LAST_SIGNAL,
73 static guint signals[LAST_SIGNAL] = { 0, };
75 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
77 static EmpathyAuthFactory *auth_factory_singleton = NULL;
79 typedef struct {
80 TpHandleChannelsContext *context;
81 EmpathyAuthFactory *self;
82 } HandlerContextData;
84 static void
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);
102 if (context != NULL)
103 data->context = g_object_ref (context);
105 return data;
108 static void
109 server_tls_handler_ready_cb (GObject *source,
110 GAsyncResult *res,
111 gpointer user_data)
113 EmpathyServerTLSHandler *handler;
114 GError *error = NULL;
115 HandlerContextData *data = user_data;
117 handler = empathy_server_tls_handler_new_finish (res, &error);
119 if (error != NULL)
121 DEBUG ("Failed to create a server TLS handler; error %s",
122 error->message);
123 tp_handle_channels_context_fail (data->context, error);
125 g_error_free (error);
127 else
129 tp_handle_channels_context_accept (data->context);
130 g_signal_emit (data->self, signals[NEW_SERVER_TLS_HANDLER], 0,
131 handler);
133 g_object_unref (handler);
136 handler_context_data_free (data);
139 static void
140 sasl_handler_invalidated_cb (EmpathyServerSASLHandler *handler,
141 gpointer user_data)
143 EmpathyAuthFactory *self = user_data;
144 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
145 TpChannel * channel;
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));
156 static void
157 sasl_handler_auth_password_failed_cb (EmpathyServerSASLHandler *handler,
158 const gchar *password,
159 EmpathyAuthFactory *self)
161 TpAccount *account;
163 account = empathy_server_sasl_handler_get_account (handler);
165 g_signal_emit (self, signals[AUTH_PASSWORD_FAILED], 0, account, password);
168 static void
169 server_sasl_handler_ready_cb (GObject *source,
170 GAsyncResult *res,
171 gpointer user_data)
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);
181 if (error != NULL)
183 DEBUG ("Failed to create a server SASL handler; error %s",
184 error->message);
186 if (data->context != NULL)
187 tp_handle_channels_context_fail (data->context, error);
189 g_error_free (error);
191 else
193 TpChannel *channel;
194 const gchar *password;
195 TpAccount *account;
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,
217 account);
218 if (password != NULL)
220 gboolean save;
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,
230 password, save);
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,
237 handler);
240 handler_context_data_free (data);
243 static gboolean
244 common_checks (EmpathyAuthFactory *self,
245 GList *channels,
246 gboolean observe,
247 GError **error)
249 EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
250 TpChannel *channel;
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");
264 return FALSE;
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. */
275 if (observe
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));
284 return FALSE;
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 &&
294 !observe)
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));
300 return FALSE;
303 dbus_error = tp_proxy_get_invalidated (channel);
304 if (dbus_error != NULL)
306 *error = g_error_copy (dbus_error);
307 return FALSE;
310 return TRUE;
313 static void
314 handle_channels (TpBaseClient *handler,
315 TpAccount *account,
316 TpConnection *connection,
317 GList *channels,
318 GList *requests_satisfied,
319 gint64 user_action_time,
320 TpHandleChannelsContext *context)
322 TpChannel *channel;
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);
334 return;
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);
351 return;
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,
362 data);
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);
372 typedef struct
374 EmpathyAuthFactory *self;
375 TpObserveChannelsContext *context;
376 TpChannelDispatchOperation *dispatch_operation;
377 TpAccount *account;
378 TpChannel *channel;
379 } ObserveChannelsData;
381 static void
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);
391 static void
392 password_claim_cb (GObject *source,
393 GAsyncResult *result,
394 gpointer user_data)
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);
405 else
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);
420 static void
421 get_password_cb (GObject *source,
422 GAsyncResult *result,
423 gpointer user_data)
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);
438 else
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);
450 #ifdef HAVE_GOA
451 static void
452 goa_claim_cb (GObject *source,
453 GAsyncResult *result,
454 gpointer user_data)
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,
461 result, &error))
463 DEBUG ("Failed to claim: %s", error->message);
464 g_clear_error (&error);
466 else
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 */
476 static void
477 observe_channels (TpBaseClient *client,
478 TpAccount *account,
479 TpConnection *connection,
480 GList *channels,
481 TpChannelDispatchOperation *dispatch_operation,
482 GList *requests,
483 TpObserveChannelsContext *context)
485 EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (client);
486 TpChannel *channel;
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);
497 return;
500 /* The common checks above have checked this is fine. */
501 channel = channels->data;
503 data = g_slice_new0 (ObserveChannelsData);
504 data->self = self;
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);
510 #ifdef HAVE_GOA
511 /* GOA auth? */
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);
520 return;
522 #endif /* HAVE_GOA */
524 /* Password auth? */
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);
537 return;
540 empathy_keyring_get_account_password_async (data->account,
541 get_password_cb, data);
542 tp_observe_channels_context_delay (context);
543 return;
546 /* Unknown auth */
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);
555 static GObject *
556 empathy_auth_factory_constructor (GType type,
557 guint n_params,
558 GObjectConstructParam *params)
560 GObject *retval;
562 if (auth_factory_singleton != NULL)
564 retval = g_object_ref (auth_factory_singleton);
566 else
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);
575 return retval;
578 static void
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);
587 #ifdef HAVE_GOA
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);
595 static void
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 (
608 /* ChannelType */
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 (
616 /* ChannelType */
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,
622 NULL));
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
628 * sense. */
630 /* Observe ServerAuthentication channels */
631 tp_base_client_take_observer_filter (client, tp_asv_new (
632 /* ChannelType */
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,
638 NULL));
640 tp_base_client_set_observer_delay_approvers (client, TRUE);
643 static void
644 empathy_auth_factory_dispose (GObject *object)
646 EmpathyAuthFactoryPriv *priv = GET_PRIV (object);
648 if (priv->dispose_run)
649 return;
651 priv->dispose_run = TRUE;
653 g_hash_table_unref (priv->sasl_handlers);
655 #ifdef HAVE_GOA
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);
664 static void
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,
683 NULL, NULL,
684 g_cclosure_marshal_generic,
685 G_TYPE_NONE,
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,
692 NULL, NULL,
693 g_cclosure_marshal_generic,
694 G_TYPE_NONE,
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,
701 NULL, NULL,
702 g_cclosure_marshal_generic,
703 G_TYPE_NONE,
704 2, TP_TYPE_ACCOUNT, G_TYPE_STRING);
707 EmpathyAuthFactory *
708 empathy_auth_factory_new (TpSimpleClientFactory *factory)
710 return g_object_new (EMPATHY_TYPE_AUTH_FACTORY,
711 "factory", factory,
712 "name", "Empathy.Auth",
713 NULL);
716 gboolean
717 empathy_auth_factory_register (EmpathyAuthFactory *self,
718 GError **error)
720 return tp_base_client_register (TP_BASE_CLIENT (self), error);
723 void
724 empathy_auth_factory_save_retry_password (EmpathyAuthFactory *self,
725 TpAccount *account,
726 const gchar *password)
728 g_hash_table_insert (self->priv->retry_passwords,
729 g_object_ref (account), g_strdup (password));