From 870da403e79ab50350803b45f200e0b272334989 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Fri, 27 May 2022 05:48:29 +1000 Subject: Rework user management (#1220) * Rework users * Update user installation * Fix config serialization * Update * Update schemas and documentation * Update * Fix flake8 * Make users mypy compatible * Fix minor copy Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- archinstall/lib/user_interaction/__init__.py | 2 +- .../lib/user_interaction/manage_users_conf.py | 177 +++++++-------------- archinstall/lib/user_interaction/network_conf.py | 2 + .../lib/user_interaction/subvolume_config.py | 2 + 4 files changed, 64 insertions(+), 119 deletions(-) (limited to 'archinstall/lib/user_interaction') diff --git a/archinstall/lib/user_interaction/__init__.py b/archinstall/lib/user_interaction/__init__.py index b0174d94..8aba4b4d 100644 --- a/archinstall/lib/user_interaction/__init__.py +++ b/archinstall/lib/user_interaction/__init__.py @@ -1,5 +1,5 @@ from .save_conf import save_config -from .manage_users_conf import ask_for_superuser_account, ask_for_additional_users +from .manage_users_conf import ask_for_additional_users from .backwards_compatible_conf import generic_select, generic_multi_select from .locale_conf import select_locale_lang, select_locale_enc from .system_conf import select_kernel, select_harddrives, select_driver, ask_for_bootloader, ask_for_swap diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py index ea909f35..567a2964 100644 --- a/archinstall/lib/user_interaction/manage_users_conf.py +++ b/archinstall/lib/user_interaction/manage_users_conf.py @@ -1,14 +1,12 @@ from __future__ import annotations -import logging import re -from typing import Any, Dict, TYPE_CHECKING, List +from typing import Any, Dict, TYPE_CHECKING, List, Optional +from .utils import get_password from ..menu import Menu from ..menu.list_manager import ListManager -from ..output import log -from ..storage import storage -from .utils import get_password +from ..models.users import User if TYPE_CHECKING: _: Any @@ -19,7 +17,7 @@ class UserList(ListManager): subclass of ListManager for the managing of user accounts """ - def __init__(self, prompt: str, lusers: dict, sudo: bool = None): + def __init__(self, prompt: str, lusers: List[User]): """ param: prompt type: str @@ -27,140 +25,83 @@ class UserList(ListManager): type: Dict param: sudo. boolean to determine if we handle superusers or users. If None handles both types """ - self.sudo = sudo - self.actions = [ + self._actions = [ str(_('Add a user')), str(_('Change password')), str(_('Promote/Demote user')), str(_('Delete User')) ] - super().__init__(prompt, lusers, self.actions, self.actions[0]) - - def reformat(self, data: List) -> Dict: - def format_element(elem :str): - # secret gives away the length of the password - if data[elem].get('!password'): - pwd = '*' * 16 - else: - pwd = '' - if data[elem].get('sudoer'): - super_user = 'Superuser' - else: - super_user = ' ' - return f"{elem:16}: password {pwd:16} {super_user}" + super().__init__(prompt, lusers, self._actions, self._actions[0]) - return {format_element(e): e for e in data} + def reformat(self, data: List[User]) -> Dict[str, User]: + return {e.display(): e for e in data} def action_list(self): - if self.target: - active_user = list(self.target.keys())[0] - else: - active_user = None - sudoer = self.target[active_user].get('sudoer', False) - if self.sudo is None: - return self.actions - if self.sudo and sudoer: - return self.actions - elif self.sudo and not sudoer: - return [self.actions[2]] - elif not self.sudo and sudoer: - return [self.actions[2]] + active_user = self.target if self.target else None + + if active_user is None: + return [self._actions[0]] else: - return self.actions + return self._actions[1:] - def exec_action(self, data: Any): + def exec_action(self, data: List[User]) -> List[User]: if self.target: - active_user = list(self.target.keys())[0] + active_user = self.target else: active_user = None - if self.action == self.actions[0]: # add - new_user = self.add_user() - # no unicity check, if exists will be replaced - data.update(new_user) - elif self.action == self.actions[1]: # change password - data[active_user]['!password'] = get_password( - prompt=str(_('Password for user "{}": ').format(active_user))) - elif self.action == self.actions[2]: # promote/demote - data[active_user]['sudoer'] = not data[active_user]['sudoer'] - elif self.action == self.actions[3]: # delete - del data[active_user] + if self.action == self._actions[0]: # add + new_user = self._add_user() + if new_user is not None: + # in case a user with the same username as an existing user + # was created we'll replace the existing one + data = [d for d in data if d.username != new_user.username] + data += [new_user] + elif self.action == self._actions[1]: # change password + prompt = str(_('Password for user "{}": ').format(active_user.username)) + new_password = get_password(prompt=prompt) + if new_password: + user = next(filter(lambda x: x == active_user, data), 1) + user.password = new_password + elif self.action == self._actions[2]: # promote/demote + user = next(filter(lambda x: x == active_user, data), 1) + user.sudo = False if user.sudo else True + elif self.action == self._actions[3]: # delete + data = [d for d in data if d != active_user] + + return data def _check_for_correct_username(self, username: str) -> bool: if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32: return True - log("The username you entered is invalid. Try again", level=logging.WARNING, fg='red') return False - def add_user(self): + def _add_user(self) -> Optional[User]: print(_('\nDefine a new user\n')) - prompt = str(_("User Name : ")) + prompt = str(_('Enter username (leave blank to skip): ')) + while True: - userid = input(prompt).strip(' ') - if not userid: - return {} # end - if not self._check_for_correct_username(userid): - pass + username = input(prompt).strip(' ') + if not username: + return None + if not self._check_for_correct_username(username): + prompt = str(_("The username you entered is invalid. Try again")) + '\n' + prompt else: break - if self.sudo: - sudoer = True - elif self.sudo is not None and not self.sudo: - sudoer = False - else: - sudoer = False - sudo_choice = Menu(str(_('Should {} be a superuser (sudoer)?')).format(userid), Menu.yes_no(), - skip=False, - preset_values=Menu.yes() if sudoer else Menu.no(), - default_option=Menu.no()).run() - sudoer = True if sudo_choice == Menu.yes() else False - - password = get_password(prompt=str(_('Password for user "{}": ').format(userid))) - - return {userid: {"!password": password, "sudoer": sudoer}} - - -def manage_users(prompt: str, sudo: bool) -> tuple[dict, dict]: - # TODO Filtering and some kind of simpler code - lusers = {} - if storage['arguments'].get('!superusers', {}): - lusers.update({ - uid: { - '!password': storage['arguments']['!superusers'][uid].get('!password'), - 'sudoer': True - } - for uid in storage['arguments'].get('!superusers', {}) - }) - if storage['arguments'].get('!users', {}): - lusers.update({ - uid: { - '!password': storage['arguments']['!users'][uid].get('!password'), - 'sudoer': False - } - for uid in storage['arguments'].get('!users', {}) - }) - # processing - lusers = UserList(prompt, lusers, sudo).run() - # return data - superusers = { - uid: { - '!password': lusers[uid].get('!password') - } - for uid in lusers if lusers[uid].get('sudoer', False) - } - users = {uid: {'!password': lusers[uid].get('!password')} for uid in lusers if not lusers[uid].get('sudoer', False)} - storage['arguments']['!superusers'] = superusers - storage['arguments']['!users'] = users - return superusers, users - - -def ask_for_superuser_account(prompt: str) -> Dict[str, Dict[str, str]]: - prompt = prompt if prompt else str(_('Define users with sudo privilege, by username: ')) - superusers, dummy = manage_users(prompt, sudo=True) - return superusers - - -def ask_for_additional_users(prompt: str = '') -> Dict[str, Dict[str, str | None]]: - prompt = prompt if prompt else _('Any additional users to install (leave blank for no users): ') - dummy, users = manage_users(prompt, sudo=False) + + password = get_password(prompt=str(_('Password for user "{}": ').format(username))) + + choice = Menu( + str(_('Should "{}" be a superuser (sudo)?')).format(username), Menu.yes_no(), + skip=False, + default_option=Menu.no() + ).run() + + sudo = True if choice.value == Menu.yes() else False + return User(username, password, sudo) + + +def ask_for_additional_users(prompt: str = '', defined_users: List[User] = []) -> List[User]: + prompt = prompt if prompt else _('Enter username (leave blank to skip): ') + users = UserList(prompt, defined_users).run() return users diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index 25e9d4c6..5154d8b1 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -64,6 +64,8 @@ class ManualNetworkConfig(ListManager): elif self.action == self._action_delete: del data[iface_name] + return data + def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]: all_ifaces = list_interfaces().values() available = set(all_ifaces) - set(existing_ifaces) diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py index 7383b13d..af783639 100644 --- a/archinstall/lib/user_interaction/subvolume_config.py +++ b/archinstall/lib/user_interaction/subvolume_config.py @@ -57,6 +57,8 @@ class SubvolumeList(ListManager): data.update(self.target) + return data + class SubvolumeMenu(GeneralMenu): def __init__(self,parameters,action=None): -- cgit v1.2.3-70-g09d2