[gnome-control-center/wip/oholy/username-validity] user-accounts: Check username validity over usermod



commit b5dd69b44ef961414f3b0e2375d129fd60abca29
Author: Ondrej Holy <oholy redhat com>
Date:   Fri Jan 25 16:32:20 2019 +0100

    user-accounts: Check username validity over usermod
    
    Username policies differ across the distributions. See the discussion on:
    https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/35
    
    It is not possible to hard-code some rules here without the need for
    downstream modifications. Let's check the validity of usernames dynamically
    over "usermod" to prevent this.
    
    Just be careful that this is based on undocumented usermod behavior, which
    may change in the future.

 panels/user-accounts/cc-add-user-dialog.c |  51 +++++++--
 panels/user-accounts/user-utils.c         | 169 ++++++++++++++++++++++--------
 panels/user-accounts/user-utils.h         |   9 +-
 3 files changed, 171 insertions(+), 58 deletions(-)
---
diff --git a/panels/user-accounts/cc-add-user-dialog.c b/panels/user-accounts/cc-add-user-dialog.c
index e15170ee9..98c0f1887 100644
--- a/panels/user-accounts/cc-add-user-dialog.c
+++ b/panels/user-accounts/cc-add-user-dialog.c
@@ -94,6 +94,7 @@ struct _CcAddUserDialog {
         gint                local_username_timeout_id;
         ActUserPasswordMode local_password_mode;
         gint                local_password_timeout_id;
+        gboolean            local_valid_username;
 
         guint               realmd_watch;
         CcRealmManager     *realm_manager;
@@ -277,22 +278,14 @@ update_password_strength (CcAddUserDialog *self)
 static gboolean
 local_validate (CcAddUserDialog *self)
 {
-        gboolean valid_login;
         gboolean valid_name;
         gboolean valid_password;
         const gchar *name;
         const gchar *password;
         const gchar *verify;
-        gchar *tip;
         gint strength;
 
-        name = gtk_combo_box_text_get_active_text (self->local_username_combo);
-        valid_login = is_valid_username (name, &tip);
-
-        gtk_label_set_label (self->local_username_hint_label, tip);
-        g_free (tip);
-
-        if (valid_login) {
+        if (self->local_valid_username) {
                 set_entry_validation_checkmark (self->local_username_entry);
         }
 
@@ -311,15 +304,50 @@ local_validate (CcAddUserDialog *self)
                 valid_password = TRUE;
         }
 
-        return valid_name && valid_login && valid_password;
+        return valid_name && self->local_valid_username && valid_password;
+}
+
+static void local_username_timeout_cb (GObject *source_object,
+                                       GAsyncResult *result,
+                                       gpointer user_data)
+{
+        CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data);
+        gchar *tip = NULL;
+        GError *error = NULL;
+        gboolean valid;
+        gchar *name;
+        gchar *username = NULL;
+
+        valid = is_valid_username_finish (result, &tip, &username, &error);
+        if (error != NULL) {
+                g_warning ("Could not check username by usermod: %s", error->message);
+                valid = TRUE;
+                g_free (error);
+        }
+
+        name = gtk_combo_box_text_get_active_text (self->local_username_combo);
+        if (g_strcmp0 (name, username) == 0) {
+                self->local_valid_username = valid;
+                gtk_label_set_label (self->local_username_hint_label, tip);
+                dialog_validate (self);
+        }
+
+        g_free (tip);
+        g_free (username);
+        g_free (name);
+        g_object_unref (self);
 }
 
 static gboolean
 local_username_timeout (CcAddUserDialog *self)
 {
+        gchar *name;
+
         self->local_username_timeout_id = 0;
 
-        dialog_validate (self);
+        name = gtk_combo_box_text_get_active_text (self->local_username_combo);
+        is_valid_username_async (name, local_username_timeout_cb, g_object_ref (self));
+        g_free (name);
 
         return FALSE;
 }
@@ -357,6 +385,7 @@ local_username_combo_changed_cb (CcAddUserDialog *self)
         clear_entry_validation_error (self->local_username_entry);
         gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE);
 
+        self->local_valid_username = FALSE;
         self->local_username_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT, (GSourceFunc) 
local_username_timeout, self);
 }
 
diff --git a/panels/user-accounts/user-utils.c b/panels/user-accounts/user-utils.c
index 1aafe82e0..9b319a576 100644
--- a/panels/user-accounts/user-utils.c
+++ b/panels/user-accounts/user-utils.c
@@ -385,59 +385,138 @@ is_valid_name (const gchar *name)
         return !is_empty;
 }
 
