]> git.0d.be Git - empathy.git/blob - libempathy/empathy-sasl-mechanisms.c
Add missing telepathy-glib-dbus.h includes
[empathy.git] / libempathy / empathy-sasl-mechanisms.c
1 /*
2  * empathy-sasl-mechanisms.h - Header for SASL authentication mechanisms
3  * Copyright (C) 2012 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 #include "empathy-sasl-mechanisms.h"
23
24 #include <libsoup/soup.h>
25 #include <tp-account-widgets/tpaw-utils.h>
26 #include <telepathy-glib/telepathy-glib-dbus.h>
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
29 #include "empathy-debug.h"
30 #include "empathy-utils.h"
31
32 #define MECH_FACEBOOK "X-FACEBOOK-PLATFORM"
33 #define MECH_WLM "X-MESSENGER-OAUTH2"
34 #define MECH_GOOGLE "X-OAUTH2"
35 #define MECH_PASSWORD "X-TELEPATHY-PASSWORD"
36
37 typedef struct
38 {
39   EmpathySaslMechanism id;
40   const gchar *name;
41 } SupportedMech;
42
43 static SupportedMech supported_mechanisms[] = {
44   { EMPATHY_SASL_MECHANISM_FACEBOOK, MECH_FACEBOOK },
45   { EMPATHY_SASL_MECHANISM_WLM, MECH_WLM },
46   { EMPATHY_SASL_MECHANISM_GOOGLE, MECH_GOOGLE },
47
48   /* Must be the last one, otherwise empathy_sasl_channel_select_mechanism()
49    * will prefer password over web auth for servers supporting both. */
50   { EMPATHY_SASL_MECHANISM_PASSWORD, MECH_PASSWORD }
51 };
52
53 static void
54 generic_cb (TpChannel *proxy,
55     const GError *error,
56     gpointer user_data,
57     GObject *weak_object)
58 {
59   GSimpleAsyncResult *result = user_data;
60
61   if (error != NULL)
62     {
63       g_simple_async_result_set_from_error (result, error);
64       g_simple_async_result_complete (result);
65     }
66 }
67
68 static void
69 sasl_status_changed_cb (TpChannel *channel,
70     guint status,
71     const gchar *dbus_error,
72     GHashTable *details,
73     gpointer user_data,
74     GObject *self)
75 {
76   GSimpleAsyncResult *result = user_data;
77
78   switch (status)
79     {
80       case TP_SASL_STATUS_SERVER_SUCCEEDED:
81         tp_cli_channel_interface_sasl_authentication_call_accept_sasl (channel,
82             -1, generic_cb, g_object_ref (result), g_object_unref, NULL);
83         break;
84
85       case TP_SASL_STATUS_SERVER_FAILED:
86       case TP_SASL_STATUS_CLIENT_FAILED:
87         {
88           GError *error = NULL;
89
90           tp_proxy_dbus_error_to_gerror (channel, dbus_error,
91               tp_asv_get_string (details, "debug-message"), &error);
92
93           DEBUG ("SASL failed: %s", error->message);
94
95           g_simple_async_result_take_error (result, error);
96           g_simple_async_result_complete (result);
97         }
98         break;
99
100       case TP_SASL_STATUS_SUCCEEDED:
101         DEBUG ("SASL succeeded");
102
103         g_simple_async_result_complete (result);
104         break;
105
106       default:
107         break;
108     }
109 }
110
111 static GSimpleAsyncResult *
112 empathy_sasl_auth_common_async (TpChannel *channel,
113     GAsyncReadyCallback callback,
114     gpointer user_data)
115 {
116   GSimpleAsyncResult *result;
117   GError *error = NULL;
118
119   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
120   g_return_val_if_fail (tp_proxy_has_interface_by_id (channel,
121       TP_IFACE_QUARK_CHANNEL_INTERFACE_SASL_AUTHENTICATION), NULL);
122
123   result = g_simple_async_result_new ((GObject *) channel,
124       callback, user_data, empathy_sasl_auth_common_async);
125
126   tp_cli_channel_interface_sasl_authentication_connect_to_sasl_status_changed (
127       channel, sasl_status_changed_cb,
128       g_object_ref (result), g_object_unref, NULL, &error);
129   g_assert_no_error (error);
130
131   return result;
132 }
133
134 typedef struct
135 {
136   TpChannel *channel;
137   gchar *client_id;
138   gchar *access_token;
139 } FacebookData;
140
141 static void
142 facebook_data_free (FacebookData *data)
143 {
144   g_object_unref (data->channel);
145   g_free (data->client_id);
146   g_free (data->access_token);
147   g_slice_free (FacebookData, data);
148 }
149
150 static void
151 facebook_new_challenge_cb (TpChannel *channel,
152     const GArray *challenge,
153     gpointer user_data,
154     GObject *weak_object)
155 {
156   GSimpleAsyncResult *result = user_data;
157   FacebookData *data;
158   GHashTable *h;
159   GHashTable *params;
160   gchar *response;
161   GArray *response_array;
162
163   DEBUG ("new challenge: %s", challenge->data);
164
165   data = g_simple_async_result_get_op_res_gpointer (result);
166
167   h = soup_form_decode (challenge->data);
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", data->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, generic_cb, g_object_ref (result), g_object_unref, NULL);
186
187   g_hash_table_unref (h);
188   g_hash_table_unref (params);
189   g_free (response);
190   g_array_unref (response_array);
191 }
192
193 void
194 empathy_sasl_auth_facebook_async (TpChannel *channel,
195     const gchar *client_id,
196     const gchar *access_token,
197     GAsyncReadyCallback callback,
198     gpointer user_data)
199 {
200   GSimpleAsyncResult *result;
201   FacebookData *data;
202   GError *error = NULL;
203
204   result = empathy_sasl_auth_common_async (channel, callback, user_data);
205
206   g_return_if_fail (result != NULL);
207   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
208       MECH_FACEBOOK));
209   g_return_if_fail (!tp_str_empty (client_id));
210   g_return_if_fail (!tp_str_empty (access_token));
211
212   DEBUG ("Start %s mechanism", MECH_FACEBOOK);
213
214   data = g_slice_new0 (FacebookData);
215   data->channel = g_object_ref (channel);
216   data->client_id = g_strdup (client_id);
217   data->access_token = g_strdup (access_token);
218
219   g_simple_async_result_set_op_res_gpointer (result, data,
220       (GDestroyNotify) facebook_data_free);
221
222   tp_cli_channel_interface_sasl_authentication_connect_to_new_challenge (
223       channel, facebook_new_challenge_cb,
224       g_object_ref (result), g_object_unref,
225       NULL, &error);
226   g_assert_no_error (error);
227
228   tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
229       channel, -1, MECH_FACEBOOK, generic_cb,
230       g_object_ref (result), g_object_unref, NULL);
231
232   g_object_unref (result);
233 }
234
235 void
236 empathy_sasl_auth_wlm_async (TpChannel *channel,
237     const gchar *access_token,
238     GAsyncReadyCallback callback,
239     gpointer user_data)
240 {
241   GSimpleAsyncResult *result;
242   guchar *token_decoded;
243   gsize token_decoded_len;
244   GArray *token_decoded_array;
245
246   result = empathy_sasl_auth_common_async (channel, callback, user_data);
247
248   g_return_if_fail (result != NULL);
249   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
250       MECH_WLM));
251   g_return_if_fail (!tp_str_empty (access_token));
252
253   DEBUG ("Start %s mechanism", MECH_WLM);
254
255   /* Wocky will base64 encode, but token actually already is base64, so we
256    * decode now and it will be re-encoded. */
257   token_decoded = g_base64_decode (access_token, &token_decoded_len);
258   token_decoded_array = g_array_new (FALSE, FALSE, sizeof (guchar));
259   g_array_append_vals (token_decoded_array, token_decoded, token_decoded_len);
260
261   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
262       channel, -1, MECH_WLM, token_decoded_array,
263       generic_cb, g_object_ref (result), g_object_unref, NULL);
264
265   g_array_unref (token_decoded_array);
266   g_free (token_decoded);
267   g_object_unref (result);
268 }
269
270 void
271 empathy_sasl_auth_google_async (TpChannel *channel,
272     const gchar *username,
273     const gchar *access_token,
274     GAsyncReadyCallback callback,
275     gpointer user_data)
276 {
277   GSimpleAsyncResult *result;
278   GArray *credential;
279
280   result = empathy_sasl_auth_common_async (channel, callback, user_data);
281
282   g_return_if_fail (result != NULL);
283   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
284       MECH_GOOGLE));
285   g_return_if_fail (!tp_str_empty (username));
286   g_return_if_fail (!tp_str_empty (access_token));
287
288   DEBUG ("Start %s mechanism", MECH_GOOGLE);
289
290   credential = g_array_sized_new (FALSE, FALSE, sizeof (gchar),
291       strlen (access_token) + strlen (username) + 2);
292
293   g_array_append_val (credential, "\0");
294   g_array_append_vals (credential, username, strlen (username));
295   g_array_append_val (credential, "\0");
296   g_array_append_vals (credential, access_token, strlen (access_token));
297
298   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
299       channel, -1, MECH_GOOGLE, credential,
300       generic_cb, g_object_ref (result), g_object_unref, NULL);
301
302   g_array_unref (credential);
303   g_object_unref (result);
304 }
305
306 void
307 empathy_sasl_auth_password_async (TpChannel *channel,
308     const gchar *password,
309     GAsyncReadyCallback callback,
310     gpointer user_data)
311 {
312   GSimpleAsyncResult *result;
313   GArray *password_array;
314
315   result = empathy_sasl_auth_common_async (channel, callback, user_data);
316
317   g_return_if_fail (result != NULL);
318   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
319       MECH_PASSWORD));
320   g_return_if_fail (!tp_str_empty (password));
321
322   DEBUG ("Start %s mechanism", MECH_PASSWORD);
323
324   password_array = g_array_sized_new (FALSE, FALSE, sizeof (gchar),
325       strlen (password));
326   g_array_append_vals (password_array, password, strlen (password));
327
328   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
329       channel, -1, MECH_PASSWORD, password_array,
330       generic_cb, g_object_ref (result), g_object_unref, NULL);
331
332   g_array_unref (password_array);
333   g_object_unref (result);
334 }
335
336 gboolean
337 empathy_sasl_auth_finish (TpChannel *channel,
338     GAsyncResult *result,
339     GError **error)
340 {
341   tpaw_implement_finish_void (channel, empathy_sasl_auth_common_async);
342 }
343
344 gboolean
345 empathy_sasl_channel_supports_mechanism (TpChannel *channel,
346     const gchar *mechanism)
347 {
348   GVariant *props;
349   GStrv available_mechanisms;
350   gboolean result;
351
352   props = tp_channel_dup_immutable_properties (channel);
353
354   g_variant_lookup (props,
355       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_AVAILABLE_MECHANISMS,
356       "^as", &available_mechanisms);
357
358   result = tp_strv_contains ((const gchar * const *) available_mechanisms,
359       mechanism);
360
361   g_variant_unref (props);
362   g_strfreev (available_mechanisms);
363   return result;
364 }
365
366 EmpathySaslMechanism
367 empathy_sasl_channel_select_mechanism (TpChannel *channel)
368 {
369   guint i;
370
371   for (i = 0; i < G_N_ELEMENTS (supported_mechanisms); i++)
372     {
373       if (empathy_sasl_channel_supports_mechanism (channel,
374               supported_mechanisms[i].name))
375         return supported_mechanisms[i].id;
376     }
377
378   return EMPATHY_SASL_MECHANISM_UNSUPPORTED;
379 }