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.
* `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_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).
## `auth:*` groups

View File

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

View File

@ -22,6 +22,7 @@ from __future__ import annotations
from typing import Optional, Set, Type
from ahriman.core.configuration import Configuration
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user_access import UserAccess
@ -38,16 +39,17 @@ class Auth:
ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html", "/login", "/logout"}
ALLOWED_PATHS_GROUPS: Set[str] = set()
def __init__(self, configuration: Configuration) -> None:
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
"""
default constructor
:param configuration: configuration instance
:param provider: authorization type definition
"""
self.allowed_paths = set(configuration.getlist("auth", "allowed_paths"))
self.allowed_paths.update(self.ALLOWED_PATHS)
self.allowed_paths_groups = set(configuration.getlist("auth", "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
def load(cls: Type[Auth], configuration: Configuration) -> Auth:
@ -56,7 +58,8 @@ class Auth:
:param configuration: configuration instance
: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
return MappingAuth(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.configuration import Configuration
from ahriman.core.exceptions import DuplicateUser
from ahriman.models.auth_settings import AuthSettings
from ahriman.models.user import User
from ahriman.models.user_access import UserAccess
@ -33,12 +34,13 @@ class MappingAuth(Auth):
: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
: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._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
:return: auth service instance
"""
configuration.set_option("auth", "enabled", "yes")
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
"""
configuration.set_option("auth", "enabled", "no")
configuration.set_option("auth", "target", "disabled")
auth = Auth.load(configuration)
assert isinstance(auth, Auth)
@ -26,7 +26,7 @@ def test_load_mapping(configuration: Configuration) -> None:
"""
must load mapping validator if option set
"""
configuration.set_option("auth", "enabled", "yes")
configuration.set_option("auth", "target", "configuration")
auth = Auth.load(configuration)
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
: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("pathlib.Path.mkdir")
application = setup_service("x86_64", configuration)

View File

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