Skip to content

Commit

Permalink
webui: add user account validation to accounts screen
Browse files Browse the repository at this point in the history
  • Loading branch information
rvykydal committed Nov 23, 2023
1 parent 6886d7f commit fa5a24f
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 6 deletions.
69 changes: 65 additions & 4 deletions src/components/users/Accounts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@

import cockpit from "cockpit";
import React, { useState, useEffect } from "react";
import { debounce } from "throttle-debounce";

import {
Form,
FormGroup,
FormHelperText,
HelperText,
TextInput,
Title,
} from "@patternfly/react-core";
Expand Down Expand Up @@ -55,6 +58,33 @@ export const accountsToDbusUsers = (accounts) => {
}];
};

const reservedNames = [
"root",
"bin",
"daemon",
"adm",
"lp",
"sync",
"shutdown",
"halt",
"mail",
"operator",
"games",
"ftp",
"nobody",
"home",
"system",
];

const isUserAccountWithInvalidCharacters = (userAccount) => {
return (
userAccount === "." ||
userAccount === ".." ||
userAccount.match(/^[0-9]+$/) ||
!userAccount.match(/^[A-Za-z0-9._][A-Za-z0-9._-]{0,30}([A-Za-z0-9._-]|\$)?$/)
);
};

const CreateAccount = ({
idPrefix,
passwordPolicy,
Expand All @@ -63,14 +93,39 @@ const CreateAccount = ({
setAccounts,
}) => {
const [fullName, setFullName] = useState(accounts.fullName);
const [_userAccount, _setUserAccount] = useState(accounts.userAccount);
const [userAccount, setUserAccount] = useState(accounts.userAccount);
const [userAccountInvalidHint, setUserAccountInvalidHint] = useState("");
const [isUserAccountValid, setIsUserAccountValid] = useState(null);
const [password, setPassword] = useState(accounts.password);
const [confirmPassword, setConfirmPassword] = useState(accounts.confirmPassword);
const [isPasswordValid, setIsPasswordValid] = useState(false);

useEffect(() => {
setIsUserValid(isPasswordValid && userAccount.length > 0);
}, [setIsUserValid, isPasswordValid, userAccount]);
debounce(300, () => setUserAccount(_userAccount))();
}, [_userAccount, setUserAccount]);

useEffect(() => {
setIsUserValid(isPasswordValid && isUserAccountValid);
}, [setIsUserValid, isPasswordValid, isUserAccountValid]);

useEffect(() => {
let valid = true;
setUserAccountInvalidHint("");
if (userAccount.length === 0) {
valid = null;
} else if (userAccount.length > 32) {
valid = false;
setUserAccountInvalidHint(_("User names must be shorter than 33 characters"));
} else if (reservedNames.includes(userAccount)) {
valid = false;
setUserAccountInvalidHint(_("User name must not be a reserved word"));
} else if (isUserAccountWithInvalidCharacters(userAccount)) {
valid = false;
setUserAccountInvalidHint(cockpit.format(_("User name may only contain: letters from a-z, digits 0-9, dash $0, period $1, underscore $2"), "-", ".", "_"));
}
setIsUserAccountValid(valid);
}, [userAccount]);

const passphraseForm = (
<PasswordFormFields
Expand Down Expand Up @@ -119,9 +174,15 @@ const CreateAccount = ({
>
<TextInput
id={idPrefix + "-user-account"}
value={userAccount}
onChange={(_event, val) => setUserAccount(val)}
value={_userAccount}
onChange={(_event, val) => _setUserAccount(val)}
validated={isUserAccountValid === null ? "default" : isUserAccountValid ? "success" : "error"}
/>
<FormHelperText>
<HelperText component="ul" aria-live="polite" id={idPrefix + "-full-name-helper"}>
{userAccountInvalidHint}
</HelperText>
</FormHelperText>
</FormGroup>
{passphraseForm}
</Form>
Expand Down
47 changes: 45 additions & 2 deletions test/check-users
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ class TestUsers(anacondalib.VirtInstallMachineCase):
self.assertIn('"is-crypted" b false', users)
self.assertIn('"password" s "password"', users)

@staticmethod
def _test_user_account(user, installer, valid, invalid):
installer.check_next_disabled(disabled=False)

n_valid = len(valid) - 1
n_invalid = len(invalid) - 1

# Set valid and invalid value in turns and check the next button
for i in range(max(n_valid, n_invalid)):
i_valid = min(i, n_valid)
i_invalid = min(i, n_invalid)
user.set_user_account(invalid[i_invalid])
installer.check_next_disabled()
user.set_user_account(valid[i_valid])
installer.check_next_disabled(disabled=False)

def testAccessAndValidation(self):
b = self.browser
i = Installer(b, self.machine)
Expand Down Expand Up @@ -101,8 +117,35 @@ class TestUsers(anacondalib.VirtInstallMachineCase):
u.set_full_name(full_name)

# Test account validation
u.set_user_account("")
i.check_next_disabled()
# FIXME this should be tested by unit tests?
invalid_user_accounts = [
"",
# reserved
"root", "system", "daemon", "home",
"33333",
".",
"..",
"-tester",
"123",
"$",
"$tester",
"tester?",
"longer_than_32_characteeeeeeeeeeeeeeeeeeers",
]
valid_user_accounts = [
"tester-",
"...",
"12.3",
"tester1",
"tester$",
"test-er",
"test_er",
"test.er",
"_tester",
".tester",
"_",
]
self._test_user_account(u, i, valid_user_accounts, invalid_user_accounts)
u.set_user_account(user_account)

# Test password validation
Expand Down

0 comments on commit fa5a24f

Please sign in to comment.