add settings object for auth provider

This commit is contained in:
Evgenii Alekseev 2021-09-02 03:30:14 +03:00
parent cb5756ea76
commit 5e24d81415
10 changed files with 110 additions and 12 deletions

View File

@ -22,9 +22,9 @@ libalpm and AUR related configuration.
Base authorization settings. Base authorization settings.
* `target` - specifies authorization provider, string, optional, default `disabled`. Allowed values are `disabled`, `configuration`.
* `allowed_paths` - URI paths (exact match) which can be accessed without authorization, space separated list of strings, optional. * `allowed_paths` - URI paths (exact match) which can be accessed without authorization, space separated list of strings, optional.
* `allowed_paths_groups` - URI paths prefixes which can be accessed without authorization, space separated list of strings, optional. * `allowed_paths_groups` - URI paths prefixes which can be accessed without authorization, space separated list of strings, optional.
* `enabled` - enables web services authorization, boolean, optional, default `no`.
* `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand). * `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand).
## `auth:*` groups ## `auth:*` groups

View File

@ -9,7 +9,7 @@ repositories = core extra community multilib
root = / root = /
[auth] [auth]
enabled = no target = disabled
[build] [build]
archbuild_flags = archbuild_flags =

View File

@ -22,6 +22,7 @@ from __future__ import annotations
from typing import Optional, Set, Type from typing import Optional, Set, Type
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@ -38,16 +39,17 @@ class Auth:
ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html", "/login", "/logout"} ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html", "/login", "/logout"}
ALLOWED_PATHS_GROUPS: Set[str] = set() ALLOWED_PATHS_GROUPS: Set[str] = set()
def __init__(self, configuration: Configuration) -> None: def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance :param configuration: configuration instance
:param provider: authorization type definition
""" """
self.allowed_paths = set(configuration.getlist("auth", "allowed_paths")) self.allowed_paths = set(configuration.getlist("auth", "allowed_paths"))
self.allowed_paths.update(self.ALLOWED_PATHS) self.allowed_paths.update(self.ALLOWED_PATHS)
self.allowed_paths_groups = set(configuration.getlist("auth", "allowed_paths_groups")) self.allowed_paths_groups = set(configuration.getlist("auth", "allowed_paths_groups"))
self.allowed_paths_groups.update(self.ALLOWED_PATHS_GROUPS) self.allowed_paths_groups.update(self.ALLOWED_PATHS_GROUPS)
self.enabled = configuration.getboolean("auth", "enabled", fallback=False) self.enabled = provider.is_enabled
@classmethod @classmethod
def load(cls: Type[Auth], configuration: Configuration) -> Auth: def load(cls: Type[Auth], configuration: Configuration) -> Auth:
@ -56,7 +58,8 @@ class Auth:
:param configuration: configuration instance :param configuration: configuration instance
:return: authorization module according to current settings :return: authorization module according to current settings
""" """
if configuration.getboolean("auth", "enabled", fallback=False): provider = AuthSettings.from_option(configuration.get("auth", "target", fallback="disabled"))
if provider == AuthSettings.Configuration:
from ahriman.core.auth.mapping_auth import MappingAuth from ahriman.core.auth.mapping_auth import MappingAuth
return MappingAuth(configuration) return MappingAuth(configuration)
return cls(configuration) return cls(configuration)

View File

@ -22,6 +22,7 @@ from typing import Dict, Optional
from ahriman.core.auth.auth import Auth from ahriman.core.auth.auth import Auth
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import DuplicateUser from ahriman.core.exceptions import DuplicateUser
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user import User from ahriman.models.user import User
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@ -33,12 +34,13 @@ class MappingAuth(Auth):
:ivar _users: map of username to its descriptor :ivar _users: map of username to its descriptor
""" """
def __init__(self, configuration: Configuration) -> None: def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Configuration) -> None:
""" """
default constructor default constructor
:param configuration: configuration instance :param configuration: configuration instance
:param provider: authorization type definition
""" """
Auth.__init__(self, configuration) Auth.__init__(self, configuration, provider)
self.salt = configuration.get("auth", "salt") self.salt = configuration.get("auth", "salt")
self._users = self.get_users(configuration) self._users = self.get_users(configuration)

View File

