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