]> git.0d.be Git - empathy.git/blob - libempathy/empathy-uoa-auth-handler.c
Center the 'smiley images' inside the menu items
[empathy.git] / libempathy / empathy-uoa-auth-handler.c
1 /*
2  * empathy-auth-uoa.c - Source for Uoa SASL authentication
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 #include "empathy-uoa-auth-handler.h"
23
24 #include <libaccounts-glib/ag-account.h>
25 #include <libaccounts-glib/ag-account-service.h>
26 #include <libaccounts-glib/ag-auth-data.h>
27 #include <libaccounts-glib/ag-manager.h>
28 #include <libaccounts-glib/ag-service.h>
29
30 #include <libsignon-glib/signon-identity.h>
31 #include <libsignon-glib/signon-auth-session.h>
32
33 #include <tp-account-widgets/tpaw-keyring.h>
34 #include <tp-account-widgets/tpaw-uoa-utils.h>
35
36 #include "empathy-utils.h"
37 #include "empathy-sasl-mechanisms.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
40 #include "empathy-debug.h"
41
42 struct _EmpathyUoaAuthHandlerPriv
43 {
44   AgManager *manager;
45 };
46
47 G_DEFINE_TYPE (EmpathyUoaAuthHandler, empathy_uoa_auth_handler, G_TYPE_OBJECT);
48
49 static void
50 empathy_uoa_auth_handler_init (EmpathyUoaAuthHandler *self)
51 {
52   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
53       EMPATHY_TYPE_UOA_AUTH_HANDLER, EmpathyUoaAuthHandlerPriv);
54
55   self->priv->manager = tpaw_uoa_manager_dup ();
56 }
57
58 static void
59 empathy_uoa_auth_handler_dispose (GObject *object)
60 {
61   EmpathyUoaAuthHandler *self = (EmpathyUoaAuthHandler *) object;
62
63   tp_clear_object (&self->priv->manager);
64
65   G_OBJECT_CLASS (empathy_uoa_auth_handler_parent_class)->dispose (object);
66 }
67
68 static void
69 empathy_uoa_auth_handler_class_init (EmpathyUoaAuthHandlerClass *klass)
70 {
71   GObjectClass *oclass = G_OBJECT_CLASS (klass);
72
73   oclass->dispose = empathy_uoa_auth_handler_dispose;
74
75   g_type_class_add_private (klass, sizeof (EmpathyUoaAuthHandlerPriv));
76 }
77
78 EmpathyUoaAuthHandler *
79 empathy_uoa_auth_handler_new (void)
80 {
81   return g_object_new (EMPATHY_TYPE_UOA_AUTH_HANDLER, NULL);
82 }
83
84 typedef struct
85 {
86   TpChannel *channel;
87   AgAccountService *service;
88   AgAuthData *auth_data;
89   SignonIdentity *identity;
90   SignonAuthSession *session;
91
92   gchar *username;
93 } AuthContext;
94
95 static AuthContext *
96 auth_context_new (TpChannel *channel,
97     AgAccountService *service)
98 {
99   AuthContext *ctx;
100   guint cred_id;
101
102   ctx = g_slice_new0 (AuthContext);
103   ctx->channel = g_object_ref (channel);
104   ctx->service = g_object_ref (service);
105
106   ctx->auth_data = ag_account_service_get_auth_data (service);
107   if (ctx->auth_data == NULL)
108     goto out;
109
110   cred_id = ag_auth_data_get_credentials_id (ctx->auth_data);
111   if (cred_id == 0)
112     goto out;
113
114   ctx->identity = signon_identity_new_from_db (cred_id);
115   if (ctx->identity == NULL)
116     goto out;
117
118   ctx->session = signon_identity_create_session (ctx->identity,
119       ag_auth_data_get_method (ctx->auth_data), NULL);
120   if (ctx->session == NULL)
121     goto out;
122
123 out:
124   return ctx;
125 }
126
127 static void
128 auth_context_free (AuthContext *ctx)
129 {
130   g_clear_object (&ctx->channel);
131   g_clear_object (&ctx->service);
132   tp_clear_pointer (&ctx->auth_data, ag_auth_data_unref);
133   g_clear_object (&ctx->session);
134   g_clear_object (&ctx->identity);
135   g_free (ctx->username);
136
137   g_slice_free (AuthContext, ctx);
138 }
139
140 static void
141 auth_context_done (AuthContext *ctx)
142 {
143   tp_channel_close_async (ctx->channel, NULL, NULL);
144   auth_context_free (ctx);
145 }
146
147 static void
148 request_password_session_process_cb (GObject *source,
149     GAsyncResult *result,
150     gpointer user_data)
151 {
152   SignonAuthSession *session = (SignonAuthSession *) source;
153   AuthContext *ctx = user_data;
154   GVariant *variant;
155   GError *error = NULL;
156
157   variant = signon_auth_session_process_finish (session, result, &error);
158   if (error != NULL)
159     {
160       DEBUG ("Error processing the session to request user's attention: %s",
161           error->message);
162       g_clear_error (&error);
163     }
164
165   g_variant_unref (variant);
166
167   auth_context_done (ctx);
168 }
169
170 static void
171 request_password (AuthContext *ctx)
172 {
173   GVariantBuilder builder;
174
175   DEBUG ("Invalid credentials, request user action");
176
177   /* Inform SSO that the access token (or password) didn't work and it should
178    * ask user to re-grant access (or retype password). */
179   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
180   g_variant_builder_add (&builder, "{sv}",
181       SIGNON_SESSION_DATA_UI_POLICY,
182       g_variant_new_int32 (SIGNON_POLICY_REQUEST_PASSWORD));
183
184   signon_auth_session_process_async (ctx->session,
185       ag_auth_data_get_login_parameters (ctx->auth_data,
186           g_variant_builder_end (&builder)),
187       ag_auth_data_get_mechanism (ctx->auth_data),
188       NULL,
189       request_password_session_process_cb, ctx);
190 }
191
192 static void
193 auth_cb (GObject *source,
194     GAsyncResult *result,
195     gpointer user_data)
196 {
197   TpChannel *channel = (TpChannel *) source;
198   AuthContext *ctx = user_data;
199   GError *error = NULL;
200
201   if (!empathy_sasl_auth_finish (channel, result, &error))
202     {
203       DEBUG ("SASL Mechanism error: %s", error->message);
204       g_clear_error (&error);
205
206       request_password (ctx);
207     }
208   else
209     {
210       DEBUG ("Auth on %s suceeded", tp_proxy_get_object_path (channel));
211       auth_context_done (ctx);
212     }
213 }
214
215 static void
216 session_process_cb (GObject *source,
217     GAsyncResult *result,
218     gpointer user_data)
219 {
220   SignonAuthSession *session = (SignonAuthSession *) source;
221   AuthContext *ctx = user_data;
222   GVariant *session_data;
223   const gchar *access_token;
224   const gchar *client_id;
225   const gchar *secret;
226   GVariant *params;
227   GError *error = NULL;
228
229   session_data = signon_auth_session_process_finish (session, result, &error);
230   if (error != NULL)
231     {
232       DEBUG ("Error processing the session: %s", error->message);
233       auth_context_done (ctx);
234       g_clear_error (&error);
235       return;
236     }
237
238   params = g_variant_ref_sink (
239       ag_auth_data_get_login_parameters (ctx->auth_data, NULL));
240
241   g_variant_lookup (params, "ClientId", "&s", &client_id);
242   g_variant_lookup (session_data, "AccessToken", "&s", &access_token);
243   g_variant_lookup (session_data, "Secret", "&s", &secret);
244
245   switch (empathy_sasl_channel_select_mechanism (ctx->channel))
246     {
247       case EMPATHY_SASL_MECHANISM_FACEBOOK:
248         empathy_sasl_auth_facebook_async (ctx->channel,
249             client_id, access_token,
250             auth_cb, ctx);
251         break;
252
253       case EMPATHY_SASL_MECHANISM_WLM:
254         empathy_sasl_auth_wlm_async (ctx->channel,
255             access_token,
256             auth_cb, ctx);
257         break;
258
259       case EMPATHY_SASL_MECHANISM_GOOGLE:
260         empathy_sasl_auth_google_async (ctx->channel,
261             ctx->username, access_token,
262             auth_cb, ctx);
263         break;
264
265       case EMPATHY_SASL_MECHANISM_PASSWORD:
266         empathy_sasl_auth_password_async (ctx->channel,
267             secret,
268             auth_cb, ctx);
269         break;
270
271       default:
272         g_assert_not_reached ();
273     }
274
275   g_variant_unref (params);
276 }
277
278 static void
279 identity_query_info_cb (SignonIdentity *identity,
280     const SignonIdentityInfo *info,
281     const GError *error,
282     gpointer user_data)
283 {
284   AuthContext *ctx = user_data;
285
286   if (error != NULL)
287     {
288       DEBUG ("Error querying info from identity: %s", error->message);
289       auth_context_done (ctx);
290       return;
291     }
292
293   ctx->username = g_strdup (signon_identity_info_get_username (info));
294
295   signon_auth_session_process_async (ctx->session,
296       ag_auth_data_get_login_parameters (ctx->auth_data, NULL),
297       ag_auth_data_get_mechanism (ctx->auth_data),
298       NULL,
299       session_process_cb,
300       ctx);
301 }
302
303 static void
304 set_account_password_cb (GObject *source,
305     GAsyncResult *result,
306     gpointer user_data)
307 {
308   TpAccount *tp_account = (TpAccount *) source;
309   AuthContext *ctx = user_data;
310   AuthContext *new_ctx;
311   GError *error = NULL;
312
313   if (!tpaw_keyring_set_account_password_finish (tp_account, result, &error))
314     {
315       DEBUG ("Failed to set empty password on UOA account: %s", error->message);
316       auth_context_done (ctx);
317       return;
318     }
319
320   new_ctx = auth_context_new (ctx->channel, ctx->service);
321   auth_context_free (ctx);
322
323   if (new_ctx->session != NULL)
324     {
325       /* The trick worked! */
326       request_password (new_ctx);
327       return;
328     }
329
330   DEBUG ("Still can't get a signon session, even after setting empty pwd");
331   auth_context_done (new_ctx);
332 }
333
334 void
335 empathy_uoa_auth_handler_start (EmpathyUoaAuthHandler *self,
336     TpChannel *channel,
337     TpAccount *tp_account)
338 {
339   const GValue *id_value;
340   AgAccountId id;
341   AgAccount *account;
342   GList *l = NULL;
343   AgAccountService *service;
344   AuthContext *ctx;
345
346   g_return_if_fail (TP_IS_CHANNEL (channel));
347   g_return_if_fail (TP_IS_ACCOUNT (tp_account));
348   g_return_if_fail (empathy_uoa_auth_handler_supports (self, channel,
349       tp_account));
350
351   DEBUG ("Start UOA auth for account: %s",
352       tp_proxy_get_object_path (tp_account));
353
354   id_value = tp_account_get_storage_identifier (tp_account);
355   id = g_value_get_uint (id_value);
356
357   account = ag_manager_get_account (self->priv->manager, id);
358   if (account != NULL)
359     l = ag_account_list_services_by_type (account, TPAW_UOA_SERVICE_TYPE);
360   if (l == NULL)
361     {
362       DEBUG ("Couldn't find IM service for AgAccountId %u", id);
363       g_object_unref (account);
364       tp_channel_close_async (channel, NULL, NULL);
365       return;
366     }
367
368   /* Assume there is only one IM service */
369   service = ag_account_service_new (account, l->data);
370   ag_service_list_free (l);
371   g_object_unref (account);
372
373   ctx = auth_context_new (channel, service);
374   if (ctx->session == NULL)
375     {
376       /* This (usually?) means we never stored credentials for this account.
377        * To ask user to type his password SSO needs a SignonIdentity bound to
378        * our account. Let's store an empty password. */
379       DEBUG ("Couldn't create a signon session");
380       tpaw_keyring_set_account_password_async (tp_account, "", FALSE,
381           set_account_password_cb, ctx);
382     }
383   else
384     {
385       /* All is fine! Query UOA for more info */
386       signon_identity_query_info (ctx->identity,
387           identity_query_info_cb, ctx);
388     }
389
390   g_object_unref (service);
391 }
392
393 gboolean
394 empathy_uoa_auth_handler_supports (EmpathyUoaAuthHandler *self,
395     TpChannel *channel,
396     TpAccount *account)
397 {
398   const gchar *provider;
399   EmpathySaslMechanism mech;
400
401   g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
402   g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
403
404   provider = tp_account_get_storage_provider (account);
405
406   if (tp_strdiff (provider, EMPATHY_UOA_PROVIDER))
407     return FALSE;
408
409   mech = empathy_sasl_channel_select_mechanism (channel);
410   return mech == EMPATHY_SASL_MECHANISM_FACEBOOK ||
411       mech == EMPATHY_SASL_MECHANISM_WLM ||
412       mech == EMPATHY_SASL_MECHANISM_GOOGLE ||
413       mech == EMPATHY_SASL_MECHANISM_PASSWORD;
414 }