diff --git a/src/ahriman/application/handlers/users.py b/src/ahriman/application/handlers/users.py index 9ebf243e..bf8a5b4b 100644 --- a/src/ahriman/application/handlers/users.py +++ b/src/ahriman/application/handlers/users.py @@ -26,6 +26,7 @@ from typing import Type from ahriman.application.handlers import Handler from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite +from ahriman.core.exceptions import PasswordError from ahriman.core.formatters import UserPrinter from ahriman.models.action import Action from ahriman.models.user import User @@ -149,7 +150,15 @@ class Users(Handler): Returns: User: built user descriptor """ + def read_password() -> str: + first_password = getpass.getpass() + second_password = getpass.getpass("Repeat password: ") + if first_password != second_password: + raise PasswordError("passwords don't match") + return first_password + password = args.password if password is None: - password = getpass.getpass() + password = read_password() + return User(username=args.username, password=password, access=args.role) diff --git a/src/ahriman/core/exceptions.py b/src/ahriman/core/exceptions.py index 710e5f08..e61cf8f0 100644 --- a/src/ahriman/core/exceptions.py +++ b/src/ahriman/core/exceptions.py @@ -179,6 +179,21 @@ class PathError(ValueError): ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`") +class PasswordError(ValueError): + """ + exception which will be raised in case of password related errors + """ + + def __init__(self, details: Any) -> None: + """ + default constructor + + Args: + details(Any); error details + """ + ValueError.__init__(self, f"Password error: {details}") + + class ReportError(RuntimeError): """ report generation exception diff --git a/tests/ahriman/application/handlers/test_handler_users.py b/tests/ahriman/application/handlers/test_handler_users.py index d7139a17..0990b6c0 100644 --- a/tests/ahriman/application/handlers/test_handler_users.py +++ b/tests/ahriman/application/handlers/test_handler_users.py @@ -3,11 +3,12 @@ import pytest from pathlib import Path from pytest_mock import MockerFixture +from unittest.mock import call as MockCall from ahriman.application.handlers import Users from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite -from ahriman.core.exceptions import InitializeError +from ahriman.core.exceptions import InitializeError, PasswordError from ahriman.models.action import Action from ahriman.models.user import User from ahriman.models.user_access import UserAccess @@ -214,14 +215,25 @@ def test_user_create_getpass(args: argparse.Namespace, mocker: MockerFixture) -> """ args = _default_args(args) args.password = None - getpass_mock = mocker.patch("getpass.getpass", return_value="password") - generated = Users.user_create(args) - getpass_mock.assert_called_once_with() + generated = Users.user_create(args) + getpass_mock.assert_has_calls([MockCall(), MockCall("Repeat password: ")]) assert generated.password == "password" +def test_user_create_getpass_exception(args: argparse.Namespace, mocker: MockerFixture) -> None: + """ + must raise password error in case if password doesn't match + """ + args = _default_args(args) + args.password = None + mocker.patch("getpass.getpass", side_effect=lambda *_: User.generate_password(10)) + + with pytest.raises(PasswordError): + Users.user_create(args) + + def test_disallow_auto_architecture_run() -> None: """ must not allow multi architecture run