@ -0,0 +1,58 @@
#
# 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 __future__ import annotations
from enum import Enum, auto
from typing import Type
from ahriman.core.exceptions import InvalidOption
class AuthSettings(Enum):
"""
web authorization type
:cvar Disabled: authorization is disabled
:cvar Configuration: configuration based authorization
"""
Disabled = auto()
Configuration = auto()
@classmethod
def from_option(cls: Type[AuthSettings], value: str) -> AuthSettings:
"""
construct value from configuration
:param value: configuration value
:return: parsed value
"""
if value.lower() in ("disabled", "no"):
return cls.Disabled
if value.lower() in ("configuration", "mapping"):
return cls.Configuration
raise InvalidOption(value)
@property
def is_enabled(self) -> bool:
"""
:return: False in case if authorization is disabled and True otherwise
"""
if self == AuthSettings.Disabled:
return False
return True

View File

@ -10,5 +10,4 @@ def mapping_auth(configuration: Configuration) -> MappingAuth:
auth provider fixture auth provider fixture
:return: auth service instance :return: auth service instance
""" """
configuration.set_option("auth", "enabled", "yes")
return MappingAuth(configuration) return MappingAuth(configuration)

View File

@ -9,7 +9,7 @@ def test_load_dummy(configuration: Configuration) -> None:
""" """
must load dummy validator if authorization is not enabled must load dummy validator if authorization is not enabled
""" """
configuration.set_option("auth", "enabled", "no") configuration.set_option("auth", "target", "disabled")
auth = Auth.load(configuration) auth = Auth.load(configuration)
assert isinstance(auth, Auth) assert isinstance(auth, Auth)
@ -26,7 +26,7 @@ def test_load_mapping(configuration: Configuration) -> None:
""" """
must load mapping validator if option set must load mapping validator if option set
""" """
configuration.set_option("auth", "enabled", "yes") configuration.set_option("auth", "target", "configuration")
auth = Auth.load(configuration) auth = Auth.load(configuration)
assert isinstance(auth, MappingAuth) assert isinstance(auth, MappingAuth)

View File

@ -0,0 +1,36 @@
import pytest
from ahriman.core.exceptions import InvalidOption
from ahriman.models.auth_settings import AuthSettings
def test_from_option_invalid() -> None:
"""
must raise exception on invalid option
"""
with pytest.raises(InvalidOption, match=".* `invalid`$"):
AuthSettings.from_option("invalid")
def test_from_option_valid() -> None:
"""
must return value from valid options
"""
assert AuthSettings.from_option("disabled") == AuthSettings.Disabled
assert AuthSettings.from_option("DISABLED") == AuthSettings.Disabled
assert AuthSettings.from_option("no") == AuthSettings.Disabled
assert AuthSettings.from_option("NO") == AuthSettings.Disabled
assert AuthSettings.from_option("configuration") == AuthSettings.Configuration
assert AuthSettings.from_option("ConFigUration") == AuthSettings.Configuration
assert AuthSettings.from_option("mapping") == AuthSettings.Configuration
assert AuthSettings.from_option("MAPPing") == AuthSettings.Configuration
def test_is_enabled() -> None:
"""
must mark as disabled authorization for disabled and enabled otherwise
"""
assert not AuthSettings.Disabled.is_enabled
for option in filter(lambda o: o != AuthSettings.Disabled, AuthSettings):
assert option.is_enabled

View File

@ -32,7 +32,7 @@ def application_with_auth(configuration: Configuration, user: User, mocker: Mock
:param mocker: mocker object :param mocker: mocker object
:return: application test instance :return: application test instance
""" """
configuration.set_option("auth", "enabled", "yes") configuration.set_option("auth", "target", "configuration")
mocker.patch.object(ahriman.core.auth.helpers, "_has_aiohttp_security", True) mocker.patch.object(ahriman.core.auth.helpers, "_has_aiohttp_security", True)
mocker.patch("pathlib.Path.mkdir") mocker.patch("pathlib.Path.mkdir")
application = setup_service("x86_64", configuration) application = setup_service("x86_64", configuration)

View File

@ -25,7 +25,7 @@ def authorization_policy(configuration: Configuration, user: User) -> Authorizat
fixture for authorization policy fixture for authorization policy
:return: authorization policy fixture :return: authorization policy fixture
""" """
configuration.set_option("auth", "enabled", "yes") configuration.set_option("auth", "target", "configuration")
validator = Auth.load(configuration) validator = Auth.load(configuration)
policy = AuthorizationPolicy(validator) policy = AuthorizationPolicy(validator)
policy.validator._users = {user.username: user} policy.validator._users = {user.username: user}