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/359 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.
This commit is contained in:
parent
14b9f65ba4
commit
1f71ae6046
3 changed files with 174 additions and 58 deletions
|
@ -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,45 @@ 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_is_valid_cb (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcAddUserDialog *self = CC_ADD_USER_DIALOG (user_data);
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree gchar *tip = NULL;
|
||||
g_autofree gchar *name = NULL;
|
||||
g_autofree gchar *username = NULL;
|
||||
gboolean valid;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_object_unref (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
local_username_timeout (CcAddUserDialog *self)
|
||||
{
|
||||
g_autofree gchar *name = NULL;
|
||||
|
||||
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, NULL, local_username_is_valid_cb, g_object_ref (self));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -357,6 +380,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);
|
||||
}
|
||||
|
||||
|
|
|
@ -389,61 +389,147 @@ 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);
|
||||
}
|
||||
|
||||
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 ();
|
||||
}
|
||||
valid = TRUE;
|
||||
/* Taken from usermod.c in shadow-utils. */
|
||||
#define E_SUCCESS 0
|
||||
#define E_BAD_ARG 3
|
||||
#define E_NOTFOUND 6
|
||||
|
||||
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;
|
||||
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 E_NOTFOUND:
|
||||
valid = TRUE;
|
||||
break;
|
||||
case E_BAD_ARG:
|
||||
tip = _("The username should usually only consist of lower case letters from a-z, digits and the following characters: - _");
|
||||
valid = FALSE;
|
||||
break;
|
||||
case E_SUCCESS:
|
||||
tip = _("Sorry, that user name isn’t available. Please try another.");
|
||||
valid = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
valid = !empty && !in_use && !too_long && valid;
|
||||
|
||||
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: . - _"));
|
||||
}
|
||||
if (valid || tip != NULL) {
|
||||
data->tip = g_strdup (tip);
|
||||
g_task_return_boolean (task, valid);
|
||||
}
|
||||
else {
|
||||
*tip = g_strdup (_("This will be used to name your home folder and can’t be changed."));
|
||||
g_spawn_check_exit_status (status, &error);
|
||||
g_task_return_error (task, error);
|
||||
}
|
||||
|
||||
return valid;
|
||||
g_spawn_close_pid (pid);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
void
|
||||
is_valid_username_async (const gchar *username,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data)
|
||||
{
|
||||
GTask *task;
|
||||
isValidUsernameData *data;
|
||||
gchar *argv[6];
|
||||
GPid pid;
|
||||
GError *error;
|
||||
|
||||
task = g_task_new (NULL, cancellable, 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') {
|
||||
g_task_return_boolean (task, FALSE);
|
||||
g_object_unref (task);
|
||||
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* "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] = "--";
|
||||
argv[4] = data->username;
|
||||
argv[5] = 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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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."));
|
||||
}
|
||||
|
||||
if (username != NULL)
|
||||
*username = g_steal_pointer (&data->username);
|
||||
|
||||
return g_task_propagate_boolean (task, error);
|
||||
}
|
||||
|
||||
GdkPixbuf *
|
||||
|
|
|
@ -41,8 +41,14 @@ 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,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer callback_data);
|
||||
gboolean is_valid_username_finish (GAsyncResult *result,
|
||||
gchar **tip,
|
||||
gchar **username,
|
||||
GError **error);
|
||||
GdkPixbuf *round_image (GdkPixbuf *pixbuf);
|
||||
GdkPixbuf *generate_default_avatar (ActUser *user,
|
||||
gint size);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue