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