diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index c6607a2..8fc3e47 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -264,7 +264,7 @@ class Application( # Create principal collection if user: principal_path = "/%s/" % user - if self._rights.authorized(user, principal_path, "W"): + if "W" in self._rights.authorization(user, principal_path): with self._storage.acquire_lock("r", user): principal = next( self._storage.discover(principal_path, depth="1"), @@ -328,12 +328,14 @@ class Application( else: permissions = "" parent_permissions = permission - if permissions and self._rights.authorized(user, path, permissions): + if permissions and rights.intersect( + self._rights.authorization(user, path), permissions): return True if parent_permissions: parent_path = pathutils.unstrip_path( posixpath.dirname(pathutils.strip_path(path)), True) - if self._rights.authorized(user, parent_path, parent_permissions): + if rights.intersect(self._rights.authorization(user, parent_path), + parent_permissions): return True return False diff --git a/radicale/app/mkcalendar.py b/radicale/app/mkcalendar.py index 256a736..0b8d059 100644 --- a/radicale/app/mkcalendar.py +++ b/radicale/app/mkcalendar.py @@ -30,7 +30,7 @@ from radicale.log import logger class ApplicationMkcalendarMixin: def do_MKCALENDAR(self, environ, base_prefix, path, user): """Manage MKCALENDAR request.""" - if not self._rights.authorized(user, path, "w"): + if "w" not in self._rights.authorization(user, path): return httputils.NOT_ALLOWED try: xml_content = self._read_xml_content(environ) diff --git a/radicale/app/mkcol.py b/radicale/app/mkcol.py index e6610da..56c3908 100644 --- a/radicale/app/mkcol.py +++ b/radicale/app/mkcol.py @@ -23,14 +23,15 @@ from http import client from radicale import httputils from radicale import item as radicale_item -from radicale import pathutils, storage, xmlutils +from radicale import pathutils, rights, storage, xmlutils from radicale.log import logger class ApplicationMkcolMixin: def do_MKCOL(self, environ, base_prefix, path, user): """Manage MKCOL request.""" - permissions = self._rights.authorized(user, path, "Ww") + permissions = rights.intersect( + self._rights.authorization(user, path), "Ww") if not permissions: return httputils.NOT_ALLOWED try: diff --git a/radicale/app/propfind.py b/radicale/app/propfind.py index 80d0c48..f348395 100644 --- a/radicale/app/propfind.py +++ b/radicale/app/propfind.py @@ -314,19 +314,22 @@ class ApplicationPropfindMixin: if isinstance(item, storage.BaseCollection): path = pathutils.unstrip_path(item.path, True) if item.get_meta("tag"): - permissions = self._rights.authorized(user, path, "rw") + permissions = rights.intersect( + self._rights.authorization(user, path), "rw") target = "collection with tag %r" % item.path else: - permissions = self._rights.authorized(user, path, "RW") + permissions = rights.intersect( + self._rights.authorization(user, path), "RW") target = "collection %r" % item.path else: path = pathutils.unstrip_path(item.collection.path, True) - permissions = self._rights.authorized(user, path, "rw") + permissions = rights.intersect( + self._rights.authorization(user, path), "rw") target = "item %r from %r" % (item.href, item.collection.path) - if rights.intersect_permissions(permissions, "Ww"): + if rights.intersect(permissions, "Ww"): permission = "w" status = "write" - elif rights.intersect_permissions(permissions, "Rr"): + elif rights.intersect(permissions, "Rr"): permission = "r" status = "read" else: diff --git a/radicale/app/put.py b/radicale/app/put.py index 6a256c6..59b9c6c 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -27,7 +27,7 @@ import vobject from radicale import httputils from radicale import item as radicale_item -from radicale import pathutils, storage, xmlutils +from radicale import pathutils, rights, storage, xmlutils from radicale.log import logger MIMETYPE_TAGS = {value: key for key, value in xmlutils.MIMETYPES.items()} @@ -128,8 +128,10 @@ class ApplicationPutMixin: content_type = environ.get("CONTENT_TYPE", "").split(";")[0] parent_path = pathutils.unstrip_path( posixpath.dirname(pathutils.strip_path(path)), True) - permissions = self._rights.authorized(user, path, "Ww") - parent_permissions = self._rights.authorized(user, parent_path, "w") + permissions = rights.intersect( + self._rights.authorization(user, path), "Ww") + parent_permissions = rights.intersect( + self._rights.authorization(user, parent_path), "w") try: vobject_items = tuple(vobject.readComponents(content or "")) except Exception as e: @@ -157,10 +159,10 @@ class ApplicationPutMixin: tag = parent_item.get_meta("tag") if write_whole_collection: - if not self._rights.authorized( - user, path, "w" if tag else "W"): + if ("w" if tag else "W") not in self._rights.authorization( + user, path): return httputils.NOT_ALLOWED - elif not self._rights.authorized(user, parent_path, "w"): + elif "w" not in self._rights.authorization(user, parent_path): return httputils.NOT_ALLOWED etag = environ.get("HTTP_IF_MATCH", "") diff --git a/radicale/rights/__init__.py b/radicale/rights/__init__.py index 31a7477..899f5ac 100644 --- a/radicale/rights/__init__.py +++ b/radicale/rights/__init__.py @@ -40,7 +40,12 @@ def load(configuration): return utils.load_plugin(INTERNAL_TYPES, "rights", "Rights", configuration) -def intersect_permissions(a, b="RrWw"): +def intersect(a, b): + """Intersect two lists of rights. + + Returns all rights that are both in ``a`` and ``b``. + + """ return "".join(set(a).intersection(set(b))) @@ -55,16 +60,14 @@ class BaseRights: """ self.configuration = configuration - def authorized(self, user, path, permissions): - """Check if the user is allowed to read or write the collection. + def authorization(self, user, path): + """Get granted rights of ``user`` for the collection ``path``. If ``user`` is empty, check for anonymous rights. ``path`` is sanitized. - ``permissions`` can include "R", "r", "W", "w" - - Returns granted rights. + Returns granted rights (e.g. ``"RW"``). """ raise NotImplementedError diff --git a/radicale/rights/authenticated.py b/radicale/rights/authenticated.py index 4836f3b..1d171d3 100644 --- a/radicale/rights/authenticated.py +++ b/radicale/rights/authenticated.py @@ -29,12 +29,12 @@ class Rights(rights.BaseRights): super().__init__(*args, **kwargs) self._verify_user = self.configuration.get("auth", "type") != "none" - def authorized(self, user, path, permissions): + def authorization(self, user, path): if self._verify_user and not user: return "" sane_path = pathutils.strip_path(path) if "/" not in sane_path: - return rights.intersect_permissions(permissions, "RW") + return "RW" if sane_path.count("/") == 1: - return rights.intersect_permissions(permissions, "rw") + return "rw" return "" diff --git a/radicale/rights/from_file.py b/radicale/rights/from_file.py index 9548112..82aaeb4 100644 --- a/radicale/rights/from_file.py +++ b/radicale/rights/from_file.py @@ -45,7 +45,7 @@ class Rights(rights.BaseRights): super().__init__(configuration) self._filename = configuration.get("rights", "file") - def authorized(self, user, path, permissions): + def authorization(self, user, path): user = user or "" sane_path = pathutils.strip_path(path) # Prevent "regex injection" @@ -75,8 +75,7 @@ class Rights(rights.BaseRights): logger.debug("Rule %r:%r matches %r:%r from section %r", user, sane_path, user_pattern, collection_pattern, section) - return rights.intersect_permissions( - permissions, rights_config.get(section, "permissions")) + return rights_config.get(section, "permissions") logger.debug("Rule %r:%r doesn't match %r:%r from section %r", user, sane_path, user_pattern, collection_pattern, section) diff --git a/radicale/rights/owner_only.py b/radicale/rights/owner_only.py index d188d0e..339e6fc 100644 --- a/radicale/rights/owner_only.py +++ b/radicale/rights/owner_only.py @@ -22,20 +22,20 @@ calendars and address books. """ import radicale.rights.authenticated as authenticated -from radicale import pathutils, rights +from radicale import pathutils class Rights(authenticated.Rights): - def authorized(self, user, path, permissions): + def authorization(self, user, path): if self._verify_user and not user: return "" sane_path = pathutils.strip_path(path) if not sane_path: - return rights.intersect_permissions(permissions, "R") + return "R" if self._verify_user and user != sane_path.split("/", maxsplit=1)[0]: return "" if "/" not in sane_path: - return rights.intersect_permissions(permissions, "RW") + return "RW" if sane_path.count("/") == 1: - return rights.intersect_permissions(permissions, "rw") + return "rw" return "" diff --git a/radicale/rights/owner_write.py b/radicale/rights/owner_write.py index 8f1206e..e718e02 100644 --- a/radicale/rights/owner_write.py +++ b/radicale/rights/owner_write.py @@ -22,24 +22,22 @@ address books but only grants write access to their own. """ import radicale.rights.authenticated as authenticated -from radicale import pathutils, rights +from radicale import pathutils class Rights(authenticated.Rights): - def authorized(self, user, path, permissions): + def authorization(self, user, path): if self._verify_user and not user: return "" sane_path = pathutils.strip_path(path) if not sane_path: - return rights.intersect_permissions(permissions, "R") + return "R" if self._verify_user: owned = user == sane_path.split("/", maxsplit=1)[0] else: owned = True if "/" not in sane_path: - return rights.intersect_permissions(permissions, - "RW" if owned else "R") + return "RW" if owned else "R" if sane_path.count("/") == 1: - return rights.intersect_permissions(permissions, - "rw" if owned else "r") + return "rw" if owned else "r" return "" diff --git a/radicale/tests/custom/rights.py b/radicale/tests/custom/rights.py index 9a06fd4..5696ad5 100644 --- a/radicale/tests/custom/rights.py +++ b/radicale/tests/custom/rights.py @@ -23,8 +23,8 @@ from radicale import pathutils, rights class Rights(rights.BaseRights): - def authorized(self, user, path, permissions): + def authorization(self, user, path): sane_path = pathutils.strip_path(path) if sane_path not in ("tmp", "other"): return "" - return rights.intersect_permissions(permissions) + return "RrWw"