diff --git a/CONFIGURING.md b/CONFIGURING.md index fee7876a..45f311cf 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -23,6 +23,7 @@ libalpm and AUR related configuration. Base authorization settings. * `target` - specifies authorization provider, string, optional, default `disabled`. Allowed values are `disabled`, `configuration`. +* `allow_read_only` - allow to request read only pages without authorization, boolean, required. * `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. * `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand). diff --git a/package/etc/ahriman.ini b/package/etc/ahriman.ini index 767bcbe1..70adc524 100644 --- a/package/etc/ahriman.ini +++ b/package/etc/ahriman.ini @@ -10,6 +10,7 @@ root = / [auth] target = disabled +allow_read_only = yes [build] archbuild_flags = diff --git a/src/ahriman/core/auth/auth.py b/src/ahriman/core/auth/auth.py index 9ec1a1d8..6ff0a8b8 100644 --- a/src/ahriman/core/auth/auth.py +++ b/src/ahriman/core/auth/auth.py @@ -45,6 +45,7 @@ class Auth: :param configuration: configuration instance :param provider: authorization type definition """ + self.allow_read_only = configuration.getboolean("auth", "allow_read_only") 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")) @@ -74,14 +75,17 @@ class Auth: del username, password return True - def is_safe_request(self, uri: Optional[str]) -> bool: + def is_safe_request(self, uri: Optional[str], required: UserAccess) -> bool: """ check if requested path are allowed without authorization :param uri: request uri + :param required: required access level :return: True in case if this URI can be requested without authorization and False otherwise """ if not uri: return False # request without context is not allowed + if required == UserAccess.Read and self.allow_read_only: + return True # in case if read right requested and allowed in options return uri in self.allowed_paths or any(uri.startswith(path) for path in self.allowed_paths_groups) def known_username(self, username: str) -> bool: # pylint: disable=no-self-use diff --git a/src/ahriman/web/middlewares/auth_handler.py b/src/ahriman/web/middlewares/auth_handler.py index e2b8f860..56a324a3 100644 --- a/src/ahriman/web/middlewares/auth_handler.py +++ b/src/ahriman/web/middlewares/auth_handler.py @@ -80,7 +80,7 @@ def auth_handler(validator: Auth) -> MiddlewareType: else: permission = UserAccess.Write - if not validator.is_safe_request(request.path): + if not validator.is_safe_request(request.path, permission): await aiohttp_security.check_permission(request, permission, request.path) return await handler(request) diff --git a/src/ahriman/web/views/index.py b/src/ahriman/web/views/index.py index 165e0614..341ab8bf 100644 --- a/src/ahriman/web/views/index.py +++ b/src/ahriman/web/views/index.py @@ -85,7 +85,7 @@ class IndexView(BaseView): # auth block auth_username = await authorized_userid(self.request) - authorized = not self.validator.enabled or auth_username is not None + authorized = not self.validator.enabled or self.validator.allow_read_only or auth_username is not None return { "architecture": self.service.architecture, diff --git a/tests/ahriman/core/auth/test_auth.py b/tests/ahriman/core/auth/test_auth.py index fe42b1d4..20d99218 100644 --- a/tests/ahriman/core/auth/test_auth.py +++ b/tests/ahriman/core/auth/test_auth.py @@ -46,24 +46,33 @@ def test_is_safe_request(auth: Auth) -> None: must validate safe request """ # login and logout are always safe - assert auth.is_safe_request("/login") - assert auth.is_safe_request("/logout") + assert auth.is_safe_request("/login", UserAccess.Write) + assert auth.is_safe_request("/logout", UserAccess.Write) auth.allowed_paths.add("/safe") auth.allowed_paths_groups.add("/unsafe/safe") - assert auth.is_safe_request("/safe") - assert not auth.is_safe_request("/unsafe") - assert auth.is_safe_request("/unsafe/safe") - assert auth.is_safe_request("/unsafe/safe/suffix") + assert auth.is_safe_request("/safe", UserAccess.Write) + assert not auth.is_safe_request("/unsafe", UserAccess.Write) + assert auth.is_safe_request("/unsafe/safe", UserAccess.Write) + assert auth.is_safe_request("/unsafe/safe/suffix", UserAccess.Write) def test_is_safe_request_empty(auth: Auth) -> None: """ must not allow requests without path """ - assert not auth.is_safe_request(None) - assert not auth.is_safe_request("") + assert not auth.is_safe_request(None, UserAccess.Read) + assert not auth.is_safe_request("", UserAccess.Read) + + +def test_is_safe_request_read_only(auth: Auth) -> None: + """ + must allow read-only requests if it is set in settings + """ + assert auth.is_safe_request("/", UserAccess.Read) + auth.allow_read_only = True + assert auth.is_safe_request("/unsafe", UserAccess.Read) def test_known_username(auth: Auth, user: User) -> None: diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini index d28582ea..0c568715 100644 --- a/tests/testresources/core/ahriman.ini +++ b/tests/testresources/core/ahriman.ini @@ -9,6 +9,7 @@ repositories = core extra community multilib root = / [auth] +allow_read_only = no salt = salt [build]