aboutsummaryrefslogtreecommitdiffstats
path: root/libempathy/empathy-goa-auth-handler.c
diff options
context:
space:
mode:
authorXavier Claessens <xclaesse@gmail.com>2011-10-27 18:09:59 +0800
committerXavier Claessens <xclaesse@gmail.com>2011-11-04 17:10:19 +0800
commitd3e575fd305487eb8a15a66941651325904ba90b (patch)
tree2aa27a2f1142ba3f9a34bc04263172ccc4494a39 /libempathy/empathy-goa-auth-handler.c
parentbaeff8af237dd7c4a18d578ed71b3ee87ceb278d (diff)
downloadgsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar.gz
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar.bz2
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar.lz
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar.xz
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.tar.zst
gsoc2013-empathy-d3e575fd305487eb8a15a66941651325904ba90b.zip
Import Facebook and windows Live GOA accounts
Implement their auth mechanisms Fixes bug #661068 and #652544
Diffstat (limited to 'libempathy/empathy-goa-auth-handler.c')
-rw-r--r--libempathy/empathy-goa-auth-handler.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/libempathy/empathy-goa-auth-handler.c b/libempathy/empathy-goa-auth-handler.c
new file mode 100644
index 000000000..a439c72c2
--- /dev/null
+++ b/libempathy/empathy-goa-auth-handler.c
@@ -0,0 +1,436 @@
+/*
+ * empathy-auth-goa.c - Source for Goa SASL authentication
+ * Copyright (C) 2011 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 "config.h"
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE /* awesome! */
+#include <goa/goa.h>
+
+#include <libsoup/soup.h>
+#include <string.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_SASL
+#include "empathy-debug.h"
+#include "empathy-utils.h"
+#include "empathy-goa-auth-handler.h"
+
+#define MECH_FACEBOOK "X-FACEBOOK-PLATFORM"
+#define MECH_MSN "X-MESSENGER-OAUTH2"
+
+static const gchar *supported_mechanisms[] = {
+ MECH_FACEBOOK,
+ MECH_MSN,
+ NULL};
+
+struct _EmpathyGoaAuthHandlerPriv
+{
+ GoaClient *client;
+ gboolean client_preparing;
+
+ /* List of AuthData waiting for client to be created */
+ GList *auth_queue;
+};
+
+G_DEFINE_TYPE (EmpathyGoaAuthHandler, empathy_goa_auth_handler, G_TYPE_OBJECT);
+
+static void
+empathy_goa_auth_handler_init (EmpathyGoaAuthHandler *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EMPATHY_TYPE_GOA_AUTH_HANDLER, EmpathyGoaAuthHandlerPriv);
+}
+
+static void
+empathy_goa_auth_handler_dispose (GObject *object)
+{
+ EmpathyGoaAuthHandler *self = (EmpathyGoaAuthHandler *) object;
+
+ /* AuthData keeps a ref on self */
+ g_assert (self->priv->auth_queue == NULL);
+
+ tp_clear_object (&self->priv->client);
+
+ G_OBJECT_CLASS (empathy_goa_auth_handler_parent_class)->dispose (object);
+}
+
+static void
+empathy_goa_auth_handler_class_init (EmpathyGoaAuthHandlerClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = empathy_goa_auth_handler_dispose;
+
+ g_type_class_add_private (klass, sizeof (EmpathyGoaAuthHandlerPriv));
+}
+
+EmpathyGoaAuthHandler *
+empathy_goa_auth_handler_new (void)
+{
+ return g_object_new (EMPATHY_TYPE_GOA_AUTH_HANDLER, NULL);
+}
+
+typedef struct
+{
+ EmpathyGoaAuthHandler *self;
+ TpChannel *channel;
+ TpAccount *account;
+
+ GoaObject *goa_object;
+ gchar *access_token;
+} AuthData;
+
+static void
+auth_data_free (AuthData *data)
+{
+ tp_clear_object (&data->self);
+ tp_clear_object (&data->channel);
+ tp_clear_object (&data->account);
+ tp_clear_object (&data->goa_object);
+ g_free (data->access_token);
+ g_slice_free (AuthData, data);
+}
+
+static void
+fail_auth (AuthData *data)
+{
+ DEBUG ("Auth failed for account %s",
+ tp_proxy_get_object_path (data->account));
+
+ tp_channel_close_async (data->channel, NULL, NULL);
+ auth_data_free (data);
+}
+
+static void
+sasl_status_changed_cb (TpChannel *channel,
+ guint status,
+ const gchar *reason,
+ GHashTable *details,
+ gpointer user_data,
+ GObject *self)
+{
+ switch (status)
+ {
+ case TP_SASL_STATUS_SERVER_SUCCEEDED:
+ tp_cli_channel_interface_sasl_authentication_call_accept_sasl (channel,
+ -1, NULL, NULL, NULL, NULL);
+ break;
+
+ case TP_SASL_STATUS_SUCCEEDED:
+ case TP_SASL_STATUS_SERVER_FAILED:
+ case TP_SASL_STATUS_CLIENT_FAILED:
+ tp_cli_channel_call_close (channel, -1, NULL, NULL, NULL, NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+facebook_new_challenge_cb (TpChannel *channel,
+ const GArray *challenge,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ AuthData *data = user_data;
+ GoaOAuth2Based *oauth2;
+ const gchar *client_id;
+ GHashTable *h;
+ GHashTable *params;
+ gchar *response;
+ GArray *response_array;
+
+ DEBUG ("new challenge for %s:\n%s",
+ tp_proxy_get_object_path (data->account),
+ challenge->data);
+
+ h = soup_form_decode (challenge->data);
+
+ oauth2 = goa_object_get_oauth2_based (data->goa_object);
+ client_id = goa_oauth2_based_get_client_id (oauth2);
+
+ /* See https://developers.facebook.com/docs/chat/#platauth */
+ params = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (params, "method", g_hash_table_lookup (h, "method"));
+ g_hash_table_insert (params, "nonce", g_hash_table_lookup (h, "nonce"));
+ g_hash_table_insert (params, "access_token", data->access_token);
+ g_hash_table_insert (params, "api_key", (gpointer) client_id);
+ g_hash_table_insert (params, "call_id", "0");
+ g_hash_table_insert (params, "v", "1.0");
+
+ response = soup_form_encode_hash (params);
+ DEBUG ("Response: %s", response);
+
+ response_array = g_array_new (FALSE, FALSE, sizeof (gchar));
+ g_array_append_vals (response_array, response, strlen (response));
+
+ tp_cli_channel_interface_sasl_authentication_call_respond (data->channel, -1,
+ response_array, NULL, NULL, NULL, NULL);
+
+ g_hash_table_unref (h);
+ g_hash_table_unref (params);
+ g_object_unref (oauth2);
+ g_free (response);
+ g_array_unref (response_array);
+}
+
+static void
+got_oauth2_access_token_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GoaOAuth2Based *oauth2 = (GoaOAuth2Based *) source;
+ AuthData *data = user_data;
+ gint expires_in;
+ GError *error = NULL;
+
+ if (!goa_oauth2_based_call_get_access_token_finish (oauth2,
+ &data->access_token, &expires_in, result, &error))
+ {
+ DEBUG ("Failed to get access token: %s", error->message);
+ fail_auth (data);
+ g_clear_error (&error);
+ return;
+ }
+
+ DEBUG ("Got access token for %s:\n%s",
+ tp_proxy_get_object_path (data->account),
+ data->access_token);
+
+ tp_cli_channel_interface_sasl_authentication_connect_to_sasl_status_changed (
+ data->channel, sasl_status_changed_cb, NULL, NULL, NULL, NULL);
+ g_assert_no_error (error);
+
+ if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_FACEBOOK))
+ {
+ /* Give ownership of data to signal connection */
+ tp_cli_channel_interface_sasl_authentication_connect_to_new_challenge (
+ data->channel, facebook_new_challenge_cb,
+ data, (GDestroyNotify) auth_data_free,
+ NULL, NULL);
+
+ DEBUG ("Start %s mechanism for account %s", MECH_FACEBOOK,
+ tp_proxy_get_object_path (data->account));
+
+ tp_cli_channel_interface_sasl_authentication_call_start_mechanism (
+ data->channel, -1, MECH_FACEBOOK, NULL, NULL, NULL, NULL);
+ }
+ else if (empathy_sasl_channel_supports_mechanism (data->channel, MECH_MSN))
+ {
+ guchar *token_decoded;
+ gsize token_decoded_len;
+ GArray *token_decoded_array;
+
+ /* Wocky will base64 encode, but token actually already is base64, so we
+ * decode now and it will be re-encoded. */
+ token_decoded = g_base64_decode (data->access_token, &token_decoded_len);
+ token_decoded_array = g_array_new (FALSE, FALSE, sizeof (guchar));
+ g_array_append_vals (token_decoded_array, token_decoded, token_decoded_len);
+
+ DEBUG ("Start %s mechanism for account %s", MECH_MSN,
+ tp_proxy_get_object_path (data->account));
+
+ tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
+ data->channel, -1, MECH_MSN, token_decoded_array,
+ NULL, NULL, NULL, NULL);
+
+ g_array_unref (token_decoded_array);
+ g_free (token_decoded);
+ auth_data_free (data);
+ }
+ else
+ {
+ /* We already checked it supports one of supported_mechanisms, so this
+ * can't happen */
+ g_assert_not_reached ();
+ }
+}
+
+static void
+ensure_credentials_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ AuthData *data = user_data;
+ GoaAccount *goa_account = (GoaAccount *) source;
+ GoaOAuth2Based *oauth2;
+ gint expires_in;
+ GError *error = NULL;
+
+ if (!goa_account_call_ensure_credentials_finish (goa_account, &expires_in,
+ result, &error))
+ {
+ DEBUG ("Failed to EnsureCredentials: %s", error->message);
+ fail_auth (data);
+ g_clear_error (&error);
+ return;
+ }
+
+ /* We support only oaut2 */
+ oauth2 = goa_object_get_oauth2_based (data->goa_object);
+ if (oauth2 == NULL)
+ {
+ DEBUG ("GoaObject does not implement oauth2");
+ fail_auth (data);
+ return;
+ }
+
+ DEBUG ("Goa daemon has credentials for %s, get the access token",
+ tp_proxy_get_object_path (data->account));
+
+ goa_oauth2_based_call_get_access_token (oauth2, NULL,
+ got_oauth2_access_token_cb, data);
+
+ g_object_unref (oauth2);
+}
+
+static void
+start_auth (AuthData *data)
+{
+ EmpathyGoaAuthHandler *self = data->self;
+ const GValue *id_value;
+ const gchar *id;
+ GList *goa_accounts, *l;
+ gboolean found = FALSE;
+
+ id_value = tp_account_get_storage_identifier (data->account);
+ id = g_value_get_string (id_value);
+
+ goa_accounts = goa_client_get_accounts (self->priv->client);
+ for (l = goa_accounts; l != NULL && !found; l = l->next)
+ {
+ GoaObject *goa_object = l->data;
+ GoaAccount *goa_account;
+
+ goa_account = goa_object_get_account (goa_object);
+ if (!tp_strdiff (goa_account_get_id (goa_account), id))
+ {
+ data->goa_object = g_object_ref (goa_object);
+
+ DEBUG ("Found the GoaAccount for %s, ensure credentials",
+ tp_proxy_get_object_path (data->account));
+
+ goa_account_call_ensure_credentials (goa_account, NULL,
+ ensure_credentials_cb, data);
+
+ found = TRUE;
+ }
+
+ g_object_unref (goa_account);
+ }
+ g_list_free_full (goa_accounts, g_object_unref);
+
+ if (!found)
+ {
+ DEBUG ("Cannot find GoaAccount");
+ fail_auth (data);
+ }
+}
+
+static void
+client_new_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EmpathyGoaAuthHandler *self = user_data;
+ GList *l;
+ GError *error = NULL;
+
+ self->priv->client_preparing = FALSE;
+ self->priv->client = goa_client_new_finish (result, &error);
+ if (self->priv->client == NULL)
+ {
+ DEBUG ("Error getting GoaClient: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ /* process queued data */
+ for (l = self->priv->auth_queue; l != NULL; l = l->next)
+ {
+ AuthData *data = l->data;
+
+ if (self->priv->client != NULL)
+ start_auth (data);
+ else
+ fail_auth (data);
+ }
+
+ tp_clear_pointer (&self->priv->auth_queue, g_list_free);
+}
+
+void
+empathy_goa_auth_handler_start (EmpathyGoaAuthHandler *self,
+ TpChannel *channel,
+ TpAccount *account)
+{
+ AuthData *data;
+
+ g_return_if_fail (TP_IS_CHANNEL (channel));
+ g_return_if_fail (TP_IS_ACCOUNT (account));
+ g_return_if_fail (empathy_goa_auth_handler_supports (self, channel, account));
+
+ DEBUG ("Start Goa auth for account: %s",
+ tp_proxy_get_object_path (account));
+
+ data = g_slice_new0 (AuthData);
+ data->self = g_object_ref (self);
+ data->channel = g_object_ref (channel);
+ data->account = g_object_ref (account);
+
+ if (self->priv->client == NULL)
+ {
+ /* GOA client not ready yet, queue data */
+ if (!self->priv->client_preparing)
+ {
+ goa_client_new (NULL, client_new_cb, self);
+ self->priv->client_preparing = TRUE;
+ }
+
+ self->priv->auth_queue = g_list_prepend (self->priv->auth_queue, data);
+ }
+ else
+ {
+ start_auth (data);
+ }
+}
+
+gboolean
+empathy_goa_auth_handler_supports (EmpathyGoaAuthHandler *self,
+ TpChannel *channel,
+ TpAccount *account)
+{
+ const gchar *provider;
+ const gchar * const *iter;
+
+ 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_GOA_PROVIDER))
+ return FALSE;
+
+ for (iter = supported_mechanisms; *iter != NULL; iter++)
+ {
+ if (empathy_sasl_channel_supports_mechanism (channel, *iter))
+ return TRUE;
+ }
+
+ return FALSE;
+}