cosmetics, fix mk* conflict incl. testcases

This commit is contained in:
Peter Bieringer
2026-03-05 08:50:59 +01:00
parent 6848e852ca
commit bef0727b94
11 changed files with 239 additions and 105 deletions

View File

@@ -60,12 +60,12 @@ class ApplicationPartDelete(ApplicationBase):
permissions_filter = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("w"):
return httputils.NOT_ALLOWED

View File

@@ -79,12 +79,12 @@ class ApplicationPartGet(ApplicationBase):
permissions_filter = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("r") and "i" not in access.permissions:
return httputils.NOT_ALLOWED

View File

@@ -55,11 +55,11 @@ class ApplicationPartMkcalendar(ApplicationBase):
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)
return httputils.BAD_REQUEST
if self._sharing._enabled:
# check for shared collections (active or inactive)
collections_shared_map = self._sharing.sharing_collection_map_list(user, active=False)
if collections_shared_map:
for sharing in collections_shared_map:
if sharing['PathOrToken'] == path:
# check for shared collections (all users / active or inactive)
collections_share_map = self._sharing.sharing_collection_map_list(user=None, active=False)
if collections_share_map:
for share in collections_share_map:
if share['PathOrToken'] == path:
return httputils.CONFLICT
# TODO: use this?
# timezone = props.get("C:calendar-timezone")

View File

@@ -62,11 +62,11 @@ class ApplicationPartMkcol(ApplicationBase):
logger.warning("MKCOL request %r (type:%s): %s", path, collection_type, "rejected because of missing rights 'W'")
return httputils.NOT_ALLOWED
if self._sharing._enabled:
# check for shared collections (active or inactive)
collections_shared_map = self._sharing.sharing_collection_map_list(user, active=False)
if collections_shared_map:
for sharing in collections_shared_map:
if sharing['PathOrToken'] == path:
# check for shared collections (all users, active or inactive)
collections_share_map = self._sharing.sharing_collection_map_list(user=None, active=False)
if collections_share_map:
for share in collections_share_map:
if share['PathOrToken'] == path:
return httputils.CONFLICT
with self._storage.acquire_lock("w", user, path=path, request="MKCOL"):
item = next(iter(self._storage.discover(path)), None)

View File

@@ -72,12 +72,12 @@ class ApplicationPartMove(ApplicationBase):
permissions_filter = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("w"):
return httputils.NOT_ALLOWED
@@ -89,12 +89,12 @@ class ApplicationPartMove(ApplicationBase):
to_path = to_path[len(base_prefix):]
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(to_path, to_user)
if sharing:
share = self._sharing.sharing_collection_resolver(to_path, to_user)
if share:
# overwrite and run through extended permission check
to_path = sharing['PathMapped']
to_user = sharing['Owner']
to_permissions_filter = sharing['Permissions']
to_path = share['PathMapped']
to_user = share['Owner']
to_permissions_filter = share['Permissions']
to_access = Access(self._rights, to_user, to_path, to_permissions_filter)
to_access = Access(self._rights, to_user, to_path, to_permissions_filter)
if not to_access.check("w"):

View File

