2 * empathy-auth-goa.c - Source for Goa SASL authentication
3 * Copyright (C) 2011 Collabora Ltd.
4 * @author Xavier Claessens <xavier.claessens@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 #define GOA_API_IS_SUBJECT_TO_CHANGE /* awesome! */
26 #include <libsoup/soup.h>
29 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
30 #include "empathy-debug.h"
31 #include "empathy-utils.h"
32 #include "empathy-goa-auth-handler.h"
34 #define MECH_FACEBOOK "X-FACEBOOK-PLATFORM"
35 #define MECH_MSN "X-MESSENGER-OAUTH2"
37 static const gchar *supported_mechanisms[] = {
42 struct _EmpathyGoaAuthHandlerPriv
45 gboolean client_preparing;
47 /* List of AuthData waiting for client to be created */
51 G_DEFINE_TYPE (EmpathyGoaAuthHandler, empathy_goa_auth_handler, G_TYPE_OBJECT);
54 empathy_goa_auth_handler_init (EmpathyGoaAuthHandler *self)
56 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
57 EMPATHY_TYPE_GOA_AUTH_HANDLER, EmpathyGoaAuthHandlerPriv);
61 empathy_goa_auth_handler_dispose (GObject *object)
63 EmpathyGoaAuthHandler *self = (EmpathyGoaAuthHandler *) object;
65 /* AuthData keeps a ref on self */
66 g_assert (self->priv->auth_queue == NULL);
68 tp_clear_object (&self->priv->client);
70 G_OBJECT_CLASS (empathy_goa_auth_handler_parent_class)->dispose (object);
74 empathy_goa_auth_handler_class_init (EmpathyGoaAuthHandlerClass *klass)
76 GObjectClass *oclass = G_OBJECT_CLASS (klass);
78 oclass->dispose = empathy_goa_auth_handler_dispose;
80 g_type_class_add_private (klass, sizeof (EmpathyGoaAuthHandlerPriv));
83 EmpathyGoaAuthHandler *
84 empathy_goa_auth_handler_new (void)
86 return g_object_new (EMPATHY_TYPE_GOA_AUTH_HANDLER, NULL);
91 EmpathyGoaAuthHandler *self;
95 GoaObject *goa_object;
100 auth_data_free (AuthData *data)
102 tp_clear_object (&data->self);
103 tp_clear_object (&data->channel);
104 tp_clear_object (&data->account);
105 tp_clear_object (&data->goa_object);
106 g_free (data->access_token);
107 g_slice_free (AuthData, data);
111 fail_auth (AuthData *data)
113 DEBUG ("Auth failed for account %s",
114 tp_proxy_get_object_path (data->account));
116 tp_channel_close_async (data->channel, NULL, NULL);
117 auth_data_free (data);
121 sasl_status_changed_cb (TpChannel *channel,
130 case TP_SASL_STATUS_SERVER_SUCCEEDED:
131 tp_cli_channel_interface_sasl_authentication_call_accept_sasl (channel,
132 -1, NULL, NULL, NULL, NULL);
135 case TP_SASL_STATUS_SUCCEEDED:
136 case TP_SASL_STATUS_SERVER_FAILED:
137 case TP_SASL_STATUS_CLIENT_FAILED:
138 tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
147 facebook_new_challenge_cb (TpChannel *channel,
148 const GArray *challenge,
150 GObject *weak_object)
152 AuthData *data = user_data;
153 GoaOAuth2Based *oauth2;
154 const gchar *client_id;
158 GArray *response_array;
160 DEBUG ("new challenge for %s:\n%s",
161 tp_proxy_get_object_path (data->account),
164 h = soup_form_decode (challenge->data);
166 oauth2 = goa_object_get_oauth2_based (data->goa_object);
167 client_id = goa_oauth2_based_get_client_id (oauth2);
169 /* See https://developers.facebook.com/docs/chat/#platauth */
170 params = g_hash_table_new (g_str_hash, g_str_equal);
171 g_hash_table_insert (params, "method", g_hash_table_lookup (h, "method"));
172 g_hash_table_insert (params, "nonce", g_hash_table_lookup (h, "nonce"));
173 g_hash_table_insert (params, "access_token", data->access_token);
174 g_hash_table_insert (params, "api_key", (gpointer) client_id);
175 g_hash_table_insert (params, "call_id", "0");
176 g_hash_table_insert (params, "v", "1.0");
178 response = soup_form_encode_hash (params);
179 DEBUG ("Response: %s", response);
181 response_array = g_array_new (FALSE, FALSE, sizeof (gchar));
182 g_array_append_vals (response_array, response, strlen (response));
184 tp_cli_channel_interface_sasl_authentication_call_respond (data->channel, -1,
185 response_array, NULL, NULL, NULL, NULL);
187 g_hash_table_unref (h);
188 g_hash_table_unref (params);
189 g_object_unref (oauth2);
191 g_array_unref (response_array);
195 got_oauth2_access_token_cb (GObject *source,
196 GAsyncResult *result,
199 GoaOAuth2Based *oauth2 = (GoaOAuth2Based *) source;
200 AuthData *data = user_data;
202 GError *error = NULL;
204 if (!goa_oauth2_based_call_get_access_token_finish (oauth2,
205 &data->access_token, &expires_in, result, &error))
207 DEBUG ("Failed to get access token: %s", error->message);
209 g_clear_error (&error);
213 DEBUG ("Got access token for %s:\n%s",
214 tp_proxy_get_object_path (data->account),
217 tp_cli_channel_interface_sasl_authentication_connect_to_sasl_status_changed (
218 data->channel, sasl_status_changed_cb, NULL, NULL, NULL, NULL);
219 g_assert_no_error (error);
221 if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_FACEBOOK))
223 /* Give ownership of data to signal connection */
224 tp_cli_channel_interface_sasl_authentication_connect_to_new_challenge (
225 data->channel, facebook_new_challenge_cb,
226 data, (GDestroyNotify) auth_data_free,
229 DEBUG ("Start %s mechanism for account %s", MECH_FACEBOOK,
230 tp_proxy_get_object_path (data->account));
232 tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
233 data->channel, -1, MECH_FACEBOOK, NULL, NULL, NULL, NULL);
235 else if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_MSN))
237 guchar *token_decoded;
238 gsize token_decoded_len;
239 GArray *token_decoded_array;
241 /* Wocky will base64 encode, but token actually already is base64, so we
242 * decode now and it will be re-encoded. */
243 token_decoded = g_base64_decode (data->access_token, &token_decoded_len);
244 token_decoded_array = g_array_new (FALSE, FALSE, sizeof (guchar));
245 g_array_append_vals (token_decoded_array, token_decoded, token_decoded_len);
247 DEBUG ("Start %s mechanism for account %s", MECH_MSN,
248 tp_proxy_get_object_path (data->account));
250 tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
251 data->channel, -1, MECH_MSN, token_decoded_array,
252 NULL, NULL, NULL, NULL);
254 g_array_unref (token_decoded_array);
255 g_free (token_decoded);
256 auth_data_free (data);
260 /* We already checked it supports one of supported_mechanisms, so this
262 g_assert_not_reached ();
267 ensure_credentials_cb (GObject *source,
268 GAsyncResult *result,
271 AuthData *data = user_data;
272 GoaAccount *goa_account = (GoaAccount *) source;
273 GoaOAuth2Based *oauth2;
275 GError *error = NULL;
277 if (!goa_account_call_ensure_credentials_finish (goa_account, &expires_in,
280 DEBUG ("Failed to EnsureCredentials: %s", error->message);
282 g_clear_error (&error);
286 /* We support only oaut2 */
287 oauth2 = goa_object_get_oauth2_based (data->goa_object);
290 DEBUG ("GoaObject does not implement oauth2");
295 DEBUG ("Goa daemon has credentials for %s, get the access token",
296 tp_proxy_get_object_path (data->account));
298 goa_oauth2_based_call_get_access_token (oauth2, NULL,
299 got_oauth2_access_token_cb, data);
301 g_object_unref (oauth2);
305 start_auth (AuthData *data)
307 EmpathyGoaAuthHandler *self = data->self;
308 const GValue *id_value;
310 GList *goa_accounts, *l;
311 gboolean found = FALSE;
313 id_value = tp_account_get_storage_identifier (data->account);
314 id = g_value_get_string (id_value);
316 goa_accounts = goa_client_get_accounts (self->priv->client);
317 for (l = goa_accounts; l != NULL && !found; l = l->next)
319 GoaObject *goa_object = l->data;
320 GoaAccount *goa_account;
322 goa_account = goa_object_get_account (goa_object);
323 if (!tp_strdiff (goa_account_get_id (goa_account), id))
325 data->goa_object = g_object_ref (goa_object);
327 DEBUG ("Found the GoaAccount for %s, ensure credentials",
328 tp_proxy_get_object_path (data->account));
330 goa_account_call_ensure_credentials (goa_account, NULL,
331 ensure_credentials_cb, data);
336 g_object_unref (goa_account);
338 g_list_free_full (goa_accounts, g_object_unref);
342 DEBUG ("Cannot find GoaAccount");
348 client_new_cb (GObject *source,
349 GAsyncResult *result,
352 EmpathyGoaAuthHandler *self = user_data;
354 GError *error = NULL;
356 self->priv->client_preparing = FALSE;
357 self->priv->client = goa_client_new_finish (result, &error);
358 if (self->priv->client == NULL)
360 DEBUG ("Error getting GoaClient: %s", error->message);
361 g_clear_error (&error);
364 /* process queued data */
365 for (l = self->priv->auth_queue; l != NULL; l = l->next)
367 AuthData *data = l->data;
369 if (self->priv->client != NULL)
375 tp_clear_pointer (&self->priv->auth_queue, g_list_free);
379 empathy_goa_auth_handler_start (EmpathyGoaAuthHandler *self,
385 g_return_if_fail (TP_IS_CHANNEL (channel));
386 g_return_if_fail (TP_IS_ACCOUNT (account));
387 g_return_if_fail (empathy_goa_auth_handler_supports (self, channel, account));
389 DEBUG ("Start Goa auth for account: %s",
390 tp_proxy_get_object_path (account));
392 data = g_slice_new0 (AuthData);
393 data->self = g_object_ref (self);
394 data->channel = g_object_ref (channel);
395 data->account = g_object_ref (account);
397 if (self->priv->client == NULL)
399 /* GOA client not ready yet, queue data */
400 if (!self->priv->client_preparing)
402 goa_client_new (NULL, client_new_cb, self);
403 self->priv->client_preparing = TRUE;
406 self->priv->auth_queue = g_list_prepend (self->priv->auth_queue, data);
415 empathy_goa_auth_handler_supports (EmpathyGoaAuthHandler *self,
419 const gchar *provider;
420 const gchar * const *iter;
422 g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
423 g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
425 provider = tp_account_get_storage_provider (account);
426 if (tp_strdiff (provider, EMPATHY_GOA_PROVIDER))
429 for (iter = supported_mechanisms; *iter != NULL; iter++)
431 if (empathy_sasl_channel_supports_mechanism (channel, *iter))