diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 3fea5334..ebb10536 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -357,6 +357,7 @@ def _set_user_parser(root: SubParserAction) -> argparse.ArgumentParser: parser.add_argument("--no-reload", help="do not reload authentication module", action="store_true") parser.add_argument("-p", "--password", help="user password") parser.add_argument("-r", "--remove", help="remove user from configuration", action="store_true") + parser.add_argument("--secure", help="set file permissions to user-only", action="store_true") parser.set_defaults(handler=handlers.User, architecture=[""], lock=None, no_log=True, no_report=True, unsafe=True) return parser diff --git a/src/ahriman/application/handlers/user.py b/src/ahriman/application/handlers/user.py index 50b9b4f0..4efd50c7 100644 --- a/src/ahriman/application/handlers/user.py +++ b/src/ahriman/application/handlers/user.py @@ -52,7 +52,7 @@ class User(Handler): User.clear_user(auth_configuration, user) if not args.remove: User.create_configuration(auth_configuration, user, salt, args.as_service) - User.write_configuration(auth_configuration) + User.write_configuration(auth_configuration, args.secure) if not args.no_reload: client = Application(architecture, configuration, no_report=False).repository.reporter @@ -127,13 +127,15 @@ class User(Handler): return MUser.generate_password(salt_length) @staticmethod - def write_configuration(configuration: Configuration) -> None: + def write_configuration(configuration: Configuration, secure: bool) -> None: """ write configuration file :param configuration: configuration instance + :param secure: if true then set file permissions to 0o600 """ if configuration.path is None: return # should never happen actually with configuration.path.open("w") as ahriman_configuration: configuration.write(ahriman_configuration) - configuration.path.chmod(0o600) + if secure: + configuration.path.chmod(0o600) diff --git a/tests/ahriman/application/handlers/test_handler_user.py b/tests/ahriman/application/handlers/test_handler_user.py index 699e0378..67159529 100644 --- a/tests/ahriman/application/handlers/test_handler_user.py +++ b/tests/ahriman/application/handlers/test_handler_user.py @@ -22,6 +22,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: args.access = UserAccess.Read args.as_service = False args.no_reload = False + args.secure = False args.remove = False return args @@ -227,11 +228,23 @@ def test_write_configuration(configuration: Configuration, mocker: MockerFixture write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") chmod_mock = mocker.patch("pathlib.Path.chmod") - User.write_configuration(configuration) + User.write_configuration(configuration, secure=True) write_mock.assert_called_once() chmod_mock.assert_called_once() +def test_write_configuration_insecure(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must write configuration without setting file permissions + """ + mocker.patch("pathlib.Path.open") + mocker.patch("ahriman.core.configuration.Configuration.write") + chmod_mock = mocker.patch("pathlib.Path.chmod") + + User.write_configuration(configuration, secure=False) + chmod_mock.assert_not_called() + + def test_write_configuration_not_loaded(configuration: Configuration, mocker: MockerFixture) -> None: """ must do nothing in case if configuration is not loaded @@ -241,6 +254,6 @@ def test_write_configuration_not_loaded(configuration: Configuration, mocker: Mo write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") chmod_mock = mocker.patch("pathlib.Path.chmod") - User.write_configuration(configuration) + User.write_configuration(configuration, secure=True) write_mock.assert_not_called() chmod_mock.assert_not_called()