Move users menu into authentication submenu (#3678)

* Move users menu into authentication submenu

* Tests

* Update

* Update
This commit is contained in:
Daniel Girtler 2025-07-22 06:57:39 +00:00 committed by GitHub
parent 725c3fed09
commit 3e99cfbba7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 68 deletions

View File

@ -27,5 +27,6 @@ class DockerProfile(Profile):
def post_install(self, install_session: 'Installer') -> None:
from archinstall.lib.args import arch_config_handler
for user in arch_config_handler.config.users:
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
if auth_config := arch_config_handler.config.auth_config:
for user in auth_config.users:
install_session.arch_chroot(f'usermod -a -G docker {user.username}')

View File

@ -76,16 +76,15 @@ class ArchConfig:
services: list[str] = field(default_factory=list)
custom_commands: list[str] = field(default_factory=list)
# Special fields that should be handle with care due to security implications
users: list[User] = field(default_factory=list)
def unsafe_json(self) -> dict[str, Any]:
config: dict[str, list[UserSerialization] | str | None] = {
'users': [user.json() for user in self.users],
}
config: dict[str, list[UserSerialization] | str | None] = {}
if self.auth_config and self.auth_config.root_enc_password:
config['root_enc_password'] = self.auth_config.root_enc_password.enc_password
if self.auth_config:
if self.auth_config.users:
config['users'] = [user.json() for user in self.auth_config.users]
if self.auth_config.root_enc_password:
config['root_enc_password'] = self.auth_config.root_enc_password.enc_password
if self.disk_config:
disk_encryption = self.disk_config.disk_encryption
@ -177,13 +176,6 @@ class ArchConfig:
if net_config := args_config.get('network_config', None):
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
# DEPRECATED: backwards copatibility
if users := args_config.get('!users', None):
arch_config.users = User.parse_arguments(users)
if users := args_config.get('users', None):
arch_config.users = User.parse_arguments(users)
if bootloader_config := args_config.get('bootloader', None):
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
@ -235,6 +227,19 @@ class ArchConfig:
arch_config.auth_config = AuthenticationConfiguration()
arch_config.auth_config.root_enc_password = root_password
# DEPRECATED: backwards copatibility
users: list[User] = []
if args_users := args_config.get('!users', None):
users = User.parse_arguments(args_users)
if args_users := args_config.get('users', None):
users = User.parse_arguments(args_users)
if users:
if arch_config.auth_config is None:
arch_config.auth_config = AuthenticationConfiguration()
arch_config.auth_config.users = users
if custom_commands := args_config.get('custom_commands', []):
arch_config.custom_commands = custom_commands

View File

@ -18,11 +18,10 @@ class AuthenticationHandler:
self,
install_session: 'Installer',
auth_config: AuthenticationConfiguration,
users: list['User'],
hostname: str,
) -> None:
if auth_config.u2f_config and users is not None:
self._setup_u2f_login(install_session, auth_config.u2f_config, users, hostname)
if auth_config.u2f_config and auth_config.users is not None:
self._setup_u2f_login(install_session, auth_config.u2f_config, auth_config.users, hostname)
def _setup_u2f_login(self, install_session: 'Installer', u2f_config: U2FLoginConfiguration, users: list[User], hostname: str) -> None:
self._configure_u2f_mapping(install_session, u2f_config, users, hostname)

View File

@ -1,9 +1,11 @@
from typing import override
from archinstall.lib.disk.fido import Fido2
from archinstall.lib.interactions.manage_users_conf import ask_for_additional_users
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.users import Password
from archinstall.lib.models.users import Password, User
from archinstall.lib.output import FormattedOutput
from archinstall.lib.translationhandler import tr
from archinstall.lib.utils.util import get_password
from archinstall.tui.curses_menu import SelectMenu
@ -41,6 +43,12 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
preview_action=self._prev_root_pwd,
key='root_enc_password',
),
MenuItem(
text=tr('User account'),
action=self._create_user_account,
preview_action=self._prev_users,
key='users',
),
MenuItem(
text=tr('U2F login setup'),
action=select_u2f_login,
@ -50,6 +58,18 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
),
]
def _create_user_account(self, preset: list[User] | None = None) -> list[User]:
preset = [] if preset is None else preset
users = ask_for_additional_users(defined_users=preset)
return users
def _prev_users(self, item: MenuItem) -> str | None:
users: list[User] | None = item.value
if users:
return FormattedOutput.as_table(users)
return None
def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None:
password: Password = item.value

View File