-gboolean
-is_valid_username (const gchar *username, gchar **tip)
+typedef struct {
+        gchar *username;
+        gchar *tip;
+} isValidUsernameData;
+
+static void
+is_valid_username_data_free (isValidUsernameData *data)
 {
-        gboolean empty;
-        gboolean in_use;
-        gboolean too_long;
-        gboolean valid;
-        const gchar *c;
+        g_free (data->username);
+        g_free (data->tip);
+        g_free (data);
+}
+
+static void
+is_valid_username_child_watch_cb (GPid pid,
+                                  gint status,
+                                  gpointer user_data)
+{
+        GTask *task = G_TASK (user_data);
+        isValidUsernameData *data = g_task_get_task_data (task);
+        GError *error = NULL;
+        gboolean valid = FALSE;
+        const gchar *tip = NULL;
+
+        if (WIFEXITED (status)) {
+                switch (WEXITSTATUS (status)) {
+                        case 6:
+                                valid = TRUE;
+                                break;
+                        case 3:
+                                tip = _("The username should usually only consist of lower case letters from 
a-z, digits and the following characters: - _");
+                                valid = FALSE;
+                                break;
+                        case 0:
+                                tip = _("Sorry, that user name isn’t available. Please try another.");
+                                valid = FALSE;
+                                break;
+                }
+        }
+
+        if (valid || tip != NULL) {
+                data->tip = g_strdup (tip);
+                g_task_return_boolean (task, valid);
+        }
+        else {
+                g_spawn_check_exit_status (status, &error);
+                g_task_return_error (task, error);
+        }
+
+        g_spawn_close_pid (pid);
+        g_object_unref (task);
+}
+
+void
+is_valid_username_async (const gchar *username,
+                         GAsyncReadyCallback callback,
+                         gpointer callback_data)
+{
+        GTask *task;
+        isValidUsernameData *data;
+        gchar *argv[5];
+        GPid pid;
+        GError *error;
+
+        task = g_task_new (NULL, NULL, callback, callback_data);
+        g_task_set_source_tag (task, is_valid_username_async);
+
+        data = g_new0 (isValidUsernameData, 1);
+        data->username = g_strdup (username);
+        g_task_set_task_data (task, data, (GDestroyNotify) is_valid_username_data_free);
 
         if (username == NULL || username[0] == '\0') {
-                empty = TRUE;
-                in_use = FALSE;
-                too_long = FALSE;
-        } else {
-                empty = FALSE;
-                in_use = is_username_used (username);
-                too_long = strlen (username) > get_username_max_length ();
+                g_task_return_boolean (task, FALSE);
+                g_object_unref (task);
+
+                return;
         }
-        valid = TRUE;
-
-        if (!in_use && !empty && !too_long) {
-                /* First char must be a letter, and it must only composed
-                 * of ASCII letters, digits, and a '.', '-', '_'
-                 */
-                for (c = username; *c; c++) {
-                        if (! ((*c >= 'a' && *c <= 'z') ||
-                               (*c >= 'A' && *c <= 'Z') ||
-                               (*c >= '0' && *c <= '9') ||
-                               (*c == '_') || (*c == '.') ||
-                               (*c == '-' && c != username)))
-                           valid = FALSE;
-                }
+        else if (strlen (username) > get_username_max_length ()) {
+                data->tip = g_strdup (_("The username is too long."));
+                g_task_return_boolean (task, FALSE);
+                g_object_unref (task);
+
+                return;
         }
 
-        valid = !empty && !in_use && !too_long && valid;
+        /* "usermod --login" is meant to be used to change a username, but the
+         * exit codes can be safely abused to check the validity of username.
+         * However, the current "usermod" implementation may change in the
+         * future, so it would be nice to have some official way for this
+         * instead of relying on the current "--login" implementation.
+         */
+        argv[0] = "usermod";
+        argv[1] = "--login";
+        argv[2] = data->username;
+        argv[3] = data->username;
+        argv[4] = NULL;
+
+        if (!g_spawn_async (NULL, argv, NULL,
+                            G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
+                            G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+                            NULL, NULL, &pid, &error)) {
+                g_task_return_error (task, error);
+                g_object_unref (task);
 
-        if (!empty && (in_use || too_long || !valid)) {
-                if (in_use) {
-                        *tip = g_strdup (_("Sorry, that user name isn’t available. Please try another."));
-                }
-                else if (too_long) {
-                        *tip = g_strdup_printf (_("The username is too long."));
-                }
-                else if (username[0] == '-') {
-                        *tip = g_strdup (_("The username cannot start with a “-”."));
-                }
-                else {
-                        *tip = g_strdup (_("The username should only consist of upper and lower case letters 
from a-z, digits and the following characters: . - _"));
-                }
+                return;
         }
-        else {
-                *tip = g_strdup (_("This will be used to name your home folder and can’t be changed."));
+
+        g_child_watch_add (pid, (GChildWatchFunc) is_valid_username_child_watch_cb, task);
+}
+
+gboolean
+is_valid_username_finish (GAsyncResult *result,
+                          gchar **tip,
+                          gchar **username,
+                          GError **error)
+{
+        GTask *task;
+        isValidUsernameData *data;
+
+        g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
+
+        task = G_TASK (result);
+        data = g_task_get_task_data (task);
+
+        if (tip != NULL) {
+                *tip = g_steal_pointer (&data->tip);
+                if (*tip == NULL)
+                        *tip = g_strdup (_("This will be used to name your home folder and can’t be 
changed."));
         }
 
-        return valid;
+        if (username != NULL)
+                *username = g_steal_pointer (&data->username);
+
+        return g_task_propagate_boolean (task, error);
 }
diff --git a/panels/user-accounts/user-utils.h b/panels/user-accounts/user-utils.h
index be31a5776..fb423d27c 100644
--- a/panels/user-accounts/user-utils.h
+++ b/panels/user-accounts/user-utils.h
@@ -40,7 +40,12 @@ void     clear_entry_validation_error     (GtkEntry    *entry);
 gsize    get_username_max_length          (void);
 gboolean is_username_used                 (const gchar *username);
 gboolean is_valid_name                    (const gchar *name);
-gboolean is_valid_username                (const gchar *name,
-                                           gchar      **tip);
+void     is_valid_username_async          (const gchar *username,
+                                           GAsyncReadyCallback callback,
+                                           gpointer callback_data);
+gboolean is_valid_username_finish         (GAsyncResult *result,
+                                           gchar **tip,
+                                           gchar **username,
+                                           GError **error);
 
 G_END_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]