/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright © 2011 – 2017 Red Hat, Inc.
*
* 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 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <errno.h>
#include <string.h>
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <rest/rest-proxy.h>
#include <libsoup/soup.h>
#include "goadaemon.h"
#include "goa/goa.h"
#include "goabackend/goabackend.h"
#include "goabackend/goaprovider-priv.h"
#include "goabackend/goautils.h"
struct _GoaDaemon
{
GObject parent_instance;
GDBusConnection *connection;
GFileMonitor *home_conf_file_monitor;
GFileMonitor *template_file_monitor;
gchar *home_conf_file_path;
GNetworkMonitor *network_monitor;
GDBusObjectManagerServer *object_manager;
GoaManager *manager;
GQueue *ensure_credentials_queue;
gboolean ensure_credentials_running;
guint config_timeout_id;
guint credentials_timeout_id;
};
enum
{
PROP_0,
PROP_CONNECTION
};
static void on_file_monitor_changed (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data);
static gboolean on_manager_handle_add_account (GoaManager *object,
GDBusMethodInvocation *invocation,
const gchar *provider_type,
const gchar *identity,
const gchar *presentation_identity,
GVariant *credentials,
GVariant *details,
gpointer user_data);
static gboolean on_account_handle_remove (GoaAccount *account,
GDBusMethodInvocation *invocation,
gpointer user_data);
static gboolean on_account_handle_ensure_credentials (GoaAccount *account,
GDBusMethodInvocation *invocation,
gpointer user_data);
static void ensure_credentials_queue_check (GoaDaemon *self);
static void goa_daemon_check_credentials (GoaDaemon *self);
static void goa_daemon_reload_configuration (GoaDaemon *self);
G_DEFINE_TYPE (GoaDaemon, goa_daemon, G_TYPE_OBJECT);
/* ---------------------------------------------------------------------------------------------------- */
typedef struct
{
GError **error;
GList **out_providers;
GMainLoop *loop;
gboolean op_res;
} GetAllSyncData;
static void
get_all_providers_sync_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GetAllSyncData *data = (GetAllSyncData *) user_data;
data->op_res = goa_provider_get_all_finish (data->out_providers, res, data->error);
g_main_loop_quit (data->loop);
}
static gboolean
get_all_providers_sync (GCancellable *cancellable,
GList **out_providers,
GError **error)
{
GetAllSyncData data;
data.error = error;
data.out_providers = out_providers;
/* HACK: Since telepathy-glib doesn't use the thread-default
* GMainContext for invoking the asynchronous callbacks, we can't
* push a new GMainContext here.
*/
data.loop = g_main_loop_new (NULL, FALSE);
goa_provider_get_all (get_all_providers_sync_cb, &data);
g_main_loop_run (data.loop);
g_main_loop_unref (data.loop);
return data.op_res;
}
/* ---------------------------------------------------------------------------------------------------- */
static void
goa_daemon_constructed (GObject *object)
{
GoaDaemon *self = GOA_DAEMON (object);
G_OBJECT_CLASS (goa_daemon_parent_class)->constructed (object);
/* prime the list of accounts */
goa_daemon_reload_configuration (self);
/* Export objects */
g_dbus_object_manager_server_set_connection (self->object_manager, self->connection);
}
static void
goa_daemon_finalize (GObject *object)
{
GoaDaemon *self = GOA_DAEMON (object);
if (self->config_timeout_id != 0)
{
g_source_remove (self->config_timeout_id);
}
if (self->credentials_timeout_id != 0)
{
g_source_remove (self->credentials_timeout_id);
}
if (self->home_conf_file_monitor != NULL)
{
g_signal_handlers_disconnect_by_func (self->home_conf_file_monitor, on_file_monitor_changed, self);
g_object_unref (self->home_conf_file_monitor);
}
if (self->template_file_monitor != NULL)
{
g_signal_handlers_disconnect_by_func (self->template_file_monitor, on_file_monitor_changed, self);
g_object_unref (self->template_file_monitor);
}
g_free (self->home_conf_file_path);
g_object_unref (self->manager);
g_object_unref (self->object_manager);
g_object_unref (self->connection);
g_queue_free_full (self->ensure_credentials_queue, g_object_unref);
G_OBJECT_CLASS (goa_daemon_parent_class)->finalize (object);
}
static void
goa_daemon_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
GoaDaemon *self = GOA_DAEMON (object);
switch (prop_id)
{
case PROP_CONNECTION:
self->connection = G_DBUS_CONNECTION (g_value_dup_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GFileMonitor *
create_monitor (const gchar *path, gboolean is_dir)
{
GFile *file;
GFileMonitor *monitor;
GError *error;
error = NULL;
file = g_file_new_for_path (path);
if (is_dir)
monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, &error);
else
monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
if (monitor == NULL)
{
g_warning ("Error monitoring %s at %s: %s (%s, %d)",
is_dir ? "directory" : "file",
path,
error->message, g_quark_to_string (error->domain), error->code);
g_error_free (error);
}
g_object_unref (file);
return monitor;
}
static gboolean
on_config_file_monitor_timeout (gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
self->config_timeout_id = 0;
g_debug ("Reloading configuration files");
goa_daemon_reload_configuration (self);
return G_SOURCE_REMOVE;
}
static void
on_file_monitor_changed (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
if (self->config_timeout_id == 0)
{
self->config_timeout_id = g_timeout_add (200, on_config_file_monitor_timeout, self);
}
}
static gboolean
on_check_credentials_timeout (gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
self->credentials_timeout_id = 0;
g_debug ("Calling EnsureCredentials due to network changes");
goa_daemon_check_credentials (self);
return G_SOURCE_REMOVE;
}
static void
queue_check_credentials (GoaDaemon *self)
{
if (self->credentials_timeout_id != 0)
{
g_source_remove (self->credentials_timeout_id);
}
self->credentials_timeout_id = g_timeout_add_seconds (1, on_check_credentials_timeout, self);
}
static void
on_network_monitor_network_changed (GoaDaemon *self, gboolean available)
{
queue_check_credentials (self);
}
static void
goa_daemon_init (GoaDaemon *self)
{
static volatile GQuark goa_error_domain = 0;
GError *error;
GList *l;
GList *providers = NULL;
GoaObjectSkeleton *object;
gchar *path;
/* this will force associating errors in the GOA_ERROR error domain
* with org.freedesktop.Goa.Error.* errors via g_dbus_error_register_error_domain().
*/
goa_error_domain = GOA_ERROR;
goa_error_domain; /* shut up -Wunused-but-set-variable */
error = NULL;
if (!get_all_providers_sync (NULL, &providers, &error))
{
g_warning ("Unable to get the list of providers: %s (%s, %d)",
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
}
for (l = providers; l != NULL; l = l->next)
{
GoaProvider *provider = GOA_PROVIDER (l->data);
goa_provider_initialize (provider);
}
/* Create object manager */
self->object_manager = g_dbus_object_manager_server_new ("/org/gnome/OnlineAccounts");
/* Create and export Manager */
self->manager = goa_manager_skeleton_new ();
g_signal_connect (self->manager,
"handle-add-account",
G_CALLBACK (on_manager_handle_add_account),
self);
object = goa_object_skeleton_new ("/org/gnome/OnlineAccounts/Manager");
goa_object_skeleton_set_manager (object, self->manager);
g_dbus_object_manager_server_export (self->object_manager, G_DBUS_OBJECT_SKELETON (object));
g_object_unref (object);
self->home_conf_file_path = g_strdup_printf ("%s/goa-1.0/accounts.conf", g_get_user_config_dir ());
/* create ~/.config/goa-1.0 directory */
path = g_path_get_dirname (self->home_conf_file_path);
if (g_mkdir_with_parents (path, 0755) != 0)
{
g_warning ("Error creating directory %s: %s", path, strerror (errno));
}
g_free (path);
/* set up file monitoring */
self->home_conf_file_monitor = create_monitor (self->home_conf_file_path, FALSE);
if (self->home_conf_file_monitor != NULL)
g_signal_connect (self->home_conf_file_monitor, "changed", G_CALLBACK (on_file_monitor_changed), self);
if (GOA_TEMPLATE_FILE != NULL && GOA_TEMPLATE_FILE[0] != '\0')
{
self->template_file_monitor = create_monitor (GOA_TEMPLATE_FILE, FALSE);
if (self->template_file_monitor != NULL)
g_signal_connect (self->template_file_monitor, "changed", G_CALLBACK (on_file_monitor_changed), self);
}
self->network_monitor = g_network_monitor_get_default ();
g_signal_connect_object (self->network_monitor,
"network-changed",
G_CALLBACK (on_network_monitor_network_changed),
self,
G_CONNECT_SWAPPED);
self->ensure_credentials_queue = g_queue_new ();
queue_check_credentials (self);
g_list_free_full (providers, g_object_unref);
}
static void
goa_daemon_class_init (GoaDaemonClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->constructed = goa_daemon_constructed;
gobject_class->finalize = goa_daemon_finalize;
gobject_class->set_property = goa_daemon_set_property;
g_object_class_install_property (gobject_class,
PROP_CONNECTION,
g_param_spec_object ("connection",
"GDBusConnection object",
"A connection to a message bus",
G_TYPE_DBUS_CONNECTION,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS |
G_PARAM_WRITABLE));
}
GoaDaemon *
goa_daemon_new (GDBusConnection *connection)
{
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
return GOA_DAEMON (g_object_new (GOA_TYPE_DAEMON, "connection", connection, NULL));
}
/* ---------------------------------------------------------------------------------------------------- */
static void
diff_sorted_lists (GList *list1,
GList *list2,
GCompareFunc compare,
GList **out_added,
GList **out_removed,
GList **out_unchanged)
{
GList *added = NULL;
GList *removed = NULL;
GList *unchanged = NULL;
gint order;
while (list1 != NULL && list2 != NULL)
{
order = (*compare) (list1->data, list2->data);
if (order < 0)
{
removed = g_list_prepend (removed, list1->data);
list1 = list1->next;
}
else if (order > 0)
{
added = g_list_prepend (added, list2->data);
list2 = list2->next;
}
else
{ /* same item */
unchanged = g_list_prepend (unchanged, list1->data);
list1 = list1->next;
list2 = list2->next;
}
}
while (list1 != NULL)
{
removed = g_list_prepend (removed, list1->data);
list1 = list1->next;
}
while (list2 != NULL)
{
added = g_list_prepend (added, list2->data);
list2 = list2->next;
}
if (out_added != NULL)
{
*out_added = added;
added = NULL;
}
if (out_removed != NULL)
{
*out_removed = removed;
removed = NULL;
}
if (out_unchanged != NULL)
{
*out_unchanged = unchanged;
unchanged = NULL;
}
g_list_free (added);
g_list_free (removed);
g_list_free (unchanged);
}
/* ---------------------------------------------------------------------------------------------------- */
static const gchar *
account_group_to_id (const gchar *group)
{
g_return_val_if_fail (g_str_has_prefix (group, "Account "), NULL);
return group + sizeof "Account " - 1;
}
static gchar *
account_object_path_to_group (const gchar *object_path)
{
g_return_val_if_fail (g_str_has_prefix (object_path, "/org/gnome/OnlineAccounts/Accounts/"), NULL);
return g_strdup_printf ("Account %s", object_path + sizeof "/org/gnome/OnlineAccounts/Accounts/" - 1);
}
static const gchar *
template_group_to_id (const gchar *group)
{
g_return_val_if_fail (g_str_has_prefix (group, "Template "), NULL);
return group + sizeof "Template " - 1;
}
/* ---------------------------------------------------------------------------------------------------- */
typedef struct
{
GKeyFile *key_file;
gchar *path;
} KeyFileData;
static void
key_file_data_free (KeyFileData *data)
{
g_key_file_unref (data->key_file);
g_free (data->path);
g_slice_free (KeyFileData, data);
}
static KeyFileData *
key_file_data_new (GKeyFile *key_file,
const gchar *path)
{
KeyFileData *data;
data = g_slice_new (KeyFileData);
data->key_file = g_key_file_ref (key_file);
data->path = g_strdup (path);
return data;
}
/* ---------------------------------------------------------------------------------------------------- */
static void
add_config_file (GoaDaemon *self,
const gchar *path,
GHashTable *group_name_to_key_file_data)
{
GKeyFile *key_file;
GError *error;
gboolean needs_update = FALSE;
gchar **groups;
const char *guid;
gsize num_groups;
guint n;
key_file = g_key_file_new ();
error = NULL;
if (!g_key_file_load_from_file (key_file,
path,
G_KEY_FILE_NONE,
&error))
{
if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
{
g_warning ("Error loading %s: %s (%s, %d)",
path,
error->message, g_quark_to_string (error->domain), error->code);
}
g_error_free (error);
goto out;
}
guid = g_dbus_connection_get_guid (self->connection);
groups = g_key_file_get_groups (key_file, &num_groups);
for (n = 0; n < num_groups; n++)
{
if (g_str_has_prefix (groups[n], "Account "))
{
gboolean is_temporary;
char *session_id;
is_temporary = g_key_file_get_boolean (key_file,
groups[n],
"IsTemporary",
NULL);
if (is_temporary)
{
session_id = g_key_file_get_string (key_file,
groups[n],
"SessionId",
NULL);
/* discard temporary accounts from older sessions */
if (session_id != NULL &&
g_strcmp0 (session_id, guid) != 0)
{
GoaProvider *provider = NULL;
const gchar *id;
gchar *provider_type = NULL;
g_debug ("ignoring account \"%s\" in file %s because it's stale",
groups[n], path);
id = account_group_to_id (groups[n]);
if (id == NULL)
{
g_warning ("Unable to get account ID from group: %s", groups[n]);
goto cleanup_and_continue;
}
provider_type = g_key_file_get_string (key_file, groups[n], "Provider", NULL);
if (provider_type != NULL)
provider = goa_provider_get_for_provider_type (provider_type);
if (provider == NULL)
{
g_warning ("Unsupported account type %s for ID %s (no provider)", provider_type, id);
goto cleanup_and_continue;
}
needs_update = g_key_file_remove_group (key_file, groups[n], NULL);
error = NULL;
if (!goa_utils_delete_credentials_for_id_sync (provider, id, NULL, &error))
{
g_warning ("Unable to clean-up stale keyring entries: %s", error->message);
g_error_free (error);
goto cleanup_and_continue;
}
cleanup_and_continue:
g_clear_object (&provider);
g_free (groups[n]);
g_free (provider_type);
g_free (session_id);
continue;
}
g_free (session_id);
}
else
{
needs_update = g_key_file_remove_key (key_file, groups[n], "SessionId", NULL);
}
g_hash_table_insert (group_name_to_key_file_data,
groups[n], /* steals string */
key_file_data_new (key_file, path));
}
else if (g_str_has_prefix (groups[n], "Template "))
{
g_hash_table_insert (group_name_to_key_file_data,
groups[n], /* steals string */
key_file_data_new (key_file, path));
}
else
{
g_warning ("Unexpected group \"%s\" in file %s", groups[n], path);
g_free (groups[n]);
}
}
g_free (groups);
if (needs_update)
{
error = NULL;
if (!g_key_file_save_to_file (key_file, path, &error))
{
g_prefix_error (&error, "Error writing key-value-file %s: ", path);
g_warning ("%s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code);
g_error_free (error);
}
}
out:
g_key_file_unref (key_file);
}
/* ---------------------------------------------------------------------------------------------------- */
/* returns FALSE if object is not (or no longer) valid */
static gboolean
update_account_object (GoaDaemon *self,
GoaObjectSkeleton *object,
const gchar *path,
const gchar *group,
GKeyFile *key_file,
gboolean just_added)
{
GoaAccount *account = NULL;
GoaProvider *provider = NULL;
gboolean is_locked;
gboolean is_temporary;
gboolean ret = FALSE;
gchar *identity = NULL;
gchar *presentation_identity;
gchar *type = NULL;
gchar *name = NULL;
GIcon *icon = NULL;
gchar *serialized_icon = NULL;
GError *error;
g_return_val_if_fail (GOA_IS_DAEMON (self), FALSE);
g_return_val_if_fail (G_IS_DBUS_OBJECT_SKELETON (object), FALSE);
g_return_val_if_fail (group != NULL, FALSE);
g_return_val_if_fail (key_file != NULL, FALSE);
g_debug ("updating %s %d", g_dbus_object_get_object_path (G_DBUS_OBJECT (object)), just_added);
type = g_key_file_get_string (key_file, group, "Provider", NULL);
identity = g_key_file_get_string (key_file, group, "Identity", NULL);
presentation_identity = g_key_file_get_string (key_file, group, "PresentationIdentity", NULL);
is_locked = g_key_file_get_boolean (key_file, group, "IsLocked", NULL);
is_temporary = g_key_file_get_boolean (key_file, group, "IsTemporary", NULL);
if (just_added)
{
account = goa_account_skeleton_new ();
goa_object_skeleton_set_account (object, account);
}
else
{
account = goa_object_get_account (GOA_OBJECT (object));
}
provider = goa_provider_get_for_provider_type (type);
if (provider == NULL)
{
g_warning ("Unsupported account type %s for identity %s (no provider)", type, identity);
goto out;
}
goa_account_set_id (account, g_strrstr (g_dbus_object_get_object_path (G_DBUS_OBJECT (object)), "/") + 1);
goa_account_set_provider_type (account, type);
goa_account_set_identity (account, identity);
goa_account_set_presentation_identity (account, presentation_identity);
goa_account_set_is_locked (account, is_locked);
goa_account_set_is_temporary (account, is_temporary);
error = NULL;
if (!goa_provider_build_object (provider, object, key_file, group, self->connection, just_added, &error))
{
g_warning ("Error parsing account: %s (%s, %d)",
error->message, g_quark_to_string (error->domain), error->code);
g_error_free (error);
goto out;
}
name = goa_provider_get_provider_name (provider, GOA_OBJECT (object));
goa_account_set_provider_name (account, name);
icon = goa_provider_get_provider_icon (provider, GOA_OBJECT (object));
serialized_icon = g_icon_to_string (icon);
goa_account_set_provider_icon (account, serialized_icon);
ret = TRUE;
out:
g_free (serialized_icon);
g_clear_object (&icon);
g_free (name);
g_clear_object (&provider);
g_clear_object (&account);
g_free (type);
g_free (identity);
g_free (presentation_identity);
return ret;
}
static void
process_config_entries (GoaDaemon *self,
GHashTable *group_name_to_key_file_data)
{
GHashTableIter iter;
KeyFileData *key_file_data;
GList *existing_object_paths = NULL;
GList *config_object_paths = NULL;
GList *added;
GList *removed;
GList *unchanged;
GList *l;
{
GList *existing_objects;
existing_objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->object_manager));
for (l = existing_objects; l != NULL; l = l->next)
{
GoaObject *object = GOA_OBJECT (l->data);
const gchar *object_path;
object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
if (g_str_has_prefix (object_path, "/org/gnome/OnlineAccounts/Accounts/"))
existing_object_paths = g_list_prepend (existing_object_paths, g_strdup (object_path));
}
g_list_free_full (existing_objects, g_object_unref);
}
{
const gchar *group;
g_hash_table_iter_init (&iter, group_name_to_key_file_data);
while (g_hash_table_iter_next (&iter, (gpointer*) &group, (gpointer*) &key_file_data))
{
const gchar *id;
gchar *object_path;
if (!g_str_has_prefix (group, "Account "))
continue;
id = account_group_to_id (group);
/* create and validate object path */
object_path = g_strdup_printf ("/org/gnome/OnlineAccounts/Accounts/%s", id);
if (strstr (id, "/") != NULL || !g_variant_is_object_path (object_path))
{
g_warning ("`%s' is not a valid account identifier", group);
g_free (object_path);
continue;
}
/* steals object_path variable */
config_object_paths = g_list_prepend (config_object_paths, object_path);
}
}
existing_object_paths = g_list_sort (existing_object_paths, (GCompareFunc) g_strcmp0);
config_object_paths = g_list_sort (config_object_paths, (GCompareFunc) g_strcmp0);
diff_sorted_lists (existing_object_paths,
config_object_paths,
(GCompareFunc) g_strcmp0,
&added,
&removed,
&unchanged);
for (l = removed; l != NULL; l = l->next)
{
const gchar *object_path = l->data;
GoaObject *object;
object = GOA_OBJECT (g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), object_path));
g_warn_if_fail (object != NULL);
g_signal_handlers_disconnect_by_func (goa_object_peek_account (object),
G_CALLBACK (on_account_handle_remove),
self);
g_object_unref (object);
g_debug ("removing %s", object_path);
g_warn_if_fail (g_dbus_object_manager_server_unexport (self->object_manager, object_path));
}
for (l = added; l != NULL; l = l->next)
{
const gchar *object_path = l->data;
GoaObjectSkeleton *object;
gchar *group;
g_debug ("adding %s", object_path);
group = account_object_path_to_group (object_path);
key_file_data = g_hash_table_lookup (group_name_to_key_file_data, group);
g_warn_if_fail (key_file_data != NULL);
object = goa_object_skeleton_new (object_path);
if (update_account_object (self,
object,
key_file_data->path,
group,
key_file_data->key_file,
TRUE))
{
g_dbus_object_manager_server_export (self->object_manager, G_DBUS_OBJECT_SKELETON (object));
g_signal_connect (goa_object_peek_account (GOA_OBJECT (object)),
"handle-remove",
G_CALLBACK (on_account_handle_remove),
self);
g_signal_connect (goa_object_peek_account (GOA_OBJECT (object)),
"handle-ensure-credentials",
G_CALLBACK (on_account_handle_ensure_credentials),
self);
}
g_object_unref (object);
g_free (group);
}
for (l = unchanged; l != NULL; l = l->next)
{
const gchar *object_path = l->data;
GoaObject *object;
gchar *group;
g_debug ("unchanged %s", object_path);
group = account_object_path_to_group (object_path);
key_file_data = g_hash_table_lookup (group_name_to_key_file_data, group);
g_warn_if_fail (key_file_data != NULL);
object = GOA_OBJECT (g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), object_path));
g_warn_if_fail (object != NULL);
if (!update_account_object (self,
GOA_OBJECT_SKELETON (object),
key_file_data->path,
group,
key_file_data->key_file,
FALSE))
{
g_signal_handlers_disconnect_by_func (goa_object_peek_account (object),
G_CALLBACK (on_account_handle_remove),
self);
g_signal_handlers_disconnect_by_func (goa_object_peek_account (object),
G_CALLBACK (on_account_handle_ensure_credentials),
self);
g_warn_if_fail (g_dbus_object_manager_server_unexport (self->object_manager, object_path));
}
g_object_unref (object);
g_free (group);
}
g_list_free (removed);
g_list_free (added);
g_list_free (unchanged);
g_list_free_full (existing_object_paths, g_free);
g_list_free_full (config_object_paths, g_free);
}
/* ---------------------------------------------------------------------------------------------------- */
static gint
compare_account_and_template_groups (const gchar *account_group, const gchar *template_group)
{
const gchar *account_id;
const gchar *template_id;
g_return_val_if_fail (g_str_has_prefix (account_group, "Account "), 0);
g_return_val_if_fail (g_str_has_prefix (template_group, "Template "), 0);
account_id = account_group + sizeof "Account " - 1;
template_id = template_group + sizeof "Template " - 1;
return g_strcmp0 (account_id, template_id);
}
static void
process_template_entries (GoaDaemon *self,
GHashTable *group_name_to_key_file_data)
{
GError *error;
GHashTable *key_files_to_update = NULL;
GHashTableIter iter;
GKeyFile *home_conf_key_file = NULL;
GKeyFile *key_file;
KeyFileData *key_file_data;
const gchar *group;
const gchar *key_file_path;
GList *config_object_groups = NULL;
GList *config_template_groups = NULL;
GList *added;
GList *unchanged;
GList *l;
key_files_to_update = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_key_file_unref);
g_hash_table_iter_init (&iter, group_name_to_key_file_data);
while (g_hash_table_iter_next (&iter, (gpointer *) &group, (gpointer *) &key_file_data))
{
if (home_conf_key_file == NULL && g_strcmp0 (key_file_data->path, self->home_conf_file_path) == 0)
home_conf_key_file = g_key_file_ref (key_file_data->key_file);
if (g_str_has_prefix (group, "Account "))
config_object_groups = g_list_prepend (config_object_groups, g_strdup (group));
else if (g_str_has_prefix (group, "Template "))
config_template_groups = g_list_prepend (config_template_groups, g_strdup (group));
}
if (home_conf_key_file == NULL)
home_conf_key_file = g_key_file_new ();
config_object_groups = g_list_sort (config_object_groups, (GCompareFunc) g_strcmp0);
config_template_groups = g_list_sort (config_template_groups, (GCompareFunc) g_strcmp0);
diff_sorted_lists (config_object_groups,
config_template_groups,
(GCompareFunc) compare_account_and_template_groups,
&added,
NULL,
&unchanged);
for (l = added; l != NULL; l = l->next)
{
gboolean needs_update;
const gchar *id;
const gchar *template_group = l->data;
gchar *object_group = NULL;
key_file_data = g_hash_table_lookup (group_name_to_key_file_data, template_group);
g_assert_nonnull (key_file_data);
if (goa_utils_keyfile_get_boolean (key_file_data->key_file, template_group, "ForceRemove"))
continue;
g_debug ("Adding from template %s", template_group);
id = template_group_to_id (template_group);
object_group = g_strdup_printf ("Account %s", id);
g_warn_if_fail (!g_key_file_has_group (home_conf_key_file, object_group));
needs_update = goa_utils_keyfile_copy_group (key_file_data->key_file,
template_group,
home_conf_key_file,
object_group);
if (needs_update)
{
g_key_file_set_boolean (home_conf_key_file, object_group, "IsLocked", TRUE);
g_hash_table_insert (key_files_to_update,
g_strdup (self->home_conf_file_path),
g_key_file_ref (home_conf_key_file));
}
g_free (object_group);
}
for (l = unchanged; l != NULL; l = l->next)
{
KeyFileData *object_key_file_data;
KeyFileData *template_key_file_data;
gboolean needs_update;
const gchar *id;
const gchar *object_group = l->data;
gchar *template_group = NULL;
object_key_file_data = g_hash_table_lookup (group_name_to_key_file_data, object_group);
g_assert_nonnull (object_key_file_data);
g_warn_if_fail (g_key_file_has_group (object_key_file_data->key_file, object_group));
id = account_group_to_id (object_group);
template_group = g_strdup_printf ("Template %s", id);
template_key_file_data = g_hash_table_lookup (group_name_to_key_file_data, template_group);
g_assert_nonnull (template_key_file_data);
g_assert_true (g_key_file_has_group (template_key_file_data->key_file, template_group));
if (goa_utils_keyfile_get_boolean (template_key_file_data->key_file, template_group, "ForceRemove"))
{
gboolean removed;
g_debug ("Template %s specifies ForceRemove, removing %s", template_group, object_group);
error = NULL;
needs_update = g_key_file_remove_group (object_key_file_data->key_file, object_group, &error);
if (error != NULL)
{
g_warning ("Error removing group %s from %s: %s (%s, %d)",
object_group,
key_file_data->path,
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
}
if (needs_update)
{
g_hash_table_insert (key_files_to_update,
g_strdup (object_key_file_data->path),
g_key_file_ref (object_key_file_data->key_file));
}
removed = g_hash_table_remove (group_name_to_key_file_data, object_group);
g_warn_if_fail (removed);
}
else
{
g_debug ("Updating %s from template %s", object_group, template_group);
needs_update = goa_utils_keyfile_copy_group (template_key_file_data->key_file,
template_group,
object_key_file_data->key_file,
object_group);
if (needs_update)
{
g_key_file_set_boolean (home_conf_key_file, object_group, "IsLocked", TRUE);
g_hash_table_insert (key_files_to_update,
g_strdup (object_key_file_data->path),
g_key_file_ref (object_key_file_data->key_file));
}
}
g_free (template_group);
}
g_hash_table_iter_init (&iter, key_files_to_update);
while (g_hash_table_iter_next (&iter, (gpointer *) &key_file_path, (gpointer *) &key_file))
{
error = NULL;
if (!g_key_file_save_to_file (key_file, key_file_path, &error))
{
g_prefix_error (&error, "Error writing key-value-file %s: ", key_file_path);
g_warning ("%s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code);
g_error_free (error);
}
}
g_hash_table_unref (key_files_to_update);
g_key_file_unref (home_conf_key_file);
g_list_free (added);
g_list_free (unchanged);
g_list_free_full (config_object_groups, g_free);
g_list_free_full (config_template_groups, g_free);
}
/* ---------------------------------------------------------------------------------------------------- */
/* <internal>
* goa_daemon_reload_configuration:
* @self: A #GoaDaemon
*
* Updates the accounts_objects member from stored configuration -
* typically called at startup or when a change on the configuration
* files has been detected.
*/
static void
goa_daemon_reload_configuration (GoaDaemon *self)
{
GHashTable *group_name_to_key_file_data;
group_name_to_key_file_data = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) key_file_data_free);
/* Read the main user config file at $HOME/.config/goa-1.0/accounts.conf */
add_config_file (self, self->home_conf_file_path, group_name_to_key_file_data);
if (GOA_TEMPLATE_FILE != NULL && GOA_TEMPLATE_FILE[0] != '\0')
add_config_file (self, GOA_TEMPLATE_FILE, group_name_to_key_file_data);
process_template_entries (self, group_name_to_key_file_data);
/* now process the group_name_to_key_file_data hash table */
process_config_entries (self, group_name_to_key_file_data);
g_hash_table_unref (group_name_to_key_file_data);
}
/* ---------------------------------------------------------------------------------------------------- */
static gchar *
generate_new_id (GoaDaemon *self)
{
static guint counter = 0;
GDateTime *dt;
gchar *ret;
dt = g_date_time_new_now_local ();
ret = g_strdup_printf ("account_%" G_GINT64_FORMAT "_%u",
g_date_time_to_unix (dt), /* seconds since Epoch */
counter); /* avoids collisions */
g_date_time_unref (dt);
counter++;
return ret;
}
typedef struct
{
GoaDaemon *daemon;
GoaManager *manager;
GDBusMethodInvocation *invocation;
gchar *provider_type;
gchar *identity;
gchar *presentation_identity;
GVariant *credentials;
GVariant *details;
} AddAccountData;
static void
get_all_providers_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
AddAccountData *data = user_data;
GoaProvider *provider = NULL;
GKeyFile *key_file = NULL;
GError *error;
GList *providers = NULL;
GList *l;
gchar *id = NULL;
gchar *group = NULL;
gchar *key_file_data = NULL;
gsize length;
gsize n_credentials;
gchar *object_path = NULL;
GVariantIter iter;
const gchar *key;
const gchar *value;
/* TODO: could check for @type */
if (!goa_provider_get_all_finish (&providers, res, NULL))
goto out;
for (l = providers; l != NULL; l = l->next)
{
GoaProvider *p;
const gchar *type;
p = GOA_PROVIDER (l->data);
type = goa_provider_get_provider_type (p);
if (g_strcmp0 (type, data->provider_type) == 0)
{
provider = p;
break;
}
}
if (provider == NULL)
{
error= NULL;
g_set_error (&error,
GOA_ERROR,
GOA_ERROR_FAILED, /* TODO: more specific */
_("Failed to find a provider for: %s"),
data->provider_type);
g_dbus_method_invocation_take_error (data->invocation, error);
goto out;
}
key_file = g_key_file_new ();
error = NULL;
if (!g_file_get_contents (data->daemon->home_conf_file_path,
&key_file_data,
&length,
&error))
{
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
{
g_error_free (error);
}
else
{
g_prefix_error (&error, "Error loading file %s: ", data->daemon->home_conf_file_path);
g_dbus_method_invocation_take_error (data->invocation, error);
goto out;
}
}
else
{
if (length > 0)
{
error = NULL;
if (!g_key_file_load_from_data (key_file, key_file_data, length, G_KEY_FILE_KEEP_COMMENTS, &error))
{
g_prefix_error (&error, "Error parsing key-value-file %s: ", data->daemon->home_conf_file_path);
g_dbus_method_invocation_take_error (data->invocation, error);
goto out;
}
}
}
if (!g_variant_lookup (data->details, "Id", "s", &id))
id = generate_new_id (data->daemon);
group = g_strdup_printf ("Account %s", id);
g_key_file_set_string (key_file, group, "Provider", data->provider_type);
g_key_file_set_string (key_file, group, "Identity", data->identity);
g_key_file_set_string (key_file, group, "PresentationIdentity", data->presentation_identity);
g_variant_iter_init (&iter, data->details);
while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
{
/* We treat IsTemporary special. If it's true we add in
* the current session guid, so it can be ignored after
* the session is over.
*/
if (g_strcmp0 (key, "IsTemporary") == 0)
{
if (g_strcmp0 (value, "true") == 0)
{
const char *guid;
guid = g_dbus_connection_get_guid (data->daemon->connection);
g_key_file_set_string (key_file, group, "SessionId", guid);
}
}
/* Skip Id since we already handled it above. */
if (g_strcmp0 (key, "Id") == 0)
continue;
g_key_file_set_string (key_file, group, key, value);
}
error = NULL;
if (!g_key_file_save_to_file (key_file, data->daemon->home_conf_file_path, &error))
{
g_prefix_error (&error, "Error writing key-value-file %s: ", data->daemon->home_conf_file_path);
g_dbus_method_invocation_take_error (data->invocation, error);
goto out;
}
n_credentials = g_variant_n_children (data->credentials);
if (n_credentials > 0)
{
/* We don't want to fail AddAccount if we could not store the
* credentials in the keyring.
*/
goa_utils_store_credentials_for_id_sync (provider,
id,
data->credentials,
NULL, /* GCancellable */
NULL);
}
goa_daemon_reload_configuration (data->daemon);
object_path = g_strdup_printf ("/org/gnome/OnlineAccounts/Accounts/%s", id);
goa_manager_complete_add_account (data->manager, data->invocation, object_path);
out:
g_free (object_path);
g_list_free_full (providers, g_object_unref);
g_free (key_file_data);
g_free (group);
g_free (id);
g_clear_pointer (&key_file, (GDestroyNotify) g_key_file_unref);
g_object_unref (data->daemon);
g_object_unref (data->manager);
g_object_unref (data->invocation);
g_free (data->provider_type);
g_free (data->identity);
g_free (data->presentation_identity);
g_variant_unref (data->credentials);
g_variant_unref (data->details);
g_slice_free (AddAccountData, data);
}
static gboolean
on_manager_handle_add_account (GoaManager *manager,
GDBusMethodInvocation *invocation,
const gchar *provider_type,
const gchar *identity,
const gchar *presentation_identity,
GVariant *credentials,
GVariant *details,
gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
AddAccountData *data;
data = g_slice_new0 (AddAccountData);
data->daemon = g_object_ref (self);
data->manager = g_object_ref (manager);
data->invocation = g_object_ref (invocation);
data->provider_type = g_strdup (provider_type);
data->identity = g_strdup (identity);
data->presentation_identity = g_strdup (presentation_identity);
data->credentials = g_variant_ref (credentials);
data->details = g_variant_ref (details);
goa_provider_get_all (get_all_providers_cb, data);
return TRUE; /* invocation was handled */
}
/* ---------------------------------------------------------------------------------------------------- */
typedef struct
{
GoaObject *object;
GList *invocations;
} ObjectInvocationData;
static ObjectInvocationData *
object_invocation_data_new (GoaObject *object,
GDBusMethodInvocation *invocation)
{
ObjectInvocationData *data;
data = g_slice_new0 (ObjectInvocationData);
data->object = g_object_ref (object);
data->invocations = g_list_prepend (data->invocations, invocation);
return data;
}
static void
object_invocation_data_unref (ObjectInvocationData *data)
{
g_list_free (data->invocations);
g_object_unref (data->object);
g_slice_free (ObjectInvocationData, data);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
remove_account_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
GTask *task = G_TASK (user_data);
GoaDaemon *self;
GDBusMethodInvocation *invocation;
GError *error;
GoaAccount *account;
GoaProvider *provider = GOA_PROVIDER (source_object);
ObjectInvocationData *data;
self = GOA_DAEMON (g_task_get_source_object (task));
data = g_task_get_task_data (task);
error= NULL;
if (!goa_provider_remove_account_finish (provider, res, &error))
{
g_warning ("goa_provider_remove_account() failed: %s (%s, %d)",
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
}
goa_daemon_reload_configuration (self);
account = goa_object_peek_account (data->object);
invocation = G_DBUS_METHOD_INVOCATION (data->invocations->data);
goa_account_complete_remove (account, invocation);
g_object_unref (task);
}
static gboolean
on_account_handle_remove (GoaAccount *account,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
GoaObject *object;
GoaProvider *provider = NULL;
GKeyFile *key_file = NULL;
GTask *task = NULL;
ObjectInvocationData *data;
const gchar *provider_type = NULL;
gchar *group = NULL;
GError *error;
if (goa_account_get_is_locked (account))
{
error = NULL;
g_set_error_literal (&error,
GOA_ERROR,
GOA_ERROR_NOT_SUPPORTED,
_("IsLocked property is set for account"));
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
/* update key-file - right now we only support removing the account
* if the entry is in ~/.config/goa-1.0/accounts.conf
*/
key_file = g_key_file_new ();
error = NULL;
if (!g_key_file_load_from_file (key_file,
self->home_conf_file_path,
G_KEY_FILE_KEEP_COMMENTS,
&error))
{
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
group = g_strdup_printf ("Account %s", goa_account_get_id (account));
error = NULL;
if (!g_key_file_remove_group (key_file, group, &error))
{
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
error = NULL;
if (!g_key_file_save_to_file (key_file, self->home_conf_file_path, &error))
{
g_prefix_error (&error, "Error writing key-value-file %s: ", self->home_conf_file_path);
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
provider_type = goa_account_get_provider_type (account);
if (provider_type == NULL)
{
error = NULL;
g_set_error_literal (&error,
GOA_ERROR,
GOA_ERROR_FAILED, /* TODO: more specific */
_("ProviderType property is not set for account"));
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
provider = goa_provider_get_for_provider_type (provider_type);
if (provider == NULL)
{
error = NULL;
g_set_error (&error,
GOA_ERROR,
GOA_ERROR_FAILED, /* TODO: more specific */
_("Failed to find a provider for: %s"),
provider_type);
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
error = NULL;
if (!goa_utils_delete_credentials_for_account_sync (provider, account, NULL, &error))
{
g_dbus_method_invocation_take_error (invocation, error);
goto out;
}
object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (account)));
data = object_invocation_data_new (object, invocation);
task = g_task_new (self, NULL, NULL, NULL);
g_task_set_task_data (task, data, (GDestroyNotify) object_invocation_data_unref);
goa_provider_remove_account (provider, object, NULL, remove_account_cb, g_object_ref (task));
out:
g_clear_object (&provider);
g_clear_object (&task);
g_clear_pointer (&key_file, (GDestroyNotify) g_key_file_unref);
g_free (group);
return TRUE; /* invocation was handled */
}
/* ---------------------------------------------------------------------------------------------------- */
static gboolean
is_authorization_error (GError *error)
{
gboolean ret;
g_return_val_if_fail (error != NULL, FALSE);
ret = FALSE;
if (error->domain == REST_PROXY_ERROR || error->domain == SOUP_HTTP_ERROR)
{
if (SOUP_STATUS_IS_CLIENT_ERROR (error->code))
ret = TRUE;
}
else if (error->domain == GOA_ERROR)
{
if (error->code == GOA_ERROR_NOT_AUTHORIZED)
ret = TRUE;
}
return ret;
}
static void
ensure_credentials_queue_complete (GList *invocations, GoaAccount *account, gint expires_in, GError *error)
{
GList *l;
const gchar *id;
const gchar *provider_type;
gint64 timestamp;
for (l = invocations; l != NULL; l = l->next)
{
GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (l->data);
if (invocation == NULL)
continue;
if (error == NULL)
goa_account_complete_ensure_credentials (account, invocation, expires_in);
else
g_dbus_method_invocation_return_gerror (invocation, error);
}
id = goa_account_get_id (account);
provider_type = goa_account_get_provider_type (account);
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Handled EnsureCredentials (%s, %s)", timestamp, provider_type, id);
}
static void
ensure_credentials_queue_collector (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
GTask *task = G_TASK (user_data);
GTask *task_queued;
GoaDaemon *self;
GoaAccount *account;
GoaProvider *provider = GOA_PROVIDER (source_object);
GError *error;
ObjectInvocationData *data;
gint expires_in;
self = GOA_DAEMON (g_task_get_source_object (task));
task_queued = G_TASK (g_queue_pop_head (self->ensure_credentials_queue));
g_assert (task == task_queued);
data = g_task_get_task_data (task);
account = goa_object_peek_account (data->object);
error= NULL;
if (!goa_provider_ensure_credentials_finish (provider, &expires_in, res, &error))
{
/* Set AttentionNeeded only if the error is an authorization error */
if (is_authorization_error (error))
{
if (!goa_account_get_attention_needed (account))
{
goa_account_set_attention_needed (account, TRUE);
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (account));
g_message ("%s: Setting AttentionNeeded to TRUE because EnsureCredentials() failed with: %s (%s, %d)",
g_dbus_object_get_object_path (G_DBUS_OBJECT (data->object)),
error->message, g_quark_to_string (error->domain), error->code);
}
}
ensure_credentials_queue_complete (data->invocations, account, 0, error);
g_error_free (error);
}
else
{
/* Clear AttentionNeeded flag if set */
if (goa_account_get_attention_needed (account))
{
goa_account_set_attention_needed (account, FALSE);
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (account));
g_message ("%s: Setting AttentionNeeded to FALSE because EnsureCredentials() succeded\n",
g_dbus_object_get_object_path (G_DBUS_OBJECT (data->object)));
}
ensure_credentials_queue_complete (data->invocations, account, expires_in, NULL);
}
self->ensure_credentials_running = FALSE;
ensure_credentials_queue_check (self);
g_object_unref (task);
}
static gint
ensure_credentials_queue_sort (gconstpointer a, gconstpointer b, gpointer user_data)
{
GTask *task_a = G_TASK (a);
GTask *task_b = G_TASK (b);
gint priority_a;
gint priority_b;
priority_a = g_task_get_priority (task_a);
priority_b = g_task_get_priority (task_b);
return priority_a - priority_b;
}
static void
ensure_credentials_queue_check (GoaDaemon *self)
{
GoaAccount *account;
GoaProvider *provider = NULL;
GTask *task;
ObjectInvocationData *data;
const gchar *id;
const gchar *provider_type;
gint64 timestamp;
if (self->ensure_credentials_running)
goto out;
if (self->ensure_credentials_queue->length == 0)
goto out;
g_queue_sort (self->ensure_credentials_queue, ensure_credentials_queue_sort, NULL);
task = G_TASK (g_queue_peek_head (self->ensure_credentials_queue));
self->ensure_credentials_running = TRUE;
data = (ObjectInvocationData *) g_task_get_task_data (task);
account = goa_object_peek_account (data->object);
id = goa_account_get_id (account);
provider_type = goa_account_get_provider_type (account);
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Handling EnsureCredentials (%s, %s)", timestamp, provider_type, id);
provider = goa_provider_get_for_provider_type (provider_type);
g_assert_nonnull (provider);
goa_provider_ensure_credentials (provider,
data->object,
NULL, /* GCancellable */
ensure_credentials_queue_collector,
task);
out:
g_clear_object (&provider);
}
static gboolean
ensure_credentials_queue_coalesce (GoaDaemon *self, GoaObject *object, GDBusMethodInvocation *invocation)
{
GList *l;
GoaAccount *account;
const gchar *id;
gboolean ret = FALSE;
gint priority;
account = goa_object_peek_account (object);
id = goa_account_get_id (account);
priority = (invocation == NULL) ? G_PRIORITY_LOW : G_PRIORITY_HIGH;
for (l = self->ensure_credentials_queue->head; l != NULL; l = l->next)
{
GoaAccount *account_queued;
GTask *task = G_TASK (l->data);
ObjectInvocationData *data;
const gchar *id_queued;
data = g_task_get_task_data (task);
account_queued = goa_object_peek_account (data->object);
id_queued = goa_account_get_id (account_queued);
if (g_strcmp0 (id, id_queued) == 0)
{
gint priority_queued;
priority_queued = g_task_get_priority (task);
if (priority < priority_queued)
g_task_set_priority (task, priority);
data->invocations = g_list_prepend (data->invocations, invocation);
ret = TRUE;
break;
}
}
return ret;
}
static gboolean
on_account_handle_ensure_credentials (GoaAccount *account,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
GoaDaemon *self = GOA_DAEMON (user_data);
GoaObject *object;
GoaProvider *provider = NULL;
GTask *task = NULL;
ObjectInvocationData *data;
const gchar *id;
const gchar *method_name;
const gchar *provider_type;
gint64 timestamp;
id = goa_account_get_id (account);
provider_type = goa_account_get_provider_type (account);
method_name = g_dbus_method_invocation_get_method_name (invocation);
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Received %s (%s, %s)", timestamp, method_name, provider_type, id);
provider = goa_provider_get_for_provider_type (provider_type);
if (provider == NULL)
{
g_dbus_method_invocation_return_error (invocation,
GOA_ERROR,
GOA_ERROR_FAILED,
"Unsupported account type %s for id %s (no provider)",
provider_type,
id);
goto out;
}
object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (account)));
if (ensure_credentials_queue_coalesce (self, object, invocation))
{
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Coalesced %s (%s, %s)",
timestamp,
method_name,
provider_type,
id);
goto out;
}
data = object_invocation_data_new (object, invocation);
task = g_task_new (self, NULL, NULL, NULL);
g_task_set_priority (task, G_PRIORITY_HIGH);
g_task_set_task_data (task, data, (GDestroyNotify) object_invocation_data_unref);
g_queue_push_tail (self->ensure_credentials_queue, g_object_ref (task));
ensure_credentials_queue_check (self);
out:
g_clear_object (&provider);
g_clear_object (&task);
return TRUE; /* invocation was handled */
}
/* <internal>
* goa_daemon_check_credentials:
* @self: A #GoaDaemon
*
* Checks whether credentials are valid and tries to refresh them if
* not. It also reports whether accounts are usable with the current
* network.
*/
static void
goa_daemon_check_credentials (GoaDaemon *self)
{
GList *l;
GList *objects;
objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->object_manager));
for (l = objects; l != NULL; l = l->next)
{
GoaAccount *account;
GoaObject *object = GOA_OBJECT (l->data);
GoaProvider *provider = NULL;
GTask *task = NULL;
ObjectInvocationData *data;
const gchar *id;
const gchar *provider_type;
gint64 timestamp;
account = goa_object_peek_account (object);
if (account == NULL)
goto cleanup_and_continue;
provider_type = goa_account_get_provider_type (account);
provider = goa_provider_get_for_provider_type (provider_type);
if (provider == NULL)
goto cleanup_and_continue;
id = goa_account_get_id (account);
provider_type = goa_account_get_provider_type (account);
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Calling EnsureCredentials (%s, %s)",
timestamp,
provider_type,
id);
if (ensure_credentials_queue_coalesce (self, object, NULL))
{
timestamp = g_get_monotonic_time ();
g_debug ("%" G_GINT64_FORMAT ": Coalesced EnsureCredentials (%s, %s)",
timestamp,
provider_type,
id);
goto cleanup_and_continue;
}
data = object_invocation_data_new (object, NULL);
task = g_task_new (self, NULL, NULL, NULL);
g_task_set_priority (task, G_PRIORITY_LOW);
g_task_set_task_data (task, data, (GDestroyNotify) object_invocation_data_unref);
g_queue_push_tail (self->ensure_credentials_queue, g_object_ref (task));
cleanup_and_continue:
g_clear_object (&provider);
g_clear_object (&task);
}
ensure_credentials_queue_check (self);
g_list_free_full (objects, g_object_unref);
}