Move users menu into authentication submenu (#3678)
* Move users menu into authentication submenu * Tests * Update * Update
This commit is contained in:
parent
725c3fed09
commit
3e99cfbba7
|
|
@ -27,5 +27,6 @@ class DockerProfile(Profile):
|
||||||
def post_install(self, install_session: 'Installer') -> None:
|
def post_install(self, install_session: 'Installer') -> None:
|
||||||
from archinstall.lib.args import arch_config_handler
|
from archinstall.lib.args import arch_config_handler
|
||||||
|
|
||||||
for user in arch_config_handler.config.users:
|
if auth_config := arch_config_handler.config.auth_config:
|
||||||
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
for user in auth_config.users:
|
||||||
|
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
||||||
|
|
|
||||||
|
|
@ -76,16 +76,15 @@ class ArchConfig:
|
||||||
services: list[str] = field(default_factory=list)
|
services: list[str] = field(default_factory=list)
|
||||||
custom_commands: 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]:
|
def unsafe_json(self) -> dict[str, Any]:
|
||||||
config: dict[str, list[UserSerialization] | str | None] = {
|
config: dict[str, list[UserSerialization] | str | None] = {}
|
||||||
'users': [user.json() for user in self.users],
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.auth_config and self.auth_config.root_enc_password:
|
if self.auth_config:
|
||||||
config['root_enc_password'] = self.auth_config.root_enc_password.enc_password
|
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:
|
if self.disk_config:
|
||||||
disk_encryption = self.disk_config.disk_encryption
|
disk_encryption = self.disk_config.disk_encryption
|
||||||
|
|
@ -177,13 +176,6 @@ class ArchConfig:
|
||||||
if net_config := args_config.get('network_config', None):
|
if net_config := args_config.get('network_config', None):
|
||||||
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
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):
|
if bootloader_config := args_config.get('bootloader', None):
|
||||||
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
arch_config.bootloader = Bootloader.from_arg(bootloader_config)
|
||||||
|
|
||||||
|
|
@ -235,6 +227,19 @@ class ArchConfig:
|
||||||
arch_config.auth_config = AuthenticationConfiguration()
|
arch_config.auth_config = AuthenticationConfiguration()
|
||||||
arch_config.auth_config.root_enc_password = root_password
|
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', []):
|
if custom_commands := args_config.get('custom_commands', []):
|
||||||
arch_config.custom_commands = custom_commands
|
arch_config.custom_commands = custom_commands
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,10 @@ class AuthenticationHandler:
|
||||||
self,
|
self,
|
||||||
install_session: 'Installer',
|
install_session: 'Installer',
|
||||||
auth_config: AuthenticationConfiguration,
|
auth_config: AuthenticationConfiguration,
|
||||||
users: list['User'],
|
|
||||||
hostname: str,
|
hostname: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
if auth_config.u2f_config and users is not None:
|
if auth_config.u2f_config and auth_config.users is not None:
|
||||||
self._setup_u2f_login(install_session, auth_config.u2f_config, users, hostname)
|
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:
|
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)
|
self._configure_u2f_mapping(install_session, u2f_config, users, hostname)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
from archinstall.lib.disk.fido import Fido2
|
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.menu.abstract_menu import AbstractSubMenu
|
||||||
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
|
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.translationhandler import tr
|
||||||
from archinstall.lib.utils.util import get_password
|
from archinstall.lib.utils.util import get_password
|
||||||
from archinstall.tui.curses_menu import SelectMenu
|
from archinstall.tui.curses_menu import SelectMenu
|
||||||
|
|
@ -41,6 +43,12 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
|
||||||
preview_action=self._prev_root_pwd,
|
preview_action=self._prev_root_pwd,
|
||||||
key='root_enc_password',
|
key='root_enc_password',
|
||||||
),
|
),
|
||||||
|
MenuItem(
|
||||||
|
text=tr('User account'),
|
||||||
|
action=self._create_user_account,
|
||||||
|
preview_action=self._prev_users,
|
||||||
|
key='users',
|
||||||
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr('U2F login setup'),
|
text=tr('U2F login setup'),
|
||||||
action=select_u2f_login,
|
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:
|
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||||
if item.value is not None:
|
if item.value is not None:
|
||||||
password: Password = item.value
|
password: Password = item.value
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ from .interactions.general_conf import (
|
||||||
ask_hostname,
|
ask_hostname,
|
||||||
ask_ntp,
|
ask_ntp,
|
||||||
)
|
)
|
||||||
from .interactions.manage_users_conf import ask_for_additional_users
|
|
||||||
from .interactions.network_menu import ask_to_configure_network
|
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 .interactions.system_conf import ask_for_bootloader, ask_for_swap, ask_for_uki, select_kernel
|
||||||
from .locale.locale_menu import LocaleMenu
|
from .locale.locale_menu import LocaleMenu
|
||||||
|
|
@ -33,7 +32,6 @@ from .models.mirrors import MirrorConfiguration
|
||||||
from .models.network import NetworkConfiguration, NicType
|
from .models.network import NetworkConfiguration, NicType
|
||||||
from .models.packages import Repository
|
from .models.packages import Repository
|
||||||
from .models.profile import ProfileConfiguration
|
from .models.profile import ProfileConfiguration
|
||||||
from .models.users import User
|
|
||||||
from .output import FormattedOutput
|
from .output import FormattedOutput
|
||||||
from .pacman.config import PacmanConfig
|
from .pacman.config import PacmanConfig
|
||||||
from .translationhandler import Language, tr, translation_handler
|
from .translationhandler import Language, tr, translation_handler
|
||||||
|
|
@ -115,12 +113,6 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
preview_action=self._prev_authentication,
|
preview_action=self._prev_authentication,
|
||||||
key='auth_config',
|
key='auth_config',
|
||||||
),
|
),
|
||||||
MenuItem(
|
|
||||||
text=tr('User account'),
|
|
||||||
action=self._create_user_account,
|
|
||||||
preview_action=self._prev_users,
|
|
||||||
key='users',
|
|
||||||
),
|
|
||||||
MenuItem(
|
MenuItem(
|
||||||
text=tr('Profile'),
|
text=tr('Profile'),
|
||||||
action=self._select_profile,
|
action=self._select_profile,
|
||||||
|
|
@ -207,24 +199,20 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
save_config(self._arch_config)
|
save_config(self._arch_config)
|
||||||
|
|
||||||
def _missing_configs(self) -> list[str]:
|
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:
|
def check(s: str) -> bool:
|
||||||
item = self._item_group.find_by_key(s)
|
item = self._item_group.find_by_key(s)
|
||||||
return item.has_value()
|
return item.has_value()
|
||||||
|
|
||||||
def has_superuser() -> bool:
|
def has_superuser() -> bool:
|
||||||
item = self._item_group.find_by_key('users')
|
if auth_config and auth_config.users:
|
||||||
|
return any([u.sudo for u in auth_config.users])
|
||||||
if item.has_value():
|
|
||||||
users = item.value
|
|
||||||
if users:
|
|
||||||
return any([u.sudo for u in users])
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
missing = set()
|
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():
|
if (auth_config is None or auth_config.root_enc_password is None) and not has_superuser():
|
||||||
missing.add(
|
missing.add(
|
||||||
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
|
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:
|
if auth_config.root_enc_password:
|
||||||
output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n'
|
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:
|
if auth_config.u2f_config:
|
||||||
u2f_config = auth_config.u2f_config
|
u2f_config = auth_config.u2f_config
|
||||||
login_method = u2f_config.u2f_login_method.display_value()
|
login_method = u2f_config.u2f_login_method.display_value()
|
||||||
|
|
@ -475,13 +466,6 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
|
|
||||||
return 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:
|
def _prev_profile(self, item: MenuItem) -> str | None:
|
||||||
profile_config: ProfileConfiguration | None = item.value
|
profile_config: ProfileConfiguration | None = item.value
|
||||||
|
|
||||||
|
|
@ -543,11 +527,6 @@ class GlobalMenu(AbstractMenu[None]):
|
||||||
|
|
||||||
return packages
|
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:
|
def _mirror_configuration(self, preset: MirrorConfiguration | None = None) -> MirrorConfiguration:
|
||||||
mirror_configuration = MirrorMenu(preset=preset).run()
|
mirror_configuration = MirrorMenu(preset=preset).run()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, NotRequired, TypedDict
|
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
|
from archinstall.lib.translationhandler import tr
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,6 +60,7 @@ class U2FLoginConfiguration:
|
||||||
@dataclass
|
@dataclass
|
||||||
class AuthenticationConfiguration:
|
class AuthenticationConfiguration:
|
||||||
root_enc_password: Password | None = None
|
root_enc_password: Password | None = None
|
||||||
|
users: list[User] = field(default_factory=list)
|
||||||
u2f_config: U2FLoginConfiguration | None = None
|
u2f_config: U2FLoginConfiguration | None = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,10 @@ def perform_installation(mountpoint: Path) -> None:
|
||||||
config.profile_config,
|
config.profile_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
if users := config.users:
|
if config.auth_config:
|
||||||
installation.create_users(users)
|
if config.auth_config.users:
|
||||||
|
installation.create_users(config.auth_config.users)
|
||||||
if config.auth_config and config.users:
|
auth_handler.setup_auth(installation, config.auth_config, config.hostname)
|
||||||
auth_handler.setup_auth(installation, config.auth_config, config.users, config.hostname)
|
|
||||||
|
|
||||||
if config.packages and config.packages[0] != '':
|
if config.packages and config.packages[0] != '':
|
||||||
installation.add_additional_packages(config.packages)
|
installation.add_additional_packages(config.packages)
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,14 @@ def test_config_file_parsing(
|
||||||
),
|
),
|
||||||
auth_config=AuthenticationConfiguration(
|
auth_config=AuthenticationConfiguration(
|
||||||
root_enc_password=Password(enc_password='password_hash'),
|
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_config=U2FLoginConfiguration(
|
||||||
u2f_login_method=U2FLoginMethod.Passwordless,
|
u2f_login_method=U2FLoginMethod.Passwordless,
|
||||||
passwordless_sudo=True,
|
passwordless_sudo=True,
|
||||||
|
|
@ -215,14 +223,6 @@ def test_config_file_parsing(
|
||||||
parallel_downloads=66,
|
parallel_downloads=66,
|
||||||
swap=False,
|
swap=False,
|
||||||
timezone='UTC',
|
timezone='UTC',
|
||||||
users=[
|
|
||||||
User(
|
|
||||||
username='user_name',
|
|
||||||
password=Password(enc_password='password_hash'),
|
|
||||||
sudo=True,
|
|
||||||
groups=['wheel'],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
services=['service_1', 'service_2'],
|
services=['service_1', 'service_2'],
|
||||||
custom_commands=["echo 'Hello, World!'"],
|
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 is not None
|
||||||
assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd')
|
assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd')
|
||||||
|
|
||||||
assert arch_config.users == [
|
assert arch_config.auth_config.users == [
|
||||||
User(
|
User(
|
||||||
username='user_name',
|
username='user_name',
|
||||||
password=Password(plaintext='userPwd'),
|
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 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.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(
|
User(
|
||||||
username='t',
|
username='t',
|
||||||
password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),
|
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 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.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(
|
User(
|
||||||
username='t',
|
username='t',
|
||||||
password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),
|
password=Password(enc_password='$y$j9T$3KxMigAEnjtzbjalhLewE.$gmuoQtc9RNY/PmO/GxHHYvkZNO86Eeftg1Oc7L.QSO/'),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue