New right "i": Only allowing HTTP method GET

This commit is contained in:
Unrud 2020-04-22 19:20:42 +02:00
parent 9bd852ba5e
commit 7f2d5cea62
5 changed files with 55 additions and 15 deletions

View File

@ -963,10 +963,12 @@ if you want to use regular curly braces in the `user` and `collection` regexes.
The following `permissions` are recognized: The following `permissions` are recognized:
* **R:** read a collection (excluding address book or calendar collections) * **R:** read collections (excluding address books and calendars)
* **r:** read an address book or calendar collection * **r:** read address book and calendar collections
* **W:** write a collection (excluding address book or calendar collections) * **i:** subset of **r** that only allows direct access via HTTP method GET
* **w:** write an address book or calendar collection (CalDAV/CardDAV is susceptible to expensive search requests)
* **W:** write collections (excluding address books and calendars)
* **w:** write address book and calendar collections
## Storage ## Storage

View File

@ -71,21 +71,28 @@ class ApplicationGetMixin:
if path == "/.web" or path.startswith("/.web/"): if path == "/.web" or path.startswith("/.web/"):
return self._web.get(environ, base_prefix, path, user) return self._web.get(environ, base_prefix, path, user)
access = app.Access(self._rights, user, path) 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 return httputils.NOT_ALLOWED
with self._storage.acquire_lock("r", user): with self._storage.acquire_lock("r", user):
item = next(self._storage.discover(path), None) item = next(self._storage.discover(path), None)
if not item: if not item:
return httputils.NOT_FOUND 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 return httputils.NOT_ALLOWED
if isinstance(item, storage.BaseCollection): if isinstance(item, storage.BaseCollection):
tag = item.get_meta("tag") tag = item.get_meta("tag")
if not 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_type = xmlutils.MIMETYPES[tag]
content_disposition = self._content_disposition_attachement( content_disposition = self._content_disposition_attachement(
propose_filename(item)) propose_filename(item))
elif limited_access:
return httputils.NOT_ALLOWED
else: else:
content_type = xmlutils.OBJECT_MIMETYPES[item.name] content_type = xmlutils.OBJECT_MIMETYPES[item.name]
content_disposition = "" content_disposition = ""

View File

@ -21,10 +21,12 @@ collections and entries.
Permissions: Permissions:
- R: read a collection (excluding address book or calendar collections) - R: read collections (excluding address books and calendars)
- r: read an address book or calendar collection - r: read address book and calendar collections
- W: write a collection (excluding address book or calendar collections) - i: subset of **r** that only allows direct access via HTTP method GET
- w: write an address book or calendar collection (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. Take a look at the class ``BaseRights`` if you want to implement your own.

View File

@ -136,6 +136,30 @@ permissions: Rr""")
self._test_rights("from_file", "", "/custom/sub", "w", 401) self._test_rights("from_file", "", "/custom/sub", "w", 401)
self._test_rights("from_file", "tmp", "/custom/sub", "w", 403) 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): def test_custom(self):
"""Custom rights management.""" """Custom rights management."""
self._test_rights("radicale.tests.custom.rights", "", "/", "r", 401) self._test_rights("radicale.tests.custom.rights", "", "/", "r", 401)

13
rights
View File

@ -75,19 +75,24 @@
# Example: Allow everybody (including unauthenticated users) to read # Example: Allow everybody (including unauthenticated users) to read
# the collection "public" # the collection "public"
# Allow reading collection "public" # Allow reading collection "public" for authenticated users
#[public-principal] #[public-principal]
#user: .* #user: .+
#collection: public #collection: public
#permissions: R #permissions: R
# Allow reading all calendars and address books that are direct children of # Allow reading all calendars and address books that are direct children of
# the collection "public" # the collection "public" for authenticated users
#[public-calendars] #[public-calendars]
#user: .* #user: .+
#collection: public/[^/]+ #collection: public/[^/]+
#permissions: r #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 # Example: Grant users of the form user@domain.tld read access to the
# collection "domain.tld" # collection "domain.tld"