sharing/propfind: fix/reorg collection listing

This commit is contained in:
Peter Bieringer
2026-03-14 17:28:07 +01:00
parent a0733a83e3
commit 65982e26cb

View File

@@ -25,7 +25,8 @@ import posixpath
import socket import socket
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from http import client from http import client
from typing import Dict, Iterable, Iterator, List, Optional, Sequence, Tuple from typing import (Dict, Iterable, Iterator, List, Optional, Sequence, Tuple,
Union)
from radicale import (httputils, pathutils, rights, storage, types, utils, from radicale import (httputils, pathutils, rights, storage, types, utils,
xmlutils) xmlutils)
@@ -35,7 +36,7 @@ from radicale.log import logger
def xml_propfind(base_prefix: str, path: str, def xml_propfind(base_prefix: str, path: str,
xml_request: Optional[ET.Element], xml_request: Optional[ET.Element],
allowed_items: Iterable[Tuple[types.CollectionOrItem, str]], allowed_items: Iterable[Tuple[types.CollectionOrItem, str, str]],
user: str, encoding: str, max_resource_size: int, shares: dict = {}) -> Optional[ET.Element]: user: str, encoding: str, max_resource_size: int, shares: dict = {}) -> Optional[ET.Element]:
"""Read and answer PROPFIND requests. """Read and answer PROPFIND requests.
@@ -69,11 +70,14 @@ def xml_propfind(base_prefix: str, path: str,
# Writing answer # Writing answer
multistatus = ET.Element(xmlutils.make_clark("D:multistatus")) multistatus = ET.Element(xmlutils.make_clark("D:multistatus"))
for item, permission in allowed_items: if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND/xml_propfind: shares=%r", shares)
for item, permission, sharetype in allowed_items:
write = permission == "w" write = permission == "w"
multistatus.append(xml_propfind_response( multistatus.append(xml_propfind_response(
base_prefix, path, item, props, user, encoding, write=write, base_prefix, path, item, props, user, encoding, write=write,
allprop=allprop, propname=propname, max_resource_size=max_resource_size, shares=shares)) allprop=allprop, propname=propname, max_resource_size=max_resource_size, shares=shares, sharetype=sharetype))
return multistatus return multistatus
@@ -81,7 +85,7 @@ def xml_propfind(base_prefix: str, path: str,
def xml_propfind_response( def xml_propfind_response(
base_prefix: str, path: str, item: types.CollectionOrItem, base_prefix: str, path: str, item: types.CollectionOrItem,
props: Sequence[str], user: str, encoding: str, max_resource_size: int, write: bool = False, props: Sequence[str], user: str, encoding: str, max_resource_size: int, write: bool = False,
propname: bool = False, allprop: bool = False, shares: dict = {}) -> ET.Element: propname: bool = False, allprop: bool = False, shares: dict = {}, sharetype: Union[str | None] = None) -> ET.Element:
"""Build and return a PROPFIND response.""" """Build and return a PROPFIND response."""
if propname and allprop or (props and (propname or allprop)): if propname and allprop or (props and (propname or allprop)):
raise ValueError("Only use one of props, propname and allprops") raise ValueError("Only use one of props, propname and allprops")
@@ -101,10 +105,22 @@ def xml_propfind_response(
collection.path, item.href)) collection.path, item.href))
response = ET.Element(xmlutils.make_clark("D:response")) response = ET.Element(xmlutils.make_clark("D:response"))
href = ET.Element(xmlutils.make_clark("D:href")) href = ET.Element(xmlutils.make_clark("D:href"))
if uri in shares:
share = shares[uri] # lookup share
else: share = None
share = None if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND/xml_propfind: sharetype=%r item.path=%r", sharetype, uri)
for entry in shares:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND/xml_propfind: entry=%r", entry)
if entry is not None:
if shares[entry]['PathMapped'] == uri:
if sharetype is None or shares[entry]['ShareType'] == sharetype:
share = shares[entry]
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND/xml_propfind: share=%r", share)
break
if share: if share:
# backmap # backmap
uri = uri.replace(share['PathMapped'], share['PathOrToken']) uri = uri.replace(share['PathMapped'], share['PathOrToken'])
@@ -438,6 +454,7 @@ class ApplicationPartPropfind(ApplicationBase):
http_depth = environ.get("HTTP_DEPTH", "0") http_depth = environ.get("HTTP_DEPTH", "0")
permissions_filter = None permissions_filter = None
shares: dict = {} shares: dict = {}
allowed_items: list = []
if self._sharing._enabled: if self._sharing._enabled:
# Sharing by token or map (if enabled) # Sharing by token or map (if enabled)
share = self._sharing.sharing_collection_resolver(path, user) share = self._sharing.sharing_collection_resolver(path, user)
@@ -446,7 +463,7 @@ class ApplicationPartPropfind(ApplicationBase):
path = share['PathMapped'] path = share['PathMapped']
user = share['Owner'] user = share['Owner']
permissions_filter = share['Permissions'] permissions_filter = share['Permissions']
shares[share['PathMapped']] = share shares[share['PathOrToken']] = share
access = Access(self._rights, user, path, permissions_filter) access = Access(self._rights, user, path, permissions_filter)
if not access.check("r"): if not access.check("r"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
@@ -473,15 +490,16 @@ class ApplicationPartPropfind(ApplicationBase):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
# put item back # put item back
items_iter = itertools.chain([item], items_iter) items_iter = itertools.chain([item], items_iter)
allowed_items = list(self._collect_allowed_items(items_iter, user)) for item, permission in list(self._collect_allowed_items(items_iter, user)):
allowed_items.append((item, permission, None))
if self._sharing._enabled: if self._sharing._enabled:
if http_depth == "1": if http_depth == "1":
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/PROPFIND: get shared collections") logger.debug("TRACE/PROPFIND: get shared collections")
# check for shared collections related to user, Enabled and not Hidden # check for shared collections related to user, Enabled and not Hidden
collections_share_map = self._sharing.sharing_collection_map_list(User=user, Enabled=True, Hidden=False) collections_share_list = self._sharing.sharing_collection_list(User=user, Enabled=True, Hidden=False)
if collections_share_map: if collections_share_list:
for share in collections_share_map: for share in collections_share_list:
c_share = share['PathOrToken'] c_share = share['PathOrToken']
c_path = share['PathMapped'] c_path = share['PathMapped']
c_user = share['Owner'] c_user = share['Owner']
@@ -498,8 +516,10 @@ class ApplicationPartPropfind(ApplicationBase):
with self._storage.acquire_lock("r", c_user): with self._storage.acquire_lock("r", c_user):
c_items_iter = iter(self._storage.discover(c_path, "0")) c_items_iter = iter(self._storage.discover(c_path, "0"))
c_allowed_items = list(self._collect_allowed_items(c_items_iter, c_user)) c_allowed_items = list(self._collect_allowed_items(c_items_iter, c_user))
allowed_items = allowed_items + c_allowed_items for item, permission in c_allowed_items:
shares[c_path] = share allowed_items.append((item, permission, share['ShareType']))
shares[c_share] = share
headers = {"DAV": httputils.DAV_HEADERS, headers = {"DAV": httputils.DAV_HEADERS,
"Content-Type": "text/xml; charset=%s" % self._encoding} "Content-Type": "text/xml; charset=%s" % self._encoding}
xml_answer = xml_propfind(base_prefix, path, xml_content, xml_answer = xml_propfind(base_prefix, path, xml_content,