From d3e575fd305487eb8a15a66941651325904ba90b Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Thu, 27 Oct 2011 12:09:59 +0200 Subject: Import Facebook and windows Live GOA accounts Implement their auth mechanisms Fixes bug #661068 and #652544 --- libempathy/empathy-goa-auth-handler.c | 436 ++++++++++++++++++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 libempathy/empathy-goa-auth-handler.c (limited to 'libempathy/empathy-goa-auth-handler.c') 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 + * + * 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 + +#include +#include + +#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; +} -- cgit v1.2.3