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