]> git.0d.be Git - empathy.git/blob - libempathy/empathy-goa-auth-handler.c
99968946d2cef18d1d245fad67f765601bd24a45
[empathy.git] / libempathy / empathy-goa-auth-handler.c
1 /*
2  * empathy-goa-auth-handler.c - Source for Goa SASL authentication
3  * Copyright (C) 2011 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 #define GOA_API_IS_SUBJECT_TO_CHANGE /* awesome! */
24 #include <goa/goa.h>
25
26 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
27 #include "empathy-debug.h"
28 #include "empathy-utils.h"
29 #include "empathy-goa-auth-handler.h"
30 #include "empathy-sasl-mechanisms.h"
31
32 struct _EmpathyGoaAuthHandlerPriv
33 {
34   GoaClient *client;
35   gboolean client_preparing;
36
37   /* List of AuthData waiting for client to be created */
38   GList *auth_queue;
39 };
40
41 G_DEFINE_TYPE (EmpathyGoaAuthHandler, empathy_goa_auth_handler, G_TYPE_OBJECT);
42
43 static void
44 empathy_goa_auth_handler_init (EmpathyGoaAuthHandler *self)
45 {
46   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
47       EMPATHY_TYPE_GOA_AUTH_HANDLER, EmpathyGoaAuthHandlerPriv);
48 }
49
50 static void
51 empathy_goa_auth_handler_dispose (GObject *object)
52 {
53   EmpathyGoaAuthHandler *self = (EmpathyGoaAuthHandler *) object;
54
55   /* AuthData keeps a ref on self */
56   g_assert (self->priv->auth_queue == NULL);
57
58   tp_clear_object (&self->priv->client);
59
60   G_OBJECT_CLASS (empathy_goa_auth_handler_parent_class)->dispose (object);
61 }
62
63 static void
64 empathy_goa_auth_handler_class_init (EmpathyGoaAuthHandlerClass *klass)
65 {
66   GObjectClass *oclass = G_OBJECT_CLASS (klass);
67
68   oclass->dispose = empathy_goa_auth_handler_dispose;
69
70   g_type_class_add_private (klass, sizeof (EmpathyGoaAuthHandlerPriv));
71 }
72
73 EmpathyGoaAuthHandler *
74 empathy_goa_auth_handler_new (void)
75 {
76   return g_object_new (EMPATHY_TYPE_GOA_AUTH_HANDLER, NULL);
77 }
78
79 typedef struct
80 {
81   EmpathyGoaAuthHandler *self;
82   TpChannel *channel;
83   TpAccount *account;
84
85   GoaObject *goa_object;
86   gchar *access_token;
87 } AuthData;
88
89 static AuthData *
90 auth_data_new (EmpathyGoaAuthHandler *self,
91     TpChannel *channel,
92     TpAccount *account)
93 {
94   AuthData *data;
95
96   data = g_slice_new0 (AuthData);
97   data->self = g_object_ref (self);
98   data->channel = g_object_ref (channel);
99   data->account = g_object_ref (account);
100
101   return data;
102 }
103
104 static void
105 auth_data_free (AuthData *data)
106 {
107   tp_clear_object (&data->self);
108   tp_clear_object (&data->channel);
109   tp_clear_object (&data->account);
110   tp_clear_object (&data->goa_object);
111   g_free (data->access_token);
112   g_slice_free (AuthData, data);
113 }
114
115 static void
116 fail_auth (AuthData *data)
117 {
118   DEBUG ("Auth failed for account %s",
119       tp_proxy_get_object_path (data->account));
120
121   tp_channel_close_async (data->channel, NULL, NULL);
122   auth_data_free (data);
123 }
124
125 static void
126 auth_cb (GObject *source,
127     GAsyncResult *result,
128     gpointer user_data)
129 {
130   TpChannel *channel = (TpChannel *) source;
131   AuthData *data = user_data;
132   GError *error = NULL;
133
134   if (!empathy_sasl_auth_finish (channel, result, &error))
135     {
136       DEBUG ("SASL Mechanism error: %s", error->message);
137       fail_auth (data);
138       g_clear_error (&error);
139       return;
140     }
141
142   /* Success! */
143   tp_channel_close_async (channel, NULL, NULL);
144   auth_data_free (data);
145 }
146
147 static void
148 got_oauth2_access_token_cb (GObject *source,
149     GAsyncResult *result,
150     gpointer user_data)
151 {
152   GoaOAuth2Based *oauth2 = (GoaOAuth2Based *) source;
153   AuthData *data = user_data;
154   gchar *access_token;
155   gint expires_in;
156   GError *error = NULL;
157
158   if (!goa_oauth2_based_call_get_access_token_finish (oauth2,
159           &access_token, &expires_in, result, &error))
160     {
161       DEBUG ("Failed to get access token: %s", error->message);
162       fail_auth (data);
163       g_clear_error (&error);
164       return;
165     }
166
167   DEBUG ("Got access token for %s:\n%s",
168       tp_proxy_get_object_path (data->account),
169       access_token);
170
171   switch (empathy_sasl_channel_select_mechanism (data->channel))
172     {
173       case EMPATHY_SASL_MECHANISM_FACEBOOK:
174         empathy_sasl_auth_facebook_async (data->channel,
175             goa_oauth2_based_get_client_id (oauth2), access_token,
176             auth_cb, NULL);
177         break;
178
179       case EMPATHY_SASL_MECHANISM_WLM:
180         empathy_sasl_auth_wlm_async (data->channel,
181             access_token,
182             auth_cb, NULL);
183         break;
184
185       default:
186         g_assert_not_reached ();
187     }
188
189   g_free (access_token);
190 }
191
192 static void
193 ensure_credentials_cb (GObject *source,
194     GAsyncResult *result,
195     gpointer user_data)
196 {
197   AuthData *data = user_data;
198   GoaAccount *goa_account = (GoaAccount *) source;
199   GoaOAuth2Based *oauth2;
200   gint expires_in;
201   GError *error = NULL;
202
203   if (!goa_account_call_ensure_credentials_finish (goa_account, &expires_in,
204       result, &error))
205     {
206       DEBUG ("Failed to EnsureCredentials: %s", error->message);
207       fail_auth (data);
208       g_clear_error (&error);
209       return;
210     }
211
212   /* We support only oaut2 */
213   oauth2 = goa_object_get_oauth2_based (data->goa_object);
214   if (oauth2 == NULL)
215     {
216       DEBUG ("GoaObject does not implement oauth2");
217       fail_auth (data);
218       return;
219     }
220
221   DEBUG ("Goa daemon has credentials for %s, get the access token",
222       tp_proxy_get_object_path (data->account));
223
224   goa_oauth2_based_call_get_access_token (oauth2, NULL,
225       got_oauth2_access_token_cb, data);
226
227   g_object_unref (oauth2);
228 }
229
230 static void
231 start_auth (AuthData *data)
232 {
233   EmpathyGoaAuthHandler *self = data->self;
234   const GValue *id_value;
235   const gchar *id;
236   GList *goa_accounts, *l;
237   gboolean found = FALSE;
238
239   id_value = tp_account_get_storage_identifier (data->account);
240   id = g_value_get_string (id_value);
241
242   goa_accounts = goa_client_get_accounts (self->priv->client);
243   for (l = goa_accounts; l != NULL && !found; l = l->next)
244     {
245       GoaObject *goa_object = l->data;
246       GoaAccount *goa_account;
247
248       goa_account = goa_object_get_account (goa_object);
249       if (!tp_strdiff (goa_account_get_id (goa_account), id))
250         {
251           data->goa_object = g_object_ref (goa_object);
252
253           DEBUG ("Found the GoaAccount for %s, ensure credentials",
254               tp_proxy_get_object_path (data->account));
255
256           goa_account_call_ensure_credentials (goa_account, NULL,
257               ensure_credentials_cb, data);
258
259           found = TRUE;
260         }
261
262       g_object_unref (goa_account);
263     }
264   g_list_free_full (goa_accounts, g_object_unref);
265
266   if (!found)
267     {
268       DEBUG ("Cannot find GoaAccount");
269       fail_auth (data);
270     }
271 }
272
273 static void
274 client_new_cb (GObject *source,
275     GAsyncResult *result,
276     gpointer user_data)
277 {
278   EmpathyGoaAuthHandler *self = user_data;
279   GList *l;
280   GError *error = NULL;
281
282   self->priv->client_preparing = FALSE;
283   self->priv->client = goa_client_new_finish (result, &error);
284   if (self->priv->client == NULL)
285     {
286       DEBUG ("Error getting GoaClient: %s", error->message);
287       g_clear_error (&error);
288     }
289
290   /* process queued data */
291   for (l = self->priv->auth_queue; l != NULL; l = l->next)
292     {
293       AuthData *data = l->data;
294
295       if (self->priv->client != NULL)
296         start_auth (data);
297       else
298         fail_auth (data);
299     }
300
301   tp_clear_pointer (&self->priv->auth_queue, g_list_free);
302 }
303
304 void
305 empathy_goa_auth_handler_start (EmpathyGoaAuthHandler *self,
306     TpChannel *channel,
307     TpAccount *account)
308 {
309   AuthData *data;
310
311   g_return_if_fail (TP_IS_CHANNEL (channel));
312   g_return_if_fail (TP_IS_ACCOUNT (account));
313   g_return_if_fail (empathy_goa_auth_handler_supports (self, channel, account));
314
315   DEBUG ("Start Goa auth for account: %s",
316       tp_proxy_get_object_path (account));
317
318   data = auth_data_new (self, channel, account);
319
320   if (self->priv->client == NULL)
321     {
322       /* GOA client not ready yet, queue data */
323       if (!self->priv->client_preparing)
324         {
325           goa_client_new (NULL, client_new_cb, self);
326           self->priv->client_preparing = TRUE;
327         }
328
329       self->priv->auth_queue = g_list_prepend (self->priv->auth_queue, data);
330     }
331   else
332     {
333       start_auth (data);
334     }
335 }
336
337 gboolean
338 empathy_goa_auth_handler_supports (EmpathyGoaAuthHandler *self,
339     TpChannel *channel,
340     TpAccount *account)
341 {
342   const gchar *provider;
343   EmpathySaslMechanism mech;
344
345   g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
346   g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
347
348   provider = tp_account_get_storage_provider (account);
349   if (tp_strdiff (provider, EMPATHY_GOA_PROVIDER))
350     return FALSE;
351
352   mech = empathy_sasl_channel_select_mechanism (channel);
353   return mech == EMPATHY_SASL_MECHANISM_FACEBOOK ||
354       mech == EMPATHY_SASL_MECHANISM_WLM;
355 }