LCOV - code coverage report
Current view: top level - pkg/users - account-create-dialog.js Coverage Total Hit
Test: cockpit Lines: 79.6 % 471 375
Test Date: 2026-07-13 10:00:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2020 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6           19 : import cockpit from 'cockpit';
       7           19 : import React from 'react';
       8              : 
       9              : import { Bullseye } from "@patternfly/react-core/dist/esm/layouts/Bullseye/index.js";
      10              : import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox/index.js";
      11              : import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
      12              : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
      13              : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
      14              : import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
      15              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      16              : import { Radio } from "@patternfly/react-core/dist/esm/components/Radio/index.js";
      17              : import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
      18              : import { has_errors, is_valid_char_name } from "./dialog-utils.js";
      19              : import { passwd_change } from "./password-dialogs.js";
      20              : import { FormHelper } from "cockpit-components-form-helper";
      21              : import { password_quality, PasswordFormFields } from "cockpit-components-password.jsx";
      22              : import { show_modal_dialog, apply_modal_dialog } from "cockpit-components-dialog.jsx";
      23              : import { HelpIcon } from '@patternfly/react-icons';
      24              : 
      25           19 : const _ = cockpit.gettext;
      26              : 
      27            4 : function get_default_home_dir(base_home_dir, user_name) {
      28            4 :     return base_home_dir && user_name
      29            4 :         ? base_home_dir + '/' + user_name
      30            0 :         : "";
      31            4 : }
      32              : 
      33            4 : function AccountCreateBody({ state, errors, change, shells }) {
      34            4 :     const {
      35            4 :         real_name, user_name,
      36            4 :         locked, change_passw_force,
      37            4 :         shell,
      38            4 :     } = state;
      39              : 
      40              :     // We want to let user know that password and confirmation password do not match without them having to constantly click on "Create" to validate the form.
      41              :     // But we also don't want to show an error message while they are typing the confirmation password, only when they are finished typing it.
      42              :     // To solve the issue of telling that user is finished writing the confirmation password, let's do the following:
      43              :     // The dialog does not validate if passwords match until:
      44              :     //     1. they are at least the same length (which signals user has finished typing)
      45              :     // OR
      46              :     //     2. user submits the form (which also signals they are finished typing)
      47              :     // Once that happens, and passwords do not match, the confirm password is validated after each keystroke.
      48            4 :     let dynamic_password_confirm_error;
      49            4 :     if (state.password_confirm_dirty)
      50            4 :         dynamic_password_confirm_error = validate_password_confirm(state.password_confirm, state.password);
      51              : 
      52            4 :     return (
      53            4 :         <Form isHorizontal onSubmit={apply_modal_dialog}>
      54            4 :             <FormGroup label={_("Full name")}
      55            4 :                        fieldId="accounts-create-real-name">
      56            4 :                 <TextInput id="accounts-create-real-name"
      57            0 :                            validated={(errors?.real_name) ? "error" : "default"}
      58            4 :                            value={real_name} onChange={(_event, value) => change("real_name", value)} />
      59            4 :                 <FormHelper fieldId="accounts-create-real-name" helperTextInvalid={errors?.real_name} />
      60            4 :             </FormGroup>
      61              : 
      62            4 :             <FormGroup label={_("User name")}
      63            4 :                        fieldId="accounts-create-user-name">
      64            4 :                 <TextInput id="accounts-create-user-name"
      65            0 :                            validated={(errors?.user_name) ? "error" : "default"}
      66            4 :                            value={user_name} onChange={(_event, value) => change("user_name", value)} />
      67            4 :                 <FormHelper fieldId="accounts-create-user-name" helperTextInvalid={errors?.user_name} />
      68            4 :             </FormGroup>
      69              : 
      70            4 :             <FormGroup label={_("Home directory")}
      71            4 :                        fieldId="accounts-create-user-home-dir">
      72            4 :                 <TextInput id="accounts-create-user-home-dir"
      73            1 :                     onChange={(_event, value) => change("home_dir", value)}
      74            4 :                     placeholder={_("Path to directory")}
      75            4 :                     value={state.home_dir} />
      76            4 :                 <FormHelper fieldId="accounts-create-user-home-dir" helperTextInvalid={errors?.home_dir} />
      77            4 :             </FormGroup>
      78              : 
      79            4 :             <FormGroup label={_("Shell")}
      80            4 :                        fieldId="accounts-create-user-shell">
      81            4 :                 <FormSelect
      82            4 :                         data-selected={shell}
      83            4 :                         id="accounts-create-user-shell"
      84            2 :                         onChange={(_, selection) => { change("shell", selection) }}
      85            4 :                         value={shell}>
      86            4 :                     { shells.map(shell_path => <FormSelectOption key={shell_path} value={shell_path} label={shell_path} />) }
      87            4 :                 </FormSelect>
      88            4 :             </FormGroup>
      89              : 
      90            4 :             <FormGroup label={_("User ID")}
      91            4 :                        fieldId="accounts-create-user-uid">
      92            4 :                 <TextInput id="accounts-create-user-uid"
      93            1 :                     onChange={(_event, value) => change("uid", value)}
      94            4 :                     value={state.uid} />
      95            4 :                 <FormHelper fieldId="accounts-create-user-uid" helperTextInvalid={errors?.uid} />
      96            4 :             </FormGroup>
      97              : 
      98            4 :             <FormGroup label={_("Authentication")} fieldId="accounts-create-locked" hasNoPaddingTop>
      99            4 :                 <Radio id="account-use-password"
     100            4 :                        label={_("Use password")}
     101            0 :                        isChecked={!locked} onChange={(_, checked) => change("locked", !checked)}
     102            4 :                        description={
     103            4 :                            <Checkbox id="accounts-create-force-password-change"
     104            4 :                                      className="pf-v6-u-mb-xs"
     105            4 :                                      label={_("Require password change on first login")}
     106            2 :                                      isChecked={change_passw_force} onChange={(_event, checked) => change("change_passw_force", checked)} />
     107            4 :                        } />
     108              : 
     109            4 :                 <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
     110            4 :                     <FlexItem spacer={{ default: 'spacerNone' }}>
     111            4 :                         <Radio id="accounts-create-locked"
     112            0 :                                isChecked={locked} onChange={(_, checked) => change("locked", checked)}
     113            4 :                                label={_("Disallow password authentication")} />
     114            4 :                     </FlexItem>
     115              : 
     116            4 :                     <FlexItem spacer={{ default: 'spacerNone' }}>
     117            4 :                         <Popover bodyContent={_("Other authentication methods are still available even when interactive password authentication is not allowed.")}
     118            4 :                                  showClose={false}>
     119            4 :                             <HelpIcon />
     120            4 :                         </Popover>
     121            4 :                     </FlexItem>
     122            4 :                 </Flex>
     123            4 :             </FormGroup>
     124              : 
     125            4 :             <PasswordFormFields password_label={_("Password")}
     126            4 :                                 password_confirm_label={_("Confirm password")}
     127            4 :                                 error_password={errors?.password}
     128            4 :                                 error_password_confirm={dynamic_password_confirm_error || errors?.password_confirm}
     129            4 :                                 idPrefix="accounts-create-password"
     130            4 :                                 change={change} />
     131            4 :         </Form>
     132              :     );
     133            4 : }
     134              : 
     135            4 : function validate_username(username, accounts) {
     136            4 :     if (!username)
     137            0 :         return _("No user name specified");
     138              : 
     139            4 :     for (let i = 0; i < username.length; i++) {
     140            4 :         if (!is_valid_char_name(username[i]))
     141            0 :             return _("The user name can only consist of letters from a-z, digits, dots, dashes and underscores.");
     142            4 :     }
     143              : 
     144            4 :     for (let k = 0; k < accounts.length; k++) {
     145            4 :         if (accounts[k].name == username)
     146            0 :             return _("This user name already exists");
     147            4 :     }
     148              : 
     149            4 :     return null;
     150            4 : }
     151              : 
     152            4 : function validate_real_name(real_name) {
     153            4 :     if (!real_name)
     154            0 :         return _("No real name specified");
     155              : 
     156            4 :     const real_name_chars = Array.from(real_name);
     157            4 :     if (real_name_chars.includes(':'))
     158            0 :         return _("The full name must not contain colons.");
     159            4 : }
     160              : 
     161            4 : function validate_uid(uid, accounts, min_uid, max_uid, change) {
     162            4 :     if (!uid)
     163            1 :         return undefined;
     164              : 
     165            4 :     const uid_number = Number(uid);
     166            4 :     if (!Number.isInteger(uid_number) || uid_number < 0)
     167            1 :         return _("User ID must be a positive integer");
     168              : 
     169            4 :     if (min_uid && uid_number < min_uid)
     170            1 :         return cockpit.format(_("User ID must not be lower than $0"), min_uid);
     171              : 
     172            4 :     if (max_uid && uid_number > max_uid)
     173            1 :         return cockpit.format(_("User ID must not be higher than $0"), max_uid);
     174              : 
     175            4 :     if (accounts.some(account => account.uid === uid_number)) {
     176            1 :         change("uid_exists", true);
     177            1 :         return _("User ID is already used by another user");
     178            1 :     }
     179            4 : }
     180              : 
     181            4 : function validate_home_dir(dir, directoryExpected) {
     182            4 :     return cockpit.spawn(["test", "!", directoryExpected ? "-d" : "-f", dir], { superuser: "require" });
     183            4 : }
     184              : 
     185            4 : function validate_password(password) {
     186            4 :     if (!password)
     187            0 :         return _("Empty password");
     188              : 
     189            4 :     return null;
     190            4 : }
     191              : 
     192            4 : function validate_password_confirm(password_confirm, password) {
     193            4 :     if (password_confirm !== password)
     194            0 :         return _("The passwords do not match");
     195              : 
     196            4 :     return null;
     197            4 : }
     198              : 
     199            0 : function suggest_username(realname) {
     200            0 :     function remove_diacritics(str) {
     201            0 :         const translate_table = {
     202            0 :             a: '[àáâãäå]',
     203            0 :             ae: 'æ',
     204            0 :             c: '[čç]',
     205            0 :             d: 'ď',
     206            0 :             e: '[èéêë]',
     207            0 :             i: '[íìïî]',
     208            0 :             l: '[ĺľ]',
     209            0 :             n: '[ňñ]',
     210            0 :             o: '[òóôõö]',
     211            0 :             oe: 'œ',
     212            0 :             r: '[ŕř]',
     213            0 :             s: 'š',
     214            0 :             t: 'ť',
     215            0 :             u: '[ùúůûűü]',
     216            0 :             y: '[ýÿ]',
     217            0 :             z: 'ž',
     218            0 :         };
     219            0 :         for (const i in translate_table)
     220            0 :             str = str.replace(new RegExp(translate_table[i], 'g'), i);
     221              : 
     222            0 :         for (let k = 0; k < str.length;) {
     223            0 :             if (!is_valid_char_name(str[k]))
     224            0 :                 str = str.substring(0, k) + str.substring(k + 1);
     225              :             else
     226            0 :                 k++;
     227            0 :         }
     228              : 
     229            0 :         return str;
     230            0 :     }
     231              : 
     232            0 :     let result = "";
     233            0 :     const name = realname.split(' ');
     234              : 
     235            0 :     if (name.length === 1)
     236            0 :         result = name[0].toLowerCase();
     237            0 :     else if (name.length > 1)
     238            0 :         result = name[0][0].toLowerCase() + name[name.length - 1].toLowerCase();
     239              : 
     240            0 :     return remove_diacritics(result);
     241            0 : }
     242              : 
     243            4 : export function account_create_dialog(accounts, min_uid, max_uid, shells) {
     244            4 :     let dlg = null;
     245              : 
     246            4 :     const used_ids = accounts.map(a => a.uid);
     247            4 :     const uid = Math.max(min_uid, Math.max(...used_ids.filter(id => id < max_uid)) + 1);
     248              : 
     249            4 :     const state = {
     250            4 :         dialogLoading: true,
     251            4 :         real_name: "",
     252            4 :         user_name: "",
     253            4 :         password: "",
     254            4 :         password_confirm: "",
     255            4 :         password_confirm_dirty: false,
     256            4 :         locked: false,
     257            4 :         confirm_weak: false,
     258            4 :         change_passw_force: false,
     259            4 :         base_home_dir: null,
     260            4 :         shell: "",
     261            4 :         uid,
     262            4 :         uid_exists: false,
     263            4 :         min_uid,
     264            4 :         max_uid,
     265            4 :         home_dir: "",
     266            4 :         home_dir_dirty: false,
     267            4 :     };
     268            4 :     let errors = { };
     269              : 
     270            4 :     let old_password = null;
     271            4 :     let user_name_dirty = false;
     272              : 
     273            4 :     function get_defaults() {
     274            4 :         return cockpit.spawn(["useradd", "-D"], { superuser: "require", err: "message" })
     275            4 :                 .then(defaults => {
     276            4 :                     let shell = "";
     277            4 :                     let base_home_dir = null;
     278            4 :                     defaults.split("\n").forEach(item => {
     279            4 :                         if (item.indexOf("SHELL=") === 0) {
     280            0 :                             shell = item.split("=")[1] || "/bin/bash";
     281            4 :                         } else if (item.indexOf("HOME=") === 0) {
     282            0 :                             base_home_dir = item.split("=")[1] || "";
     283            4 :                         }
     284            4 :                     });
     285            4 :                     change("shell", shell);
     286            4 :                     change("base_home_dir", base_home_dir);
     287            4 :                 })
     288            0 :                 .catch(e => console.warn("Could not get useradd defaults: ", e.message))
     289            4 :                 .finally(() => change("dialogLoading", false));
     290            4 :     }
     291              : 
     292            4 :     function change(field, value) {
     293            4 :         state[field] = value;
     294            4 :         errors = { };
     295              : 
     296            4 :         if (field == "user_name") {
     297            4 :             user_name_dirty = true;
     298            4 :             if (!state.home_dir_dirty)
     299            4 :                 state.home_dir = get_default_home_dir(state.base_home_dir, value);
     300            4 :         }
     301              : 
     302            0 :         if (!user_name_dirty && field == "real_name") {
     303            0 :             const suggested_username = suggest_username(state.real_name);
     304            0 :             state.user_name = suggested_username;
     305            0 :             if (!state.home_dir_dirty)
     306            0 :                 state.home_dir = get_default_home_dir(state.base_home_dir, suggested_username);
     307            0 :         }
     308              : 
     309            4 :         if (state.password != old_password) {
     310            4 :             state.confirm_weak = false;
     311            4 :             old_password = state.password;
     312            4 :         }
     313              : 
     314            4 :         if (field == "change_passw_force")
     315            2 :             state.locked = false;
     316              : 
     317              :         // Once password and confirm password are the same length, validate them after each keystroke
     318            4 :         if (field == "password_confirm" && value.length >= state.password.length)
     319            4 :             state.password_confirm_dirty = true;
     320              : 
     321            4 :         if (field == "locked")
     322            0 :             state.change_passw_force = false;
     323              : 
     324            4 :         if (field == "uid")
     325            1 :             state.uid_exists = false;
     326              : 
     327            1 :         if (field == "home_dir") {
     328            1 :             state.home_dir_dirty = true;
     329            1 :             state.home_dir_exists = false;
     330            1 :             state.home_dir_is_file = false;
     331            1 :         }
     332              : 
     333            4 :         update();
     334            4 :     }
     335              : 
     336            4 :     function validate(force_weak, force_home, force_uid, real_name, user_name, password, password_confirm, uid, accounts, min_uid, max_uid, change) {
     337            4 :         const errs = { };
     338              : 
     339            4 :         errs.real_name = validate_real_name(real_name);
     340            4 :         errs.password = validate_password(password);
     341            4 :         errs.password_confirm = validate_password_confirm(password_confirm, password);
     342              : 
     343            4 :         if (password.length > 256)
     344            0 :             errs.password = _("Password is longer than 256 characters");
     345              : 
     346            4 :         errs.user_name = validate_username(user_name, accounts);
     347              : 
     348            4 :         const promises = [];
     349              :         // only evaluate password score if no other password error si present
     350            4 :         if (!errs.password) {
     351            4 :             promises.push(
     352            4 :                 password_quality(password, force_weak)
     353            0 :                         .catch(ex => {
     354            0 :                             errs.password = (ex.message || ex.toString()).replaceAll("\n", " "); // not-covered: OS error
     355            0 :                         })
     356            4 :             );
     357            4 :         }
     358            4 :         if (!force_uid)
     359            4 :             errs.uid = validate_uid(uid, accounts, min_uid, max_uid, change);
     360              : 
     361            4 :         promises.push(
     362            4 :             validate_home_dir(state.home_dir, false)
     363            1 :                     .catch(() => {
     364            1 :                         errs.home_dir = cockpit.format(_("$0 is an existing file"), state.home_dir);
     365            1 :                         state.home_dir_is_file = true;
     366            1 :                     })
     367            4 :         );
     368              : 
     369            4 :         if (!force_home) {
     370            4 :             promises.push(
     371            4 :                 validate_home_dir(state.home_dir, true)
     372            1 :                         .catch(() => {
     373            1 :                             errs.home_dir = cockpit.format(_("The home directory $0 already exists. Its ownership will be changed to the new user."), state.home_dir);
     374            1 :                             state.home_dir_exists = true;
     375            1 :                         })
     376            4 :             );
     377            4 :         }
     378              : 
     379            4 :         return Promise.all(promises)
     380            4 :                 .then(() => {
     381            4 :                     errors = errs;
     382            4 :                     return !has_errors(errs);
     383            4 :                 });
     384            4 :     }
     385              : 
     386            4 :     function create(real_name, user_name, password, locked, uid, force_change, home_dir, force_home, force_uid) {
     387            4 :         const prog = ["useradd", "--create-home", "-s", state.shell];
     388            4 :         if (real_name) {
     389            4 :             prog.push('-c');
     390            4 :             prog.push(real_name);
     391            4 :         }
     392              : 
     393            4 :         if (uid) {
     394            4 :             prog.push('-u');
     395            4 :             prog.push(uid);
     396            4 :         }
     397              : 
     398            4 :         if (force_uid)
     399            1 :             prog.push('-o'); // Create user with non-unique user-specified UID, useful at certain use cases
     400              : 
     401            4 :         if (home_dir) {
     402            4 :             prog.push('-d');
     403            4 :             prog.push(home_dir);
     404            4 :         }
     405              : 
     406            4 :         prog.push(user_name);
     407            4 :         return cockpit.spawn(prog, { superuser: "require", err: "message" })
     408            4 :                 .then(() => passwd_change(user_name, password))
     409            4 :                 .then(() => {
     410            4 :                     if (locked)
     411            0 :                         return cockpit.spawn([
     412            0 :                             "usermod",
     413            0 :                             user_name,
     414            0 :                             "--lock"
     415            0 :                         ], { superuser: "require", err: "message" });
     416            4 :                     if (force_change)
     417            2 :                         return cockpit.spawn([
     418            2 :                             "passwd",
     419            2 :                             "-e",
     420            2 :                             user_name
     421            2 :                         ], { superuser: "require", err: "message" });
     422            4 :                 })
     423            4 :                 .then(() => {
     424            1 :                     if (force_home) {
     425            1 :                         return cockpit.spawn(["id", user_name, "--group"], { superuser: "require", err: "message" })
     426            1 :                                 .then(gid => cockpit.spawn(["chown", "-hR", `${user_name}:${gid.trim()}`, home_dir], { superuser: "require", err: "message" }));
     427            1 :                     }
     428            4 :                 });
     429            4 :     }
     430              : 
     431            4 :     function passwd_check(force_weak, force_home, force_uid, real_name, user_name, password, password_confirm, locked, home_dir, force_passwd_change, uid, accounts, min_uid, max_uid, change) {
     432            4 :         return validate(force_weak, force_home, force_uid, real_name, user_name, password, password_confirm, uid, accounts, min_uid, max_uid, change).then(valid => {
     433            4 :             if (valid)
     434            2 :                 return create(real_name, user_name, password, locked, uid, force_passwd_change, home_dir, force_home, force_uid);
     435            2 :             else {
     436            0 :                 if (!errors.real_name && !errors.user_name && !errors.home_dir && !errors.uid && !errors.password_confirm && state.password.length <= 256)
     437            0 :                     state.confirm_weak = true;
     438              : 
     439              :                 // Once the form is submitted and passwords do not match, validate confirm password after each keystroke
     440            2 :                 if (errors.password_confirm)
     441            0 :                     state.password_confirm_dirty = true;
     442              : 
     443            2 :                 update();
     444            2 :                 return Promise.reject();
     445            2 :             }
     446            4 :         });
     447            4 :     }
     448              : 
     449            4 :     function update() {
     450            4 :         const props = {
     451            4 :             id: "accounts-create-dialog",
     452            4 :             title: _("Create new account"),
     453            4 :         };
     454            4 :         if (state.dialogLoading) {
     455            4 :             props.body = (
     456            4 :                 <Bullseye>
     457            4 :                     <Spinner />
     458            4 :                 </Bullseye>
     459              :             );
     460            4 :         } else {
     461            4 :             props.body = <AccountCreateBody state={state} errors={errors} change={change} shells={shells} />;
     462            4 :         }
     463              : 
     464            4 :         const footer = {
     465            4 :             actions: [
     466            4 :                 {
     467            4 :                     caption: _("Create"),
     468            4 :                     style: "primary",
     469            4 :                     clicked: () => passwd_check(
     470            4 :                         false, // force weak password was NOT clicked
     471            4 :                         false, // force user with existing home directory was NOT clicked
     472            4 :                         false, // force user with non-unique UID was NOT clicked
     473            4 :                         state.real_name,
     474            4 :                         state.user_name,
     475            4 :                         state.password,
     476            4 :                         state.password_confirm,
     477            4 :                         state.locked,
     478            4 :                         state.home_dir,
     479            4 :                         state.change_passw_force,
     480            4 :                         state.uid,
     481            4 :                         accounts,
     482            4 :                         state.min_uid,
     483            4 :                         state.max_uid,
     484            4 :                         change
     485            4 :                     ),
     486            4 :                     disabled: state.confirm_weak || state.uid_exists || state.home_dir_exists || state.home_dir_is_file
     487            4 :                 }
     488            4 :             ]
     489            4 :         };
     490            1 :         if (state.home_dir_exists) {
     491            1 :             footer.actions.push(
     492            1 :                 {
     493            1 :                     caption: _("Create and change ownership of home directory"),
     494            1 :                     style: "warning",
     495            1 :                     clicked: () => passwd_check(
     496            1 :                         false, // force weak password was NOT clicked
     497            1 :                         true, // force user with existing home directory was WAS clicked
     498            1 :                         false, // force user with non-unique UID was NOT clicked
     499            1 :                         state.real_name,
     500            1 :                         state.user_name,
     501            1 :                         state.password,
     502            1 :                         state.password_confirm,
     503            1 :                         state.locked,
     504            1 :                         state.home_dir,
     505            1 :                         state.change_passw_force,
     506            1 :                         state.uid,
     507            1 :                         accounts,
     508            1 :                         state.min_uid,
     509            1 :                         state.max_uid,
     510            1 :                         change
     511            1 :                     ),
     512            1 :                 }
     513            1 :             );
     514            1 :         }
     515            0 :         if (state.confirm_weak) {
     516            0 :             footer.actions.push(
     517            0 :                 {
     518            0 :                     caption: _("Create account with weak password"),
     519            0 :                     style: "warning",
     520            0 :                     clicked: () => passwd_check(
     521            0 :                         true, // force weak password was WAS clicked
     522            0 :                         false, // force user with existing home directory was NOT clicked
     523            0 :                         false, // force user with non-unique UID was NOT clicked
     524            0 :                         state.real_name,
     525            0 :                         state.user_name,
     526            0 :                         state.password,
     527            0 :                         state.password_confirm,
     528            0 :                         state.locked,
     529            0 :                         state.home_dir,
     530            0 :                         state.change_passw_force,
     531            0 :                         state.uid,
     532            0 :                         accounts,
     533            0 :                         state.min_uid,
     534            0 :                         state.max_uid,
     535            0 :                         change
     536            0 :                     ),
     537            0 :                 }
     538            0 :             );
     539            0 :         }
     540            1 :         if (state.uid_exists) {
     541            1 :             footer.actions.push(
     542            1 :                 {
     543            1 :                     caption: _("Create account with non-unique UID"),
     544            1 :                     style: "warning",
     545            1 :                     clicked: () => passwd_check(
     546            1 :                         false, // force weak password was NOT clicked
     547            1 :                         false, // force user with existing home directory was NOT clicked
     548            1 :                         true, // force user with non-unique UID was WAS clicked
     549            1 :                         state.real_name,
     550            1 :                         state.user_name,
     551            1 :                         state.password,
     552            1 :                         state.password_confirm,
     553            1 :                         state.locked,
     554            1 :                         state.home_dir,
     555            1 :                         state.change_passw_force,
     556            1 :                         state.uid,
     557            1 :                         accounts,
     558            1 :                         state.min_uid,
     559            1 :                         state.max_uid,
     560            1 :                         change
     561            1 :                     ),
     562            1 :                 }
     563            1 :             );
     564            1 :         }
     565              : 
     566            4 :         if (!dlg)
     567            4 :             dlg = show_modal_dialog(props, footer);
     568            4 :         else {
     569            4 :             dlg.setProps(props);
     570            4 :             dlg.setFooterProps(footer);
     571            4 :         }
     572            4 :     }
     573              : 
     574            4 :     update();
     575            4 :     get_defaults();
     576            4 : }
        

Generated by: LCOV version 2.0-1