/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2012 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include "oauth-ask-authorization-dialog.h"
#include "oauth-consumer.h"
#include "oauth-service.h"
#define OAUTH_VERSION "1.0"
#define OAUTH_SIGNATURE_METHOD "HMAC-SHA1"
#define OAUTH_CALLBACK "http://localhost/"
enum {
PROP_0,
PROP_CONSUMER
};
struct _OAuthServicePrivate {
OAuthConsumer *consumer;
char *timestamp;
char *nonce;
char *signature;
char *token;
char *token_secret;
char *verifier;
};
G_DEFINE_TYPE_WITH_CODE (OAuthService,
oauth_service,
WEB_TYPE_SERVICE,
G_ADD_PRIVATE (OAuthService))
static void
oauth_service_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
OAuthService *self;
self = OAUTH_SERVICE (object);
switch (property_id) {
case PROP_CONSUMER:
self->priv->consumer = g_value_get_pointer (value);
break;
default:
break;
}
}
static void
oauth_service_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
OAuthService *self;
self = OAUTH_SERVICE (object);
switch (property_id) {
case PROP_CONSUMER:
g_value_set_pointer (value, self->priv->consumer);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
oauth_service_finalize (GObject *object)
{
OAuthService *self;
self = OAUTH_SERVICE (object);
g_free (self->priv->verifier);
g_free (self->priv->token);
g_free (self->priv->token_secret);
g_free (self->priv->signature);
g_free (self->priv->nonce);
g_free (self->priv->timestamp);
G_OBJECT_CLASS (oauth_service_parent_class)->finalize (object);
}
/* -- oauth_service_get_request_token -- */
static void
_oauth_service_get_request_token_ready_cb (SoupSession *session,
SoupMessage *msg,
gpointer user_data)
{
OAuthService *self = user_data;
GTask *task;
SoupBuffer *body;
GHashTable *values;
char *token;
char *token_secret;
task = _web_service_get_task (WEB_SERVICE (self));
if (msg->status_code != 200) {
g_task_return_new_error (task,
SOUP_HTTP_ERROR,
msg->status_code,
"%s",
soup_status_get_phrase (msg->status_code));
return;
}
body = soup_message_body_flatten (msg->response_body);
values = soup_form_decode (body->data);
token = g_hash_table_lookup (values, "oauth_token");
token_secret = g_hash_table_lookup (values, "oauth_token_secret");
if ((token != NULL) && (token_secret != NULL)) {
oauth_service_set_token (self, token);
oauth_service_set_token_secret (self, token_secret);
g_task_return_boolean (task, TRUE);
}
else
g_task_return_error (task, g_error_new_literal (WEB_SERVICE_ERROR, WEB_SERVICE_ERROR_GENERIC, _("Unknown error")));
g_hash_table_destroy (values);
soup_buffer_free (body);
}
static void
_oauth_service_get_request_token (OAuthService *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GHashTable *data_set;
SoupMessage *msg;
data_set = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_insert (data_set, "oauth_callback", OAUTH_CALLBACK);
oauth_service_add_signature (self, "POST", self->priv->consumer->request_token_url, data_set);
msg = soup_form_request_new_from_hash ("POST", self->priv->consumer->request_token_url, data_set);
_web_service_send_message (WEB_SERVICE (self),
msg,
cancellable,
callback,
user_data,
_oauth_service_get_request_token,
_oauth_service_get_request_token_ready_cb,
self);
g_hash_table_destroy (data_set);
}
static gboolean
oauth_service_get_request_token_finish (OAuthService *self,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
/* -- _oauth_service_get_access_token -- */
static void
_oauth_service_get_access_token_ready_cb (SoupSession *session,
SoupMessage *msg,
gpointer user_data)
{
OAuthService *self = user_data;
GTask *task;
SoupBuffer *body;
task = _web_service_get_task (WEB_SERVICE (self));
if (msg->status_code != 200) {
g_task_return_new_error (task,
SOUP_HTTP_ERROR,
msg->status_code,
"%s",
soup_status_get_phrase (msg->status_code));
return;
}
body = soup_message_body_flatten (msg->response_body);
self->priv->consumer->access_token_response (self, msg, body, task);
soup_buffer_free (body);
}
static void
_oauth_service_get_access_token (OAuthService *self,
const char *verifier,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GHashTable *data_set;
SoupMessage *msg;
data_set = g_hash_table_new (g_str_hash, g_str_equal);
if (verifier != NULL)
g_hash_table_insert (data_set, "oauth_verifier", (gpointer) verifier);
oauth_service_add_signature (self, "POST", self->priv->consumer->access_token_url, data_set);
msg = soup_form_request_new_from_hash ("POST", self->priv->consumer->access_token_url, data_set);
_web_service_send_message (WEB_SERVICE (self),
msg,
cancellable,
callback,
user_data,
_oauth_service_get_access_token,
_oauth_service_get_access_token_ready_cb,
self);
g_hash_table_destroy (data_set);
}
static OAuthAccount *
_oauth_service_get_access_token_finish (OAuthService *self,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
/* -- oauth_service_ask_authorization -- */
static void
get_access_token_ready_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
OAuthService *self = user_data;
GError *error = NULL;
GtkWidget *dialog;
OAuthAccount *account;
dialog = _web_service_get_auth_dialog (WEB_SERVICE (self));
account = _oauth_service_get_access_token_finish (self, result, &error);
if (account == NULL) {
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
gth_task_completed (GTH_TASK (self), error);
return;
}
web_service_set_current_account (WEB_SERVICE (self), account);
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
g_object_unref (account);
}
static void
ask_authorization_dialog_load_request_cb (OAuthAskAuthorizationDialog *dialog,
gpointer user_data)
{
OAuthService *self = user_data;
const char *uri;
uri = oauth_ask_authorization_dialog_get_uri (dialog);
if (uri == NULL)
return;
if (g_str_has_prefix (uri, OAUTH_CALLBACK)) {
const char *uri_data;
GHashTable *data;
gboolean success = FALSE;
uri_data = uri + strlen (OAUTH_CALLBACK "?");
data = soup_form_decode (uri_data);
_g_str_set (&self->priv->token, g_hash_table_lookup (data, "oauth_token"));
if (self->priv->token != NULL) {
gtk_widget_hide (GTK_WIDGET (dialog));
gth_task_dialog (GTH_TASK (self), FALSE, NULL);
success = TRUE;
_oauth_service_get_access_token (self,
g_hash_table_lookup (data, "oauth_verifier"),
gth_task_get_cancellable (GTH_TASK (self)),
get_access_token_ready_cb,
self);
}
if (! success)
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
g_hash_table_destroy (data);
}
}
static void
get_request_token_ready_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
OAuthService *self = user_data;
GError *error = NULL;
char *url;
GtkWidget *dialog;
if (! oauth_service_get_request_token_finish (self, result, &error)) {
gth_task_completed (GTH_TASK (self), error);
return;
}
url = self->priv->consumer->get_authorization_url (self);
dialog = oauth_ask_authorization_dialog_new (url);
_gtk_window_resize_to_fit_screen_height (dialog, 1024);
_web_service_set_auth_dialog (WEB_SERVICE (self), GTK_DIALOG (dialog));
g_signal_connect (OAUTH_ASK_AUTHORIZATION_DIALOG (dialog),
"load-request",
G_CALLBACK (ask_authorization_dialog_load_request_cb),
self);
gtk_widget_show (dialog);
g_free (url);
}
static void
oauth_service_ask_authorization (WebService *base)
{
OAuthService *self = OAUTH_SERVICE (base);
oauth_service_set_token (self, NULL);
oauth_service_set_token_secret (self, NULL);
_oauth_service_get_request_token (self,
gth_task_get_cancellable (GTH_TASK (self)),
get_request_token_ready_cb,
self);
}
static void
oauth_service_class_init (OAuthServiceClass *klass)
{
GObjectClass *object_class;
WebServiceClass *service_class;
object_class = (GObjectClass*) klass;
object_class->set_property = oauth_service_set_property;
object_class->get_property = oauth_service_get_property;
object_class->finalize = oauth_service_finalize;
service_class = (WebServiceClass*) klass;
service_class->ask_authorization = oauth_service_ask_authorization;
/* properties */
g_object_class_install_property (object_class,
PROP_CONSUMER,
g_param_spec_pointer ("consumer",
"Consumer",
"",
G_PARAM_READWRITE));
}
static void
oauth_service_init (OAuthService *self)
{
self->priv = oauth_service_get_instance_private (self);
self->priv->consumer = NULL;
self->priv->timestamp = NULL;
self->priv->nonce = NULL;
self->priv->signature = NULL;
self->priv->token = NULL;
self->priv->token_secret = NULL;
self->priv->verifier = NULL;
}
/* -- oauth_service_add_signature -- */
static char *
oauth_create_timestamp (GTimeVal *t)
{
return g_strdup_printf ("%ld", t->tv_sec);
}
static char *
oauth_create_nonce (GTimeVal *t)
{
char *s;
char *v;
s = g_strdup_printf ("%ld%u", t->tv_usec, g_random_int ());
v = g_compute_checksum_for_string (G_CHECKSUM_MD5, s, -1);
g_free (s);
return v;
}
void
oauth_service_add_signature (OAuthService *self,
const char *method,
const char *url,
GHashTable *parameters)
{
GTimeVal t;
GString *param_string;
GList *keys;
GList *scan;
GString *base_string;
GString *signature_key;
/* Add the OAuth specific parameters */
g_get_current_time (&t);
g_free (self->priv->timestamp);
self->priv->timestamp = oauth_create_timestamp (&t);
g_hash_table_insert (parameters, "oauth_timestamp", self->priv->timestamp);
g_free (self->priv->nonce);
self->priv->nonce = oauth_create_nonce (&t);
g_hash_table_insert (parameters, "oauth_nonce", self->priv->nonce);
g_hash_table_insert (parameters, "oauth_version", OAUTH_VERSION);
g_hash_table_insert (parameters, "oauth_signature_method", OAUTH_SIGNATURE_METHOD);
g_hash_table_insert (parameters, "oauth_consumer_key", (gpointer) self->priv->consumer->consumer_key);
if (self->priv->token != NULL)
g_hash_table_insert (parameters, "oauth_token", self->priv->token);
/* Create the parameter string */
param_string = g_string_new ("");
keys = g_hash_table_get_keys (parameters);
keys = g_list_sort (keys, (GCompareFunc) strcmp);
for (scan = keys; scan; scan = scan->next) {
char *key = scan->data;
char *value = g_hash_table_lookup (parameters, key);
g_string_append_uri_escaped (param_string, key, NULL, FALSE);
g_string_append (param_string, "=");
g_string_append_uri_escaped (param_string, value, NULL, FALSE);
if (scan->next != NULL)
g_string_append (param_string, "&");
}
/* Create the Base String */
base_string = g_string_new ("");
g_string_append_uri_escaped (base_string, method, NULL, FALSE);
g_string_append (base_string, "&");
g_string_append_uri_escaped (base_string, url, NULL, FALSE);
g_string_append (base_string, "&");
g_string_append_uri_escaped (base_string, param_string->str, NULL, FALSE);
/* Calculate the signature value */
signature_key = g_string_new ("");
g_string_append_uri_escaped (signature_key, self->priv->consumer->consumer_secret, NULL, FALSE);
g_string_append (signature_key, "&");
if (self->priv->token_secret != NULL)
g_string_append_uri_escaped (signature_key, self->priv->token_secret, NULL, FALSE);
g_free (self->priv->signature);
self->priv->signature = g_compute_signature_for_string (G_CHECKSUM_SHA1,
G_SIGNATURE_ENC_BASE64,
signature_key->str,
signature_key->len,
base_string->str,
base_string->len);
g_hash_table_insert (parameters, "oauth_signature", self->priv->signature);
g_string_free (signature_key, TRUE);
g_string_free (base_string, TRUE);
g_list_free (keys);
g_string_free (param_string, TRUE);
}
void
oauth_service_set_token (OAuthService *self,
const char *token)
{
_g_str_set (&self->priv->token, token);
}
const char *
oauth_service_get_token (OAuthService *self)
{
return self->priv->token;
}
void
oauth_service_set_token_secret (OAuthService *self,
const char *token_secret)
{
_g_str_set (&self->priv->token_secret, token_secret);
}
const char *
oauth_service_get_token_secret (OAuthService *self)
{
return self->priv->token_secret;
}