diff --git a/radicale/rights.py b/radicale/rights.py index 72837e6..2b7def8 100644 --- a/radicale/rights.py +++ b/radicale/rights.py @@ -41,51 +41,29 @@ import os.path import re from configparser import ConfigParser from importlib import import_module -from io import StringIO from . import storage def load(configuration, logger): """Load the rights manager chosen in configuration.""" - auth_type = configuration.get("auth", "type") rights_type = configuration.get("rights", "type") - if auth_type == "None" or rights_type == "None": - return lambda user, collection, permission: True - elif rights_type in DEFINED_RIGHTS or rights_type == "from_file": - return Rights(configuration, logger).authorized + if configuration.get("auth", "type") == "None": + rights_type = "None" + logger.info("Rights type is %r", rights_type) + if rights_type == "None": + rights_class = NoneRights + elif rights_type == "authenticated": + rights_class = AuthenticatedRights + elif rights_type == "owner_write": + rights_class = OwnerWriteRights + elif rights_type == "owner_only": + rights_class = OwnerOnlyRights + elif rights_type == "from_file": + rights_class = Rights else: - module = import_module(rights_type) - return module.Rights(configuration, logger).authorized - - -DEFINED_RIGHTS = { - "authenticated": """ -[rw] -user:.+ -collection:.* -permission:rw - """, - "owner_write": """ -[w] -user:.+ -collection:%(login)s(/.*)? -permission:rw -[r] -user:.+ -collection:.* -permission:r - """, - "owner_only": """ -[rw] -user:.+ -collection:%(login)s(/.*)? -permission:rw -[r] -user:.+ -collection: -permission:r - """} + rights_class = import_module(rights_type).Rights + return rights_class(configuration, logger).authorized class BaseRights: @@ -102,32 +80,48 @@ class BaseRights: raise NotImplementedError +class NoneRights(BaseRights): + def authorized(self, user, path, permission): + return True + + +class AuthenticatedRights(BaseRights): + def authorized(self, user, path, permission): + return bool(user) + + +class OwnerWriteRights(BaseRights): + def authorized(self, user, path, permission): + sane_path = storage.sanitize_path(path).strip("/") + return bool(user) and (permission == "r" or + user == sane_path.split("/", maxsplit=1)[0]) + + +class OwnerOnlyRights(BaseRights): + def authorized(self, user, path, permission): + sane_path = storage.sanitize_path(path).strip("/") + return bool(user) and ( + permission == "r" and not sane_path.strip("/") or + user == sane_path.split("/", maxsplit=1)[0]) + + class Rights(BaseRights): def __init__(self, configuration, logger): super().__init__(configuration, logger) self.filename = os.path.expanduser(configuration.get("rights", "file")) - self.rights_type = configuration.get("rights", "type").lower() def authorized(self, user, path, permission): user = user or "" - if user and not storage.is_safe_path_component(user): - # Prevent usernames like "user/calendar.ics" - raise ValueError("Refused unsafe username: %s", user) sane_path = storage.sanitize_path(path).strip("/") # Prevent "regex injection" user_escaped = re.escape(user) sane_path_escaped = re.escape(sane_path) regex = ConfigParser( {"login": user_escaped, "path": sane_path_escaped}) - if self.rights_type in DEFINED_RIGHTS: - self.logger.debug("Rights type '%s'", self.rights_type) - regex.readfp(StringIO(DEFINED_RIGHTS[self.rights_type])) - else: - self.logger.debug("Reading rights from file '%s'", self.filename) - if not regex.read(self.filename): - self.logger.error( - "File '%s' not found for rights", self.filename) - return False + if not regex.read(self.filename): + raise RuntimeError("Failed to read rights from file %r", + self.filename) + return False for section in regex.sections(): re_user = regex.get(section, "user")