]> git.0d.be Git - empathy.git/blob - libempathy/empathy-sasl-mechanisms.c
Updated Czech translation
[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   GString *response_string;
160   GArray *response_array;
161
162   DEBUG ("new challenge: %s", challenge->data);
163
164   data = g_simple_async_result_get_op_res_gpointer (result);
165
166   h = soup_form_decode (challenge->data);
167
168   /* See https://developers.facebook.com/docs/chat/#platauth.
169    * We don't use soup_form_encode() here because it would escape parameters
170    * and facebook server is not expecting that and would reject the response. */
171   response_string = g_string_new ("v=1.0&call_id=0");
172   g_string_append (response_string, "&access_token=");
173   g_string_append_uri_escaped (response_string, data->access_token, NULL, TRUE);
174   g_string_append (response_string, "&api_key=");
175   g_string_append_uri_escaped (response_string, data->client_id, NULL, TRUE);
176   g_string_append (response_string, "&method=");
177   g_string_append_uri_escaped (response_string, g_hash_table_lookup (h, "method"), NULL, TRUE);
178   g_string_append (response_string, "&nonce=");
179   g_string_append_uri_escaped (response_string, g_hash_table_lookup (h, "nonce"), NULL, TRUE);
180
181   DEBUG ("Response: %s", response_string->str);
182
183   response_array = g_array_new (FALSE, FALSE, sizeof (gchar));
184   g_array_append_vals (response_array, response_string->str, response_string->len);
185
186   tp_cli_channel_interface_sasl_authentication_call_respond (data->channel, -1,
187       response_array, generic_cb, g_object_ref (result), g_object_unref, NULL);
188
189   g_hash_table_unref (h);
190   g_string_free (response_string, TRUE);
191   g_array_unref (response_array);
192 }
193
194 void
195 empathy_sasl_auth_facebook_async (TpChannel *channel,
196     const gchar *client_id,
197     const gchar *access_token,
198     GAsyncReadyCallback callback,
199     gpointer user_data)
200 {
201   GSimpleAsyncResult *result;
202   FacebookData *data;
203   GError *error = NULL;
204
205   result = empathy_sasl_auth_common_async (channel, callback, user_data);
206
207   g_return_if_fail (result != NULL);
208   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
209       MECH_FACEBOOK));
210   g_return_if_fail (!tp_str_empty (client_id));
211   g_return_if_fail (!tp_str_empty (access_token));
212
213   DEBUG ("Start %s mechanism", MECH_FACEBOOK);
214
215   data = g_slice_new0 (FacebookData);
216   data->channel = g_object_ref (channel);
217   data->client_id = g_strdup (client_id);
218   data->access_token = g_strdup (access_token);
219
220   g_simple_async_result_set_op_res_gpointer (result, data,
221       (GDestroyNotify) facebook_data_free);
222
223   tp_cli_channel_interface_sasl_authentication_connect_to_new_challenge (
224       channel, facebook_new_challenge_cb,
225       g_object_ref (result), g_object_unref,
226       NULL, &error);
227   g_assert_no_error (error);
228
229   tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
230       channel, -1, MECH_FACEBOOK, generic_cb,
231       g_object_ref (result), g_object_unref, NULL);
232
233   g_object_unref (result);
234 }
235
236 void
237 empathy_sasl_auth_wlm_async (TpChannel *channel,
238     const gchar *access_token,
239     GAsyncReadyCallback callback,
240     gpointer user_data)
241 {
242   GSimpleAsyncResult *result;
243   guchar *token_decoded;
244   gsize token_decoded_len;
245   GArray *token_decoded_array;
246
247   result = empathy_sasl_auth_common_async (channel, callback, user_data);
248
249   g_return_if_fail (result != NULL);
250   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
251       MECH_WLM));
252   g_return_if_fail (!tp_str_empty (access_token));
253
254   DEBUG ("Start %s mechanism", MECH_WLM);
255
256   /* Wocky will base64 encode, but token actually already is base64, so we
257    * decode now and it will be re-encoded. */
258   token_decoded = g_base64_decode (access_token, &token_decoded_len);
259   token_decoded_array = g_array_new (FALSE, FALSE, sizeof (guchar));
260   g_array_append_vals (token_decoded_array, token_decoded, token_decoded_len);
261
262   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
263       channel, -1, MECH_WLM, token_decoded_array,
264       generic_cb, g_object_ref (result), g_object_unref, NULL);
265
266   g_array_unref (token_decoded_array);
267   g_free (token_decoded);
268   g_object_unref (result);
269 }
270
271 void
272 empathy_sasl_auth_google_async (TpChannel *channel,
273     const gchar *username,
274     const gchar *access_token,
275     GAsyncReadyCallback callback,
276     gpointer user_data)
277 {
278   GSimpleAsyncResult *result;
279   GArray *credential;
280
281   result = empathy_sasl_auth_common_async (channel, callback, user_data);
282
283   g_return_if_fail (result != NULL);
284   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
285       MECH_GOOGLE));
286   g_return_if_fail (!tp_str_empty (username));
287   g_return_if_fail (!tp_str_empty (access_token));
288
289   DEBUG ("Start %s mechanism", MECH_GOOGLE);
290
291   credential = g_array_sized_new (FALSE, FALSE, sizeof (gchar),
292       strlen (access_token) + strlen (username) + 2);
293
294   g_array_append_val (credential, "\0");
295   g_array_append_vals (credential, username, strlen (username));
296   g_array_append_val (credential, "\0");
297   g_array_append_vals (credential, access_token, strlen (access_token));
298
299   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
300       channel, -1, MECH_GOOGLE, credential,
301       generic_cb, g_object_ref (result), g_object_unref, NULL);
302
303   g_array_unref (credential);
304   g_object_unref (result);
305 }
306
307 void
308 empathy_sasl_auth_password_async (TpChannel *channel,
309     const gchar *password,
310     GAsyncReadyCallback callback,
311     gpointer user_data)
312 {
313   GSimpleAsyncResult *result;
314   GArray *password_array;
315
316   result = empathy_sasl_auth_common_async (channel, callback, user_data);
317
318   g_return_if_fail (result != NULL);
319   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
320       MECH_PASSWORD));
321   g_return_if_fail (!tp_str_empty (password));
322
323   DEBUG ("Start %s mechanism", MECH_PASSWORD);
324
325   password_array = g_array_sized_new (FALSE, FALSE, sizeof (gchar),
326       strlen (password));
327   g_array_append_vals (password_array, password, strlen (password));
328
329   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
330       channel, -1, MECH_PASSWORD, password_array,
331       generic_cb, g_object_ref (result), g_object_unref, NULL);
332
333   g_array_unref (password_array);
334   g_object_unref (result);
335 }
336
337 gboolean
338 empathy_sasl_auth_finish (TpChannel *channel,
339     GAsyncResult *result,
340     GError **error)
341 {
342   tpaw_implement_finish_void (channel, empathy_sasl_auth_common_async);
343 }
344
345 gboolean
346 empathy_sasl_channel_supports_mechanism (TpChannel *channel,
347     const gchar *mechanism)
348 {
349   GVariant *props;
350   GStrv available_mechanisms;
351   gboolean result;
352
353   props = tp_channel_dup_immutable_properties (channel);
354
355   g_variant_lookup (props,
356       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_AVAILABLE_MECHANISMS,
357       "^as", &available_mechanisms);
358
359   result = tp_strv_contains ((const gchar * const *) available_mechanisms,
360       mechanism);
361
362   g_variant_unref (props);
363   g_strfreev (available_mechanisms);
364   return result;
365 }
366
367 EmpathySaslMechanism
368 empathy_sasl_channel_select_mechanism (TpChannel *channel)
369 {
370   guint i;
371
372   for (i = 0; i < G_N_ELEMENTS (supported_mechanisms); i++)
373     {
374       if (empathy_sasl_channel_supports_mechanism (channel,
375               supported_mechanisms[i].name))
376         return supported_mechanisms[i].id;
377     }
378
379   return EMPATHY_SASL_MECHANISM_UNSUPPORTED;
380 }