empathy-uoa-auth-handler.c 12.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 * empathy-auth-uoa.c - Source for Uoa SASL authentication
 * Copyright (C) 2012 Collabora Ltd.
 * @author Xavier Claessens <xavier.claessens@collabora.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <libaccounts-glib/ag-account.h>
#include <libaccounts-glib/ag-account-service.h>
#include <libaccounts-glib/ag-auth-data.h>
#include <libaccounts-glib/ag-manager.h>
#include <libaccounts-glib/ag-service.h>

#include <libsignon-glib/signon-identity.h>
#include <libsignon-glib/signon-auth-session.h>
29
#include <libsignon-glib/signon-errors.h>
30

31 32
#include <sailfishkeyprovider.h>

33 34
#include <string.h>

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
#define DEBUG_FLAG EMPATHY_DEBUG_SASL
#include "empathy-debug.h"
#include "empathy-keyring.h"
#include "empathy-utils.h"
#include "empathy-uoa-auth-handler.h"
#include "empathy-uoa-utils.h"
#include "empathy-sasl-mechanisms.h"

struct _EmpathyUoaAuthHandlerPriv
{
  AgManager *manager;
};

G_DEFINE_TYPE (EmpathyUoaAuthHandler, empathy_uoa_auth_handler, G_TYPE_OBJECT);

static void
empathy_uoa_auth_handler_init (EmpathyUoaAuthHandler *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      EMPATHY_TYPE_UOA_AUTH_HANDLER, EmpathyUoaAuthHandlerPriv);

  self->priv->manager = empathy_uoa_manager_dup ();
}

static void
empathy_uoa_auth_handler_dispose (GObject *object)
{
  EmpathyUoaAuthHandler *self = (EmpathyUoaAuthHandler *) object;

  tp_clear_object (&self->priv->manager);

  G_OBJECT_CLASS (empathy_uoa_auth_handler_parent_class)->dispose (object);
}

static void
empathy_uoa_auth_handler_class_init (EmpathyUoaAuthHandlerClass *klass)
{
  GObjectClass *oclass = G_OBJECT_CLASS (klass);

  oclass->dispose = empathy_uoa_auth_handler_dispose;

  g_type_class_add_private (klass, sizeof (EmpathyUoaAuthHandlerPriv));
}

EmpathyUoaAuthHandler *
empathy_uoa_auth_handler_new (void)
{
  return g_object_new (EMPATHY_TYPE_UOA_AUTH_HANDLER, NULL);
}

typedef struct
{
  TpChannel *channel;
  AgAccountService *service;
  AgAuthData *auth_data;
  SignonIdentity *identity;
  SignonAuthSession *session;

  gchar *username;
94
  gchar *client_id;
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
} AuthContext;

static AuthContext *
auth_context_new (TpChannel *channel,
    AgAccountService *service)
{
  AuthContext *ctx;
  guint cred_id;

  ctx = g_slice_new0 (AuthContext);
  ctx->channel = g_object_ref (channel);
  ctx->service = g_object_ref (service);

  ctx->auth_data = ag_account_service_get_auth_data (service);
  if (ctx->auth_data == NULL)
    goto out;

  cred_id = ag_auth_data_get_credentials_id (ctx->auth_data);
  if (cred_id == 0)
    goto out;

  ctx->identity = signon_identity_new_from_db (cred_id);
  if (ctx->identity == NULL)
    goto out;

  ctx->session = signon_identity_create_session (ctx->identity,
      ag_auth_data_get_method (ctx->auth_data), NULL);
  if (ctx->session == NULL)
    goto out;

125 126 127
  ctx->username = 0;
  ctx->client_id = 0;

128 129 130 131 132 133 134 135 136 137 138 139 140
out:
  return ctx;
}

static void
auth_context_free (AuthContext *ctx)
{
  g_clear_object (&ctx->channel);
  g_clear_object (&ctx->service);
  tp_clear_pointer (&ctx->auth_data, ag_auth_data_unref);
  g_clear_object (&ctx->session);
  g_clear_object (&ctx->identity);
  g_free (ctx->username);
141
  g_free (ctx->client_id);
142 143 144 145 146 147 148 149 150 151 152 153

  g_slice_free (AuthContext, ctx);
}

static void
auth_context_done (AuthContext *ctx)
{
  tp_channel_close_async (ctx->channel, NULL, NULL);
  auth_context_free (ctx);
}

static void
154 155 156
request_password_account_store_cb (GObject *source_object,
                                   GAsyncResult *res,
                                   gpointer user_data)
