add ability to reload authentication module

This commit is contained in:
2021-09-17 16:05:38 +03:00
parent 0cf7756ec4
commit 6d1f641e50
15 changed files with 219 additions and 12 deletions

View File

@ -354,6 +354,7 @@ def _set_user_parser(root: SubParserAction) -> argparse.ArgumentParser:
type=UserAccess,
choices=UserAccess,
default=UserAccess.Read)
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.set_defaults(handler=handlers.User, architecture=[""], lock=None, no_log=True, no_report=True, unsafe=True)

View File

@ -23,6 +23,7 @@ import getpass
from pathlib import Path
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.user import User as MUser
@ -53,6 +54,10 @@ class User(Handler):
User.create_configuration(auth_configuration, user, salt, args.as_service)
User.write_configuration(configuration)
if not args.no_reload:
client = Application(architecture, configuration, no_report=False).repository.reporter
client.reload_auth()
@staticmethod
def clear_user(configuration: Configuration, user: MUser) -> None:
"""

View File

@ -26,10 +26,13 @@ from logging.config import fileConfig
from pathlib import Path
from typing import Any, Dict, List, Optional, Type
from ahriman.core.exceptions import InitializeException
class Configuration(configparser.RawConfigParser):
"""
extension for built-in configuration parser
:ivar architecture: repository architecture
:ivar path: path to root configuration file
:cvar ARCHITECTURE_SPECIFIC_SECTIONS: known sections which can be architecture specific (required by dump)
:cvar DEFAULT_LOG_FORMAT: default log format (in case of fallback)
@ -49,6 +52,7 @@ class Configuration(configparser.RawConfigParser):
"list": lambda value: value.split(),
"path": self.__convert_path,
})
self.architecture: Optional[str] = None
self.path: Optional[Path] = None
@property
@ -90,16 +94,16 @@ class Configuration(configparser.RawConfigParser):
"""
return f"{section}:{suffix}"
def __convert_path(self, parsed: str) -> Path:
def __convert_path(self, value: str) -> Path:
"""
convert string value to path object
:param parsed: string configuration value
:param value: string configuration value
:return: path object which represents the configuration value
"""
value = Path(parsed)
if self.path is None or value.is_absolute():
return value
return self.path.parent / value
path = Path(value)
if self.path is None or path.is_absolute():
return path
return self.path.parent / path
def dump(self) -> Dict[str, Dict[str, str]]:
"""
@ -165,6 +169,7 @@ class Configuration(configparser.RawConfigParser):
merge architecture specific sections into main configuration
:param architecture: repository architecture
"""
self.architecture = architecture
for section in self.ARCHITECTURE_SPECIFIC_SECTIONS:
# get overrides
specific = self.section_name(section, architecture)
@ -180,6 +185,15 @@ class Configuration(configparser.RawConfigParser):
continue
self.remove_section(foreign)
def reload(self) -> None:
"""
reload configuration if possible or raise exception otherwise
"""
if self.path is None or self.architecture is None:
raise InitializeException("Configuration path and/or architecture are not set")
self.load(self.path)
self.merge_sections(self.architecture)
def set_option(self, section: str, option: str, value: Optional[str]) -> None:
"""
set option. Unlike default `configparser.RawConfigParser.set` it also creates section if it does not exist

View File

@ -77,6 +77,11 @@ class Client:
"""
return BuildStatus()
def reload_auth(self) -> None:
"""
reload authentication module call
"""
def remove(self, base: str) -> None:
"""
remove packages from watcher

View File

@ -67,6 +67,13 @@ class WebClient(Client):
"""
return f"{self.address}/user-api/v1/login"
@property
def _reload_auth_url(self) -> str:
"""
:return: full url for web service to reload authentication module
"""
return f"{self.address}/status-api/v1/reload-auth"
@property
def _status_url(self) -> str:
"""
@ -191,6 +198,18 @@ class WebClient(Client):
self.logger.exception("could not get service status")
return BuildStatus()
def reload_auth(self) -> None:
"""
reload authentication module call
"""
try:
response = self.__session.post(self._reload_auth_url)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
self.logger.exception("could not reload auth module: %s", exception_response_text(e))
except Exception:
self.logger.exception("could not reload auth module")
def remove(self, base: str) -> None:
"""
remove packages from watcher

View File

@ -22,6 +22,7 @@ from pathlib import Path
from ahriman.web.views.index import IndexView
from ahriman.web.views.service.add import AddView
from ahriman.web.views.service.reload_auth import ReloadAuthView
from ahriman.web.views.service.remove import RemoveView
from ahriman.web.views.service.search import SearchView
from ahriman.web.views.status.ahriman import AhrimanView
@ -43,12 +44,14 @@ def setup_routes(application: Application, static_path: Path) -> None:
POST /service-api/v1/add add new packages to repository
POST /service-api/v1/reload-auth reload authentication module
POST /service-api/v1/remove remove existing package from repository
POST /service-api/v1/update update packages in repository, actually it is just alias for add
GET /service-api/v1/search search for substring in AUR
POST /service-api/v1/update update packages in repository, actually it is just alias for add
GET /status-api/v1/ahriman get current service status
POST /status-api/v1/ahriman update service status
@ -75,6 +78,8 @@ def setup_routes(application: Application, static_path: Path) -> None:
application.router.add_post("/service-api/v1/add", AddView)
application.router.add_post("/service-api/v1/reload-auth", ReloadAuthView)
application.router.add_post("/service-api/v1/remove", RemoveView)
application.router.add_get("/service-api/v1/search", SearchView, allow_head=False)

View File

@ -21,6 +21,7 @@ from aiohttp.web import View
from typing import Any, Dict, List, Optional
from ahriman.core.auth.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.spawn import Spawn
from ahriman.core.status.watcher import Watcher
@ -30,6 +31,14 @@ class BaseView(View):
base web view to make things typed
"""
@property
def configuration(self) -> Configuration:
"""
:return: configuration instance
"""
configuration: Configuration = self.request.app["configuration"]
return configuration
@property
def service(self) -> Watcher:
"""

View File

@ -0,0 +1,40 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response
from aiohttp.web_exceptions import HTTPNoContent
from ahriman.core.auth.auth import Auth
from ahriman.web.views.base import BaseView
class ReloadAuthView(BaseView):
"""
reload authentication module web view
"""
async def post(self) -> Response:
"""
reload authentication module. No parameters supported here
:return: 204 on success
"""
self.configuration.reload()
self.request.app["validator"] = Auth.load(self.configuration)
return HTTPNoContent()