@@ -37,7 +37,7 @@ from radicale.log import logger
def xml_propfind(base_prefix: str, path: str,
xml_request: Optional[ET.Element],
allowed_items: Iterable[Tuple[types.CollectionOrItem, str]],
user: str, encoding: str, max_resource_size: int, sharing: Union[dict, None] = None) -> Optional[ET.Element]:
user: str, encoding: str, max_resource_size: int, share: Union[dict, None] = None) -> Optional[ET.Element]:
"""Read and answer PROPFIND requests.
Read rfc4918-9.1 for info.
@@ -74,7 +74,7 @@ def xml_propfind(base_prefix: str, path: str,
write = permission == "w"
multistatus.append(xml_propfind_response(
base_prefix, path, item, props, user, encoding, write=write,
allprop=allprop, propname=propname, max_resource_size=max_resource_size, sharing=sharing))
allprop=allprop, propname=propname, max_resource_size=max_resource_size, share=share))
return multistatus
@@ -82,7 +82,7 @@ def xml_propfind(base_prefix: str, path: str,
def xml_propfind_response(
base_prefix: str, path: str, item: types.CollectionOrItem,
props: Sequence[str], user: str, encoding: str, max_resource_size: int, write: bool = False,
propname: bool = False, allprop: bool = False, sharing: Union[dict, None] = None) -> ET.Element:
propname: bool = False, allprop: bool = False, share: Union[dict, None] = None) -> ET.Element:
"""Build and return a PROPFIND response."""
if propname and allprop or (props and (propname or allprop)):
raise ValueError("Only use one of props, propname and allprops")
@@ -102,9 +102,9 @@ def xml_propfind_response(
collection.path, item.href))
response = ET.Element(xmlutils.make_clark("D:response"))
href = ET.Element(xmlutils.make_clark("D:href"))
if sharing:
if share:
# backmap
uri = uri.replace(sharing['PathMapped'], sharing['PathOrToken'])
uri = uri.replace(share['PathMapped'], share['PathOrToken'])
href.text = xmlutils.make_href(base_prefix, uri)
response.append(href)
@@ -183,9 +183,9 @@ def xml_propfind_response(
is_collection and collection.is_principal):
child_element = ET.Element(xmlutils.make_clark("D:href"))
child_element.text = xmlutils.make_href(base_prefix, path)
if sharing:
if share:
# backmap
child_element.text = child_element.text.replace(sharing['PathMapped'], sharing['PathOrToken'])
child_element.text = child_element.text.replace(share['PathMapped'], share['PathOrToken'])
element.append(child_element)
elif tag == xmlutils.make_clark("C:supported-calendar-component-set"):
human_tag = xmlutils.make_human_tag(tag)
@@ -221,9 +221,9 @@ def xml_propfind_response(
child_element = ET.Element(xmlutils.make_clark("D:href"))
child_element.text = xmlutils.make_href(
base_prefix, "/%s/" % user)
if sharing:
if share:
# backmap
child_element.text = child_element.text.replace(sharing['Owner'], sharing['User'])
child_element.text = child_element.text.replace(share['Owner'], share['User'])
element.append(child_element)
else:
element.append(ET.Element(
@@ -345,12 +345,12 @@ def xml_propfind_response(
human_tag = xmlutils.make_human_tag(tag)
tag_text = collection.get_meta(human_tag)
if tag_text is not None:
if sharing:
if share:
# map from overlay
if sharing['Properties']:
if human_tag in sharing['Properties']:
if sharing['Properties'][human_tag] is not None:
tag_text = sharing['Properties'][human_tag]
if share['Properties']:
if human_tag in share['Properties']:
if share['Properties'][human_tag] is not None:
tag_text = share['Properties'][human_tag]
element.text = tag_text
else:
is404 = True
@@ -426,15 +426,15 @@ class ApplicationPartPropfind(ApplicationBase):
"""Manage PROPFIND request."""
http_depth = environ.get("HTTP_DEPTH", "0")
permissions_filter = None
sharing = None
share = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("r"):
return httputils.NOT_ALLOWED
@@ -467,13 +467,13 @@ class ApplicationPartPropfind(ApplicationBase):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND: get shared collections")
# check for shared collections
collections_shared_map = self._sharing.sharing_collection_map_list(user)
if collections_shared_map:
for sharing in collections_shared_map:
c_share = sharing['PathOrToken']
c_path = sharing['PathMapped']
c_user = sharing['Owner']
c_permissions_filter = sharing['Permissions']
collections_share_map = self._sharing.sharing_collection_map_list(user)
if collections_share_map:
for share in collections_share_map:
c_share = share['PathOrToken']
c_path = share['PathMapped']
c_user = share['Owner']
c_permissions_filter = share['Permissions']
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND: test shared collection: PathOrToken=%r PathMapped=%r Owner=%r Permissions=%s", c_share, c_path, c_user, c_permissions_filter)
c_access = Access(self._rights, c_user, c_path, c_permissions_filter)
@@ -490,7 +490,7 @@ class ApplicationPartPropfind(ApplicationBase):
headers = {"DAV": httputils.DAV_HEADERS,
"Content-Type": "text/xml; charset=%s" % self._encoding}
xml_answer = xml_propfind(base_prefix, path, xml_content,
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size, sharing=sharing)
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size, share=share)
if xml_answer is None:
return httputils.NOT_ALLOWED
return client.MULTI_STATUS, headers, self._xml_response(xml_answer), xmlutils.pretty_xml(xml_content)

View File

@@ -37,7 +37,7 @@ from radicale.log import logger
def xml_proppatch(base_prefix: str, path: str,
xml_request: Optional[ET.Element],
collection: Union[storage.BaseCollection, None], sharing: Union[dict, None] = None, sharing_overlay: bool = False, _sharing: Union[sharing.BaseSharing, None] = None) -> ET.Element:
collection: Union[storage.BaseCollection, None], share: Union[dict, None] = None, share_overlay: bool = False, _sharing: Union[sharing.BaseSharing, None] = None) -> ET.Element:
"""Read and answer PROPPATCH requests.
Read rfc4918-9.2 for info.
@@ -48,9 +48,9 @@ def xml_proppatch(base_prefix: str, path: str,
multistatus.append(response)
href = ET.Element(xmlutils.make_clark("D:href"))
href.text = xmlutils.make_href(base_prefix, path)
if sharing:
if share:
# backmap
href.text = href.text.replace(sharing['PathMapped'], sharing['PathOrToken'])
href.text = href.text.replace(share['PathMapped'], share['PathOrToken'])
response.append(href)
# Create D:propstat element for props with status 200 OK
propstat = ET.Element(xmlutils.make_clark("D:propstat"))
@@ -62,27 +62,27 @@ def xml_proppatch(base_prefix: str, path: str,
response.append(propstat)
props_with_remove = xmlutils.props_from_request(xml_request)
if sharing and sharing_overlay:
if share and share_overlay:
# PROPPATCH overlay adjustment
logger.debug("TRACE/PROPPATCH/xml_proppatch: sharing+sharing_overlay is active: %r", sharing)
if sharing['Properties'] is not None:
all_props_with_remove = cast(Dict[str, Optional[str]], radicale_item.check_and_sanitize_props(sharing['Properties']))
logger.debug("TRACE/PROPPATCH/xml_proppatch: share+share_overlay is active: %r", share)
if share['Properties'] is not None:
all_props_with_remove = cast(Dict[str, Optional[str]], radicale_item.check_and_sanitize_props(share['Properties']))
else:
all_props_with_remove = {}
all_props_with_remove.update(props_with_remove)
all_props = radicale_item.check_and_sanitize_props(all_props_with_remove)
logger.debug("TRACE/PROPPATCH/xml_proppatch: sharing+sharing_overlay result: %r", all_props)
logger.debug("TRACE/PROPPATCH/xml_proppatch: share+share_overlay result: %r", all_props)
else:
if collection is not None:
# always the case, but makes mypy happy
all_props_with_remove = cast(Dict[str, Optional[str]], dict(collection.get_meta()))
all_props_with_remove.update(props_with_remove)
all_props = radicale_item.check_and_sanitize_props(all_props_with_remove)
if sharing and sharing_overlay and _sharing is not None:
if share and share_overlay and _sharing is not None:
# _sharing is not None: always the case, but makes mypy happy
_sharing.database_update_sharing(ShareType=sharing['ShareType'],
PathOrToken=sharing['PathOrToken'],
OwnerOrUser=sharing['User'],
_sharing.database_update_sharing(ShareType=share['ShareType'],
PathOrToken=share['PathOrToken'],
OwnerOrUser=share['User'],
Properties=cast(Dict[str, str], all_props))
else:
if collection is not None:
@@ -100,21 +100,21 @@ class ApplicationPartProppatch(ApplicationBase):
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
"""Manage PROPPATCH request."""
permissions_filter = None
sharing = None
sharing_overlay = False
share = None
share_overlay = False
path_orig = path
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("w"):
logger.debug("TRACE/PROPPATCH/xml_proppatch: no write-access: %r", path)
if sharing:
if share:
# no write access -> use properties overlay
if self._sharing.permit_properties_overlay:
if permissions_filter is not None and "p" in permissions_filter:
@@ -122,11 +122,11 @@ class ApplicationPartProppatch(ApplicationBase):
return httputils.NOT_ALLOWED
else:
logger.info("PROPPATCH request on shared %r: no write-permissions, overlay permitted by option", path_orig)
sharing_overlay = True
share_overlay = True
else:
if permissions_filter is not None and "P" in permissions_filter:
logger.info("PROPPATCH request on shared %r: no write-permissions, overlay denied, but granted by permission 'P'", path_orig)
sharing_overlay = True
share_overlay = True
else:
logger.info("PROPPATCH request on shared %r: no write-permissions and overlay denied by option", path_orig)
return httputils.NOT_ALLOWED
@@ -134,18 +134,18 @@ class ApplicationPartProppatch(ApplicationBase):
return httputils.NOT_ALLOWED
else:
logger.debug("TRACE/PROPPATCH/xml_proppatch: write-access: %r", path)
if sharing:
if share:
# write access -> check for enforced properties overlay
logger.debug("TRACE/PROPPATCH/xml_proppatch: write-access/sharing: %r", path_orig)
if self._sharing.enforce_properties_overlay:
if permissions_filter is not None and "e" in permissions_filter:
logger.info("PROPPATCH request on shared %r: write-permissions, overlay enforced, but disabled by permission 'e'", path_orig)
else:
sharing_overlay = True
share_overlay = True
else:
if permissions_filter is not None and "E" in permissions_filter:
logger.info("PROPPATCH request on shared %r: write-permissions, overlay not enforced, but enforced by permission 'E'", path_orig)
sharing_overlay = True
share_overlay = True
try:
xml_content = self._read_xml_request_body(environ)
except RuntimeError as e:
@@ -156,13 +156,13 @@ class ApplicationPartProppatch(ApplicationBase):
logger.debug("Client timed out", exc_info=True)
return httputils.REQUEST_TIMEOUT
if sharing_overlay:
if share_overlay:
# call API function internally and no not trigger any hook
headers = {"DAV": httputils.DAV_HEADERS,
"Content-Type": "text/xml; charset=%s" % self._encoding}
try:
xml_answer = xml_proppatch(base_prefix, path, xml_content,
None, sharing, sharing_overlay, self._sharing)
None, share, share_overlay, self._sharing)
if xml_content is not None:
content = DefusedET.tostring(
xml_content,
@@ -199,7 +199,7 @@ class ApplicationPartProppatch(ApplicationBase):
"Content-Type": "text/xml; charset=%s" % self._encoding}
try:
xml_answer = xml_proppatch(base_prefix, path, xml_content,
item, sharing)
item, share)
if xml_content is not None:
content = DefusedET.tostring(
xml_content,

View File

@@ -184,12 +184,12 @@ class ApplicationPartPut(ApplicationBase):
permissions_filter = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
access = Access(self._rights, user, path, permissions_filter)
if not access.check("w"):

View File

@@ -151,7 +151,7 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
collection: storage.BaseCollection, encoding: str,
unlock_storage_fn: Callable[[], None],
max_occurrence: int = 0, user: str = "", remote_addr: str = "", remote_useragent: str = "",
sharing: Union[dict, None] = None) -> Tuple[int, ET.Element]:
share: Union[dict, None] = None) -> Tuple[int, ET.Element]:
"""Read and answer REPORT requests that return XML.
Read rfc3253-3.6 for info.
@@ -360,7 +360,7 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
if found_props or not_found_props:
multistatus.append(xml_item_response(
base_prefix, uri, found_props=found_props,
not_found_props=not_found_props, found_item=True, sharing=sharing))
not_found_props=not_found_props, found_item=True, share=share))
return client.MULTI_STATUS, multistatus
@@ -711,13 +711,13 @@ def _find_overridden(
def xml_item_response(base_prefix: str, href: str,
found_props: Sequence[ET.Element] = (),
not_found_props: Sequence[ET.Element] = (),
found_item: bool = True, sharing: Union[dict, None] = None) -> ET.Element:
found_item: bool = True, share: Union[dict, None] = None) -> ET.Element:
response = ET.Element(xmlutils.make_clark("D:response"))
href_element = ET.Element(xmlutils.make_clark("D:href"))
href_element.text = xmlutils.make_href(base_prefix, href)
if sharing:
href_element.text = href_element.text.replace(sharing['PathMapped'], sharing['PathOrToken'])
if share:
href_element.text = href_element.text.replace(share['PathMapped'], share['PathOrToken'])
response.append(href_element)
if found_item:
@@ -820,15 +820,15 @@ class ApplicationPartReport(ApplicationBase):
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
"""Manage REPORT request."""
permissions_filter = None
sharing = None
share = None
if self._sharing._enabled:
# Sharing by token or map (if enabled)
sharing = self._sharing.sharing_collection_resolver(path, user)
if sharing:
share = self._sharing.sharing_collection_resolver(path, user)
if share:
# overwrite and run through extended permission check
path = sharing['PathMapped']
user = sharing['Owner']
permissions_filter = sharing['Permissions']
path = share['PathMapped']
user = share['Owner']
permissions_filter = share['Permissions']
access = Access(self._rights, user, path, permissions_filter)
if not access.check("r"):
return httputils.NOT_ALLOWED
@@ -871,7 +871,7 @@ class ApplicationPartReport(ApplicationBase):
try:
status, xml_answer = xml_report(
base_prefix, path, xml_content, collection, self._encoding,
lock_stack.close, max_occurrence, user, remote_host, remote_useragent, sharing=sharing)
lock_stack.close, max_occurrence, user, remote_host, remote_useragent, share=share)
except ValueError as e:
logger.warning(
"Bad REPORT request on %r: %s", path, e, exc_info=True)