]> git.0d.be Git - empathy.git/blob - libempathy/empathy-goa-auth-handler.c
individual-menu: remove link-contacts-activated signal
[empathy.git] / libempathy / empathy-goa-auth-handler.c
1 /*
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>
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include "config.h"
22
23 #define GOA_API_IS_SUBJECT_TO_CHANGE /* awesome! */
24 #include <goa/goa.h>
25
26 #include <libsoup/soup.h>
27 #include <string.h>
28
29 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
30 #include "empathy-debug.h"
31 #include "empathy-utils.h"
32 #include "empathy-goa-auth-handler.h"
33
34 #define MECH_FACEBOOK "X-FACEBOOK-PLATFORM"
35 #define MECH_MSN "X-MESSENGER-OAUTH2"
36
37 static const gchar *supported_mechanisms[] = {
38     MECH_FACEBOOK,
39     MECH_MSN,
40     NULL};
41
42 struct _EmpathyGoaAuthHandlerPriv
43 {
44   GoaClient *client;
45   gboolean client_preparing;
46
47   /* List of AuthData waiting for client to be created */
48   GList *auth_queue;
49 };
50
51 G_DEFINE_TYPE (EmpathyGoaAuthHandler, empathy_goa_auth_handler, G_TYPE_OBJECT);
52
53 static void
54 empathy_goa_auth_handler_init (EmpathyGoaAuthHandler *self)
55 {
56   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
57       EMPATHY_TYPE_GOA_AUTH_HANDLER, EmpathyGoaAuthHandlerPriv);
58 }
59
60 static void
61 empathy_goa_auth_handler_dispose (GObject *object)
62 {
63   EmpathyGoaAuthHandler *self = (EmpathyGoaAuthHandler *) object;
64
65   /* AuthData keeps a ref on self */
66   g_assert (self->priv->auth_queue == NULL);
67
68   tp_clear_object (&self->priv->client);
69
70   G_OBJECT_CLASS (empathy_goa_auth_handler_parent_class)->dispose (object);
71 }
72
73 static void
74 empathy_goa_auth_handler_class_init (EmpathyGoaAuthHandlerClass *klass)
75 {
76   GObjectClass *oclass = G_OBJECT_CLASS (klass);
77
78   oclass->dispose = empathy_goa_auth_handler_dispose;
79
80   g_type_class_add_private (klass, sizeof (EmpathyGoaAuthHandlerPriv));
81 }
82
83 EmpathyGoaAuthHandler *
84 empathy_goa_auth_handler_new (void)
85 {
86   return g_object_new (EMPATHY_TYPE_GOA_AUTH_HANDLER, NULL);
87 }
88
89 typedef struct
90 {
91   EmpathyGoaAuthHandler *self;
92   TpChannel *channel;
93   TpAccount *account;
94
95   GoaObject *goa_object;
96   gchar *access_token;
97 } AuthData;
98
99 static void
100 auth_data_free (AuthData *data)
101 {
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);
108 }
109
110 static void
111 fail_auth (AuthData *data)
112 {
113   DEBUG ("Auth failed for account %s",
114       tp_proxy_get_object_path (data->account));
115
116   tp_channel_close_async (data->channel, NULL, NULL);
117   auth_data_free (data);
118 }
119
120 static void
121 sasl_status_changed_cb (TpChannel *channel,
122     guint status,
123     const gchar *reason,
124     GHashTable *details,
125     gpointer user_data,
126     GObject *self)
127 {
128   switch (status)
129     {
130       case TP_SASL_STATUS_SERVER_SUCCEEDED:
131         tp_cli_channel_interface_sasl_authentication_call_accept_sasl (channel,
132             -1, NULL, NULL, NULL, NULL);
133         break;
134
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);
139         break;
140
141       default:
142         break;
143     }
144 }
145
146 static void
147 facebook_new_challenge_cb (TpChannel *channel,
148     const GArray *challenge,
149     gpointer user_data,
150     GObject *weak_object)
151 {
152   AuthData *data = user_data;
153   GoaOAuth2Based *oauth2;
154   const gchar *client_id;
155   GHashTable *h;
156   GHashTable *params;
157   gchar *response;
158   GArray *response_array;
159
160   DEBUG ("new challenge for %s:\n%s",
161       tp_proxy_get_object_path (data->account),
162       challenge->data);
163
164   h = soup_form_decode (challenge->data);
165
166   oauth2 = goa_object_get_oauth2_based (data->goa_object);
167   client_id = goa_oauth2_based_get_client_id (oauth2);
168
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");
177
178   response = soup_form_encode_hash (params);
179   DEBUG ("Response: %s", response);
180
181   response_array = g_array_new (FALSE, FALSE, sizeof (gchar));
182   g_array_append_vals (response_array, response, strlen (response));
183
184   tp_cli_channel_interface_sasl_authentication_call_respond (data->channel, -1,
185       response_array, NULL, NULL, NULL, NULL);
186
187   g_hash_table_unref (h);
188   g_hash_table_unref (params);
189   g_object_unref (oauth2);
190   g_free (response);
191   g_array_unref (response_array);
192 }
193
194 static void
195 got_oauth2_access_token_cb (GObject *source,
196     GAsyncResult *result,
197     gpointer user_data)
198 {
199   GoaOAuth2Based *oauth2 = (GoaOAuth2Based *) source;
200   AuthData *data = user_data;
201   gint expires_in;
202   GError *error = NULL;
203
204   if (!goa_oauth2_based_call_get_access_token_finish (oauth2,
205       &data->access_token, &expires_in, result, &error))
206     {
207       DEBUG ("Failed to get access token: %s", error->message);
208       fail_auth (data);
209       g_clear_error (&error);
210       return;
211     }
212
213   DEBUG ("Got access token for %s:\n%s",
214       tp_proxy_get_object_path (data->account),
215       data->access_token);
216
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);
220
221   if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_FACEBOOK))
222     {
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,
227           NULL, NULL);
228
229       DEBUG ("Start %s mechanism for account %s", MECH_FACEBOOK,
230           tp_proxy_get_object_path (data->account));
231
232       tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
233           data->channel, -1, MECH_FACEBOOK, NULL, NULL, NULL, NULL);
234     }
235   else if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_MSN))
236     {
237       guchar *token_decoded;
238       gsize token_decoded_len;
239       GArray *token_decoded_array;
240
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);
246
247       DEBUG ("Start %s mechanism for account %s", MECH_MSN,
248           tp_proxy_get_object_path (data->account));
249
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);
253
254       g_array_unref (token_decoded_array);
255       g_free (token_decoded);
256       auth_data_free (data);
257     }
258   else
259     {
260       /* We already checked it supports one of supported_mechanisms, so this
261        * can't happen */
262       g_assert_not_reached ();
263     }
264 }
265
266 static void
267 ensure_credentials_cb (GObject *source,
268     GAsyncResult *result,
269     gpointer user_data)
270 {
271   AuthData *data = user_data;
272   GoaAccount *goa_account = (GoaAccount *) source;
273   GoaOAuth2Based *oauth2;
274   gint expires_in;
275   GError *error = NULL;
276
277   if (!goa_account_call_ensure_credentials_finish (goa_account, &expires_in,
278       result, &error))
279     {
280       DEBUG ("Failed to EnsureCredentials: %s", error->message);
281       fail_auth (data);
282       g_clear_error (&error);
283       return;
284     }
285
286   /* We support only oaut2 */
287   oauth2 = goa_object_get_oauth2_based (data->goa_object);
288   if (oauth2 == NULL)
289     {
290       DEBUG ("GoaObject does not implement oauth2");
291       fail_auth (data);
292       return;
293     }
294
295   DEBUG ("Goa daemon has credentials for %s, get the access token",
296       tp_proxy_get_object_path (data->account));
297
298   goa_oauth2_based_call_get_access_token (oauth2, NULL,
299       got_oauth2_access_token_cb, data);
300
301   g_object_unref (oauth2);
302 }
303
304 static void
305 start_auth (AuthData *data)
306 {
307   EmpathyGoaAuthHandler *self = data->self;
308   const GValue *id_value;
309   const gchar *id;
310   GList *goa_accounts, *l;
311   gboolean found = FALSE;
312
313   id_value = tp_account_get_storage_identifier (data->account);
314   id = g_value_get_string (id_value);
315
316   goa_accounts = goa_client_get_accounts (self->priv->client);
317   for (l = goa_accounts; l != NULL && !found; l = l->next)
318     {
319       GoaObject *goa_object = l->data;
320       GoaAccount *goa_account;
321
322       goa_account = goa_object_get_account (goa_object);
323       if (!tp_strdiff (goa_account_get_id (goa_account), id))
324         {
325           data->goa_object = g_object_ref (goa_object);
326
327           DEBUG ("Found the GoaAccount for %s, ensure credentials",
328               tp_proxy_get_object_path (data->account));
329
330           goa_account_call_ensure_credentials (goa_account, NULL,
331               ensure_credentials_cb, data);
332
333           found = TRUE;
334         }
335
336       g_object_unref (goa_account);
337     }
338   g_list_free_full (goa_accounts, g_object_unref);
339
340   if (!found)
341     {
342       DEBUG ("Cannot find GoaAccount");
343       fail_auth (data);
344     }
345 }
346
347 static void
348 client_new_cb (GObject *source,
349     GAsyncResult *result,
350     gpointer user_data)
351 {
352   EmpathyGoaAuthHandler *self = user_data;
353   GList *l;
354   GError *error = NULL;
355
356   self->priv->client_preparing = FALSE;
357   self->priv->client = goa_client_new_finish (result, &error);
358   if (self->priv->client == NULL)
359     {
360       DEBUG ("Error getting GoaClient: %s", error->message);
361       g_clear_error (&error);
362     }
363
364   /* process queued data */
365   for (l = self->priv->auth_queue; l != NULL; l = l->next)
366     {
367       AuthData *data = l->data;
368
369       if (self->priv->client != NULL)
370         start_auth (data);
371       else
372         fail_auth (data);
373     }
374
375   tp_clear_pointer (&self->priv->auth_queue, g_list_free);
376 }
377
378 void
379 empathy_goa_auth_handler_start (EmpathyGoaAuthHandler *self,
380     TpChannel *channel,
381     TpAccount *account)
382 {
383   AuthData *data;
384
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));
388
389   DEBUG ("Start Goa auth for account: %s",
390       tp_proxy_get_object_path (account));
391
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);
396
397   if (self->priv->client == NULL)
398     {
399       /* GOA client not ready yet, queue data */
400       if (!self->priv->client_preparing)
401         {
402           goa_client_new (NULL, client_new_cb, self);
403           self->priv->client_preparing = TRUE;
404         }
405
406       self->priv->auth_queue = g_list_prepend (self->priv->auth_queue, data);
407     }
408   else
409     {
410       start_auth (data);
411     }
412 }
413
414 gboolean
415 empathy_goa_auth_handler_supports (EmpathyGoaAuthHandler *self,
416     TpChannel *channel,
417     TpAccount *account)
418 {
419   const gchar *provider;
420   const gchar * const *iter;
421
422   g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
423   g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
424
425   provider = tp_account_get_storage_provider (account);
426   if (tp_strdiff (provider, EMPATHY_GOA_PROVIDER))
427     return FALSE;
428
429   for (iter = supported_mechanisms; *iter != NULL; iter++)
430     {
431       if (empathy_sasl_channel_supports_mechanism (channel, *iter))
432         return TRUE;
433     }
434
435   return FALSE;
436 }