]> git.0d.be Git - empathy.git/blob - libempathy/empathy-goa-auth-handler.c
update gnome-contacts's desktop file
[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 #include "empathy-goa-auth-handler.h"
23
24 #define GOA_API_IS_SUBJECT_TO_CHANGE /* awesome! */
25 #include <goa/goa.h>
26
27 #include "empathy-sasl-mechanisms.h"
28
29 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
30 #include "empathy-debug.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, data);
177         break;
178
179       case EMPATHY_SASL_MECHANISM_WLM:
180         empathy_sasl_auth_wlm_async (data->channel,
181             access_token,
182             auth_cb, data);
183         break;
184
185       case EMPATHY_SASL_MECHANISM_GOOGLE:
186         empathy_sasl_auth_google_async (data->channel,
187             goa_account_get_identity (goa_object_peek_account (data->goa_object)),
188             access_token, auth_cb, data);
189         break;
190
191       default:
192         g_assert_not_reached ();
193     }
194
195   g_free (access_token);
196 }
197
198 static void
199 got_password_passwd_cb (GObject *source,
200     GAsyncResult *result,
201     gpointer user_data)
202 {
203   GoaPasswordBased *password = (GoaPasswordBased *) source;
204   AuthData *data = user_data;
205   gchar *passwd;
206   GError *error = NULL;
207
208   if (!goa_password_based_call_get_password_finish (password,
209           &passwd, result, &error))
210     {
211       DEBUG ("Failed to get password: %s", error->message);
212       fail_auth (data);
213       g_clear_error (&error);
214       return;
215     }
216
217   DEBUG ("Got password for %s", tp_proxy_get_object_path (data->account));
218
219   empathy_sasl_auth_password_async (data->channel, passwd, auth_cb, data);
220   g_free (passwd);
221 }
222
223 static void
224 ensure_credentials_cb (GObject *source,
225     GAsyncResult *result,
226     gpointer user_data)
227 {
228   AuthData *data = user_data;
229   GoaAccount *goa_account = (GoaAccount *) source;
230   GoaOAuth2Based *oauth2;
231   GoaPasswordBased *password;
232   EmpathySaslMechanism mech;
233   gboolean supports_password;
234   gint expires_in;
235   GError *error = NULL;
236
237   if (!goa_account_call_ensure_credentials_finish (goa_account, &expires_in,
238       result, &error))
239     {
240       DEBUG ("Failed to EnsureCredentials: %s", error->message);
241       fail_auth (data);
242       g_clear_error (&error);
243       return;
244     }
245
246   /* We prefer oauth2, if available */
247   oauth2 = goa_object_get_oauth2_based (data->goa_object);
248   mech = empathy_sasl_channel_select_mechanism (data->channel);
249   if (oauth2 != NULL && mech != EMPATHY_SASL_MECHANISM_PASSWORD)
250     {
251       DEBUG ("Goa daemon has credentials for %s, get the access token",
252           tp_proxy_get_object_path (data->account));
253
254       goa_oauth2_based_call_get_access_token (oauth2, NULL,
255           got_oauth2_access_token_cb, data);
256
257       g_object_unref (oauth2);
258       return;
259     }
260
261   /* Else we use the password */
262   password = goa_object_get_password_based (data->goa_object);
263   supports_password = empathy_sasl_channel_supports_mechanism (data->channel,
264       "X-TELEPATHY-PASSWORD");
265   if (password != NULL && supports_password)
266     {
267       DEBUG ("Goa daemon has credentials for %s, get the password",
268           tp_proxy_get_object_path (data->account));
269
270       /* arg_id is currently unused */
271       goa_password_based_call_get_password (password, "", NULL,
272           got_password_passwd_cb, data);
273
274       g_object_unref (password);
275       return;
276     }
277
278   DEBUG ("GoaObject does not implement oauth2 or password");
279   fail_auth (data);
280 }
281
282 static void
283 start_auth (AuthData *data)
284 {
285   EmpathyGoaAuthHandler *self = data->self;
286   const GValue *id_value;
287   const gchar *id;
288   GList *goa_accounts, *l;
289   gboolean found = FALSE;
290
291   id_value = tp_account_get_storage_identifier (data->account);
292   id = g_value_get_string (id_value);
293
294   goa_accounts = goa_client_get_accounts (self->priv->client);
295   for (l = goa_accounts; l != NULL && !found; l = l->next)
296     {
297       GoaObject *goa_object = l->data;
298       GoaAccount *goa_account;
299
300       goa_account = goa_object_get_account (goa_object);
301       if (!tp_strdiff (goa_account_get_id (goa_account), id))
302         {
303           data->goa_object = g_object_ref (goa_object);
304
305           DEBUG ("Found the GoaAccount for %s, ensure credentials",
306               tp_proxy_get_object_path (data->account));
307
308           goa_account_call_ensure_credentials (goa_account, NULL,
309               ensure_credentials_cb, data);
310
311           found = TRUE;
312         }
313
314       g_object_unref (goa_account);
315     }
316   g_list_free_full (goa_accounts, g_object_unref);
317
318   if (!found)
319     {
320       DEBUG ("Cannot find GoaAccount");
321       fail_auth (data);
322     }
323 }
324
325 static void
326 client_new_cb (GObject *source,
327     GAsyncResult *result,
328     gpointer user_data)
329 {
330   EmpathyGoaAuthHandler *self = user_data;
331   GList *l;
332   GError *error = NULL;
333
334   self->priv->client_preparing = FALSE;
335   self->priv->client = goa_client_new_finish (result, &error);
336   if (self->priv->client == NULL)
337     {
338       DEBUG ("Error getting GoaClient: %s", error->message);
339       g_clear_error (&error);
340     }
341
342   /* process queued data */
343   for (l = self->priv->auth_queue; l != NULL; l = l->next)
344     {
345       AuthData *data = l->data;
346
347       if (self->priv->client != NULL)
348         start_auth (data);
349       else
350         fail_auth (data);
351     }
352
353   tp_clear_pointer (&self->priv->auth_queue, g_list_free);
354 }
355
356 void
357 empathy_goa_auth_handler_start (EmpathyGoaAuthHandler *self,
358     TpChannel *channel,
359     TpAccount *account)
360 {
361   AuthData *data;
362
363   g_return_if_fail (TP_IS_CHANNEL (channel));
364   g_return_if_fail (TP_IS_ACCOUNT (account));
365   g_return_if_fail (empathy_goa_auth_handler_supports (self, channel, account));
366
367   DEBUG ("Start Goa auth for account: %s",
368       tp_proxy_get_object_path (account));
369
370   data = auth_data_new (self, channel, account);
371
372   if (self->priv->client == NULL)
373     {
374       /* GOA client not ready yet, queue data */
375       if (!self->priv->client_preparing)
376         {
377           goa_client_new (NULL, client_new_cb, self);
378           self->priv->client_preparing = TRUE;
379         }
380
381       self->priv->auth_queue = g_list_prepend (self->priv->auth_queue, data);
382     }
383   else
384     {
385       start_auth (data);
386     }
387 }
388
389 gboolean
390 empathy_goa_auth_handler_supports (EmpathyGoaAuthHandler *self,
391     TpChannel *channel,
392     TpAccount *account)
393 {
394   const gchar *provider;
395   EmpathySaslMechanism mech;
396
397   g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
398   g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
399
400   provider = tp_account_get_storage_provider (account);
401   if (tp_strdiff (provider, EMPATHY_GOA_PROVIDER))
402     return FALSE;
403
404   mech = empathy_sasl_channel_select_mechanism (channel);
405   return mech == EMPATHY_SASL_MECHANISM_FACEBOOK ||
406       mech == EMPATHY_SASL_MECHANISM_WLM ||
407       mech == EMPATHY_SASL_MECHANISM_GOOGLE ||
408       mech == EMPATHY_SASL_MECHANISM_PASSWORD;
409 }