157
{
158
  AgAccount *account = AG_ACCOUNT(source_object);
159
  AuthContext *ctx = user_data;
160
  GError *error = NULL;
161

162
  if (!ag_account_store_finish (account, res, &error))
163
    {
164 165 166
      g_assert (error != NULL);
      DEBUG ("Error setting CredentialsNeedUpdate on account %u: %s",
             account->id,
167
             error->message);
168
      g_error_free (error);
169 170
    }

171
  auth_context_done(ctx);
172 173 174 175 176 177 178
}

static void
request_password (AuthContext *ctx)
{
  DEBUG ("Invalid credentials, request user action");

179
  AgAccount *account = ag_account_service_get_account (ctx->service);
180

181 182
  ag_account_set_variant (account, "CredentialsNeedUpdate", g_variant_new_boolean(TRUE));
  ag_account_set_variant (account, "CredentialsNeedUpdateFrom", g_variant_new_string("telepathy-sasl-signon"));
183

184 185
  g_message ("telepathy-sasl-signon: setting CredentialsNeedUpdate on service %s for account: %u",
             ag_service_get_name(ag_account_service_get_service (ctx->service)), account->id);
186

187
  ag_account_store_async (account, 0, request_password_account_store_cb, ctx);
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
}

static void
auth_cb (GObject *source,
    GAsyncResult *result,
    gpointer user_data)
{
  TpChannel *channel = (TpChannel *) source;
  AuthContext *ctx = user_data;
  GError *error = NULL;

  if (!empathy_sasl_auth_finish (channel, result, &error))
    {
      DEBUG ("SASL Mechanism error: %s", error->message);

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
      // If the error looks like credential failure, set the CredentialsNeedUpdate flag.
      // We cannot set the flag for all SASL errors, because this includes things like a
      // connection reset during authentication.
      if (strstr(error->message, "WOCKY_AUTH_ERROR_FAILURE") ||
          strstr(error->message, "WOCKY_AUTH_ERROR_NOT_AUTHORIZED"))
        {
          request_password (ctx);
        }
      else
        {
          DEBUG ("Auth on %s failed", tp_proxy_get_object_path (channel));
          auth_context_done (ctx);
        }

      g_clear_error (&error);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    }
  else
    {
      DEBUG ("Auth on %s suceeded", tp_proxy_get_object_path (channel));
      auth_context_done (ctx);
    }
}

static void
session_process_cb (SignonAuthSession *session,
    GHashTable *session_data,
    const GError *error,
    gpointer user_data)
{
  AuthContext *ctx = user_data;
  const gchar *access_token;
234
  const gchar *auth_method;
235 236 237 238

  if (error != NULL)
    {
      DEBUG ("Error processing the session: %s", error->message);
239
      if (g_error_matches(error, SIGNON_ERROR, SIGNON_ERROR_USER_INTERACTION))
240 241
        {
          request_password(ctx);
242
        }
243 244 245 246
      else
        {
          auth_context_done (ctx);
        }
247 248 249 250
      return;
    }

  access_token = tp_asv_get_string (session_data, "AccessToken");
251
  auth_method = signon_auth_session_get_method (session);
252

253
  switch (empathy_sasl_channel_select_mechanism (ctx->channel, auth_method))
254 255 256
    {
      case EMPATHY_SASL_MECHANISM_FACEBOOK:
        empathy_sasl_auth_facebook_async (ctx->channel,
257
            ctx->client_id, access_token,
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
            auth_cb, ctx);
        break;

      case EMPATHY_SASL_MECHANISM_WLM:
        empathy_sasl_auth_wlm_async (ctx->channel,
            access_token,
            auth_cb, ctx);
        break;

      case EMPATHY_SASL_MECHANISM_GOOGLE:
        empathy_sasl_auth_google_async (ctx->channel,
            ctx->username, access_token,
            auth_cb, ctx);
        break;

      case EMPATHY_SASL_MECHANISM_PASSWORD:
        empathy_sasl_auth_password_async (ctx->channel,
            tp_asv_get_string (session_data, "Secret"),
            auth_cb, ctx);
        break;

      default:
        g_assert_not_reached ();
    }
}