@ -21,7 +21,6 @@ from .interactions.general_conf import (
ask_hostname,
ask_ntp,
)
from .interactions.manage_users_conf import ask_for_additional_users
from .interactions.network_menu import ask_to_configure_network
from .interactions.system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_kernel
from .locale.locale_menu import LocaleMenu
@ -33,7 +32,6 @@ from .models.mirrors import MirrorConfiguration
from .models.network import NetworkConfiguration, NicType
from .models.packages import Repository
from .models.profile import ProfileConfiguration
from .models.users import User
from .output import FormattedOutput
from .pacman.config import PacmanConfig
from .translationhandler import Language, tr, translation_handler
@ -115,12 +113,6 @@ class GlobalMenu(AbstractMenu[None]):
preview_action=self._prev_authentication,
key='auth_config',
),
MenuItem(
text=tr('User account'),
action=self._create_user_account,
preview_action=self._prev_users,
key='users',
),
MenuItem(
text=tr('Profile'),
action=self._select_profile,
@ -207,24 +199,20 @@ class GlobalMenu(AbstractMenu[None]):
save_config(self._arch_config)
def _missing_configs(self) -> list[str]:
item: MenuItem = self._item_group.find_by_key('auth_config')
auth_config: AuthenticationConfiguration | None = item.value
def check(s: str) -> bool:
item = self._item_group.find_by_key(s)
return item.has_value()
def has_superuser() -> bool:
item = self._item_group.find_by_key('users')
if item.has_value():
users = item.value
if users:
return any([u.sudo for u in users])
if auth_config and auth_config.users:
return any([u.sudo for u in auth_config.users])
return False
missing = set()
item: MenuItem = self._item_group.find_by_key('auth_config')
auth_config: AuthenticationConfiguration | None = item.value
if (auth_config is None or auth_config.root_enc_password is None) and not has_superuser():
missing.add(
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
@ -312,6 +300,9 @@ class GlobalMenu(AbstractMenu[None]):
if auth_config.root_enc_password:
output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n'
if auth_config.users:
output += FormattedOutput.as_table(auth_config.users) + '\n'
if auth_config.u2f_config:
u2f_config = auth_config.u2f_config
login_method = u2f_config.u2f_login_method.display_value()
@ -475,13 +466,6 @@ class GlobalMenu(AbstractMenu[None]):
return None
def _prev_users(self, item: MenuItem) -> str | None:
users: list[User] | None = item.value
if users:
return FormattedOutput.as_table(users)
return None
def _prev_profile(self, item: MenuItem) -> str | None:
profile_config: ProfileConfiguration | None = item.value
@ -543,11 +527,6 @@ class GlobalMenu(AbstractMenu[None]):
return packages
def _create_user_account(self, preset: list[User] | None = None) -> list[User]:
preset = [] if preset is None else preset
users = ask_for_additional_users(defined_users=preset)
return users
def _mirror_configuration(self, preset: MirrorConfiguration | None = None) -> MirrorConfiguration:
mirror_configuration = MirrorMenu(preset=preset).run()

View File

@ -1,8 +1,8 @@
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, NotRequired, TypedDict
from archinstall.lib.models.users import Password
from archinstall.lib.models.users import Password, User
from archinstall.lib.translationhandler import tr
@ -60,6 +60,7 @@ class U2FLoginConfiguration:
@dataclass
class AuthenticationConfiguration:
root_enc_password: Password | None = None
users: list[User] = field(default_factory=list)
u2f_config: U2FLoginConfiguration | None = None
@staticmethod

View File

@ -114,11 +114,10 @@ def perform_installation(mountpoint: Path) -> None:
config.profile_config,
)
if users := config.users:
installation.create_users(users)
if config.auth_config and config.users:
auth_handler.setup_auth(installation, config.auth_config, config.users, config.hostname)
if config.auth_config:
if config.auth_config.users:
installation.create_users(config.auth_config.users)
auth_handler.setup_auth(installation, config.auth_config, config.hostname)
if config.packages and config.packages[0] != '':
installation.add_additional_packages(config.packages)

View File

@ -135,6 +135,14 @@ def test_config_file_parsing(
),
auth_config=AuthenticationConfiguration(
root_enc_password=Password(enc_password='password_hash'),
users=[
User(
username='user_name',
password=Password(enc_password='password_hash'),
sudo=True,
groups=['wheel'],
),
],
u2f_config=U2FLoginConfiguration(
u2f_login_method=U2FLoginMethod.Passwordless,
passwordless_sudo=True,
@ -215,14 +223,6 @@ def test_config_file_parsing(
parallel_downloads=66,
swap=False,
timezone='UTC',
users=[
User(
username='user_name',
password=Password(enc_password='password_hash'),
sudo=True,
groups=['wheel'],
),
],
services=['service_1', 'service_2'],
custom_commands=["echo 'Hello, World!'"],
)
@ -283,7 +283,7 @@ def test_deprecated_creds_config_parsing(
assert arch_config.auth_config is not None
assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd')
assert arch_config.users == [
assert arch_config.auth_config.users == [
User(
username='user_name',
password=Password(plaintext='userPwd'),
@ -334,7 +334,7 @@ def test_encrypted_creds_with_arg(
assert arch_config.auth_config is not None
assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
assert arch_config.users == [
assert arch_config.auth_config.users == [
User(
username='t',
password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),
@ -363,7 +363,7 @@ def test_encrypted_creds_with_env_var(
assert arch_config.auth_config is not None
assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
assert arch_config.users == [
assert arch_config.auth_config.users == [
User(
username='t',
password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),