]> git.0d.be Git - empathy.git/blob - libempathy/empathy-sasl-mechanisms.c
5bdc7590f5349f0bf5607e148799075369068ae6
[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
23 #include <libsoup/soup.h>
24 #include <string.h>
25
26 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
27 #include "empathy-debug.h"
28 #include "empathy-utils.h"
29 #include "empathy-sasl-mechanisms.h"
30
31 #define MECH_FACEBOOK "X-FACEBOOK-PLATFORM"
32 #define MECH_WLM "X-MESSENGER-OAUTH2"
33
34 typedef struct
35 {
36   EmpathySaslMechanism id;
37   const gchar *name;
38 } SupportedMech;
39
40 static SupportedMech supported_mechanisms[] = {
41   { EMPATHY_SASL_MECHANISM_FACEBOOK, MECH_FACEBOOK },
42   { EMPATHY_SASL_MECHANISM_WLM, MECH_WLM },
43 };
44
45 static void
46 generic_cb (TpChannel *proxy,
47     const GError *error,
48     gpointer user_data,
49     GObject *weak_object)
50 {
51   GSimpleAsyncResult *result = user_data;
52
53   if (error != NULL)
54     {
55       g_simple_async_result_set_from_error (result, error);
56       g_simple_async_result_complete (result);
57     }
58 }
59
60 static void
61 sasl_status_changed_cb (TpChannel *channel,
62     guint status,
63     const gchar *dbus_error,
64     GHashTable *details,
65     gpointer user_data,
66     GObject *self)
67 {
68   GSimpleAsyncResult *result = user_data;
69
70   switch (status)
71     {
72       case TP_SASL_STATUS_SERVER_SUCCEEDED:
73         tp_cli_channel_interface_sasl_authentication_call_accept_sasl (channel,
74             -1, generic_cb, g_object_ref (result), g_object_unref, NULL);
75         break;
76
77       case TP_SASL_STATUS_SERVER_FAILED:
78       case TP_SASL_STATUS_CLIENT_FAILED:
79         {
80           GError *error = NULL;
81
82           tp_proxy_dbus_error_to_gerror (channel, dbus_error,
83               tp_asv_get_string (details, "debug-message"), &error);
84
85           g_simple_async_result_take_error (result, error);
86         }
87         break;
88
89       case TP_SASL_STATUS_SUCCEEDED:
90         g_simple_async_result_complete (result);
91         break;
92
93       default:
94         break;
95     }
96 }
97
98 static GSimpleAsyncResult *
99 empathy_sasl_auth_common_async (TpChannel *channel,
100     GAsyncReadyCallback callback,
101     gpointer user_data)
102 {
103   GSimpleAsyncResult *result;
104   GError *error = NULL;
105
106   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
107   g_return_val_if_fail (tp_proxy_has_interface_by_id (channel,
108       TP_IFACE_QUARK_CHANNEL_INTERFACE_SASL_AUTHENTICATION), NULL);
109
110   result = g_simple_async_result_new ((GObject *) channel,
111       callback, user_data, empathy_sasl_auth_common_async);
112
113   tp_cli_channel_interface_sasl_authentication_connect_to_sasl_status_changed (
114       channel, sasl_status_changed_cb,
115       g_object_ref (result), g_object_unref, NULL, &error);
116   g_assert_no_error (error);
117
118   return result;
119 }
120
121 typedef struct
122 {
123   TpChannel *channel;
124   gchar *client_id;
125   gchar *access_token;
126 } FacebookData;
127
128 static void
129 facebook_data_free (FacebookData *data)
130 {
131   g_object_unref (data->channel);
132   g_free (data->client_id);
133   g_free (data->access_token);
134   g_slice_free (FacebookData, data);
135 }
136
137 static void
138 facebook_new_challenge_cb (TpChannel *channel,
139     const GArray *challenge,
140     gpointer user_data,
141     GObject *weak_object)
142 {
143   GSimpleAsyncResult *result = user_data;
144   FacebookData *data;
145   GHashTable *h;
146   GHashTable *params;
147   gchar *response;
148   GArray *response_array;
149
150   DEBUG ("new challenge: %s", challenge->data);
151
152   data = g_simple_async_result_get_op_res_gpointer (result);
153
154   h = soup_form_decode (challenge->data);
155
156   /* See https://developers.facebook.com/docs/chat/#platauth */
157   params = g_hash_table_new (g_str_hash, g_str_equal);
158   g_hash_table_insert (params, "method", g_hash_table_lookup (h, "method"));
159   g_hash_table_insert (params, "nonce", g_hash_table_lookup (h, "nonce"));
160   g_hash_table_insert (params, "access_token", data->access_token);
161   g_hash_table_insert (params, "api_key", data->client_id);
162   g_hash_table_insert (params, "call_id", "0");
163   g_hash_table_insert (params, "v", "1.0");
164
165   response = soup_form_encode_hash (params);
166   DEBUG ("Response: %s", response);
167
168   response_array = g_array_new (FALSE, FALSE, sizeof (gchar));
169   g_array_append_vals (response_array, response, strlen (response));
170
171   tp_cli_channel_interface_sasl_authentication_call_respond (data->channel, -1,
172       response_array, generic_cb, g_object_ref (result), g_object_unref, NULL);
173
174   g_hash_table_unref (h);
175   g_hash_table_unref (params);
176   g_free (response);
177   g_array_unref (response_array);
178 }
179
180 void
181 empathy_sasl_auth_facebook_async (TpChannel *channel,
182     const gchar *client_id,
183     const gchar *access_token,
184     GAsyncReadyCallback callback,
185     gpointer user_data)
186 {
187   GSimpleAsyncResult *result;
188   FacebookData *data;
189   GError *error = NULL;
190
191   result = empathy_sasl_auth_common_async (channel, callback, user_data);
192
193   g_return_if_fail (result != NULL);
194   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
195       MECH_FACEBOOK));
196   g_return_if_fail (!tp_str_empty (client_id));
197   g_return_if_fail (!tp_str_empty (access_token));
198
199   DEBUG ("Start %s mechanism", MECH_FACEBOOK);
200
201   data = g_slice_new0 (FacebookData);
202   data->channel = g_object_ref (channel);
203   data->client_id = g_strdup (client_id);
204   data->access_token = g_strdup (access_token);
205
206   g_simple_async_result_set_op_res_gpointer (result, data,
207       (GDestroyNotify) facebook_data_free);
208
209   tp_cli_channel_interface_sasl_authentication_connect_to_new_challenge (
210       channel, facebook_new_challenge_cb,
211       g_object_ref (result), g_object_unref,
212       NULL, &error);
213   g_assert_no_error (error);
214
215   tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
216       channel, -1, MECH_FACEBOOK, generic_cb,
217       g_object_ref (result), g_object_unref, NULL);
218
219   g_object_unref (result);
220 }
221
222 void
223 empathy_sasl_auth_wlm_async (TpChannel *channel,
224     const gchar *access_token,
225     GAsyncReadyCallback callback,
226     gpointer user_data)
227 {
228   GSimpleAsyncResult *result;
229   guchar *token_decoded;
230   gsize token_decoded_len;
231   GArray *token_decoded_array;
232
233   result = empathy_sasl_auth_common_async (channel, callback, user_data);
234
235   g_return_if_fail (result != NULL);
236   g_return_if_fail (empathy_sasl_channel_supports_mechanism (channel,
237       MECH_WLM));
238   g_return_if_fail (!tp_str_empty (access_token));
239
240   DEBUG ("Start %s mechanism", MECH_WLM);
241
242   /* Wocky will base64 encode, but token actually already is base64, so we
243    * decode now and it will be re-encoded. */
244   token_decoded = g_base64_decode (access_token, &token_decoded_len);
245   token_decoded_array = g_array_new (FALSE, FALSE, sizeof (guchar));
246   g_array_append_vals (token_decoded_array, token_decoded, token_decoded_len);
247
248   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
249       channel, -1, MECH_WLM, token_decoded_array,
250       generic_cb, g_object_ref (result), g_object_unref, NULL);
251
252   g_array_unref (token_decoded_array);
253   g_free (token_decoded);
254   g_object_unref (result);
255 }
256
257 gboolean
258 empathy_sasl_auth_finish (TpChannel *channel,
259     GAsyncResult *result,
260     GError **error)
261 {
262   empathy_implement_finish_void (channel, empathy_sasl_auth_common_async);
263 }
264
265 gboolean
266 empathy_sasl_channel_supports_mechanism (TpChannel *channel,
267     const gchar *mechanism)
268 {
269   GHashTable *props;
270   const gchar * const *available_mechanisms;
271
272   props = tp_channel_borrow_immutable_properties (channel);
273   available_mechanisms = tp_asv_get_boxed (props,
274       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_AVAILABLE_MECHANISMS,
275       G_TYPE_STRV);
276
277   return tp_strv_contains (available_mechanisms, mechanism);
278 }
279
280 EmpathySaslMechanism
281 empathy_sasl_channel_select_mechanism (TpChannel *channel)
282 {
283   guint i;
284
285   for (i = 0; i < G_N_ELEMENTS (supported_mechanisms); i++)
286     {
287       if (empathy_sasl_channel_supports_mechanism (channel,
288               supported_mechanisms[i].name))
289         return supported_mechanisms[i].id;
290     }
291
292   return EMPATHY_SASL_MECHANISM_UNSUPPORTED;
293 }