Skip to content

Commit e0c3bb3

Browse files
authored
Move root password menu into authentication menu (#3650)
1 parent 8736926 commit e0c3bb3

File tree

7 files changed

+71
-39
lines changed

7 files changed

+71
-39
lines changed

archinstall/lib/args.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from archinstall.lib.models.packages import Repository
2424
from archinstall.lib.models.profile import ProfileConfiguration
2525
from archinstall.lib.models.users import Password, User
26+
from archinstall.lib.models.profile_model import ProfileConfiguration
27+
from archinstall.lib.models.users import Password, User, UserSerialization
2628
from archinstall.lib.output import debug, error, logger, warn
2729
from archinstall.lib.plugins import load_plugin
2830
from archinstall.lib.translationhandler import Language, tr, translation_handler
@@ -78,14 +80,15 @@ class ArchConfig:
7880

7981
# Special fields that should be handle with care due to security implications
8082
users: list[User] = field(default_factory=list)
81-
root_enc_password: Password | None = None
8283

8384
def unsafe_json(self) -> dict[str, Any]:
84-
config = {
85+
config: dict[str, list[UserSerialization] | str | None] = {
8586
'users': [user.json() for user in self.users],
86-
'root_enc_password': self.root_enc_password.enc_password if self.root_enc_password else None,
8787
}
8888

89+
if self.auth_config and self.auth_config.root_enc_password:
90+
config['root_enc_password'] = self.auth_config.root_enc_password.enc_password
91+
8992
if self.disk_config:
9093
disk_encryption = self.disk_config.disk_encryption
9194
if disk_encryption and disk_encryption.encryption_password:
@@ -222,11 +225,17 @@ def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig':
222225
arch_config.services = services
223226

224227
# DEPRECATED: backwards compatibility
228+
root_password = None
225229
if root_password := args_config.get('!root-password', None):
226-
arch_config.root_enc_password = Password(plaintext=root_password)
230+
root_password = Password(plaintext=root_password)
227231

228232
if enc_password := args_config.get('root_enc_password', None):
229-
arch_config.root_enc_password = Password(enc_password=enc_password)
233+
root_password = Password(enc_password=enc_password)
234+
235+
if root_password is not None:
236+
if arch_config.auth_config is None:
237+
arch_config.auth_config = AuthenticationConfiguration()
238+
arch_config.auth_config.root_enc_password = root_password
230239

231240
if custom_commands := args_config.get('custom_commands', []):
232241
arch_config.custom_commands = custom_commands

archinstall/lib/authentication/authentication_menu.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from archinstall.lib.disk.fido import Fido2
44
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
55
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
6+
from archinstall.lib.models.users import Password
67
from archinstall.lib.translationhandler import tr
8+
from archinstall.lib.utils.util import get_password
79
from archinstall.tui.curses_menu import SelectMenu
810
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
911
from archinstall.tui.result import ResultType
@@ -33,6 +35,12 @@ def run(self, additional_title: str | None = None) -> AuthenticationConfiguratio
3335

3436
def _define_menu_options(self) -> list[MenuItem]:
3537
return [
38+
MenuItem(
39+
text=tr('Root password'),
40+
action=select_root_password,
41+
preview_action=self._prev_root_pwd,
42+
key='root_enc_password',
43+
),
3644
MenuItem(
3745
text=tr('U2F login setup'),
3846
action=select_u2f_login,
@@ -42,6 +50,18 @@ def _define_menu_options(self) -> list[MenuItem]:
4250
),
4351
]
4452

53+
def _prev_root_pwd(self, item: MenuItem) -> str | None:
54+
if item.value is not None:
55+
password: Password = item.value
56+
return f'{tr("Root password")}: {password.hidden()}'
57+
return None
58+
59+
def _depends_on_u2f(self) -> bool:
60+
devices = Fido2.get_fido2_devices()
61+
if not devices:
62+
return False
63+
return True
64+
4565
def _prev_u2f_login(self, item: MenuItem) -> str | None:
4666
if item.value is not None:
4767
u2f_config: U2FLoginConfiguration = item.value
@@ -61,6 +81,11 @@ def _prev_u2f_login(self, item: MenuItem) -> str | None:
6181
return None
6282

6383

84+
def select_root_password(preset: str | None = None) -> Password | None:
85+
password = get_password(text=tr('Root password'), allow_skip=True)
86+
return password
87+
88+
6489
def select_u2f_login(preset: U2FLoginConfiguration) -> U2FLoginConfiguration | None:
6590
devices = Fido2.get_fido2_devices()
6691
if not devices:

archinstall/lib/global_menu.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@
3434
from .models.packages import Repository
3535
from .models.profile import ProfileConfiguration
3636
from .models.users import Password, User
37+
from .models.profile_model import ProfileConfiguration
38+
from .models.users import User
3739
from .output import FormattedOutput
3840
from .pacman.config import PacmanConfig
3941
from .translationhandler import Language, tr, translation_handler
40-
from .utils.util import get_password
4142

4243

4344
class GlobalMenu(AbstractMenu[None]):
@@ -110,16 +111,9 @@ def _get_menu_options(self) -> list[MenuItem]:
110111
preview_action=self._prev_hostname,
111112
key='hostname',
112113
),
113-
MenuItem(
114-
text=tr('Root password'),
115-
action=self._set_root_password,
116-
preview_action=self._prev_root_pwd,
117-
key='root_enc_password',
118-
),
119114
MenuItem(
120115
text=tr('Authentication'),
121116
action=self._select_authentication,
122-
value=[],
123117
preview_action=self._prev_authentication,
124118
key='auth_config',
125119
),
@@ -230,13 +224,16 @@ def has_superuser() -> bool:
230224

231225
missing = set()
232226

227+
item: MenuItem = self._item_group.find_by_key('auth_config')
228+
auth_config: AuthenticationConfiguration | None = item.value
229+
230+
if (auth_config is None or auth_config.root_enc_password is None) and not has_superuser():
231+
missing.add(
232+
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
233+
)
234+
233235
for item in self._item_group.items:
234-
if item.key in ['root_enc_password', 'users']:
235-
if not check('root_enc_password') and not has_superuser():
236-
missing.add(
237-
tr('Either root-password or at least 1 user with sudo privileges must be specified'),
238-
)
239-
elif item.mandatory:
236+
if item.mandatory:
240237
assert item.key is not None
241238
if not check(item.key):
242239
missing.add(item.text)
@@ -314,6 +311,9 @@ def _prev_authentication(self, item: MenuItem) -> str | None:
314311
auth_config: AuthenticationConfiguration = item.value
315312
output = ''
316313

314+
if auth_config.root_enc_password:
315+
output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n'
316+
317317
if auth_config.u2f_config:
318318
u2f_config = auth_config.u2f_config
319319
login_method = u2f_config.u2f_login_method.display_value()
@@ -400,12 +400,6 @@ def _prev_hostname(self, item: MenuItem) -> str | None:
400400
return f'{tr("Hostname")}: {item.value}'
401401
return None
402402

403-
def _prev_root_pwd(self, item: MenuItem) -> str | None:
404-
if item.value is not None:
405-
password: Password = item.value
406-
return f'{tr("Root password")}: {password.hidden()}'
407-
return None
408-
409403
def _prev_parallel_dw(self, item: MenuItem) -> str | None:
410404
if item.value is not None:
411405
return f'{tr("Parallel Downloads")}: {item.value}'
@@ -510,10 +504,6 @@ def _prev_profile(self, item: MenuItem) -> str | None:
510504

511505
return None
512506

513-
def _set_root_password(self, preset: str | None = None) -> Password | None:
514-
password = get_password(text=tr('Root password'), allow_skip=True)
515-
return password
516-
517507
def _select_disk_config(
518508
self,
519509
preset: DiskLayoutConfiguration | None = None,

archinstall/lib/models/authentication.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from enum import Enum
33
from typing import Any, NotRequired, TypedDict
44

5+
from archinstall.lib.models.users import Password
56
from archinstall.lib.translationhandler import tr
67

78

@@ -58,6 +59,7 @@ def parse_arg(args: U2FLoginConfigSerialization) -> 'U2FLoginConfiguration | Non
5859

5960
@dataclass
6061
class AuthenticationConfiguration:
62+
root_enc_password: Password | None = None
6163
u2f_config: U2FLoginConfiguration | None = None
6264

6365
@staticmethod
@@ -67,6 +69,9 @@ def parse_arg(args: dict[str, Any]) -> 'AuthenticationConfiguration':
6769
if (u2f_config := args.get('u2f_config')) is not None:
6870
auth_config.u2f_config = U2FLoginConfiguration.parse_arg(u2f_config)
6971

72+
if enc_password := args.get('root_enc_password'):
73+
auth_config.root_enc_password = Password(enc_password=enc_password)
74+
7075
return auth_config
7176

7277
def json(self) -> AuthenticationSerialization:

archinstall/lib/models/users.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def _check_password_strength(
100100
return PasswordStrength.VERY_WEAK
101101

102102

103-
_UserSerialization = TypedDict(
104-
'_UserSerialization',
103+
UserSerialization = TypedDict(
104+
'UserSerialization',
105105
{
106106
'username': str,
107107
'!password': NotRequired[str],
@@ -173,7 +173,7 @@ def table_data(self) -> dict[str, str | bool | list[str]]:
173173
'groups': self.groups,
174174
}
175175

176-
def json(self) -> _UserSerialization:
176+
def json(self) -> UserSerialization:
177177
return {
178178
'username': self.username,
179179
'enc_password': self.password.enc_password,
@@ -184,7 +184,7 @@ def json(self) -> _UserSerialization:
184184
@classmethod
185185
def parse_arguments(
186186
cls,
187-
args: list[_UserSerialization],
187+
args: list[UserSerialization],
188188
) -> list['User']:
189189
users: list[User] = []
190190

archinstall/scripts/guided.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ def perform_installation(mountpoint: Path) -> None:
138138
if accessibility_tools_in_use():
139139
installation.enable_espeakup()
140140

141-
if root_pw := config.root_enc_password:
142-
root_user = User('root', root_pw, False)
141+
if config.auth_config and config.auth_config.root_enc_password:
142+
root_user = User('root', config.auth_config.root_enc_password, False)
143143
installation.set_user_password(root_user)
144144

145145
if (profile_config := config.profile_config) and profile_config.profile:

tests/test_args.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def test_config_file_parsing(
134134
audio_config=AudioConfiguration(audio=Audio.PIPEWIRE),
135135
),
136136
auth_config=AuthenticationConfiguration(
137+
root_enc_password=Password(enc_password='password_hash'),
137138
u2f_config=U2FLoginConfiguration(
138139
u2f_login_method=U2FLoginMethod.Passwordless,
139140
passwordless_sudo=True,
@@ -223,7 +224,6 @@ def test_config_file_parsing(
223224
),
224225
],
225226
services=['service_1', 'service_2'],
226-
root_enc_password=Password(enc_password='password_hash'),
227227
custom_commands=["echo 'Hello, World!'"],
228228
)
229229

@@ -280,7 +280,8 @@ def test_deprecated_creds_config_parsing(
280280
handler = ArchConfigHandler()
281281
arch_config = handler.config
282282

283-
assert arch_config.root_enc_password == Password(plaintext='rootPwd')
283+
assert arch_config.auth_config is not None
284+
assert arch_config.auth_config.root_enc_password == Password(plaintext='rootPwd')
284285

285286
assert arch_config.users == [
286287
User(
@@ -331,7 +332,8 @@ def test_encrypted_creds_with_arg(
331332
handler = ArchConfigHandler()
332333
arch_config = handler.config
333334

334-
assert arch_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
335+
assert arch_config.auth_config is not None
336+
assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
335337
assert arch_config.users == [
336338
User(
337339
username='t',
@@ -359,7 +361,8 @@ def test_encrypted_creds_with_env_var(
359361
handler = ArchConfigHandler()
360362
arch_config = handler.config
361363

362-
assert arch_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
364+
assert arch_config.auth_config is not None
365+
assert arch_config.auth_config.root_enc_password == Password(enc_password='$y$j9T$FWCInXmSsS.8KV4i7O50H.$Hb6/g.Sw1ry888iXgkVgc93YNuVk/Rw94knDKdPVQw7')
363366
assert arch_config.users == [
364367
User(
365368
username='t',

0 commit comments

Comments
 (0)