mirror of
https://github.com/Kozea/Radicale.git
synced 2025-08-10 18:40:53 +00:00
Merge pull request #1584 from pbiering/change-default-permit_delete_collection
permit_delete_collection per collection control
This commit is contained in:
commit
bfe0ccc463
5 changed files with 62 additions and 11 deletions
|
@ -4,6 +4,7 @@
|
|||
|
||||
* Adjustment: option [auth] htpasswd_encryption change default from "md5" to "autodetect"
|
||||
* Add: option [auth] type=ldap with (group) rights management via LDAP/LDAPS
|
||||
* Enhancement: permit_delete_collection can be now controlled also per collection by rights 'D' or 'd'
|
||||
|
||||
## 3.2.3
|
||||
* Add: support for Python 3.13
|
||||
|
|
|
@ -913,6 +913,9 @@ File for the rights backend `from_file`. See the
|
|||
|
||||
Global control of permission to delete complete collection (default: True)
|
||||
|
||||
If False it can be permitted by permissions per section with: D
|
||||
If True it can be forbidden by permissions per section with: d
|
||||
|
||||
#### storage
|
||||
|
||||
##### type
|
||||
|
@ -1295,6 +1298,8 @@ The following `permissions` are recognized:
|
|||
(CalDAV/CardDAV is susceptible to expensive search requests)
|
||||
* **W:** write collections (excluding address books and calendars)
|
||||
* **w:** write address book and calendar collections
|
||||
* **D:** permit delete of collection in case permit_delete_collection=False
|
||||
* **d:** forbid delete of collection in case permit_delete_collection=True
|
||||
|
||||
### Storage
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ class Access:
|
|||
|
||||
def check(self, permission: str,
|
||||
item: Optional[types.CollectionOrItem] = None) -> bool:
|
||||
if permission not in "rw":
|
||||
if permission not in "rwdD":
|
||||
raise ValueError("Invalid permission argument: %r" % permission)
|
||||
if not item:
|
||||
permissions = permission + permission.upper()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -24,6 +25,7 @@ from typing import Optional
|
|||
from radicale import httputils, storage, types, xmlutils
|
||||
from radicale.app.base import Access, ApplicationBase
|
||||
from radicale.hook import HookNotificationItem, HookNotificationItemTypes
|
||||
from radicale.log import logger
|
||||
|
||||
|
||||
def xml_delete(base_prefix: str, path: str, collection: storage.BaseCollection,
|
||||
|
@ -71,17 +73,22 @@ class ApplicationPartDelete(ApplicationBase):
|
|||
hook_notification_item_list = []
|
||||
if isinstance(item, storage.BaseCollection):
|
||||
if self._permit_delete_collection:
|
||||
for i in item.get_all():
|
||||
hook_notification_item_list.append(
|
||||
HookNotificationItem(
|
||||
HookNotificationItemTypes.DELETE,
|
||||
access.path,
|
||||
i.uid
|
||||
)
|
||||
)
|
||||
xml_answer = xml_delete(base_prefix, path, item)
|
||||
if access.check("d", item):
|
||||
logger.info("delete of collection is permitted by config/option [rights] permit_delete_collection but explicit forbidden by permission 'd': %s", path)
|
||||
return httputils.NOT_ALLOWED
|
||||
else:
|
||||
return httputils.NOT_ALLOWED
|
||||
if not access.check("D", item):
|
||||
logger.info("delete of collection is prevented by config/option [rights] permit_delete_collection and not explicit allowed by permission 'D': %s", path)
|
||||
return httputils.NOT_ALLOWED
|
||||
for i in item.get_all():
|
||||
hook_notification_item_list.append(
|
||||
HookNotificationItem(
|
||||
HookNotificationItemTypes.DELETE,
|
||||
access.path,
|
||||
i.uid
|
||||
)
|
||||
)
|
||||
xml_answer = xml_delete(base_prefix, path, item)
|
||||
else:
|
||||
assert item.collection is not None
|
||||
assert item.href is not None
|
||||
|
|
|
@ -41,8 +41,19 @@ class TestBaseRequests(BaseTest):
|
|||
def setup_method(self) -> None:
|
||||
BaseTest.setup_method(self)
|
||||
rights_file_path = os.path.join(self.colpath, "rights")
|
||||
self.configure({"rights": {"permit_delete_collection": True}})
|
||||
with open(rights_file_path, "w") as f:
|
||||
f.write("""\
|
||||
[permit delete collection]
|
||||
user: .*
|
||||
collection: test-permit-delete
|
||||
permissions: RrWwD
|
||||
|
||||
[forbid delete collection]
|
||||
user: .*
|
||||
collection: test-forbid-delete
|
||||
permissions: RrWwd
|
||||
|
||||
[allow all]
|
||||
user: .*
|
||||
collection: .*
|
||||
|
@ -439,6 +450,33 @@ permissions: RrWw""")
|
|||
assert responses["/calendar.ics/"] == 200
|
||||
self.get("/calendar.ics/", check=404)
|
||||
|
||||
def test_delete_collection_not_permitted(self) -> None:
|
||||
"""Delete a collection (try if not permitted)."""
|
||||
self.configure({"rights": {"permit_delete_collection": False}})
|
||||
self.mkcalendar("/calendar.ics/")
|
||||
event = get_file_content("event1.ics")
|
||||
self.put("/calendar.ics/event1.ics", event)
|
||||
_, responses = self.delete("/calendar.ics/", check=401)
|
||||
self.get("/calendar.ics/", check=200)
|
||||
|
||||
def test_delete_collection_global_forbid_explicit_permit(self) -> None:
|
||||
"""Delete a collection with permitted path (expect permit)."""
|
||||
self.configure({"rights": {"permit_delete_collection": False}})
|
||||
self.mkcalendar("/test-permit-delete/")
|
||||
event = get_file_content("event1.ics")
|
||||
self.put("/test-permit-delete/event1.ics", event)
|
||||
_, responses = self.delete("/test-permit-delete/", check=200)
|
||||
self.get("/test-permit-delete/", check=404)
|
||||
|
||||
def test_delete_collection_global_permit_explicit_forbid(self) -> None:
|
||||
"""Delete a collection with permitted path (expect forbid)."""
|
||||
self.configure({"rights": {"permit_delete_collection": True}})
|
||||
self.mkcalendar("/test-forbid-delete/")
|
||||
event = get_file_content("event1.ics")
|
||||
self.put("/test-forbid-delete/event1.ics", event)
|
||||
_, responses = self.delete("/test-forbid-delete/", check=401)
|
||||
self.get("/test-forbid-delete/", check=200)
|
||||
|
||||
def test_delete_root_collection(self) -> None:
|
||||
"""Delete the root collection."""
|
||||
self.mkcalendar("/calendar.ics/")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue