From 4a2a52076d39780d21b6a6eeb877cd9391f152b5 Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Mon, 21 Oct 2013 16:30:38 +0300 Subject: [PATCH 2/2] Library: Support for groups. --- doc/libaccountsservice/Makefile.am | 1 + doc/libaccountsservice/libaccountsservice-docs.xml | 1 + .../libaccountsservice-sections.txt | 40 +- src/libaccountsservice/Makefile.am | 7 +- src/libaccountsservice/act-group-private.h | 43 + src/libaccountsservice/act-group.c | 697 ++++++++++++++++ src/libaccountsservice/act-group.h | 62 ++ src/libaccountsservice/act-types.h | 31 + src/libaccountsservice/act-user-manager-private.h | 40 + src/libaccountsservice/act-user-manager.c | 922 ++++++++++++++++++++- src/libaccountsservice/act-user-manager.h | 44 +- src/libaccountsservice/act-user-private.h | 6 +- src/libaccountsservice/act-user.c | 229 ++++- src/libaccountsservice/act-user.h | 18 +- src/libaccountsservice/act.h | 1 + 15 files changed, 2104 insertions(+), 38 deletions(-) create mode 100644 src/libaccountsservice/act-group-private.h create mode 100644 src/libaccountsservice/act-group.c create mode 100644 src/libaccountsservice/act-group.h create mode 100644 src/libaccountsservice/act-types.h create mode 100644 src/libaccountsservice/act-user-manager-private.h diff --git a/doc/libaccountsservice/Makefile.am b/doc/libaccountsservice/Makefile.am index 8f5486b..087c8ac 100644 --- a/doc/libaccountsservice/Makefile.am +++ b/doc/libaccountsservice/Makefile.am @@ -13,6 +13,7 @@ HFILE_GLOB=$(top_srcdir)/src/libaccountsservice/*.h IGNORE_HFILES= \ accounts-generated.h \ accounts-user-generated.h \ + accounts-group-generated.h \ ck-manager-generated.h \ ck-seat-generated.h \ ck-session-generated.h \ diff --git a/doc/libaccountsservice/libaccountsservice-docs.xml b/doc/libaccountsservice/libaccountsservice-docs.xml index 69009c5..5247655 100644 --- a/doc/libaccountsservice/libaccountsservice-docs.xml +++ b/doc/libaccountsservice/libaccountsservice-docs.xml @@ -12,6 +12,7 @@ libaccountsservice + diff --git a/doc/libaccountsservice/libaccountsservice-sections.txt b/doc/libaccountsservice/libaccountsservice-sections.txt index 88b0a78..98855b4 100644 --- a/doc/libaccountsservice/libaccountsservice-sections.txt +++ b/doc/libaccountsservice/libaccountsservice-sections.txt @@ -15,7 +15,9 @@ act_user_get_locked act_user_get_login_frequency act_user_get_login_history act_user_get_login_time +act_user_get_manager act_user_get_num_sessions +act_user_get_num_sessions_anywhere act_user_get_object_path act_user_get_password_hint act_user_get_password_mode @@ -25,9 +27,11 @@ act_user_get_shell act_user_get_uid act_user_get_user_name act_user_get_x_session +act_user_get_cached_groups act_user_is_loaded act_user_is_local_account act_user_is_logged_in +act_user_is_logged_in_anywhere act_user_is_system_account act_user_set_account_type act_user_set_automatic_login @@ -41,6 +45,9 @@ act_user_set_password_mode act_user_set_real_name act_user_set_user_name act_user_set_x_session +act_user_find_groups +act_user_find_groups_async +act_user_find_groups_finish ActUserClass ACT_IS_USER @@ -50,6 +57,37 @@ act_user_get_type
+act-group +ActGroup +act_group_get_gid +act_group_get_group_name +act_group_get_manager +act_group_get_object_path +act_group_get_users +act_group_is_loaded +act_group_is_nonexistent +act_group_set_group_name +act_group_add_user +act_group_remove_user +act_user_is_nonexistent +act_user_manager_create_group +act_user_manager_create_group_async +act_user_manager_create_group_finish +act_user_manager_delete_group +act_user_manager_delete_group_async +act_user_manager_delete_group_finish +act_user_manager_get_group +act_user_manager_get_group_by_id +act_user_manager_list_groups + +ACT_GROUP +ACT_IS_GROUP +ACT_TYPE_GROUP +ActGroupClass +act_group_get_type +
+ +
act-user-manager ACT_USER_MANAGER_ERROR ActUserManager @@ -108,4 +146,4 @@ act_user_manager_error_quark
ConsoleKitManager -
\ No newline at end of file +
diff --git a/src/libaccountsservice/Makefile.am b/src/libaccountsservice/Makefile.am index 5c1684e..fe9c21c 100644 --- a/src/libaccountsservice/Makefile.am +++ b/src/libaccountsservice/Makefile.am @@ -58,7 +58,9 @@ CLEANFILES += $(BUILT_SOURCES) libaccountsservicedir = $(includedir)/accountsservice-1.0/act libaccountsservice_headers = \ + act-types.h \ act-user.h \ + act-group.h \ act-user-manager.h \ act-user-enum-types.h \ $(END_OF_LIST) @@ -77,7 +79,7 @@ libaccountsservice_la_LDFLAGS = \ $(END_OF_LIST) libaccountsservice_la_LIBADD = \ - ../libaccounts-generated.la \ + ../libaccounts-generated.la \ $(LIBACCOUNTSSERVICE_LIBS) \ -lcrypt \ $(END_OF_LIST) @@ -85,12 +87,15 @@ libaccountsservice_la_LIBADD = \ libaccountsservice_la_sources = \ $(libaccountsservice_headers) \ act-user.c \ + act-group.c \ act-user-manager.c \ $(END_OF_LIST) libaccountsservice_la_SOURCES = \ $(libaccountsservice_la_sources) \ + act-user-manager-private.h \ act-user-private.h \ + act-group-private.h \ $(BUILT_SOURCES) \ $(END_OF_LIST) diff --git a/src/libaccountsservice/act-group-private.h b/src/libaccountsservice/act-group-private.h new file mode 100644 index 0000000..e73ac5b --- /dev/null +++ b/src/libaccountsservice/act-group-private.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape . + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Private interfaces to the ActGroup object + */ + +#ifndef __ACT_GROUP_PRIVATE_H_ +#define __ACT_GROUP_PRIVATE_H_ + +#include + +#include "act-user-manager.h" +#include "act-group.h" + +G_BEGIN_DECLS + +void _act_group_update_from_object_path (ActUserManager *manager, + ActGroup *group, + const char *object_path); +void _act_group_update_as_nonexistent (ActGroup *group); +void _act_group_load_from_group (ActGroup *group, + ActGroup *group_to_copy); + +G_END_DECLS + +#endif /* !__ACT_GROUP_PRIVATE_H_ */ diff --git a/src/libaccountsservice/act-group.c b/src/libaccountsservice/act-group.c new file mode 100644 index 0000000..6fb124c --- /dev/null +++ b/src/libaccountsservice/act-group.c @@ -0,0 +1,697 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape . + * Copyright (C) 2007-2008 William Jon McCann + * Copyright (C) 2013 Red Hat + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "act-user-manager-private.h" +#include "act-group-private.h" +#include "accounts-group-generated.h" + +/** + * SECTION:act-group + * @title: ActGroup + * @short_description: information about a group + * + * An ActGroup object represents a group on the system. + */ + +/** + * ActGroup: + * + * Represents a group on the system. + */ + +#define ACT_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ACT_TYPE_GROUP, ActGroupClass)) +#define ACT_IS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ACT_TYPE_GROUP)) +#define ACT_GROUP_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), ACT_TYPE_GROUP, ActGroupClass)) + +#define ACCOUNTS_NAME "org.freedesktop.Accounts" +#define ACCOUNTS_GROUP_INTERFACE "org.freedesktop.Accounts.Group" + +enum { + PROP_0, + PROP_GID, + PROP_GROUP_NAME, + PROP_LOCAL_GROUP, + PROP_USERS, // XXX - don't know how to define this + PROP_NONEXISTENT, + PROP_IS_LOADED +}; + +enum { + CHANGED, + LAST_SIGNAL +}; + +struct _ActGroup { + GObject parent; + + ActUserManager *manager; + GDBusConnection *connection; + AccountsGroup *accounts_proxy; + GDBusProxy *object_proxy; + GCancellable *get_all_call; + char *object_path; + + gid_t gid; + char *group_name; + ActUser **users; // NULL terminated + + guint gid_set : 1; + guint is_loaded : 1; + guint local_group : 1; + guint nonexistent : 1; +}; + +struct _ActGroupClass +{ + GObjectClass parent_class; +}; + +static void act_group_finalize (GObject *object); + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (ActGroup, act_group, G_TYPE_OBJECT) + +static void +act_group_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + ActGroup *group; + + group = ACT_GROUP (object); + + switch (param_id) { + case PROP_GID: + g_value_set_int (value, group->gid); + break; + case PROP_GROUP_NAME: + g_value_set_string (value, group->group_name); + break; + case PROP_LOCAL_GROUP: + g_value_set_boolean (value, group->local_group); + break; + case PROP_IS_LOADED: + g_value_set_boolean (value, group->is_loaded); + break; + case PROP_NONEXISTENT: + g_value_set_boolean (value, group->nonexistent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + +static void +act_group_class_init (ActGroupClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = act_group_finalize; + gobject_class->get_property = act_group_get_property; + + g_object_class_install_property (gobject_class, + PROP_GID, + g_param_spec_int ("gid", + "Group ID", + "The GID for this group.", + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_GROUP_NAME, + g_param_spec_string ("group-name", + "Group Name", + "The name for this group.", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_NONEXISTENT, + g_param_spec_boolean ("nonexistent", + "Doesn't exist", + "Determines whether or not the group object represents a valid group.", + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_IS_LOADED, + g_param_spec_boolean ("is-loaded", + "Is loaded", + "Determines whether or not the group object is loaded and ready to read from.", + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (gobject_class, + PROP_LOCAL_GROUP, + g_param_spec_boolean ("local-group", + "Local Group", + "Local Group", + FALSE, + G_PARAM_READABLE)); + /** + * ActGroup::changed: + * + * Emitted when the group changes in some way. + */ + signals [CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +act_group_init (ActGroup *group) +{ + GError *error = NULL; + + group->local_group = TRUE; + group->group_name = NULL; + + group->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (group->connection == NULL) { + g_warning ("Couldn't connect to system bus: %s", error->message); + g_error_free (error); + } +} + +static void +free_user_array (ActUser **a) +{ + int i; + if (a) { + for (i = 0; a[i]; i++) + g_object_unref (a[i]); + g_free (a); + } +} + +static void +act_group_finalize (GObject *object) +{ + ActGroup *group; + + group = ACT_GROUP (object); + + g_free (group->group_name); + free_user_array (group->users); + + if (G_OBJECT_CLASS (act_group_parent_class)->finalize) + (*G_OBJECT_CLASS (act_group_parent_class)->finalize) (object); +} + +static void +set_is_loaded (ActGroup *group, + gboolean is_loaded) +{ + if (group->is_loaded != is_loaded) { + group->is_loaded = is_loaded; + g_object_notify (G_OBJECT (group), "is-loaded"); + } +} + +/** + * _act_group_update_as_nonexistent: + * @group: the group object to update. + * + * Set's the 'non-existent' property of @group to #TRUE + * Can only be called before the group is loaded. + **/ +void +_act_group_update_as_nonexistent (ActGroup *group) +{ + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (!act_group_is_loaded (group)); + g_return_if_fail (group->object_path == NULL); + + group->nonexistent = TRUE; + g_object_notify (G_OBJECT (group), "nonexistent"); + + set_is_loaded (group, TRUE); +} + +/** + * act_group_get_gid: + * @group: the group object to examine. + * + * Retrieves the ID of @group. + * + * Returns: the group id. + **/ + +gid_t +act_group_get_gid (ActGroup *group) +{ + g_return_val_if_fail (ACT_IS_GROUP (group), -1); + + return group->gid; +} + +/** + * act_group_get_object_path: + * @group: a #ActGroup + * + * Returns the group accounts service object path of @group, + * or %NULL if @group doesn't have an object path associated + * with it. + * + * Returns: (transfer none): the object path of the group + */ +const char * +act_group_get_object_path (ActGroup *group) +{ + g_return_val_if_fail (ACT_IS_GROUP (group), NULL); + + return group->object_path; +} + +ActUserManager * +act_group_get_manager (ActGroup *group) +{ + return group->manager; +} + +/** + * act_group_get_group_name: + * @group: the group object to examine. + * + * Retrieves the name of @group. + * + * Returns: (transfer none): a pointer to an array of characters which must not be modified or + * freed, or %NULL. + **/ + +const char * +act_group_get_group_name (ActGroup *group) +{ + g_return_val_if_fail (ACT_IS_GROUP (group), NULL); + + return group->group_name; +} + +/** + * act_group_get_users: + * @group: the group object to examine. + * + * Retrieves the members of @group that are users. + * + * Returns: (transfer none): a NULL-terminated array of pointers. + **/ + +ActUser ** +act_group_get_users (ActGroup *group) +{ + g_return_val_if_fail (ACT_IS_GROUP (group), NULL); + + return group->users; +} + +/** + * act_group_is_nonexistent: + * @group: the group object to examine. + * + * Retrieves whether the group is nonexistent or not. + * + * Returns: (transfer none): %TRUE if the group is nonexistent + **/ +gboolean +act_group_is_nonexistent (ActGroup *group) +{ + g_return_val_if_fail (ACT_IS_GROUP (group), FALSE); + + return group->nonexistent; +} + +static void +collect_props (const gchar *key, + GVariant *value, + ActGroup *group) +{ + gboolean handled = TRUE; + + if (strcmp (key, "Gid") == 0) { + guint64 new_gid; + + new_gid = g_variant_get_uint64 (value); + if (!group->gid_set || (guint64) group->gid != new_gid) { + group->gid = (uid_t) new_gid; + group->gid_set = TRUE; + g_object_notify (G_OBJECT (group), "gid"); + } + } else if (strcmp (key, "GroupName") == 0) { + const char *new_group_name; + + new_group_name = g_variant_get_string (value, NULL); + if (g_strcmp0 (group->group_name, new_group_name) != 0) { + g_free (group->group_name); + group->group_name = g_strdup (new_group_name); + g_object_notify (G_OBJECT (group), "group-name"); + } + } else if (strcmp (key, "LocalGroup") == 0) { + gboolean new_local; + + new_local = g_variant_get_boolean (value); + if (group->local_group != new_local) { + group->local_group = new_local; + g_object_notify (G_OBJECT (group), "local-group"); + } + } else if (strcmp (key, "Users") == 0) { + gboolean changed; + GVariantIter iter; + int i; + const gchar *user_path; + + if (group->users == NULL) + changed = TRUE; + else { + changed = FALSE; + i = 0; + g_variant_iter_init (&iter, value); + while (group->users[i] && g_variant_iter_next (&iter, "&o", &user_path)) { + if (g_strcmp0 (act_user_get_object_path (group->users[i]), user_path) != 0) { + changed = TRUE; + break; + } + i++; + } + if (group->users[i] != NULL || i != g_variant_n_children (value)) + changed = TRUE; + } + + if (changed) { + free_user_array (group->users); + group->users = g_new0 (ActUser*, g_variant_n_children (value)+1); + i = 0; + g_variant_iter_init (&iter, value); + while (g_variant_iter_next (&iter, "&o", &user_path)) { + group->users[i] = g_object_ref(_act_user_manager_get_user (group->manager, + user_path)); + i++; + } + // g_object_notify (G_OBJECT (group), "users"); + } + } else { + handled = FALSE; + } + + if (!handled) { + g_debug ("unhandled property %s", key); + } +} + +static void +on_get_all_finished (GObject *object, + GAsyncResult *result, + gpointer data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (object); + ActGroup *group = data; + GError *error; + GVariant *res; + GVariantIter *iter; + gchar *key; + GVariant *value; + + g_assert (G_IS_DBUS_PROXY (group->object_proxy)); + g_assert (group->object_proxy == proxy); + + error = NULL; + res = g_dbus_proxy_call_finish (proxy, result, &error); + + if (! res) { + g_debug ("Error calling GetAll() when retrieving properties for %s: %s", + group->object_path, error->message); + g_error_free (error); + + if (!group->is_loaded) { + set_is_loaded (group, TRUE); + } + return; + } + + g_object_unref (group->get_all_call); + group->get_all_call = NULL; + + g_variant_get (res, "(a{sv})", &iter); + while (g_variant_iter_next (iter, "{sv}", &key, &value)) { + collect_props (key, value, group); + g_free (key); + g_variant_unref (value); + } + g_variant_iter_free (iter); + g_variant_unref (res); + + if (!group->is_loaded) { + set_is_loaded (group, TRUE); + } + + g_signal_emit (group, signals[CHANGED], 0); +} + +static void +update_info (ActGroup *group) +{ + g_assert (G_IS_DBUS_PROXY (group->object_proxy)); + + if (group->get_all_call != NULL) { + g_cancellable_cancel (group->get_all_call); + g_object_unref (group->get_all_call); + } + + group->get_all_call = g_cancellable_new (); + g_dbus_proxy_call (group->object_proxy, + "GetAll", + g_variant_new ("(s)", ACCOUNTS_GROUP_INTERFACE), + G_DBUS_CALL_FLAGS_NONE, + -1, + group->get_all_call, + on_get_all_finished, + group); +} + +static void +changed_handler (AccountsGroup *object, + gpointer *data) +{ + ActGroup *group = ACT_GROUP (data); + + update_info (group); +} + +/** + * _act_group_update_from_object_path: + * @group: the group object to update. + * @object_path: the object path of the group to use. + * + * Updates the properties of @group from the accounts service via + * the object path in @object_path. + **/ +void +_act_group_update_from_object_path (ActUserManager *manager, + ActGroup *group, + const char *object_path) +{ + GError *error = NULL; + + g_return_if_fail (ACT_IS_USER_MANAGER (manager)); + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (object_path != NULL); + g_return_if_fail (group->object_path == NULL); + + group->manager = manager; + group->object_path = g_strdup (object_path); + + group->accounts_proxy = accounts_group_proxy_new_sync (group->connection, + G_DBUS_PROXY_FLAGS_NONE, + ACCOUNTS_NAME, + group->object_path, + NULL, + &error); + if (!group->accounts_proxy) { + g_warning ("Couldn't create accounts proxy: %s", error->message); + g_error_free (error); + return; + } + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (group->accounts_proxy), INT_MAX); + + g_signal_connect (group->accounts_proxy, "changed", G_CALLBACK (changed_handler), group); + + group->object_proxy = g_dbus_proxy_new_sync (group->connection, + G_DBUS_PROXY_FLAGS_NONE, + 0, + ACCOUNTS_NAME, + group->object_path, + "org.freedesktop.DBus.Properties", + NULL, + &error); + if (!group->object_proxy) { + g_warning ("Couldn't create accounts property proxy: %s", error->message); + g_error_free (error); + return; + } + + update_info (group); +} + +void +_act_group_load_from_group (ActGroup *group, + ActGroup *group_to_copy) +{ + if (!group_to_copy->is_loaded) { + return; + } + + /* loading groups may already have a gid, group name, or session list + * from creation, so only update them if necessary + */ + if (!group->gid_set) { + group->gid = group_to_copy->gid; + g_object_notify (G_OBJECT (group), "gid"); + } + + if (group->group_name == NULL) { + group->group_name = g_strdup (group_to_copy->group_name); + g_object_notify (G_OBJECT (group), "group-name"); + } + + set_is_loaded (group, TRUE); +} + +/** + * act_group_is_loaded: + * @group: a #ActGroup + * + * Determines whether or not the group object is loaded and ready to read from. + * #ActUserManager:is-loaded property must be %TRUE before calling + * act_user_manager_list_groups() + * + * Returns: %TRUE or %FALSE + */ +gboolean +act_group_is_loaded (ActGroup *group) +{ + return group->is_loaded; +} + +/** + * act_group_set_group_name: + * @group: the group object to alter + * @new_group_name: the new group name + * + * Changes the group name of @group to @new_group_name. + * + * Note this function is synchronous and ignores errors. + **/ +void +act_group_set_group_name (ActGroup *group, + const gchar *new_group_name) +{ + GError *error = NULL; + + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (ACCOUNTS_IS_GROUP (group->accounts_proxy)); + + if (!accounts_group_call_set_group_name_sync (group->accounts_proxy, + new_group_name, + NULL, + &error)) { + g_warning ("SetGroupName call failed: %s", error->message); + g_error_free (error); + } +} + +/** + * act_group_add_user: + * @group: the group object to alter + * @user: the user to add + * + * Makes @user a direct member of @group. The @user object must + * correspond to an existing user. + * + * Note this function is synchronous and ignores errors. + **/ +void +act_group_add_user (ActGroup *group, + ActUser *user) +{ + GError *error = NULL; + + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (ACT_IS_USER (user)); + g_return_if_fail (act_user_get_object_path (user) != NULL); + g_return_if_fail (ACCOUNTS_IS_GROUP (group->accounts_proxy)); + + if (!accounts_group_call_add_user_sync (group->accounts_proxy, + act_user_get_object_path (user), + NULL, + &error)) { + g_warning ("AddUser call failed: %s", error->message); + g_error_free (error); + } +} + +/** + * act_group_remove_user: + * @group: the group object to alter + * @user: the user to remove + * + * Makes sure that @user is not a direct member of @group. The @user + * object must correspond to an existing user. + * + * Note this function is synchronous and ignores errors. + **/ +void +act_group_remove_user (ActGroup *group, + ActUser *user) +{ + GError *error = NULL; + + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (ACT_IS_USER (user)); + g_return_if_fail (act_user_get_object_path (user) != NULL); + g_return_if_fail (ACCOUNTS_IS_GROUP (group->accounts_proxy)); + + if (!accounts_group_call_remove_user_sync (group->accounts_proxy, + act_user_get_object_path (user), + NULL, + &error)) { + g_warning ("RemoveUser call failed: %s", error->message); + g_error_free (error); + } +} diff --git a/src/libaccountsservice/act-group.h b/src/libaccountsservice/act-group.h new file mode 100644 index 0000000..fb7dcfe --- /dev/null +++ b/src/libaccountsservice/act-group.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Red Hat + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Facade object for group data, owned by ActUserManager + */ + +#ifndef __ACT_GROUP_H__ +#define __ACT_GROUP_H__ + +#include +#include +#include + +#include "act-types.h" + +G_BEGIN_DECLS + +#define ACT_TYPE_GROUP (act_group_get_type ()) +#define ACT_GROUP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), ACT_TYPE_GROUP, ActGroup)) +#define ACT_IS_GROUP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), ACT_TYPE_GROUP)) + +typedef struct _ActGroupClass ActGroupClass; + +GType act_group_get_type (void) G_GNUC_CONST; +ActUserManager *act_group_get_manager (ActGroup *group); + +const char *act_group_get_object_path (ActGroup *group); + +gboolean act_group_is_loaded (ActGroup *group); +gboolean act_group_is_nonexistent (ActGroup *group); + +gid_t act_group_get_gid (ActGroup *group); +const char *act_group_get_group_name (ActGroup *group); +ActUser **act_group_get_users (ActGroup *group); + +void act_group_set_group_name (ActGroup *group, + const gchar *new_group_name); +void act_group_add_user (ActGroup *group, + ActUser *user); +void act_group_remove_user (ActGroup *group, + ActUser *user); + +G_END_DECLS + +#endif /* __ACT_GROUP_H__ */ diff --git a/src/libaccountsservice/act-types.h b/src/libaccountsservice/act-types.h new file mode 100644 index 0000000..305dd4d --- /dev/null +++ b/src/libaccountsservice/act-types.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Red Hat, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ACT_TYPES_H__ +#define __ACT_TYPES_H__ + +G_BEGIN_DECLS + +typedef struct _ActUser ActUser; +typedef struct _ActGroup ActGroup; +typedef struct _ActUserManager ActUserManager; + +G_END_DECLS + +#endif /* __ACT_TYPES_H__ */ diff --git a/src/libaccountsservice/act-user-manager-private.h b/src/libaccountsservice/act-user-manager-private.h new file mode 100644 index 0000000..76e1752 --- /dev/null +++ b/src/libaccountsservice/act-user-manager-private.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Red Hat + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Private interfaces to the ActUserManager object + */ + +#ifndef __ACT_USER_MANAGER_PRIVATE_H_ +#define __ACT_USER_MANAGER_PRIVATE_H_ + +#include + +#include "act-user-manager.h" + +G_BEGIN_DECLS + +ActUser *_act_user_manager_get_user (ActUserManager *manager, + const char *object_path); +ActGroup *_act_user_manager_get_group (ActUserManager *manager, + const char *group_path); + +G_END_DECLS + +#endif /* !__ACT_GROUP_PRIVATE_H_ */ diff --git a/src/libaccountsservice/act-user-manager.c b/src/libaccountsservice/act-user-manager.c index 32a7893..0d6dff8 100644 --- a/src/libaccountsservice/act-user-manager.c +++ b/src/libaccountsservice/act-user-manager.c @@ -49,7 +49,9 @@ #endif #include "act-user-manager.h" +#include "act-user-manager-private.h" #include "act-user-private.h" +#include "act-group-private.h" #include "accounts-generated.h" #include "ck-manager-generated.h" #include "ck-seat-generated.h" @@ -86,6 +88,8 @@ * @ACT_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST: The user does not exist * @ACT_USER_MANAGER_ERROR_PERMISSION_DENIED: Permission denied * @ACT_USER_MANAGER_ERROR_NOT_SUPPORTED: Operation not supported + * @ACT_USER_MANAGER_ERROR_GROUP_EXISTS: The group already exists + * @ACT_USER_MANAGER_ERROR_GROUP_DOES_NOT_EXIST: The group does not exist * * Various error codes returned by the accounts service. */ @@ -158,6 +162,8 @@ typedef enum { typedef enum { ACT_USER_MANAGER_FETCH_USER_FROM_USERNAME_REQUEST, ACT_USER_MANAGER_FETCH_USER_FROM_ID_REQUEST, + ACT_USER_MANAGER_FETCH_GROUP_FROM_GROUPNAME_REQUEST, + ACT_USER_MANAGER_FETCH_GROUP_FROM_ID_REQUEST, } ActUserManagerFetchUserRequestType; typedef struct @@ -165,10 +171,13 @@ typedef struct ActUserManager *manager; ActUserManagerGetUserState state; ActUser *user; + ActGroup *group; ActUserManagerFetchUserRequestType type; union { char *username; uid_t uid; + char *groupname; + gid_t gid; }; char *object_path; char *description; @@ -179,6 +188,8 @@ struct ActUserManagerPrivate GHashTable *normal_users_by_name; GHashTable *system_users_by_name; GHashTable *users_by_object_path; + GHashTable *groups_by_name; + GHashTable *groups_by_object_path; GHashTable *sessions; GDBusConnection *connection; AccountsAccounts *accounts_proxy; @@ -190,6 +201,8 @@ struct ActUserManagerPrivate GSList *new_users; GSList *new_users_inhibiting_load; GSList *fetch_user_requests; + GSList *new_groups; + GSList *new_groups_inhibiting_load; GSList *exclude_usernames; GSList *include_usernames; @@ -200,6 +213,7 @@ struct ActUserManagerPrivate gboolean has_multiple_users; gboolean getting_sessions; gboolean listing_cached_users; + gboolean listing_cached_groups; }; enum { @@ -215,6 +229,9 @@ enum { USER_REMOVED, USER_IS_LOGGED_IN_CHANGED, USER_CHANGED, + GROUP_ADDED, + GROUP_REMOVED, + GROUP_CHANGED, LAST_SIGNAL }; @@ -227,6 +244,7 @@ static void act_user_manager_finalize (GObject *object); static gboolean load_seat_incrementally (ActUserManager *manager); static void unload_seat (ActUserManager *manager); static void load_users (ActUserManager *manager); +static void load_groups (ActUserManager *manager); static void act_user_manager_queue_load (ActUserManager *manager); static void queue_load_seat_and_users (ActUserManager *manager); @@ -236,6 +254,9 @@ static void set_is_loaded (ActUserManager *manager, gboolean is_loaded); static void on_new_user_loaded (ActUser *user, GParamSpec *pspec, ActUserManager *manager); +static void on_new_group_loaded (ActGroup *group, + GParamSpec *pspec, + ActUserManager *manager); static void give_up (ActUserManager *manager, ActUserManagerFetchUserRequest *request); static void fetch_user_incrementally (ActUserManagerFetchUserRequest *request); @@ -243,6 +264,8 @@ static void fetch_user_incrementally (ActUserManagerFetchUserRequest * static void maybe_set_is_loaded (ActUserManager *manager); static void update_user (ActUserManager *manager, ActUser *user); +static void update_group (ActUserManager *manager, + ActGroup *group); static gpointer user_manager_object = NULL; G_DEFINE_TYPE (ActUserManager, act_user_manager, G_TYPE_OBJECT) @@ -252,7 +275,9 @@ static const GDBusErrorEntry error_entries[] = { { ACT_USER_MANAGER_ERROR_USER_EXISTS, "org.freedesktop.Accounts.Error.UserExists" }, { ACT_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.UserDoesNotExist" }, { ACT_USER_MANAGER_ERROR_PERMISSION_DENIED, "org.freedesktop.Accounts.Error.PermissionDenied" }, - { ACT_USER_MANAGER_ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" } + { ACT_USER_MANAGER_ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" }, + { ACT_USER_MANAGER_ERROR_GROUP_EXISTS, "org.freedesktop.Accounts.Error.GroupExists" }, + { ACT_USER_MANAGER_ERROR_GROUP_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.GroupDoesNotExist" } }; GQuark @@ -638,6 +663,12 @@ describe_user (ActUser *user) { ActUserManagerFetchUserRequest *request; + request = g_object_get_data (G_OBJECT (user), "fetch-user-request"); + + if (request != NULL) { + return request->description; + } + if (act_user_is_loaded (user)) { static char *description = NULL; g_clear_pointer (&description, (GDestroyNotify) g_free); @@ -646,7 +677,23 @@ describe_user (ActUser *user) return description; } - request = g_object_get_data (G_OBJECT (user), "fetch-user-request"); + return "user"; +} + +static const char * +describe_group (ActGroup *group) +{ + ActUserManagerFetchUserRequest *request; + + if (act_group_is_loaded (group)) { + static char *description = NULL; + g_clear_pointer (&description, (GDestroyNotify) g_free); + + description = g_strdup_printf ("group %s", act_group_get_group_name (group)); + return description; + } + + request = g_object_get_data (G_OBJECT (group), "fetch-user-request"); if (request != NULL) { return request->description; @@ -694,6 +741,20 @@ on_user_changed (ActUser *user, } static void +on_group_changed (ActGroup *group, + ActUserManager *manager) +{ + if (manager->priv->is_loaded) { + g_debug ("ActUserManager: %s changed", + describe_group (group)); + + g_signal_emit (manager, signals[GROUP_CHANGED], 0, group); + + update_group (manager, group); + } +} + +static void queue_load_seat_incrementally (ActUserManager *manager) { if (manager->priv->seat.load_idle_id == 0) { @@ -846,12 +907,24 @@ create_new_user (ActUserManager *manager) return g_object_ref (user); } +static ActGroup * +create_new_group (ActUserManager *manager) +{ + ActGroup *group; + + group = g_object_new (ACT_TYPE_GROUP, NULL); + + manager->priv->new_groups = g_slist_prepend (manager->priv->new_groups, group); + + g_signal_connect_object (group, "notify::is-loaded", G_CALLBACK (on_new_group_loaded), manager, 0); + + return g_object_ref (group); +} + static void add_user (ActUserManager *manager, ActUser *user) { - const char *object_path; - g_debug ("ActUserManager: tracking user '%s'", act_user_get_user_name (user)); if (act_user_is_system_account (user)) { g_hash_table_insert (manager->priv->system_users_by_name, @@ -863,13 +936,6 @@ add_user (ActUserManager *manager, g_object_ref (user)); } - object_path = act_user_get_object_path (user); - if (object_path != NULL) { - g_hash_table_insert (manager->priv->users_by_object_path, - (gpointer) object_path, - g_object_ref (user)); - } - g_signal_connect_object (user, "sessions-changed", G_CALLBACK (on_user_sessions_changed), @@ -892,6 +958,29 @@ add_user (ActUserManager *manager, } static void +add_group (ActUserManager *manager, + ActGroup *group) +{ + g_debug ("ActUserManager: tracking group '%s'", act_group_get_group_name (group)); + + g_hash_table_insert (manager->priv->groups_by_name, + g_strdup (act_group_get_group_name (group)), + g_object_ref (group)); + + g_signal_connect_object (group, + "changed", + G_CALLBACK (on_group_changed), + manager, 0); + + if (manager->priv->is_loaded) { + g_debug ("ActUserManager: loaded, so emitting group-added signal"); + g_signal_emit (manager, signals[GROUP_ADDED], 0, group); + } else { + g_debug ("ActUserManager: not yet loaded, so not emitting group-added signal"); + } +} + +static void remove_user (ActUserManager *manager, ActUser *user) { @@ -927,6 +1016,34 @@ remove_user (ActUserManager *manager, } static void +remove_group (ActUserManager *manager, + ActGroup *group) +{ + g_debug ("ActUserManager: no longer tracking group '%s' (with object path %s)", + act_group_get_group_name (group), + act_group_get_object_path (group)); + + g_object_ref (group); + + g_signal_handlers_disconnect_by_func (group, on_group_changed, manager); + if (act_group_get_object_path (group) != NULL) { + g_hash_table_remove (manager->priv->groups_by_object_path, act_group_get_object_path (group)); + } + if (act_group_get_group_name (group) != NULL) { + g_hash_table_remove (manager->priv->groups_by_name, act_group_get_group_name (group)); + } + + if (manager->priv->is_loaded) { + g_debug ("ActUserManager: loaded, so emitting group-removed signal"); + g_signal_emit (manager, signals[GROUP_REMOVED], 0, group); + } else { + g_debug ("ActUserManager: not yet loaded, so not emitting group-removed signal"); + } + + g_object_unref (group); +} + +static void update_user (ActUserManager *manager, ActUser *user) { @@ -960,6 +1077,13 @@ update_user (ActUserManager *manager, } } +static void +update_group (ActUserManager *manager, + ActGroup *group) +{ + /* Nothing to do... */ +} + static ActUser * lookup_user_by_name (ActUserManager *manager, const char *username) @@ -975,6 +1099,13 @@ lookup_user_by_name (ActUserManager *manager, return user; } +static ActGroup * +lookup_group_by_name (ActUserManager *manager, + const char *groupname) +{ + return g_hash_table_lookup (manager->priv->groups_by_name, groupname); +} + static void on_new_user_loaded (ActUser *user, GParamSpec *pspec, @@ -998,19 +1129,21 @@ on_new_user_loaded (ActUser *user, username = act_user_get_user_name (user); if (username == NULL) { - const char *object_path; - - object_path = act_user_get_object_path (user); - - if (object_path != NULL) { - g_warning ("ActUserManager: %s has no username " - "(object path: %s, uid: %d)", - describe_user (user), - object_path, (int) act_user_get_uid (user)); - } else { - g_warning ("ActUserManager: %s has no username (uid: %d)", - describe_user (user), - (int) act_user_get_uid (user)); + if (!act_user_is_nonexistent (user)) { + const char *object_path; + + object_path = act_user_get_object_path (user); + + if (object_path != NULL) { + g_warning ("ActUserManager: %s has no username " + "(object path: %s, uid: %d)", + describe_user (user), + object_path, (int) act_user_get_uid (user)); + } else { + g_warning ("ActUserManager: %s has no username (uid: %d)", + describe_user (user), + (int) act_user_get_uid (user)); + } } g_object_unref (user); goto out; @@ -1047,6 +1180,74 @@ out: } } +static void +on_new_group_loaded (ActGroup *group, + GParamSpec *pspec, + ActUserManager *manager) +{ + const char *groupname; + ActGroup *old_group; + + if (!act_group_is_loaded (group)) { + g_debug ("ActUserManager: %s loaded function called when not loaded", + describe_group (group)); + return; + } + g_signal_handlers_disconnect_by_func (group, on_new_group_loaded, manager); + + manager->priv->new_groups = g_slist_remove (manager->priv->new_groups, + group); + manager->priv->new_groups_inhibiting_load = g_slist_remove (manager->priv->new_groups_inhibiting_load, + group); + + groupname = act_group_get_group_name (group); + + if (groupname == NULL) { + if (!act_group_is_nonexistent (group)) { + const char *object_path; + + object_path = act_group_get_object_path (group); + + if (object_path != NULL) { + g_warning ("ActUserManager: %s has no groupname " + "(object path: %s, gid: %d)", + describe_group (group), + object_path, (int) act_group_get_gid (group)); + } else { + g_warning ("ActUserManager: %s has no groupname (gid: %d)", + describe_group (group), + (int) act_group_get_gid (group)); + } + } + g_object_unref (group); + goto out; + } + + g_debug ("ActUserManager: %s is now loaded", describe_group (group)); + + old_group = lookup_group_by_name (manager, groupname); + + /* If groupname hasn't been added, yet, add it now + */ + if (old_group == NULL) { + g_debug ("ActUserManager: %s was not yet known, adding it", + describe_group (group)); + add_group (manager, group); + } else { + _act_group_load_from_group (old_group, group); + } + + g_object_unref (group); + +out: + if (manager->priv->new_groups_inhibiting_load == NULL) { + g_debug ("ActUserManager: no pending groups, trying to set loaded property"); + maybe_set_is_loaded (manager); + } else { + g_debug ("ActUserManager: not all groups loaded yet"); + } +} + static ActUser * add_new_user_for_object_path (const char *object_path, ActUserManager *manager) @@ -1064,11 +1265,39 @@ add_new_user_for_object_path (const char *object_path, g_debug ("ActUserManager: tracking new user with object path %s", object_path); user = create_new_user (manager); - _act_user_update_from_object_path (user, object_path); + _act_user_update_from_object_path (manager, user, object_path); + g_hash_table_insert (manager->priv->users_by_object_path, + (gchar *)act_user_get_object_path (user), + g_object_ref (user)); return user; } +static ActGroup * +add_new_group_for_object_path (const char *object_path, + ActUserManager *manager) +{ + ActGroup *group; + + group = g_hash_table_lookup (manager->priv->groups_by_object_path, object_path); + + if (group != NULL) { + g_debug ("ActUserManager: tracking existing %s with object path %s", + describe_group (group), object_path); + return group; + } + + g_debug ("ActUserManager: tracking new group with object path %s", object_path); + + group = create_new_group (manager); + _act_group_update_from_object_path (manager, group, object_path); + g_hash_table_insert (manager->priv->groups_by_object_path, + (gchar *)act_group_get_object_path (group), + g_object_ref (group)); + + return group; +} + static void on_new_user_in_accounts_service (GDBusProxy *proxy, const char *object_path, @@ -1086,6 +1315,22 @@ on_new_user_in_accounts_service (GDBusProxy *proxy, } static void +on_new_group_in_accounts_service (GDBusProxy *proxy, + const char *object_path, + gpointer user_data) +{ + ActUserManager *manager = ACT_USER_MANAGER (user_data); + + if (!manager->priv->is_loaded) { + g_debug ("ActUserManager: ignoring new group in accounts service with object path %s since not loaded yet", object_path); + return; + } + + g_debug ("ActUserManager: new group in accounts service with object path %s", object_path); + add_new_group_for_object_path (object_path, manager); +} + +static void on_user_removed_in_accounts_service (GDBusProxy *proxy, const char *object_path, gpointer user_data) @@ -1108,6 +1353,28 @@ on_user_removed_in_accounts_service (GDBusProxy *proxy, } static void +on_group_removed_in_accounts_service (GDBusProxy *proxy, + const char *object_path, + gpointer user_data) +{ + ActUserManager *manager = ACT_USER_MANAGER (user_data); + ActGroup *group; + + group = g_hash_table_lookup (manager->priv->groups_by_object_path, object_path); + + if (group == NULL) { + g_debug ("ActUserManager: ignoring untracked group %s", object_path); + return; + } else { + g_debug ("ActUserManager: tracked group %s removed from accounts service", object_path); + } + + manager->priv->new_groups = g_slist_remove (manager->priv->new_groups, group); + + remove_group (manager, group); +} + +static void on_get_current_session_finished (GObject *object, GAsyncResult *result, gpointer data) @@ -1374,6 +1641,37 @@ on_find_user_by_name_finished (GObject *object, } static void +on_find_group_by_name_finished (GObject *object, + GAsyncResult *result, + gpointer data) +{ + AccountsAccounts *proxy = ACCOUNTS_ACCOUNTS (object); + ActUserManagerFetchUserRequest *request = data; + GError *error = NULL; + char *group; + + if (!accounts_accounts_call_find_group_by_name_finish (proxy, &group, result, &error)) { + if (error != NULL) { + g_debug ("ActUserManager: Failed to find %s: %s", + request->description, error->message); + g_error_free (error); + } else { + g_debug ("ActUserManager: Failed to find %s", + request->description); + } + give_up (request->manager, request); + return; + } + + g_debug ("ActUserManager: Found object path of %s: %s", + request->description, group); + request->object_path = group; + request->state++; + + fetch_user_incrementally (request); +} + +static void on_find_user_by_id_finished (GObject *object, GAsyncResult *result, gpointer data) @@ -1405,6 +1703,37 @@ on_find_user_by_id_finished (GObject *object, } static void +on_find_group_by_id_finished (GObject *object, + GAsyncResult *result, + gpointer data) +{ + AccountsAccounts *proxy = ACCOUNTS_ACCOUNTS (object); + ActUserManagerFetchUserRequest *request = data; + GError *error = NULL; + char *group; + + if (!accounts_accounts_call_find_group_by_id_finish (proxy, &group, result, &error)) { + if (error != NULL) { + g_debug ("ActUserManager: Failed to find user %lu: %s", + (gulong) request->uid, error->message); + g_error_free (error); + } else { + g_debug ("ActUserManager: Failed to find user with id %lu", + (gulong) request->uid); + } + give_up (request->manager, request); + return; + } + + g_debug ("ActUserManager: Found object path of %s: %s", + request->description, group); + request->object_path = group; + request->state++; + + fetch_user_incrementally (request); +} + +static void find_user_in_accounts_service (ActUserManager *manager, ActUserManagerFetchUserRequest *request) { @@ -1428,7 +1757,20 @@ find_user_in_accounts_service (ActUserManager *manager, on_find_user_by_id_finished, request); break; - + case ACT_USER_MANAGER_FETCH_GROUP_FROM_GROUPNAME_REQUEST: + accounts_accounts_call_find_group_by_name (manager->priv->accounts_proxy, + request->groupname, + NULL, + on_find_group_by_name_finished, + request); + break; + case ACT_USER_MANAGER_FETCH_GROUP_FROM_ID_REQUEST: + accounts_accounts_call_find_group_by_id (manager->priv->accounts_proxy, + request->gid, + NULL, + on_find_group_by_id_finished, + request); + break; } } @@ -1511,6 +1853,50 @@ on_list_cached_users_finished (GObject *object, } static void +on_list_cached_groups_finished (GObject *object, + GAsyncResult *result, + gpointer data) +{ + AccountsAccounts *proxy = ACCOUNTS_ACCOUNTS (object); + ActUserManager *manager = data; + gchar **group_paths; + GError *error = NULL; + + manager->priv->listing_cached_groups = FALSE; + if (!accounts_accounts_call_list_cached_groups_finish (proxy, &group_paths, result, &error)) { + g_debug ("ActUserManager: ListCachedGroups failed: %s", error->message); + g_error_free (error); + + g_object_unref (manager->priv->accounts_proxy); + manager->priv->accounts_proxy = NULL; + + g_object_unref (manager); + return; + } + + if (g_strv_length (group_paths) > 0) { + int i; + + g_debug ("ActUserManager: ListCachedGroups finished, will set loaded property after list is fully loaded"); + for (i = 0; group_paths[i] != NULL; i++) { + ActGroup *group; + + group = add_new_group_for_object_path (group_paths[i], manager); + if (!manager->priv->is_loaded) { + manager->priv->new_groups_inhibiting_load = g_slist_prepend (manager->priv->new_groups_inhibiting_load, group); + } + } + } else { + g_debug ("ActUserManager: ListCachedGroups finished with empty list, maybe setting loaded property now"); + maybe_set_is_loaded (manager); + } + + g_strfreev (group_paths); + + g_object_unref (manager); +} + +static void on_get_x11_display_finished (GObject *object, GAsyncResult *result, gpointer data) @@ -2126,12 +2512,18 @@ free_fetch_user_request (ActUserManagerFetchUserRequest *request) manager = request->manager; - g_object_set_data (G_OBJECT (request->user), "fetch-user-request", NULL); + if (request->user) + g_object_set_data (G_OBJECT (request->user), "fetch-user-request", NULL); + if (request->group) + g_object_set_data (G_OBJECT (request->group), "fetch-user-request", NULL); manager->priv->fetch_user_requests = g_slist_remove (manager->priv->fetch_user_requests, request); if (request->type == ACT_USER_MANAGER_FETCH_USER_FROM_USERNAME_REQUEST) { g_free (request->username); } + if (request->type == ACT_USER_MANAGER_FETCH_GROUP_FROM_GROUPNAME_REQUEST) { + g_free (request->groupname); + } g_free (request->object_path); g_free (request->description); @@ -2151,6 +2543,8 @@ give_up (ActUserManager *manager, if (request->user) _act_user_update_as_nonexistent (request->user); + if (request->group) + _act_group_update_as_nonexistent (request->group); } static void @@ -2202,7 +2596,10 @@ fetch_user_incrementally (ActUserManagerFetchUserRequest *request) break; case ACT_USER_MANAGER_GET_USER_STATE_FETCHED: g_debug ("ActUserManager: %s fetched", request->description); - _act_user_update_from_object_path (request->user, request->object_path); + if (request->user) + _act_user_update_from_object_path (request->manager, request->user, request->object_path); + if (request->group) + _act_group_update_from_object_path (request->manager, request->group, request->object_path); break; case ACT_USER_MANAGER_GET_USER_STATE_UNFETCHED: g_debug ("ActUserManager: %s was not fetched", request->description); @@ -2242,6 +2639,28 @@ fetch_user_with_username_from_accounts_service (ActUserManager *manager, } static void +fetch_group_with_groupname_from_accounts_service (ActUserManager *manager, + ActGroup *group, + const char *groupname) +{ + ActUserManagerFetchUserRequest *request; + + request = g_slice_new0 (ActUserManagerFetchUserRequest); + + request->manager = g_object_ref (manager); + request->type = ACT_USER_MANAGER_FETCH_GROUP_FROM_GROUPNAME_REQUEST; + request->groupname = g_strdup (groupname); + request->group = group; + request->state = ACT_USER_MANAGER_GET_USER_STATE_UNFETCHED + 1; + request->description = g_strdup_printf ("group '%s'", request->groupname); + + manager->priv->fetch_user_requests = g_slist_prepend (manager->priv->fetch_user_requests, + request); + g_object_set_data (G_OBJECT (group), "fetch-user-request", request); + fetch_user_incrementally (request); +} + +static void fetch_user_with_id_from_accounts_service (ActUserManager *manager, ActUser *user, uid_t id) @@ -2263,6 +2682,28 @@ fetch_user_with_id_from_accounts_service (ActUserManager *manager, fetch_user_incrementally (request); } +static void +fetch_group_with_id_from_accounts_service (ActUserManager *manager, + ActGroup *group, + gid_t id) +{ + ActUserManagerFetchUserRequest *request; + + request = g_slice_new0 (ActUserManagerFetchUserRequest); + + request->manager = g_object_ref (manager); + request->type = ACT_USER_MANAGER_FETCH_GROUP_FROM_ID_REQUEST; + request->gid = id; + request->group = group; + request->state = ACT_USER_MANAGER_GET_USER_STATE_UNFETCHED + 1; + request->description = g_strdup_printf ("group with id %lu", (gulong) request->gid); + + manager->priv->fetch_user_requests = g_slist_prepend (manager->priv->fetch_user_requests, + request); + g_object_set_data (G_OBJECT (group), "fetch-user-request", request); + fetch_user_incrementally (request); +} + /** * act_user_manager_get_user: * @manager: the manager to query. @@ -2300,6 +2741,42 @@ act_user_manager_get_user (ActUserManager *manager, } /** + * act_user_manager_get_group: + * @manager: the manager to query. + * @groupname: the login name of the group to get. + * + * Retrieves a pointer to the #ActGroup object for the login @groupname + * from @manager. Trying to use this object before its + * #ActGroup:is-loaded property is %TRUE will result in undefined + * behavior. + * + * Returns: (transfer none): #ActGroup object + **/ +ActGroup * +act_user_manager_get_group (ActUserManager *manager, + const char *groupname) +{ + ActGroup *group; + + g_return_val_if_fail (ACT_IS_USER_MANAGER (manager), NULL); + g_return_val_if_fail (groupname != NULL && groupname[0] != '\0', NULL); + + group = lookup_group_by_name (manager, groupname); + + /* if we don't have it loaded try to load it now */ + if (group == NULL) { + g_debug ("ActUserManager: trying to track new group with groupname %s", groupname); + group = create_new_group (manager); + + if (manager->priv->accounts_proxy != NULL) { + fetch_group_with_groupname_from_accounts_service (manager, group, groupname); + } + } + + return group; +} + +/** * act_user_manager_get_user_by_id: * @manager: the manager to query. * @id: the uid of the user to get. @@ -2338,6 +2815,45 @@ act_user_manager_get_user_by_id (ActUserManager *manager, return user; } +/** + * act_user_manager_get_group_by_id: + * @manager: the manager to query. + * @id: the uid of the group to get. + * + * Retrieves a pointer to the #ActGroup object for the group with the + * given uid from @manager. Trying to use this object before its + * #ActGroup:is-loaded property is %TRUE will result in undefined + * behavior. + * + * Returns: (transfer none): #ActGroup object + */ +ActGroup * +act_user_manager_get_group_by_id (ActUserManager *manager, + gid_t id) +{ + ActGroup *group; + gchar *object_path; + + g_return_val_if_fail (ACT_IS_USER_MANAGER (manager), NULL); + + object_path = g_strdup_printf ("/org/freedesktop/Accounts/Group%lu", (gulong) id); + group = g_hash_table_lookup (manager->priv->groups_by_object_path, object_path); + g_free (object_path); + + if (group != NULL) { + return g_object_ref (group); + } else { + g_debug ("ActUserManager: trying to track new group with uid %lu", (gulong) id); + group = create_new_group (manager); + + if (manager->priv->accounts_proxy != NULL) { + fetch_group_with_id_from_accounts_service (manager, group, id); + } + } + + return group; +} + static void listify_hash_values_hfunc (gpointer key, gpointer value, @@ -2392,6 +2908,16 @@ maybe_set_is_loaded (ActUserManager *manager) return; } + if (manager->priv->listing_cached_groups) { + g_debug ("ActUserManager: Listing cached groups, so not setting loaded property"); + return; + } + + if (manager->priv->new_groups_inhibiting_load != NULL) { + g_debug ("ActUserManager: Loading new groups, so not setting loaded property"); + return; + } + /* Don't set is_loaded yet unless the seat is already loaded * or failed to load. */ @@ -2502,6 +3028,19 @@ load_users (ActUserManager *manager) manager->priv->listing_cached_users = TRUE; } +static void +load_groups (ActUserManager *manager) +{ + g_assert (manager->priv->accounts_proxy != NULL); + g_debug ("ActUserManager: calling 'ListCachedGroups'"); + + accounts_accounts_call_list_cached_groups (manager->priv->accounts_proxy, + NULL, + on_list_cached_groups_finished, + g_object_ref (manager)); + manager->priv->listing_cached_groups = TRUE; +} + static gboolean load_seat_incrementally (ActUserManager *manager) { @@ -2541,9 +3080,10 @@ load_idle (ActUserManager *manager) { /* The order below is important: load_seat_incrementally might set "is-loaded" immediately and we thus need to call - load_users before it. + load_users and load_groups before it. */ load_users (manager); + load_groups (manager); manager->priv->seat.state = ACT_USER_MANAGER_SEAT_STATE_UNLOADED + 1; load_seat_incrementally (manager); manager->priv->load_id = 0; @@ -2725,6 +3265,46 @@ act_user_manager_class_init (ActUserManagerClass *klass) g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ACT_TYPE_USER); + /** + * ActUserManager::group-added: + * + * Emitted when a group is added to the user manager. + */ + signals [GROUP_ADDED] = + g_signal_new ("group-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ActUserManagerClass, group_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, ACT_TYPE_GROUP); + /** + * ActUserManager::group-removed: + * + * Emitted when a group is removed from the user manager. + */ + signals [GROUP_REMOVED] = + g_signal_new ("group-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ActUserManagerClass, group_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, ACT_TYPE_GROUP); + /** + * ActUserManager::group-changed: + * + * One of the groups has changed + */ + signals [GROUP_CHANGED] = + g_signal_new ("group-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ActUserManagerClass, group_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, ACT_TYPE_GROUP); + g_type_class_add_private (klass, sizeof (ActUserManagerPrivate)); } @@ -2774,6 +3354,14 @@ act_user_manager_init (ActUserManager *manager) g_str_equal, NULL, g_object_unref); + manager->priv->groups_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + manager->priv->groups_by_object_path = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); error = NULL; manager->priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); @@ -2813,6 +3401,15 @@ act_user_manager_init (ActUserManager *manager) G_CALLBACK (on_user_removed_in_accounts_service), manager); + g_signal_connect (manager->priv->accounts_proxy, + "group-added", + G_CALLBACK (on_new_group_in_accounts_service), + manager); + g_signal_connect (manager->priv->accounts_proxy, + "group-deleted", + G_CALLBACK (on_group_removed_in_accounts_service), + manager); + manager->priv->ck_manager_proxy = console_kit_manager_proxy_new_sync (manager->priv->connection, G_DBUS_PROXY_FLAGS_NONE, CK_NAME, @@ -2921,6 +3518,8 @@ act_user_manager_finalize (GObject *object) g_hash_table_destroy (manager->priv->normal_users_by_name); g_hash_table_destroy (manager->priv->system_users_by_name); g_hash_table_destroy (manager->priv->users_by_object_path); + g_hash_table_destroy (manager->priv->groups_by_name); + g_hash_table_destroy (manager->priv->groups_by_object_path); G_OBJECT_CLASS (act_user_manager_parent_class)->finalize (object); } @@ -3412,3 +4011,268 @@ act_user_manager_delete_user_finish (ActUserManager *manager, return success; } + +/** + * act_user_manager_create_group: + * @manager: a #ActUserManager + * @groupname: a unix group name + * @error: a #GError + * + * Creates a group on the system. + * + * Returns: (transfer full): group object + */ +ActGroup * +act_user_manager_create_group (ActUserManager *manager, + const char *groupname, + GError **error) +{ + GError *local_error = NULL; + gboolean res; + gchar *path; + ActGroup *group; + + g_debug ("ActUserManager: Creating group '%s'", + groupname); + + g_assert (manager->priv->accounts_proxy != NULL); + + local_error = NULL; + res = accounts_accounts_call_create_group_sync (manager->priv->accounts_proxy, + groupname, + &path, + NULL, + &local_error); + if (! res) { + g_propagate_error (error, local_error); + return NULL; + } + + group = add_new_group_for_object_path (path, manager); + + g_free (path); + + return group; +} + +/** + * act_user_manager_create_group_async: + * @manager: a #ActUserManager + * @groupname: a unix group name + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: (closure): the data to pass to @callback + * + * Asynchronously creates a group on the system. + * + * For more details, see act_user_manager_create_group(), which + * is the synchronous version of this call. + */ +void +act_user_manager_create_group_async (ActUserManager *manager, + const char *groupname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + g_return_if_fail (ACT_IS_USER_MANAGER (manager)); + g_return_if_fail (manager->priv->accounts_proxy != NULL); + + g_debug ("ActUserManager: Creating group (async) '%s'", + groupname); + + g_assert (manager->priv->accounts_proxy != NULL); + + res = g_simple_async_result_new (G_OBJECT (manager), + callback, user_data, + act_user_manager_create_group_async); + g_simple_async_result_set_check_cancellable (res, cancellable); + + accounts_accounts_call_create_group (manager->priv->accounts_proxy, + groupname, + cancellable, + act_user_manager_async_complete_handler, res); +} + +/** + * act_user_manager_create_group_finish: + * @manager: a #ActUserManager + * @result: a #GAsyncResult + * @error: a #GError + * + * Finishes an asynchronous user creation. + * + * See act_user_manager_create_user_async(). + * + * Returns: (transfer full): group object + * + * Since: 0.6.27 + */ +ActGroup * +act_user_manager_create_group_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error) +{ + GAsyncResult *inner_result; + ActGroup *group = NULL; + gchar *path; + GSimpleAsyncResult *res; + GError *remote_error = NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (manager), act_user_manager_create_group_async), FALSE); + + res = G_SIMPLE_ASYNC_RESULT (result); + inner_result = g_simple_async_result_get_op_res_gpointer (res); + g_assert (inner_result); + + if (accounts_accounts_call_create_group_finish (manager->priv->accounts_proxy, + &path, inner_result, &remote_error)) { + group = add_new_group_for_object_path (path, manager); + g_free (path); + } + + if (remote_error) { + g_dbus_error_strip_remote_error (remote_error); + g_propagate_error (error, remote_error); + } + + return group; +} + +/** + * act_user_manager_delete_group: + * @manager: a #ActUserManager + * @group: an #ActGroup object + * @error: a #GError + * + * Deletes a group account on the system. + * + * Returns: %TRUE if the group account was successfully deleted + */ +gboolean +act_user_manager_delete_group (ActUserManager *manager, + ActGroup *group, + GError **error) +{ + GError *local_error; + gboolean res = TRUE; + + g_return_val_if_fail (ACT_IS_USER_MANAGER (manager), FALSE); + g_return_val_if_fail (ACT_IS_GROUP (group), FALSE); + g_return_val_if_fail (manager->priv->accounts_proxy != NULL, FALSE); + + g_debug ("ActUserManager: Deleting group '%s' (gid %ld)", + act_group_get_group_name (group), (long) act_group_get_gid (group)); + + local_error = NULL; + if (!accounts_accounts_call_delete_group_sync (manager->priv->accounts_proxy, + act_group_get_gid (group), + NULL, + &local_error)) { + g_propagate_error (error, local_error); + res = FALSE; + } + + return res; +} + +/** + * act_user_manager_delete_group_async: + * @manager: a #ActUserManager + * @group: a #ActGroup object + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: (closure): the data to pass to @callback + * + * Asynchronously deletes a group account from the system. + * + * For more details, see act_user_manager_delete_group(), which + * is the synchronous version of this call. + * + * Since: 0.6.27 + */ +void +act_user_manager_delete_group_async (ActUserManager *manager, + ActGroup *group, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + g_return_if_fail (ACT_IS_USER_MANAGER (manager)); + g_return_if_fail (ACT_IS_GROUP (group)); + g_return_if_fail (manager->priv->accounts_proxy != NULL); + + res = g_simple_async_result_new (G_OBJECT (manager), + callback, user_data, + act_user_manager_delete_group_async); + g_simple_async_result_set_check_cancellable (res, cancellable); + + g_debug ("ActUserManager: Deleting (async) group '%s' (gid %ld)", + act_group_get_group_name (group), (long) act_group_get_gid (group)); + + accounts_accounts_call_delete_group (manager->priv->accounts_proxy, + act_group_get_gid (group), + cancellable, + act_user_manager_async_complete_handler, res); +} + +/** + * act_user_manager_delete_group_finish: + * @manager: a #ActUserManager + * @result: a #GAsyncResult + * @error: a #GError + * + * Finishes an asynchronous group account deletion. + * + * See act_user_manager_delete_group_async(). + * + * Returns: %TRUE if the group account was successfully deleted + * + * Since: 0.6.27 + */ +gboolean +act_user_manager_delete_group_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error) +{ + GAsyncResult *inner_result; + gboolean success; + GSimpleAsyncResult *res; + GError *remote_error = NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (manager), act_user_manager_delete_group_async), FALSE); + res = G_SIMPLE_ASYNC_RESULT (result); + inner_result = g_simple_async_result_get_op_res_gpointer (res); + g_assert (inner_result); + + success = accounts_accounts_call_delete_group_finish (manager->priv->accounts_proxy, + inner_result, &remote_error); + if (remote_error) { + g_dbus_error_strip_remote_error (remote_error); + g_propagate_error (error, remote_error); + } + + return success; +} + +ActUser * +_act_user_manager_get_user (ActUserManager *manager, + const gchar *object_path) +{ + return add_new_user_for_object_path (object_path, manager); +} + +ActGroup * +_act_user_manager_get_group (ActUserManager *manager, + const gchar *object_path) +{ + return add_new_group_for_object_path (object_path, manager); +} diff --git a/src/libaccountsservice/act-user-manager.h b/src/libaccountsservice/act-user-manager.h index 3d91f83..856e313 100644 --- a/src/libaccountsservice/act-user-manager.h +++ b/src/libaccountsservice/act-user-manager.h @@ -24,7 +24,9 @@ #include #include +#include "act-types.h" #include "act-user.h" +#include "act-group.h" G_BEGIN_DECLS @@ -58,7 +60,13 @@ struct _ActUserManagerClass void (* user_is_logged_in_changed) (ActUserManager *user_manager, ActUser *user); void (* user_changed) (ActUserManager *user_manager, - ActUser *user); + ActGroup *group); + void (* group_added) (ActUserManager *user_manager, + ActGroup *group); + void (* group_removed) (ActUserManager *user_manager, + ActGroup *group); + void (* group_changed) (ActUserManager *user_manager, + ActGroup *group); }; typedef enum ActUserManagerError @@ -67,7 +75,9 @@ typedef enum ActUserManagerError ACT_USER_MANAGER_ERROR_USER_EXISTS, ACT_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST, ACT_USER_MANAGER_ERROR_PERMISSION_DENIED, - ACT_USER_MANAGER_ERROR_NOT_SUPPORTED + ACT_USER_MANAGER_ERROR_NOT_SUPPORTED, + ACT_USER_MANAGER_ERROR_GROUP_EXISTS, + ACT_USER_MANAGER_ERROR_GROUP_DOES_NOT_EXIST } ActUserManagerError; #define ACT_USER_MANAGER_ERROR act_user_manager_error_quark () @@ -84,6 +94,12 @@ ActUser * act_user_manager_get_user (ActUserManager *mana ActUser * act_user_manager_get_user_by_id (ActUserManager *manager, uid_t id); +GSList * act_user_manager_list_groups (ActUserManager *manager); +ActGroup * act_user_manager_get_group (ActUserManager *manager, + const char *groupname); +ActGroup * act_user_manager_get_group_by_id (ActUserManager *manager, + gid_t id); + gboolean act_user_manager_activate_user_session (ActUserManager *manager, ActUser *user); @@ -137,6 +153,30 @@ gboolean act_user_manager_delete_user_finish (ActUserManager * GAsyncResult *result, GError **error); +ActGroup* act_user_manager_create_group (ActUserManager *manager, + const char *groupname, + GError **error); +void act_user_manager_create_group_async (ActUserManager *manager, + const gchar *groupname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +ActGroup* act_user_manager_create_group_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error); + +gboolean act_user_manager_delete_group (ActUserManager *manager, + ActGroup *group, + GError **error); +void act_user_manager_delete_group_async (ActUserManager *manager, + ActGroup *group, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean act_user_manager_delete_group_finish (ActUserManager *manager, + GAsyncResult *result, + GError **error); + G_END_DECLS #endif /* __ACT_USER_MANAGER_H__ */ diff --git a/src/libaccountsservice/act-user-private.h b/src/libaccountsservice/act-user-private.h index 582ccfb..0ec9cef 100644 --- a/src/libaccountsservice/act-user-private.h +++ b/src/libaccountsservice/act-user-private.h @@ -26,12 +26,14 @@ #include +#include "act-user-manager.h" #include "act-user.h" G_BEGIN_DECLS -void _act_user_update_from_object_path (ActUser *user, - const char *object_path); +void _act_user_update_from_object_path (ActUserManager *manager, + ActUser *user, + const char *object_path); void _act_user_update_as_nonexistent (ActUser *user); void _act_user_update_login_frequency (ActUser *user, int login_frequency); diff --git a/src/libaccountsservice/act-user.c b/src/libaccountsservice/act-user.c index 9de689e..7ec8eec 100644 --- a/src/libaccountsservice/act-user.c +++ b/src/libaccountsservice/act-user.c @@ -31,6 +31,7 @@ #include #include "act-user-private.h" +#include "act-user-manager-private.h" #include "accounts-user-generated.h" /** @@ -106,6 +107,7 @@ enum { struct _ActUser { GObject parent; + ActUserManager *manager; GDBusConnection *connection; AccountsUser *accounts_proxy; GDBusProxy *object_proxy; @@ -132,6 +134,8 @@ struct _ActUser { ActUserAccountType account_type; ActUserPasswordMode password_mode; + ActGroup **cached_groups; + guint uid_set : 1; guint is_loaded : 1; @@ -565,6 +569,17 @@ act_user_init (ActUser *user) } static void +free_group_array (ActGroup **a) +{ + int i; + if (a) { + for (i = 0; a[i]; i++) + g_object_unref (a[i]); + g_free (a); + } +} + +static void act_user_finalize (GObject *object) { ActUser *user; @@ -600,6 +615,8 @@ act_user_finalize (GObject *object) g_object_unref (user->connection); } + free_group_array (user->cached_groups); + if (G_OBJECT_CLASS (act_user_parent_class)->finalize) (*G_OBJECT_CLASS (act_user_parent_class)->finalize) (object); } @@ -837,6 +854,21 @@ act_user_get_login_history (ActUser *user) { } /** + * act_user_get_cached_groups: + * @user: a #ActUser + * + * Returns the cached groups for @user. + * + * Returns: (transfer none): ... + */ +ActGroup ** +act_user_get_cached_groups (ActUser *user) { + g_return_val_if_fail (ACT_IS_USER (user), NULL); + + return user->cached_groups; +} + +/** * act_user_collate: * @user1: a user * @user2: a user @@ -1085,6 +1117,12 @@ act_user_get_object_path (ActUser *user) return user->object_path; } +ActUserManager * +act_user_get_manager (ActUser *user) +{ + return user->manager; +} + /** * act_user_get_primary_session_id: * @user: a #ActUser @@ -1288,6 +1326,41 @@ collect_props (const gchar *key, user->x_session = g_strdup (new_x_session); g_object_notify (G_OBJECT (user), "x-session"); } + } else if (strcmp (key, "CachedGroups") == 0) { + gboolean changed; + GVariantIter iter; + int i; + const gchar *group_path; + + if (user->cached_groups == NULL) + changed = TRUE; + else { + changed = FALSE; + i = 0; + g_variant_iter_init (&iter, value); + while (user->cached_groups[i] && g_variant_iter_next (&iter, "&o", &group_path)) { + if (g_strcmp0 (act_group_get_object_path (user->cached_groups[i]), group_path) != 0) { + changed = TRUE; + break; + } + i++; + } + if (user->cached_groups[i] != NULL || i != g_variant_n_children (value)) + changed = TRUE; + } + + if (changed) { + free_group_array (user->cached_groups); + user->cached_groups = g_new0 (ActGroup*, g_variant_n_children (value)+1); + i = 0; + g_variant_iter_init (&iter, value); + while (g_variant_iter_next (&iter, "&o", &group_path)) { + user->cached_groups[i] = g_object_ref(_act_user_manager_get_group (user->manager, + group_path)); + i++; + } + // g_object_notify (G_OBJECT (user), "cached-groups"); + } } else { handled = FALSE; } @@ -1405,15 +1478,18 @@ _act_user_update_as_nonexistent (ActUser *user) * the object path in @object_path. **/ void -_act_user_update_from_object_path (ActUser *user, - const char *object_path) +_act_user_update_from_object_path (ActUserManager *manager, + ActUser *user, + const char *object_path) { GError *error = NULL; + g_return_if_fail (ACT_IS_USER_MANAGER (manager)); g_return_if_fail (ACT_IS_USER (user)); g_return_if_fail (object_path != NULL); g_return_if_fail (user->object_path == NULL); + user->manager = manager; user->object_path = g_strdup (object_path); user->accounts_proxy = accounts_user_proxy_new_sync (user->connection, @@ -1965,3 +2041,152 @@ act_user_set_automatic_login (ActUser *user, g_error_free (error); } } + +static ActGroup ** +convert_group_paths (ActUserManager *manager, + gchar **paths) +{ + int i, n; + ActGroup **res; + + if (paths == NULL) + return NULL; + + n = g_strv_length (paths); + res = g_new0 (ActGroup*, n+1); + for (i = 0; i < n; i++) + res[i] = g_object_ref (_act_user_manager_get_group (manager, paths[i])); + + return res; +} + +/** + * act_user_find_groups: + * @user: an #ActUser object + * @indirect: %TRUE to also list groups with indirect membership + * @error: a #GError + * + * Lists the groups of a user. This operation might query remote + * databases and thus be slow. + * + * Returns: (transfer full) A %NULL-terminated array of ActGroup + * objects. Free it by unreffing all ActGroup objects and the calling + * g_free on the array. Returns %NULL in case of error. + */ +ActGroup ** +act_user_find_groups (ActUser *user, + gboolean indirect, + GError **error) +{ + GError *local_error; + gchar **res = NULL; + + g_return_val_if_fail (ACT_IS_USER (user), FALSE); + g_return_val_if_fail (user->accounts_proxy != NULL, FALSE); + + g_debug ("ActUser: finding groups of '%s' (uid %ld)", + act_user_get_user_name (user), (long) act_user_get_uid (user)); + + local_error = NULL; + if (!accounts_user_call_find_groups_sync (user->accounts_proxy, + indirect, + &res, + NULL, + &local_error)) { + g_propagate_error (error, local_error); + } + + return convert_group_paths (user->manager, res); +} + +static void +act_user_async_complete_handler (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *res = user_data; + + g_simple_async_result_set_op_res_gpointer (res, g_object_ref (result), g_object_unref); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +/** + * act_user_find_groups_async: + * @user: an #ActUser object + * @indirect: %TRUE to also list groups with indirect membership + * @cancellable: (allow-none): optional #GCancellable object, + * %NULL to ignore + * @callback: (scope async): a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: (closure): the data to pass to @callback + * + * Asynchronously finds groups. + * + * For more details, see act_user_find_groups(), which is the + * synchronous version of this call. + */ +void +act_user_find_groups_async (ActUser *user, + gboolean indirect, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + + g_return_if_fail (ACT_IS_USER (user)); + g_return_if_fail (user->accounts_proxy != NULL); + + res = g_simple_async_result_new (G_OBJECT (user), + callback, user_data, + act_user_find_groups_async); + g_simple_async_result_set_check_cancellable (res, cancellable); + + g_debug ("ActUser: Finding groups (async) of '%s' (uid %ld)", + act_user_get_user_name (user), (long) act_user_get_uid (user)); + + accounts_user_call_find_groups (user->accounts_proxy, + indirect, + cancellable, + act_user_async_complete_handler, res); +} + +/** + * act_user_find_groups_finish: + * @user: a #ActUser + * @result: a #GAsyncResult + * @error: a #GError + * + * Finishes an asynchronous group finding. + * + * See act_user_find_groups_async(). + * + * Returns: (transfer full) A %NULL-terminated array of ActGroup + * objects. Free it by unreffing all ActGroup objects and the calling + * g_free on the array. Returns %NULL in case of error. + */ +ActGroup ** +act_user_find_groups_finish (ActUser *user, + GAsyncResult *result, + GError **error) +{ + GAsyncResult *inner_result; + gchar **group_paths = NULL; + GSimpleAsyncResult *res; + GError *remote_error = NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (user), act_user_find_groups_async), FALSE); + res = G_SIMPLE_ASYNC_RESULT (result); + inner_result = g_simple_async_result_get_op_res_gpointer (res); + g_assert (inner_result); + + if (!accounts_user_call_find_groups_finish (user->accounts_proxy, + &group_paths, + inner_result, &remote_error)) { + g_dbus_error_strip_remote_error (remote_error); + g_propagate_error (error, remote_error); + } + + return convert_group_paths (user->manager, group_paths); +} diff --git a/src/libaccountsservice/act-user.h b/src/libaccountsservice/act-user.h index d188db4..8234fdf 100644 --- a/src/libaccountsservice/act-user.h +++ b/src/libaccountsservice/act-user.h @@ -28,6 +28,9 @@ #include #include #include +#include + +#include "act-types.h" G_BEGIN_DECLS @@ -46,12 +49,12 @@ typedef enum { ACT_USER_PASSWORD_MODE_NONE, } ActUserPasswordMode; -typedef struct _ActUser ActUser; typedef struct _ActUserClass ActUserClass; GType act_user_get_type (void) G_GNUC_CONST; const char *act_user_get_object_path (ActUser *user); +ActUserManager *act_user_get_manager (ActUser *user); uid_t act_user_get_uid (ActUser *user); const char *act_user_get_user_name (ActUser *user); @@ -79,6 +82,7 @@ const char *act_user_get_icon_file (ActUser *user); const char *act_user_get_language (ActUser *user); const char *act_user_get_x_session (ActUser *user); const char *act_user_get_primary_session_id (ActUser *user); +ActGroup **act_user_get_cached_groups (ActUser *user); gint act_user_collate (ActUser *user1, ActUser *user2); @@ -110,6 +114,18 @@ void act_user_set_locked (ActUser *user, void act_user_set_automatic_login (ActUser *user, gboolean enabled); +ActGroup** act_user_find_groups (ActUser *user, + gboolean indirect, + GError **error); +void act_user_find_groups_async (ActUser *user, + gboolean indirect, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +ActGroup** act_user_find_groups_finish (ActUser *user, + GAsyncResult *result, + GError **error); + G_END_DECLS #endif /* __ACT_USER_H__ */ diff --git a/src/libaccountsservice/act.h b/src/libaccountsservice/act.h index ccb9b9e..62c8385 100644 --- a/src/libaccountsservice/act.h +++ b/src/libaccountsservice/act.h @@ -23,6 +23,7 @@ #include #include +#include #include #endif /* __ACT_H__ */ -- 1.8.3.1