summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRay Strode <rstrode@redhat.com>2021-08-04 19:54:59 -0400
committerRay Strode <halfline@gmail.com>2021-09-06 12:25:56 +0000
commit607f1c7e61751ebea354196a3f694cf3a5485f81 (patch)
tree881c1abdfcca066eda7c8a475b01b600a6ab7111
parent1bcf98d173590e8e1472134db99a8a036769b7d8 (diff)
downloadaccountsservice-user-templates.tar.gz
user: Introduce user templates for setting default session etcuser-templates
At the moment there's no easy way to set a default session, or face icon or whatever for all users. If a user has never logged in before, we just generate their cache file from hardcoded defaults. This commit introduces a template system to make it possible for admins to set up defaults on their own. Admins can write either /etc/accountsservice/user-templates/administrator or /etc/accountsservice/user-templates/standard files. These files follow the same format as /var/lib/AccountsService/users/username files, but will support substituting $HOME and $USER to the appropriate user specific values. User templates also support an additional group [Template] that have an additional key EnvironmentFiles that specify a list of environment files to load (files with KEY=VALUE pairs in them). Any keys listed in those environment files will also get substituted. https://gitlab.freedesktop.org/accountsservice/accountsservice/-/issues/63
-rw-r--r--data/administrator6
-rw-r--r--data/meson.build10
-rw-r--r--data/standard6
-rw-r--r--src/daemon.c8
-rw-r--r--src/meson.build1
-rw-r--r--src/user.c284
-rw-r--r--src/user.h3
7 files changed, 305 insertions, 13 deletions
diff --git a/data/administrator b/data/administrator
new file mode 100644
index 0000000..ea043c9
--- /dev/null
+++ b/data/administrator
@@ -0,0 +1,6 @@
+[Template]
+#EnvironmentFiles=/etc/os-release;
+
+[User]
+Session=
+Icon=${HOME}/.face
diff --git a/data/meson.build b/data/meson.build
index 79f7651..70edf89 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -52,3 +52,13 @@ if install_systemd_unit_dir
install_dir: systemd_system_unit_dir,
)
endif
+
+install_data(
+ 'administrator',
+ install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'),
+)
+
+install_data(
+ 'standard',
+ install_dir: join_paths(act_datadir, 'accountsservice', 'user-templates'),
+)
diff --git a/data/standard b/data/standard
new file mode 100644
index 0000000..ea043c9
--- /dev/null
+++ b/data/standard
@@ -0,0 +1,6 @@
+[Template]
+#EnvironmentFiles=/etc/os-release;
+
+[User]
+Session=
+Icon=${HOME}/.face
diff --git a/src/daemon.c b/src/daemon.c
index cddc501..122b652 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -326,15 +326,9 @@ entry_generator_cachedir (Daemon *daemon,
/* Update all the users from the files in the cache dir */
g_hash_table_iter_init (&iter, users);
while (g_hash_table_iter_next (&iter, &key, &value)) {
- const gchar *name = key;
User *user = value;
- g_autofree gchar *filename = NULL;
- g_autoptr(GKeyFile) key_file = NULL;
- filename = g_build_filename (USERDIR, name, NULL);
- key_file = g_key_file_new ();
- if (g_key_file_load_from_file (key_file, filename, 0, NULL))
- user_update_from_keyfile (user, key_file);
+ user_update_from_cache (user);
}
*state = NULL;
diff --git a/src/meson.build b/src/meson.build
index 7db1d46..95ecc24 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -27,6 +27,7 @@ deps = [
cflags = [
'-DLOCALSTATEDIR="@0@"'.format(act_localstatedir),
'-DDATADIR="@0@"'.format(act_datadir),
+ '-DSYSCONFDIR="@0@"'.format(act_sysconfdir),
'-DICONDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'icons')),
'-DUSERDIR="@0@"'.format(join_paths(act_localstatedir, 'lib', 'AccountsService', 'users')),
]
diff --git a/src/user.c b/src/user.c
index 590d76f..431aac5 100644
--- a/src/user.c
+++ b/src/user.c
@@ -71,6 +71,7 @@ struct User {
gchar *gecos;
gboolean account_expiration_policy_known;
gboolean cached;
+ gboolean template_loaded;
guint *extension_ids;
guint n_extension_ids;
@@ -84,6 +85,7 @@ typedef struct UserClass
} UserClass;
static void user_accounts_user_iface_init (AccountsUserIface *iface);
+static void user_update_from_keyfile (User *user, GKeyFile *keyfile);
G_DEFINE_TYPE_WITH_CODE (User, user, ACCOUNTS_TYPE_USER_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_USER, user_accounts_user_iface_init));
@@ -138,6 +140,261 @@ user_reset_icon_file (User *user)
}
}
+static gboolean
+user_has_cache_file (User *user)
+{
+ g_autofree char *filename = NULL;
+
+ filename = g_build_filename (USERDIR, user_get_user_name (user), NULL);
+
+ return g_file_test (filename, G_FILE_TEST_EXISTS);
+}
+
+static gboolean
+is_valid_shell_identifier_character (char c,
+ gboolean first)
+{
+ return (!first && g_ascii_isdigit (c)) ||
+ c == '_' ||
+ g_ascii_isalpha (c);
+}
+
+static char *
+expand_template_variables (User *user,
+ GHashTable *template_variables,
+ const char *str)
+{
+ GString *s = g_string_new ("");
+ const char *p, *start;
+ char c;
+
+ p = str;
+ while (*p) {
+ c = *p;
+ if (c == '\\') {
+ p++;
+ c = *p;
+ if (c != '\0') {
+ p++;
+ switch (c) {
+ case '\\':
+ g_string_append_c (s, '\\');
+ break;
+ case '$':
+ g_string_append_c (s, '$');
+ break;
+ default:
+ g_string_append_c (s, '\\');
+ g_string_append_c (s, c);
+ break;
+ }
+ }
+ } else if (c == '$') {
+ gboolean brackets = FALSE;
+ p++;
+ if (*p == '{') {
+ brackets = TRUE;
+ p++;
+ }
+ start = p;
+ while (*p != '\0' &&
+ is_valid_shell_identifier_character (*p, p == start))
+ p++;
+ if (p == start || (brackets && *p != '}')) {
+ g_string_append_c (s, '$');
+ if (brackets)
+ g_string_append_c (s, '{');
+ g_string_append_len (s, start, p - start);
+ } else {
+ g_autofree char *variable = NULL;
+ const char *value;
+
+ if (brackets && *p == '}')
+ p++;
+
+ variable = g_strndup (start, p - start - 1);
+
+ value = g_hash_table_lookup (template_variables, variable);
+ if (value) {
+ g_string_append (s, value);
+ }
+ }
+ } else {
+ p++;
+ g_string_append_c (s, c);
+ }
+ }
+ return g_string_free (s, FALSE);
+}
+
+static void
+load_template_environment_file (User *user,
+ GHashTable *variables,
+ const char *file)
+{
+ g_autofree char *contents = NULL;
+ g_auto (GStrv) lines = NULL;
+ g_autoptr (GError) error = NULL;
+ gboolean file_loaded;
+ size_t i;
+
+ file_loaded = g_file_get_contents (file, &contents, NULL, &error);
+
+ if (!file_loaded) {
+ g_debug ("Couldn't load template environment file %s: %s",
+ file, error->message);
+ return;
+ }
+
+ lines = g_strsplit (contents, "\n", -1);
+
+ for (i = 0; lines[i] != NULL; i++) {
+ char *p;
+ char *variable_end;
+ const char *variable;
+ const char *value;
+
+ p = lines[i];
+ while (g_ascii_isspace (*p))
+ p++;
+ if (*p == '#' || *p == '\0')
+ continue;
+ variable = p;
+ while (is_valid_shell_identifier_character (*p, p == variable))
+ p++;
+ variable_end = p;
+ while (g_ascii_isspace (*p))
+ p++;
+ if (variable_end == variable || *p != '=') {
+ g_debug ("template environment file %s has invalid line '%s'\n", file, lines[i]);
+ continue;
+ }
+ *variable_end = '\0';
+ p++;
+ while (g_ascii_isspace (*p))
+ p++;
+ value = p;
+
+ if (g_hash_table_lookup (variables, variable) == NULL) {
+ g_hash_table_insert (variables,
+ g_strdup (variable),
+ g_strdup (value));
+ }
+
+ }
+}
+
+static void
+initialize_template_environment (User *user,
+ GHashTable *variables,
+ const char * const *files)
+{
+ size_t i;
+
+ g_hash_table_insert (variables, g_strdup ("HOME"), g_strdup (accounts_user_get_home_directory (ACCOUNTS_USER (user))));
+ g_hash_table_insert (variables, g_strdup ("USER"), g_strdup (user_get_user_name (user)));
+
+ if (files == NULL)
+ return;
+
+ for (i = 0; files[i] != NULL; i++) {
+ load_template_environment_file (user, variables, files[i]);
+ }
+}
+
+static void
+user_update_from_template (User *user)
+{
+ g_autofree char *filename = NULL;
+ g_autoptr (GKeyFile) key_file = NULL;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GHashTable) template_variables = NULL;
+ g_auto (GStrv) template_environment_files = NULL;
+ gboolean key_file_loaded = FALSE;
+ const char * const *system_dirs[] = {
+ (const char *[]) { "/run", SYSCONFDIR, NULL },
+ g_get_system_data_dirs (),
+ NULL
+ };
+ g_autoptr (GPtrArray) dirs = NULL;
+ AccountType account_type;
+ const char *account_type_string;
+ size_t i, j;
+ g_autofree char *contents = NULL;
+ g_autofree char *expanded = NULL;
+ g_auto (GStrv) lines = NULL;
+
+ if (user->template_loaded)
+ return;
+
+ filename = g_build_filename (USERDIR,
+ accounts_user_get_user_name (ACCOUNTS_USER (user)),
+ NULL);
+
+ account_type = accounts_user_get_account_type (ACCOUNTS_USER (user));
+ if (account_type == ACCOUNT_TYPE_ADMINISTRATOR)
+ account_type_string = "administrator";
+ else
+ account_type_string = "standard";
+
+ dirs = g_ptr_array_new ();
+ for (i = 0; system_dirs[i] != NULL; i++) {
+ for (j = 0; system_dirs[i][j] != NULL; j++) {
+ char *dir;
+
+ dir = g_build_filename (system_dirs[i][j],
+ "accountsservice",
+ "user-templates",
+ NULL);
+ g_ptr_array_add (dirs, dir);
+ }
+ }
+ g_ptr_array_add (dirs, NULL);
+
+ key_file = g_key_file_new ();
+ key_file_loaded = g_key_file_load_from_dirs (key_file,
+ account_type_string,
+ (const char **) dirs->pdata,
+ NULL,
+ G_KEY_FILE_KEEP_COMMENTS,
+ &error);
+
+ if (!key_file_loaded) {
+ g_debug ("failed to load user template: %s", error->message);
+ return;
+ }
+
+ template_variables = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ template_environment_files = g_key_file_get_string_list (key_file,
+ "Template",
+ "EnvironmentFiles",
+ NULL,
+ NULL);
+
+ initialize_template_environment (user, template_variables, (const char * const *) template_environment_files);
+
+ g_key_file_remove_group (key_file, "Template", NULL);
+ contents = g_key_file_to_data (key_file, NULL, NULL);
+ lines = g_strsplit (contents, "\n", -1);
+
+ expanded = expand_template_variables (user, template_variables, contents);
+
+ key_file_loaded = g_key_file_load_from_data (key_file,
+ expanded,
+ strlen (expanded),
+ G_KEY_FILE_KEEP_COMMENTS,
+ &error);
+
+ if (key_file_loaded)
+ user_update_from_keyfile (user, key_file);
+
+ user->template_loaded = key_file_loaded;
+}
+
void
user_update_from_pwent (User *user,
struct passwd *pwent,
@@ -242,17 +499,17 @@ user_update_from_pwent (User *user,
passwd);
accounts_user_set_system_account (ACCOUNTS_USER (user), is_system_account);
+ if (!user_has_cache_file (user))
+ user_update_from_template (user);
g_object_thaw_notify (G_OBJECT (user));
}
-void
+static void
user_update_from_keyfile (User *user,
GKeyFile *keyfile)
{
gchar *s;
- g_object_freeze_notify (G_OBJECT (user));
-
s = g_key_file_get_string (keyfile, "User", "Language", NULL);
if (s != NULL) {
accounts_user_set_language (ACCOUNTS_USER (user), s);
@@ -313,9 +570,25 @@ user_update_from_keyfile (User *user,
g_clear_pointer (&user->keyfile, g_key_file_unref);
user->keyfile = g_key_file_ref (keyfile);
+}
+
+void
+user_update_from_cache (User *user)
+{
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GKeyFile) key_file = NULL;
+
+ filename = g_build_filename (USERDIR, accounts_user_get_user_name (ACCOUNTS_USER (user)), NULL);
+
+ key_file = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (key_file, filename, 0, NULL))
+ return;
+
+ g_object_freeze_notify (G_OBJECT (user));
+ user_update_from_keyfile (user, key_file);
user_set_cached (user, TRUE);
user_set_saved (user, TRUE);
-
g_object_thaw_notify (G_OBJECT (user));
}
@@ -539,6 +812,9 @@ user_extension_authentication_done (Daemon *daemon,
GDBusInterfaceInfo *interface = user_data;
const gchar *method_name;
+ if (!user_has_cache_file (user))
+ user_update_from_template (user);
+
method_name = g_dbus_method_invocation_get_method_name (invocation);
if (g_str_equal (method_name, "Get"))
diff --git a/src/user.h b/src/user.h
index b3b3380..eb81918 100644
--- a/src/user.h
+++ b/src/user.h
@@ -57,8 +57,7 @@ User * user_new (Daemon *daemon,
void user_update_from_pwent (User *user,
struct passwd *pwent,
struct spwd *spent);
-void user_update_from_keyfile (User *user,
- GKeyFile *keyfile);
+void user_update_from_cache (User *user);
void user_update_local_account_property (User *user,
gboolean local);
void user_update_system_account_property (User *user,