From 3e044fd0064e334efb412db5ce63c7a7f93e033d Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Fri, 27 Aug 2021 03:15:50 +0300 Subject: [PATCH] add create user parser --- src/ahriman/application/ahriman.py | 27 +++++++ src/ahriman/application/handlers/__init__.py | 1 + .../application/handlers/create_user.py | 79 +++++++++++++++++++ src/ahriman/core/status/web_client.py | 2 + src/ahriman/models/user.py | 14 +++- tests/ahriman/core/status/conftest.py | 1 + 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/ahriman/application/handlers/create_user.py diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 83268a25..39d6acc6 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -30,6 +30,8 @@ from ahriman.models.sign_settings import SignSettings # pylint thinks it is bad idea, but get the fuck off +from ahriman.models.user_access import UserAccess + SubParserAction = argparse._SubParsersAction # pylint: disable=protected-access @@ -61,6 +63,7 @@ def _parser() -> argparse.ArgumentParser: _set_check_parser(subparsers) _set_clean_parser(subparsers) _set_config_parser(subparsers) + _set_create_user_parser(subparsers) _set_init_parser(subparsers) _set_key_import_parser(subparsers) _set_rebuild_parser(subparsers) @@ -138,6 +141,30 @@ def _set_config_parser(root: SubParserAction) -> argparse.ArgumentParser: return parser +def _set_create_user_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for create user subcommand + :param root: subparsers for the commands + :return: created argument parser + """ + parser = root.add_parser( + "create-user", + help="create user for web services", + description="create user for web services with password and role. In case if password was not entered it will be asked interactively", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("username", help="username for web service") + parser.add_argument("-r", "--role", help="user role", type=UserAccess, choices=UserAccess, default=UserAccess.Read) + parser.add_argument("-p", "--password", help="user password") + parser.set_defaults( + handler=handlers.CreateUser, + architecture=[""], + lock=None, + no_log=True, + no_report=True, + unsafe=True) + return parser + + def _set_init_parser(root: SubParserAction) -> argparse.ArgumentParser: """ add parser for init subcommand diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 1a9fdb9d..62047489 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -21,6 +21,7 @@ from ahriman.application.handlers.handler import Handler from ahriman.application.handlers.add import Add from ahriman.application.handlers.clean import Clean +from ahriman.application.handlers.create_user import CreateUser from ahriman.application.handlers.dump import Dump from ahriman.application.handlers.init import Init from ahriman.application.handlers.key_import import KeyImport diff --git a/src/ahriman/application/handlers/create_user.py b/src/ahriman/application/handlers/create_user.py new file mode 100644 index 00000000..72c2d165 --- /dev/null +++ b/src/ahriman/application/handlers/create_user.py @@ -0,0 +1,79 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +import argparse +import configparser + +from getpass import getpass +from pathlib import Path +from typing import Type + +from ahriman.application.handlers.handler import Handler +from ahriman.core.configuration import Configuration +from ahriman.models.user import User + + +class CreateUser(Handler): + """ + create user handler + """ + + @classmethod + def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None: + """ + callback for command line + :param args: command line args + :param architecture: repository architecture + :param configuration: configuration instance + """ + user = CreateUser.create_user(args, configuration) + CreateUser.create_configuration(user, configuration.include) + + @staticmethod + def create_configuration(user: User, include_path: Path) -> None: + """ + put new user to configuration + :param user: user descriptor + :param include_path: path to directory with configuration includes + """ + target = include_path / "auth.ini" + + configuration = configparser.ConfigParser() + configuration.read(target) + + section = Configuration.section_name("auth", user.access.value) + configuration.add_section(section) + configuration.set(section, user.username, user.password) + + with target.open("w") as ahriman_configuration: + configuration.write(ahriman_configuration) + + @staticmethod + def create_user(args: argparse.Namespace, configuration: Configuration) -> User: + """ + create user descriptor from arguments + :param args: command line args + :param configuration: configuration instance + :return: built user descriptor + """ + user = User(args.username, args.password, args.role) + if user.password is None: + user.password = getpass() + user.password = user.generate_password(user.password, configuration.get("auth", "salt")) + return user diff --git a/src/ahriman/core/status/web_client.py b/src/ahriman/core/status/web_client.py index a52b6837..729bdd96 100644 --- a/src/ahriman/core/status/web_client.py +++ b/src/ahriman/core/status/web_client.py @@ -49,7 +49,9 @@ class WebClient(Client): self.user = User.from_option( configuration.get("web", "username", fallback=None), configuration.get("web", "password", fallback=None)) + self.__session = requests.session() + self.login() @property def _ahriman_url(self) -> str: diff --git a/src/ahriman/models/user.py b/src/ahriman/models/user.py index 474334ea..bb469872 100644 --- a/src/ahriman/models/user.py +++ b/src/ahriman/models/user.py @@ -38,6 +38,8 @@ class User: password: str access: UserAccess + _HASHER = sha512_crypt + @classmethod def from_option(cls: Type[User], username: Optional[str], password: Optional[str]) -> Optional[User]: """ @@ -57,9 +59,19 @@ class User: :param salt: salt for hashed password :return: True in case if password matches, False otherwise """ - verified: bool = sha512_crypt.verify(password + salt, self.password) + verified: bool = self._HASHER.verify(password + salt, self.password) return verified + def generate_password(self, password: str, salt: str) -> str: + """ + generate hashed password from plain text + :param password: entered password + :param salt: salt for hashed password + :return: hashed string to store in configuration + """ + password_hash: str = self._HASHER.hash(password + salt) + return password_hash + def verify_access(self, required: UserAccess) -> bool: """ validate if user has access to requested resource diff --git a/tests/ahriman/core/status/conftest.py b/tests/ahriman/core/status/conftest.py index 653d67cd..7b0bddaf 100644 --- a/tests/ahriman/core/status/conftest.py +++ b/tests/ahriman/core/status/conftest.py @@ -44,6 +44,7 @@ def client() -> Client: def web_client(configuration: Configuration) -> WebClient: """ fixture for web client + :param configuration: configuration fixture :return: web client test instance """ configuration.set("web", "port", 8080)