/* -*- 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
#ifdef HAVE_LIBSECRET
#include
#endif /* HAVE_LIBSECRET */
#include "oauth-account-manager-dialog.h"
#include "oauth-account-chooser-dialog.h"
#include "web-service.h"
#undef DEBUG_WEB_CONNECTION
#define WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT 2
GQuark
web_service_error_quark (void)
{
static GQuark quark;
if (! quark)
quark = g_quark_from_static_string ("web-service-error");
return quark;
}
enum {
PROP_0,
PROP_SERVICE_NAME,
PROP_SERVICE_ADDRESS,
PROP_SERVICE_PROTOCOL,
PROP_ACCOUNT_TYPE,
PROP_CANCELLABLE,
PROP_BROWSER,
PROP_DIALOG
};
/* Signals */
enum {
ACCOUNT_READY,
ACCOUNTS_CHANGED,
LAST_SIGNAL
};
static guint web_service_signals[LAST_SIGNAL] = { 0 };
struct _WebServicePrivate
{
char *service_name;
char *service_address;
char *service_protocol;
GType account_type;
SoupSession *session;
SoupMessage *msg;
GCancellable *cancellable;
GTask *task;
GList *accounts;
OAuthAccount *account;
GtkWidget *browser;
GtkWidget *dialog;
GtkWidget *auth_dialog;
};
G_DEFINE_TYPE_WITH_CODE (WebService,
web_service,
GTH_TYPE_TASK,
G_ADD_PRIVATE (WebService))
static void
web_service_finalize (GObject *object)
{
WebService *self = WEB_SERVICE (object);
_g_object_unref (self->priv->account);
_g_object_list_unref (self->priv->accounts);
_g_object_unref (self->priv->task);
_g_object_unref (self->priv->cancellable);
_g_object_unref (self->priv->session);
g_free (self->priv->service_protocol);
g_free (self->priv->service_address);
g_free (self->priv->service_name);
G_OBJECT_CLASS (web_service_parent_class)->finalize (object);
}
static void
web_service_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
WebService *self = WEB_SERVICE (object);
switch (property_id) {
case PROP_SERVICE_NAME:
_g_str_set (&self->priv->service_name, g_value_get_string (value));
break;
case PROP_SERVICE_ADDRESS:
_g_str_set (&self->priv->service_address, g_value_get_string (value));
break;
case PROP_SERVICE_PROTOCOL:
_g_str_set (&self->priv->service_protocol, g_value_get_string (value));
break;
case PROP_ACCOUNT_TYPE:
self->priv->account_type = g_value_get_gtype (value);
break;
case PROP_CANCELLABLE:
_g_object_unref (self->priv->cancellable);
self->priv->cancellable = g_value_dup_object (value);
break;
case PROP_BROWSER:
self->priv->browser = g_value_get_pointer (value);
break;
case PROP_DIALOG:
self->priv->dialog = g_value_get_pointer (value);
break;
default:
break;
}
}
static void
web_service_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
WebService *self = WEB_SERVICE (object);
switch (property_id) {
case PROP_SERVICE_NAME:
g_value_set_string (value, self->priv->service_name);
break;
case PROP_SERVICE_ADDRESS:
g_value_set_string (value, self->priv->service_address);
break;
case PROP_SERVICE_PROTOCOL:
g_value_set_string (value, self->priv->service_protocol);
break;
case PROP_ACCOUNT_TYPE:
g_value_set_gtype (value, self->priv->account_type);
break;
case PROP_CANCELLABLE:
g_value_set_object (value, self->priv->cancellable);
break;
case PROP_BROWSER:
g_value_set_pointer (value, self->priv->browser);
break;
case PROP_DIALOG:
g_value_set_pointer (value, self->priv->dialog);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
web_service_constructed (GObject *object)
{
WebService *self = WEB_SERVICE (object);
self->priv->accounts = oauth_accounts_load_from_file (self->priv->service_name, self->priv->account_type);
self->priv->account = oauth_accounts_find_default (self->priv->accounts);
if (G_OBJECT_CLASS (web_service_parent_class)->constructed != NULL)
G_OBJECT_CLASS (web_service_parent_class)->constructed (object);
}
static void
web_service_exec (GthTask *base)
{
/* void */
}
static void
web_service_cancelled (GthTask *base)
{
WebService *self = WEB_SERVICE (base);
if ((self->priv->session == NULL) || (self->priv->msg == NULL))
return;
soup_session_cancel_message (self->priv->session,
self->priv->msg,
SOUP_STATUS_CANCELLED);
}
static void
web_service_class_init (WebServiceClass *klass)
{
GObjectClass *object_class;
GthTaskClass *task_class;
object_class = (GObjectClass*) klass;
object_class->finalize = web_service_finalize;
object_class->set_property = web_service_set_property;
object_class->get_property = web_service_get_property;
object_class->constructed = web_service_constructed;
task_class = (GthTaskClass*) klass;
task_class->exec = web_service_exec;
task_class->cancelled = web_service_cancelled;
/* properties */
g_object_class_install_property (object_class,
PROP_SERVICE_NAME,
g_param_spec_string ("service-name",
"Service Name",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class,
PROP_SERVICE_ADDRESS,
g_param_spec_string ("service-address",
"Service Address",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class,
PROP_SERVICE_PROTOCOL,
g_param_spec_string ("service-protocol",
"Service Protocol",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class,
PROP_ACCOUNT_TYPE,
g_param_spec_gtype ("account-type",
"Account type",
"",
OAUTH_TYPE_ACCOUNT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class,
PROP_CANCELLABLE,
g_param_spec_object ("cancellable",
"Cancellable",
"",
G_TYPE_CANCELLABLE,
G_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_BROWSER,
g_param_spec_pointer ("browser",
"Browser",
"",
G_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_DIALOG,
g_param_spec_pointer ("dialog",
"Dialog",
"",
G_PARAM_READWRITE));
/* signals */
web_service_signals[ACCOUNT_READY] =
g_signal_new ("account-ready",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (WebServiceClass, account_ready),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
web_service_signals[ACCOUNTS_CHANGED] =
g_signal_new ("accounts-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (WebServiceClass, accounts_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
web_service_init (WebService *self)
{
self->priv = web_service_get_instance_private (self);
self->priv->service_name = NULL;
self->priv->service_address = NULL;
self->priv->service_protocol = NULL;
self->priv->account_type = OAUTH_TYPE_ACCOUNT;
self->priv->session = NULL;
self->priv->msg = NULL;
self->priv->cancellable = NULL;
self->priv->task = NULL;
self->priv->accounts = NULL;
self->priv->account = NULL;
self->priv->browser = NULL;
self->priv->dialog = NULL;
self->priv->auth_dialog = NULL;
}
/* -- authentication error dialog -- */
static void show_choose_account_dialog (WebService *self);
static void
authentication_error_dialog_response_cb (GtkDialog *dialog,
int response_id,
gpointer user_data)
{
WebService *self = user_data;
switch (response_id) {
case GTK_RESPONSE_DELETE_EVENT:
case GTK_RESPONSE_CANCEL:
gtk_widget_destroy (GTK_WIDGET (dialog));
gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
break;
case WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT:
gtk_widget_destroy (GTK_WIDGET (dialog));
show_choose_account_dialog (self);
break;
default:
break;
}
}
static void
show_authentication_error_dialog (WebService *self,
GError **error)
{
GtkWidget *dialog;
if (g_error_matches (*error, WEB_SERVICE_ERROR, WEB_SERVICE_ERROR_TOKEN_EXPIRED)) {
web_service_ask_authorization (self);
return;
}
dialog = _gtk_message_dialog_new (GTK_WINDOW (self->priv->browser),
GTK_DIALOG_MODAL,
_GTK_ICON_NAME_DIALOG_ERROR,
_("Could not connect to the server"),
(*error)->message,
_("Choose _Account…"), WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT,
_GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL,
NULL);
gth_task_dialog (GTH_TASK (self), TRUE, dialog);
g_signal_connect (dialog,
"delete-event",
G_CALLBACK (gtk_true),
NULL);
g_signal_connect (dialog,
"response",
G_CALLBACK (authentication_error_dialog_response_cb),
self);
gtk_widget_show (dialog);
g_clear_error (error);
}
/* -- web_service_autoconnect -- */
static void
set_current_account (WebService *self,
OAuthAccount *account)
{
GList *link;
if (self->priv->account == account)
return;
link = g_list_find_custom (self->priv->accounts, account, (GCompareFunc) oauth_account_cmp);
if (link != NULL) {
self->priv->accounts = g_list_remove_link (self->priv->accounts, link);
_g_object_list_unref (link);
}
_g_object_unref (self->priv->account);
self->priv->account = NULL;
if (account != NULL) {
self->priv->account = g_object_ref (account);
self->priv->accounts = g_list_prepend (self->priv->accounts, g_object_ref (self->priv->account));
}
}
#ifdef HAVE_LIBSECRET
static char *
serialize_secret (const char *token,
const char *token_secret)
{
GVariantBuilder *builder;
GVariant *variant;
char *secret;
builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
g_variant_builder_add (builder, "ms", token);
g_variant_builder_add (builder, "ms", token_secret);
variant = g_variant_builder_end (builder);
secret = g_variant_print (variant, TRUE);
g_variant_unref (variant);
return secret;
}
static gboolean
deserialize_secret (const char *secret,
char **token,
char **token_secret)
{
GVariant *variant;
variant = g_variant_parse (NULL, secret, NULL, NULL, NULL);
if (variant == NULL)
return FALSE;
if (token != NULL)
g_variant_get_child (variant, 0, "ms", token, NULL);
if (token_secret != NULL)
g_variant_get_child (variant, 1, "ms", token_secret, NULL);
g_variant_unref (variant);
return TRUE;
}
static void
password_store_ready_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
WebService *self = user_data;
secret_password_store_finish (result, NULL);
web_service_account_ready (self);
}
#endif
static void
get_user_info_ready_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
WebService *self = user_data;
GError *error = NULL;
OAuthAccount *account;
account = web_service_get_user_info_finish (self, res, &error);
if (account == NULL) {
show_authentication_error_dialog (self, &error);
return;
}
set_current_account (self, account);
oauth_accounts_save_to_file (self->priv->service_name,
self->priv->accounts,
self->priv->account);
#ifdef HAVE_LIBSECRET
{
char *secret;
secret = serialize_secret (account->token, account->token_secret);
secret_password_store (SECRET_SCHEMA_COMPAT_NETWORK,
NULL,
self->priv->service_name,
secret,
self->priv->cancellable,
password_store_ready_cb,
self,
"user", account->id,
"server", self->priv->service_address,
"protocol", self->priv->service_protocol,
NULL);
g_free (secret);
}
#else
web_service_account_ready (self);
#endif
g_object_unref (account);
}
static void
connect_to_server_step2 (WebService *self)
{
if ((self->priv->account->token == NULL) && (self->priv->account->token_secret == NULL)) {
web_service_ask_authorization (self);
return;
}
web_service_get_user_info (self,
self->priv->cancellable,
get_user_info_ready_cb,
self);
}
#ifdef HAVE_LIBSECRET
static void
password_lookup_ready_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
WebService *self = user_data;
char *secret;
secret = secret_password_lookup_finish (result, NULL);
if (secret != NULL) {
char *token;
char *token_secret;
if (deserialize_secret (secret, &token, &token_secret)) {
g_object_set (G_OBJECT (self->priv->account),
"token", token,
"token-secret", token_secret,
NULL);
g_free (token);
g_free (token_secret);
}
g_free (secret);
}
connect_to_server_step2 (self);
}
#endif
static void
connect_to_server (WebService *self)
{
g_return_if_fail (self->priv->account != NULL);
g_return_if_fail (self->priv->account->id != NULL);
#ifdef HAVE_LIBSECRET
if (self->priv->account->token_secret == NULL) {
secret_password_lookup (SECRET_SCHEMA_COMPAT_NETWORK,
self->priv->cancellable,
password_lookup_ready_cb,
self,
"user", self->priv->account->id,
"server", self->priv->service_address,
"protocol", self->priv->service_protocol,
NULL);
return;
}
#endif
connect_to_server_step2 (self);
}
static void
account_chooser_dialog_response_cb (GtkDialog *dialog,
int response_id,
gpointer user_data)
{
WebService *self = user_data;
switch (response_id) {
case GTK_RESPONSE_DELETE_EVENT:
case GTK_RESPONSE_CANCEL:
gtk_widget_destroy (GTK_WIDGET (dialog));
gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
break;
case GTK_RESPONSE_OK:
_g_object_unref (self->priv->account);
self->priv->account = oauth_account_chooser_dialog_get_active (OAUTH_ACCOUNT_CHOOSER_DIALOG (dialog));
if (self->priv->account != NULL) {
gtk_widget_destroy (GTK_WIDGET (dialog));
connect_to_server (self);
}
break;
case OAUTH_ACCOUNT_CHOOSER_RESPONSE_NEW:
gtk_widget_destroy (GTK_WIDGET (dialog));
web_service_ask_authorization (self);
break;
default:
break;
}
}
static void
show_choose_account_dialog (WebService *self)
{
GtkWidget *dialog;
gth_task_dialog (GTH_TASK (self), TRUE, NULL);
dialog = oauth_account_chooser_dialog_new (self->priv->accounts, self->priv->account);
g_signal_connect (dialog,
"delete-event",
G_CALLBACK (gtk_true),
NULL);
g_signal_connect (dialog,
"response",
G_CALLBACK (account_chooser_dialog_response_cb),
self);
gtk_window_set_title (GTK_WINDOW (dialog), _("Choose Account"));
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
gtk_window_present (GTK_WINDOW (dialog));
}
void
web_service_autoconnect (WebService *self)
{
gtk_widget_hide (self->priv->dialog);
gth_task_dialog (GTH_TASK (self), FALSE, NULL);
if (self->priv->accounts != NULL) {
if (self->priv->account != NULL) {
connect_to_server (self);
}
else if (self->priv->accounts->next == NULL) {
self->priv->account = g_object_ref (self->priv->accounts->data);
connect_to_server (self);
}
else
show_choose_account_dialog (self);
}
else
web_service_ask_authorization (self);
}
void
web_service_connect (WebService *self,
OAuthAccount *account)
{
set_current_account (self, account);
web_service_autoconnect (self);
}
void
web_service_set_current_account (WebService *self,
OAuthAccount *account)
{
set_current_account (self, account);
}
OAuthAccount *
web_service_get_current_account (WebService *self)
{
return self->priv->account;
}
GList *
web_service_get_accounts (WebService *self)
{
return self->priv->accounts;
}
/* -- web_service_edit_accounts -- */
static void
account_manager_dialog_response_cb (GtkDialog *dialog,
int response_id,
gpointer user_data)
{
WebService *self = user_data;
switch (response_id) {
case GTK_RESPONSE_DELETE_EVENT:
case GTK_RESPONSE_CANCEL:
gtk_widget_destroy (GTK_WIDGET (dialog));
break;
case GTK_RESPONSE_OK:
_g_object_list_unref (self->priv->accounts);
self->priv->accounts = oauth_account_manager_dialog_get_accounts (OAUTH_ACCOUNT_MANAGER_DIALOG (dialog));
if (! g_list_find_custom (self->priv->accounts, self->priv->account, (GCompareFunc) oauth_account_cmp)) {
_g_object_unref (self->priv->account);
self->priv->account = NULL;
web_service_autoconnect (self);
}
else
g_signal_emit (self, web_service_signals[ACCOUNTS_CHANGED], 0);
oauth_accounts_save_to_file (self->priv->service_name, self->priv->accounts, self->priv->account);
gtk_widget_destroy (GTK_WIDGET (dialog));
break;
case OAUTH_ACCOUNT_CHOOSER_RESPONSE_NEW:
gtk_widget_destroy (GTK_WIDGET (dialog));
web_service_ask_authorization (self);
break;
default:
break;
}
}
void
web_service_edit_accounts (WebService *self,
GtkWindow *parent)
{
GtkWidget *dialog;
dialog = oauth_account_manager_dialog_new (self->priv->accounts);
g_signal_connect (dialog,
"delete-event",
G_CALLBACK (gtk_true),
NULL);
g_signal_connect (dialog,
"response",
G_CALLBACK (account_manager_dialog_response_cb),
self);
gtk_window_set_title (GTK_WINDOW (dialog), _("Edit Accounts"));
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
gtk_window_present (GTK_WINDOW (dialog));
}
void
web_service_account_ready (WebService *self)
{
g_signal_emit (self, web_service_signals[ACCOUNT_READY], 0);
}
void
web_service_ask_authorization (WebService *self)
{
gth_task_progress (GTH_TASK (self),
_("Connecting to the server"),
_("Asking authorization"),
TRUE,
0.0);
web_service_set_current_account (self, NULL);
WEB_SERVICE_GET_CLASS (self)->ask_authorization (self);
}
void
web_service_get_user_info (WebService *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
gth_task_progress (GTH_TASK (self),
_("Connecting to the server"),
_("Getting account information"),
TRUE,
0.0);
WEB_SERVICE_GET_CLASS (self)->get_user_info (self, cancellable, callback, user_data);
}
OAuthAccount *
web_service_get_user_info_finish (WebService *self,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
/* -- connection utilities -- */
void
_web_service_send_message (WebService *self,
SoupMessage *msg,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data,
gpointer source_tag,
SoupSessionCallback soup_session_cb,
gpointer soup_session_cb_data)
{
if (self->priv->session == NULL) {
self->priv->session = soup_session_new ();
#ifdef DEBUG_WEB_CONNECTION
{
SoupLogger *logger;
logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
soup_session_add_feature (self->priv->session, SOUP_SESSION_FEATURE (logger));
g_object_unref (logger);
}
#endif
}
_g_object_unref (self->priv->cancellable);
self->priv->cancellable = _g_object_ref (cancellable);
_g_object_unref (self->priv->task);
self->priv->task = g_task_new (G_OBJECT (self),
self->priv->cancellable,
callback,
user_data);
self->priv->msg = msg;
g_object_add_weak_pointer (G_OBJECT (msg), (gpointer *) &self->priv->msg);
soup_session_queue_message (self->priv->session,
msg,
soup_session_cb,
soup_session_cb_data);
}
GTask *
_web_service_get_task (WebService *self)
{
return self->priv->task;
}
void
_web_service_reset_task (WebService *self)
{
self->priv->task = NULL;
}
SoupMessage *
_web_service_get_message (WebService *self)
{
return self->priv->msg;
}
/* -- _web_service_set_auth_dialog -- */
static void
ask_authorization_dialog_response_cb (GtkDialog *dialog,
int response_id,
gpointer user_data)
{
WebService *self = user_data;
switch (response_id) {
case GTK_RESPONSE_DELETE_EVENT:
case GTK_RESPONSE_CANCEL:
gtk_widget_destroy (GTK_WIDGET (dialog));
gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
break;
case GTK_RESPONSE_OK:
gtk_widget_destroy (GTK_WIDGET (dialog));
gth_task_dialog (GTH_TASK (self), FALSE, NULL);
web_service_get_user_info (self,
self->priv->cancellable,
get_user_info_ready_cb,
self);
break;
default:
break;
}
}
void
_web_service_set_auth_dialog (WebService *self,
GtkDialog *dialog)
{
self->priv->auth_dialog = GTK_WIDGET (dialog);
g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &self->priv->auth_dialog);
gth_task_dialog (GTH_TASK (self), TRUE, self->priv->auth_dialog);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
if (gtk_widget_get_visible (self->priv->dialog))
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
else
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
g_signal_connect (dialog,
"delete-event",
G_CALLBACK (gtk_true),
NULL);
g_signal_connect (dialog,
"response",
G_CALLBACK (ask_authorization_dialog_response_cb),
self);
}
GtkWidget *
_web_service_get_auth_dialog (WebService *self)
{
return self->priv->auth_dialog;
}