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