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