static void
identity_query_info_cb (SignonIdentity *identity,
    const SignonIdentityInfo *info,
    const GError *error,
    gpointer user_data)
{
  AuthContext *ctx = user_data;
291
  gchar *client_secret = 0;
292
  (void)identity; // suppress unused parameter warning
293 294 295 296 297 298 299 300 301

  if (error != NULL)
    {
      DEBUG ("Error querying info from identity: %s", error->message);
      auth_context_done (ctx);
      return;
    }

  ctx->username = g_strdup (signon_identity_info_get_username (info));
302 303
  if (!ctx->username || !*ctx->username)
    {
304
      GVariant *v;
305
      AgAccount *account = ag_account_service_get_account (ctx->service);
306 307 308 309 310 311 312 313 314 315 316 317 318
      AgService *old_service = ag_account_get_selected_service (account);
      ag_account_select_service (account, NULL);

      g_free (ctx->username);
      v = ag_account_get_variant (account, "default_credentials_username", NULL);
      if (v)
        ctx->username = g_variant_dup_string (v, NULL);
      else
        ctx->username = NULL;

      ag_account_select_service (account, old_service);

      DEBUG ("No username in signon data, falling back to default_credentials_username '%s'", ctx->username);
319
    }
320

321 322 323 324 325
  // after upgrade to glib2.4 use:
  // GVariant *params = ag_auth_data_get_login_parameters (ctx->auth_data, NULL);
  // g_variant_dict_insert_value (params, g_strdup("ClientId"), g_variant_new_string(ctx->client_id));
  // g_variant_dict_insert_value (params, g_strdup("ClientSecret"), g_variant_new_string(client_secret));
  // g_variant_dict_insert_value (params, g_strdup(SIGNON_SESSION_DATA_UI_POLICY), g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION));
326 327 328 329 330 331
  GHashTable *params = ag_auth_data_get_parameters (ctx->auth_data);
  AgService *service = ag_account_service_get_service (ctx->service);

  SailfishKeyProvider_storedKey (ag_service_get_provider (service),
      ag_service_get_name (service), "client_id", &ctx->client_id);
  if (ctx->client_id)
332
      tp_asv_set_string (params, g_strdup("ClientId"), ctx->client_id);
333

334 335 336
  SailfishKeyProvider_storedKey (ag_service_get_provider (service),
      ag_service_get_name (service), "client_secret", &client_secret);
  if (client_secret)
337
      tp_asv_set_string (params, g_strdup("ClientSecret"), client_secret);
338 339
  g_free(client_secret);

340
  tp_asv_set_int32 (params, g_strdup(SIGNON_SESSION_DATA_UI_POLICY), SIGNON_POLICY_NO_USER_INTERACTION);
341

342 343
  signon_auth_session_process (
      ctx->session,
344
      params,
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
      ag_auth_data_get_mechanism (ctx->auth_data),
      session_process_cb,
      ctx);
}

void
empathy_uoa_auth_handler_start (EmpathyUoaAuthHandler *self,
    TpChannel *channel,
    TpAccount *tp_account)
{
  const GValue *id_value;
  AgAccountId id;
  AgAccount *account;
  GList *l = NULL;
  AgAccountService *service;
  AuthContext *ctx;

  g_return_if_fail (TP_IS_CHANNEL (channel));
  g_return_if_fail (TP_IS_ACCOUNT (tp_account));
  g_return_if_fail (empathy_uoa_auth_handler_supports (self, channel,
      tp_account));

  DEBUG ("Start UOA auth for account: %s",
      tp_proxy_get_object_path (tp_account));

  id_value = tp_account_get_storage_identifier (tp_account);
  id = g_value_get_uint (id_value);

  account = ag_manager_get_account (self->priv->manager, id);
  if (account != NULL)
    l = ag_account_list_services_by_type (account, EMPATHY_UOA_SERVICE_TYPE);
  if (l == NULL)
    {
      DEBUG ("Couldn't find IM service for AgAccountId %u", id);
379 380
      if (account != NULL)
        g_object_unref (account);
381 382 383 384 385 386 387 388 389 390 391 392
      tp_channel_close_async (channel, NULL, NULL);
      return;
    }

  /* Assume there is only one IM service */
  service = ag_account_service_new (account, l->data);
  ag_service_list_free (l);
  g_object_unref (account);

  ctx = auth_context_new (channel, service);
  if (ctx->session == NULL)
    {
393 394
      DEBUG ("Couldn't create a signon session for AgAccountId %u", id);
      auth_context_done (ctx);
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    }
  else
    {
      /* All is fine! Query UOA for more info */
      signon_identity_query_info (ctx->identity,
          identity_query_info_cb, ctx);
    }

  g_object_unref (service);
}

gboolean
empathy_uoa_auth_handler_supports (EmpathyUoaAuthHandler *self,
    TpChannel *channel,
    TpAccount *account)
{
  const gchar *provider;
412
  (void)self; // suppress unused parameter warning
413 414 415 416 417 418 419 420 421

  g_return_val_if_fail (TP_IS_CHANNEL (channel), FALSE);
  g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);

  provider = tp_account_get_storage_provider (account);

  if (tp_strdiff (provider, EMPATHY_UOA_PROVIDER))
    return FALSE;

422
  return TRUE;
423
}