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