diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 8245e82..10cf624 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -963,10 +963,12 @@ if you want to use regular curly braces in the `user` and `collection` regexes. The following `permissions` are recognized: - * **R:** read a collection (excluding address book or calendar collections) - * **r:** read an address book or calendar collection - * **W:** write a collection (excluding address book or calendar collections) - * **w:** write an address book or calendar collection + * **R:** read collections (excluding address books and calendars) + * **r:** read address book and calendar collections + * **i:** subset of **r** that only allows direct access via HTTP method GET + (CalDAV/CardDAV is susceptible to expensive search requests) + * **W:** write collections (excluding address books and calendars) + * **w:** write address book and calendar collections ## Storage diff --git a/radicale/app/get.py b/radicale/app/get.py index 6310993..79e068a 100644 --- a/radicale/app/get.py +++ b/radicale/app/get.py @@ -71,21 +71,28 @@ class ApplicationGetMixin: if path == "/.web" or path.startswith("/.web/"): return self._web.get(environ, base_prefix, path, user) access = app.Access(self._rights, user, path) - if not access.check("r"): + if not access.check("r") and "i" not in access.permissions: return httputils.NOT_ALLOWED with self._storage.acquire_lock("r", user): item = next(self._storage.discover(path), None) if not item: return httputils.NOT_FOUND - if not access.check("r", item): + if access.check("r", item): + limited_access = False + elif "i" in access.permissions: + limited_access = True + else: return httputils.NOT_ALLOWED if isinstance(item, storage.BaseCollection): tag = item.get_meta("tag") if not tag: - return httputils.DIRECTORY_LISTING + return (httputils.NOT_ALLOWED if limited_access else + httputils.DIRECTORY_LISTING) content_type = xmlutils.MIMETYPES[tag] content_disposition = self._content_disposition_attachement( propose_filename(item)) + elif limited_access: + return httputils.NOT_ALLOWED else: content_type = xmlutils.OBJECT_MIMETYPES[item.name] content_disposition = "" diff --git a/radicale/rights/__init__.py b/radicale/rights/__init__.py index 899f5ac..1f0a534 100644 --- a/radicale/rights/__init__.py +++ b/radicale/rights/__init__.py @@ -21,10 +21,12 @@ collections and entries. Permissions: - - R: read a collection (excluding address book or calendar collections) - - r: read an address book or calendar collection - - W: write a collection (excluding address book or calendar collections) - - w: write an address book or calendar collection + - R: read collections (excluding address books and calendars) + - r: read address book and calendar collections + - i: subset of **r** that only allows direct access via HTTP method GET + (CalDAV/CardDAV is susceptible to expensive search requests) + - W: write collections (excluding address books and calendars) + - w: write address book and calendar collections Take a look at the class ``BaseRights`` if you want to implement your own. diff --git a/radicale/tests/test_rights.py b/radicale/tests/test_rights.py index d44c4e7..951cf55 100644 --- a/radicale/tests/test_rights.py +++ b/radicale/tests/test_rights.py @@ -136,6 +136,30 @@ permissions: Rr""") self._test_rights("from_file", "", "/custom/sub", "w", 401) self._test_rights("from_file", "tmp", "/custom/sub", "w", 403) + def test_from_file_limited_get(self): + rights_file_path = os.path.join(self.colpath, "rights") + with open(rights_file_path, "w") as f: + f.write("""\ +[write-all] +user: tmp +collection: .* +permissions: RrWw +[limited-public] +user: .* +collection: public/[^/]* +permissions: i""") + self.configuration.update( + {"rights": {"type": "from_file", + "file": rights_file_path}}, "test") + self.application = Application(self.configuration) + self.mkcalendar("/tmp/calendar", login="tmp:bepo") + self.mkcol("/public", login="tmp:bepo") + self.mkcalendar("/public/calendar", login="tmp:bepo") + self.get("/tmp/calendar", check=401) + self.get("/public/", check=401) + self.get("/public/calendar") + self.get("/public/calendar/1.ics", check=401) + def test_custom(self): """Custom rights management.""" self._test_rights("radicale.tests.custom.rights", "", "/", "r", 401) diff --git a/rights b/rights index 7670be1..0919cb2 100644 --- a/rights +++ b/rights @@ -75,19 +75,24 @@ # Example: Allow everybody (including unauthenticated users) to read # the collection "public" -# Allow reading collection "public" +# Allow reading collection "public" for authenticated users #[public-principal] -#user: .* +#user: .+ #collection: public #permissions: R # Allow reading all calendars and address books that are direct children of -# the collection "public" +# the collection "public" for authenticated users #[public-calendars] -#user: .* +#user: .+ #collection: public/[^/]+ #permissions: r +# Allow access to public calendars and address books via HTTP GET for everyone +#[public-calendars-restricted] +#user: .* +#collection: public/[^/]+ +#permissions: i # Example: Grant users of the form user@domain.tld read access to the # collection "domain.tld"