Merge pull request #2066 from pbiering/sharing-propfind-expose-permissions
Sharing propfind expose permissions
This commit is contained in:
18
SHARING.md
18
SHARING.md
@@ -589,6 +589,24 @@ Whitelisted ones are defined in `OVERLAY_PROPERTIES_WHITELIST` in `radicale/shar
|
|||||||
* `enforce_properties_overlay`
|
* `enforce_properties_overlay`
|
||||||
* supported *share* permissions: `Ee`
|
* supported *share* permissions: `Ee`
|
||||||
|
|
||||||
|
#### Properties Overlay Control Precedence
|
||||||
|
|
||||||
|
##### General Permission for Overlay
|
||||||
|
|
||||||
|
1. permission of particular share configuration: `p` or `P`
|
||||||
|
|
||||||
|
2. permission based on `rights` per location: `p` or `P`
|
||||||
|
|
||||||
|
3. config option: `permit_properties_overlay`
|
||||||
|
|
||||||
|
##### Enforce Overlay on Write
|
||||||
|
|
||||||
|
1. permission of particular share configuration: `e` or `E`
|
||||||
|
|
||||||
|
2. permission based on `rights` per location: `e` or `E`
|
||||||
|
|
||||||
|
3. config option: `enforce_properties_overlay`
|
||||||
|
|
||||||
### Properties Overlay Example
|
### Properties Overlay Example
|
||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
|||||||
@@ -34,10 +34,16 @@ from radicale.app.base import Access, ApplicationBase
|
|||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
|
|
||||||
def xml_propfind(base_prefix: str, path: str,
|
def xml_propfind(
|
||||||
xml_request: Optional[ET.Element],
|
self,
|
||||||
allowed_items: Iterable[Tuple[types.CollectionOrItem, str, str]],
|
base_prefix: str,
|
||||||
user: str, encoding: str, max_resource_size: int, shares: dict = {}) -> Optional[ET.Element]:
|
path: str,
|
||||||
|
xml_request: Optional[ET.Element],
|
||||||
|
allowed_items: Iterable[Tuple[types.CollectionOrItem, str, str, str]],
|
||||||
|
user: str, encoding: str,
|
||||||
|
max_resource_size: int,
|
||||||
|
shares: dict = {},
|
||||||
|
) -> Optional[ET.Element]:
|
||||||
"""Read and answer PROPFIND requests.
|
"""Read and answer PROPFIND requests.
|
||||||
|
|
||||||
Read rfc4918-9.1 for info.
|
Read rfc4918-9.1 for info.
|
||||||
@@ -73,19 +79,46 @@ def xml_propfind(base_prefix: str, path: str,
|
|||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/PROPFIND/xml_propfind: shares=%r", shares)
|
logger.debug("TRACE/PROPFIND/xml_propfind: shares=%r", shares)
|
||||||
|
|
||||||
for item, permission, conversion in allowed_items:
|
for item, permission, raw_permissions, conversion in allowed_items:
|
||||||
write = permission == "w"
|
write = permission == "w"
|
||||||
multistatus.append(xml_propfind_response(
|
multistatus.append(
|
||||||
base_prefix, path, item, props, user, encoding, write=write,
|
xml_propfind_response(
|
||||||
allprop=allprop, propname=propname, max_resource_size=max_resource_size, shares=shares, conversion=conversion))
|
self,
|
||||||
|
base_prefix,
|
||||||
|
path,
|
||||||
|
item,
|
||||||
|
props,
|
||||||
|
user,
|
||||||
|
encoding,
|
||||||
|
write=write,
|
||||||
|
allprop=allprop,
|
||||||
|
propname=propname,
|
||||||
|
max_resource_size=max_resource_size,
|
||||||
|
shares=shares,
|
||||||
|
conversion=conversion,
|
||||||
|
raw_permissions=raw_permissions,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return multistatus
|
return multistatus
|
||||||
|
|
||||||
|
|
||||||
def xml_propfind_response(
|
def xml_propfind_response(
|
||||||
base_prefix: str, path: str, item: types.CollectionOrItem,
|
self,
|
||||||
props: Sequence[str], user: str, encoding: str, max_resource_size: int, write: bool = False,
|
base_prefix: str,
|
||||||
propname: bool = False, allprop: bool = False, shares: dict = {}, conversion: Union[str, None] = None) -> ET.Element:
|
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,
|
||||||
|
shares: dict = {},
|
||||||
|
conversion: Union[str, None] = None,
|
||||||
|
raw_permissions: str = "",
|
||||||
|
) -> 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")
|
||||||
@@ -274,17 +307,34 @@ def xml_propfind_response(
|
|||||||
elif tag == xmlutils.make_clark("D:current-user-privilege-set"):
|
elif tag == xmlutils.make_clark("D:current-user-privilege-set"):
|
||||||
privileges = ["D:read"]
|
privileges = ["D:read"]
|
||||||
if share:
|
if share:
|
||||||
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
|
logger.debug("TRACE/PROPFIND/xml_propfind_response/current-user-privilege-set: raw_permissions=%r share[Permissions]=%r permit_properties_overlay=%s", raw_permissions, share['Permissions'], self._sharing.permit_properties_overlay)
|
||||||
if write:
|
if write:
|
||||||
if "P" in share['Permissions']:
|
|
||||||
privileges.append("D:write-properties")
|
|
||||||
if "w" in share['Permissions']:
|
if "w" in share['Permissions']:
|
||||||
if not share_bday_automap:
|
if not share_bday_automap:
|
||||||
privileges.append("D:write-content")
|
privileges.append("D:write-content")
|
||||||
|
# priority share->rights->global
|
||||||
|
if ("P" in share['Permissions'] or
|
||||||
|
("P" in raw_permissions and "p" not in share['Permissions']) or
|
||||||
|
(self._sharing.permit_properties_overlay and "p" not in raw_permissions and "p" not in share['Permissions'])
|
||||||
|
) and not (
|
||||||
|
"p" in share['Permissions'] or
|
||||||
|
("p" in raw_permissions and "P" not in share['Permissions']) or
|
||||||
|
(self._sharing.permit_properties_overlay and "P" not in raw_permissions and "P" not in share['Permissions'])):
|
||||||
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
|
logger.debug("TRACE/PROPFIND/xml_propfind_response/current-user-privilege-set: add D:write-properties")
|
||||||
|
privileges.append("D:write-properties")
|
||||||
elif write:
|
elif write:
|
||||||
privileges.append("D:all")
|
privileges.append("D:all")
|
||||||
privileges.append("D:write")
|
privileges.append("D:write")
|
||||||
privileges.append("D:write-properties")
|
privileges.append("D:write-properties")
|
||||||
privileges.append("D:write-content")
|
privileges.append("D:write-content")
|
||||||
|
|
||||||
|
if ("T" in raw_permissions or (self._sharing.permit_create_token and "t" not in raw_permissions)):
|
||||||
|
privileges.append("RADICALE:share-token")
|
||||||
|
if ("M" in raw_permissions or (self._sharing.permit_create_map and "m" not in raw_permissions)):
|
||||||
|
privileges.append("RADICALE:share-map")
|
||||||
|
|
||||||
for human_tag in privileges:
|
for human_tag in privileges:
|
||||||
privilege = ET.Element(xmlutils.make_clark("D:privilege"))
|
privilege = ET.Element(xmlutils.make_clark("D:privilege"))
|
||||||
privilege.append(ET.Element(
|
privilege.append(ET.Element(
|
||||||
@@ -470,26 +520,25 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
|
|
||||||
def _collect_allowed_items(
|
def _collect_allowed_items(
|
||||||
self, items: Iterable[types.CollectionOrItem], user: str
|
self, items: Iterable[types.CollectionOrItem], user: str
|
||||||
) -> Iterator[Tuple[types.CollectionOrItem, str]]:
|
) -> Iterator[Tuple[types.CollectionOrItem, str, str]]:
|
||||||
"""Get items from request that user is allowed to access."""
|
"""Get items from request that user is allowed to access."""
|
||||||
for item in items:
|
for item in items:
|
||||||
if isinstance(item, storage.BaseCollection):
|
if isinstance(item, storage.BaseCollection):
|
||||||
path = pathutils.unstrip_path(item.path, True)
|
path = pathutils.unstrip_path(item.path, True)
|
||||||
|
raw_permissions = self._rights.authorization(user, path)
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/PROPFIND/_collect_allowed_items/BaseCollection: path=%r user=%r", path, user)
|
logger.debug("TRACE/PROPFIND/_collect_allowed_items/BaseCollection: path=%r user=%r raw_permissions=%r", path, user, raw_permissions)
|
||||||
if item.tag:
|
if item.tag:
|
||||||
permissions = rights.intersect(
|
permissions = rights.intersect(raw_permissions, "rw")
|
||||||
self._rights.authorization(user, path), "rw")
|
|
||||||
target = "collection with tag %r" % item.path
|
target = "collection with tag %r" % item.path
|
||||||
else:
|
else:
|
||||||
permissions = rights.intersect(
|
permissions = rights.intersect(raw_permissions, "RW")
|
||||||
self._rights.authorization(user, path), "RW")
|
|
||||||
target = "collection %r" % item.path
|
target = "collection %r" % item.path
|
||||||
else:
|
else:
|
||||||
assert item.collection is not None
|
assert item.collection is not None
|
||||||
path = pathutils.unstrip_path(item.collection.path, True)
|
path = pathutils.unstrip_path(item.collection.path, True)
|
||||||
permissions = rights.intersect(
|
raw_permissions = self._rights.authorization(user, path)
|
||||||
self._rights.authorization(user, path), "rw")
|
permissions = rights.intersect(raw_permissions, "rw")
|
||||||
target = "item %r from %r" % (item.href, item.collection.path)
|
target = "item %r from %r" % (item.href, item.collection.path)
|
||||||
if rights.intersect(permissions, "Ww"):
|
if rights.intersect(permissions, "Ww"):
|
||||||
permission = "w"
|
permission = "w"
|
||||||
@@ -504,7 +553,7 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
"%s has %s access to %s",
|
"%s has %s access to %s",
|
||||||
repr(user) if user else "anonymous user", status, target)
|
repr(user) if user else "anonymous user", status, target)
|
||||||
if permission:
|
if permission:
|
||||||
yield item, permission
|
yield item, permission, raw_permissions
|
||||||
|
|
||||||
def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
|
def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||||
@@ -548,14 +597,14 @@ 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)
|
||||||
for item, permission in list(self._collect_allowed_items(items_iter, user)):
|
for item, permission, raw_permissions in list(self._collect_allowed_items(items_iter, user)):
|
||||||
if self._sharing._enabled and share:
|
if self._sharing._enabled and share:
|
||||||
if share['Conversion'] == "bday" and not isinstance(item, storage.BaseCollection):
|
if share['Conversion'] == "bday" and not isinstance(item, storage.BaseCollection):
|
||||||
if not item.convert_vcf_to_ics():
|
if not item.convert_vcf_to_ics():
|
||||||
continue
|
continue
|
||||||
allowed_items.append((item, permission, share['Conversion']))
|
allowed_items.append((item, permission, raw_permissions, share['Conversion']))
|
||||||
else:
|
else:
|
||||||
allowed_items.append((item, permission, None))
|
allowed_items.append((item, permission, raw_permissions, 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):
|
||||||
@@ -569,24 +618,24 @@ class ApplicationPartPropfind(ApplicationBase):
|
|||||||
c_user = share['Owner']
|
c_user = share['Owner']
|
||||||
c_permissions_filter = share['Permissions']
|
c_permissions_filter = share['Permissions']
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
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)
|
logger.debug("TRACE/PROPFIND: test shared collection: PathOrToken=%r PathMapped=%r Owner=%r Permissions=%r", c_share, c_path, c_user, c_permissions_filter)
|
||||||
c_access = Access(self._rights, c_user, c_path, c_permissions_filter)
|
c_access = Access(self._rights, c_user, c_path, c_permissions_filter)
|
||||||
if not c_access.check("r"):
|
if not c_access.check("r"):
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/PROPFIND: skip shared collection: PathOrToken=%r PathMapped=%r Owner=%r Permissions=%s (permissions not matching)", c_share, c_path, c_user, c_permissions_filter)
|
logger.debug("TRACE/PROPFIND: skip shared collection: PathOrToken=%r PathMapped=%r Owner=%r Permissions=%r (permissions not matching)", c_share, c_path, c_user, c_permissions_filter)
|
||||||
continue
|
continue
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/PROPFIND: append shared collection: PathOrToken=%r PathMapped=%r Owner=%r", c_share, c_path, c_user)
|
logger.debug("TRACE/PROPFIND: append shared collection: PathOrToken=%r PathMapped=%r Owner=%r Permissions=%r", c_share, c_path, c_user, c_permissions_filter)
|
||||||
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))
|
||||||
for item, permission in c_allowed_items:
|
for item, permission, raw_permissions in c_allowed_items:
|
||||||
allowed_items.append((item, permission, share['Conversion']))
|
allowed_items.append((item, permission, raw_permissions, share['Conversion']))
|
||||||
shares[c_share] = share
|
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(self, base_prefix, path, xml_content,
|
||||||
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size, shares=shares)
|
allowed_items, user, self._encoding, max_resource_size=self._max_resource_size, shares=shares)
|
||||||
if xml_answer is None:
|
if xml_answer is None:
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@@ -112,24 +113,30 @@ class ApplicationPartProppatch(ApplicationBase):
|
|||||||
user = share['Owner']
|
user = share['Owner']
|
||||||
permissions_filter = share['Permissions']
|
permissions_filter = share['Permissions']
|
||||||
access = Access(self._rights, user, path, permissions_filter)
|
access = Access(self._rights, user, path, permissions_filter)
|
||||||
|
raw_permissions = self._rights.authorization(user, path)
|
||||||
if not access.check("w"):
|
if not access.check("w"):
|
||||||
logger.debug("TRACE/PROPPATCH/xml_proppatch: no write-access: %r", path)
|
logger.debug("TRACE/PROPPATCH/xml_proppatch: no native write-access: %r", path)
|
||||||
if share:
|
if share:
|
||||||
# no write access -> use properties overlay
|
# priority share->rights->global
|
||||||
if self._sharing.permit_properties_overlay:
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
if permissions_filter is not None and "p" in permissions_filter:
|
logger.debug("TRACE/PROPPATCH/share: raw_permissions=%r share[Permissions]=%r permit_properties_overlay=%s enforce_properties_overlay=%s", raw_permissions, share['Permissions'], self._sharing.permit_properties_overlay, self._sharing.enforce_properties_overlay)
|
||||||
logger.info("PROPPATCH request on shared %r: no write-permissions, overlay permitted, but denied by permission 'p'", path_orig)
|
if ("P" in share['Permissions'] or
|
||||||
return httputils.NOT_ALLOWED
|
("P" in raw_permissions and "p" not in share['Permissions']) or
|
||||||
|
(self._sharing.permit_properties_overlay and "p" not in raw_permissions and "p" not in share['Permissions'])
|
||||||
|
) and not (
|
||||||
|
"p" in share['Permissions'] or
|
||||||
|
("p" in raw_permissions and "P" not in share['Permissions']) or
|
||||||
|
(self._sharing.permit_properties_overlay and "P" not in raw_permissions and "P" not in share['Permissions'])):
|
||||||
|
logger.info("PROPPATCH request on shared %r: write-access", path_orig)
|
||||||
|
if permissions_filter is not None and "e" in permissions_filter:
|
||||||
|
logger.info("PROPPATCH request on shared %r: write-access, overlay enforced, but disabled by share permission 'e'", path_orig)
|
||||||
|
elif "e" in raw_permissions:
|
||||||
|
logger.info("PROPPATCH request on shared %r: write-access, overlay enforced, but disabled by rights permission 'e'", path_orig)
|
||||||
else:
|
else:
|
||||||
logger.info("PROPPATCH request on shared %r: no write-permissions, overlay permitted by option", path_orig)
|
|
||||||
share_overlay = True
|
share_overlay = True
|
||||||
else:
|
else:
|
||||||
if permissions_filter is not None and "P" in permissions_filter:
|
logger.info("PROPPATCH request on shared %r: no write-access", path_orig)
|
||||||
logger.info("PROPPATCH request on shared %r: no write-permissions, overlay denied, but granted by permission 'P'", path_orig)
|
return httputils.NOT_ALLOWED
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
else:
|
else:
|
||||||
@@ -139,7 +146,9 @@ class ApplicationPartProppatch(ApplicationBase):
|
|||||||
logger.debug("TRACE/PROPPATCH/xml_proppatch: write-access/sharing: %r", path_orig)
|
logger.debug("TRACE/PROPPATCH/xml_proppatch: write-access/sharing: %r", path_orig)
|
||||||
if self._sharing.enforce_properties_overlay:
|
if self._sharing.enforce_properties_overlay:
|
||||||
if permissions_filter is not None and "e" in permissions_filter:
|
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)
|
logger.info("PROPPATCH request on shared %r: write-permissions, overlay enforced, but disabled by share permission 'e'", path_orig)
|
||||||
|
elif "e" in raw_permissions:
|
||||||
|
logger.info("PROPPATCH request on shared %r: write-permissions, overlay enforced, but disabled by rights permission 'e'", path_orig)
|
||||||
else:
|
else:
|
||||||
share_overlay = True
|
share_overlay = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -69,7 +69,11 @@ def positive_float(value: Any) -> float:
|
|||||||
def rights_permission(value: Any) -> str:
|
def rights_permission(value: Any) -> str:
|
||||||
for permission in value:
|
for permission in value:
|
||||||
if permission not in rights.INTERNAL_PERMISSIONS:
|
if permission not in rights.INTERNAL_PERMISSIONS:
|
||||||
raise ValueError("unsupported permssion %r found in: %r" % (permission, value))
|
raise ValueError("unsupported permission %r found in: %r" % (permission, value))
|
||||||
|
if "p" in value and "P" in value:
|
||||||
|
raise ValueError("invalid combination of permissions (P+p) found in %r", value)
|
||||||
|
if "e" in value and "E" in value:
|
||||||
|
raise ValueError("invalid combination of permissions (E+e) found in %r", value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -406,16 +406,6 @@ class BaseSharing:
|
|||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/sharing/map: not active")
|
logger.debug("TRACE/sharing/map: not active")
|
||||||
|
|
||||||
if share is not None:
|
|
||||||
if self.permit_properties_overlay:
|
|
||||||
if share['Permissions'] and "p" not in share['Permissions']:
|
|
||||||
# add permit permission
|
|
||||||
share['Permissions'] += "P"
|
|
||||||
else:
|
|
||||||
if share['Permissions'] and "P" not in share['Permissions']:
|
|
||||||
# add deny permission
|
|
||||||
share['Permissions'] += "p"
|
|
||||||
|
|
||||||
return share
|
return share
|
||||||
|
|
||||||
# adjust a share
|
# adjust a share
|
||||||
@@ -749,6 +739,10 @@ class BaseSharing:
|
|||||||
for permission in request_data[key]:
|
for permission in request_data[key]:
|
||||||
if permission not in rights.INTERNAL_PERMISSIONS:
|
if permission not in rights.INTERNAL_PERMISSIONS:
|
||||||
return httputils.bad_request("Invalid value for Permissions")
|
return httputils.bad_request("Invalid value for Permissions")
|
||||||
|
if "p" in request_data[key] and "P" in request_data[key]:
|
||||||
|
return httputils.bad_request("Invalid combination of Permissions (P+p)")
|
||||||
|
if "e" in request_data[key] and "E" in request_data[key]:
|
||||||
|
return httputils.bad_request("Invalid combination of Permissions (E+e)")
|
||||||
elif key == "PathOrToken":
|
elif key == "PathOrToken":
|
||||||
if ShareType == "token":
|
if ShareType == "token":
|
||||||
if not re.search('^/.token/' + TOKEN_PATTERN_V1 + '/$', request_data[key]):
|
if not re.search('^/.token/' + TOKEN_PATTERN_V1 + '/$', request_data[key]):
|
||||||
@@ -916,8 +910,8 @@ class BaseSharing:
|
|||||||
if Conversion == "bday":
|
if Conversion == "bday":
|
||||||
# bday is read-only
|
# bday is read-only
|
||||||
for permission in Permissions:
|
for permission in Permissions:
|
||||||
if permission not in "r":
|
if permission not in "rPpEe":
|
||||||
logger.warning(api_info + ": PathMapped=%r Permissions=%r not supported for Conversion=%r", PathMapped, Permissions, Conversion)
|
logger.warning(api_info + ": PathMapped=%r Permissions=%r not supported for Conversion=%r", PathMapped, Permissions, Conversion)
|
||||||
return httputils.METHOD_NOT_ALLOWED
|
return httputils.METHOD_NOT_ALLOWED
|
||||||
|
|
||||||
if Enabled is None:
|
if Enabled is None:
|
||||||
|
|||||||
@@ -89,14 +89,14 @@ class TestSharingApiSanity(BaseTest):
|
|||||||
assert not isinstance(response, int)
|
assert not isinstance(response, int)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _propfind_priviledges(self, path: str, login) -> list[str]:
|
def _propfind_privileges(self, path: str, login) -> list[str]:
|
||||||
response = self._propfind_allprop(path, login)
|
response = self._propfind_allprop(path, login)
|
||||||
status, prop = response["D:current-user-privilege-set"]
|
status, prop = response["D:current-user-privilege-set"]
|
||||||
logging.debug("prop: %r", prop)
|
logging.debug("prop: %r", prop)
|
||||||
priviledges = prop.findall(xmlutils.make_clark("D:privilege"))
|
privileges = prop.findall(xmlutils.make_clark("D:privilege"))
|
||||||
assert len(priviledges) >= 1
|
assert len(privileges) >= 1
|
||||||
priviledges_list = [xmlutils.make_human_tag(priviledge.findall("*")[0].tag) for priviledge in priviledges]
|
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
|
||||||
return priviledges_list
|
return privileges_list
|
||||||
|
|
||||||
def _propfind_calendar_color(self, path, login) -> Union[str, None]:
|
def _propfind_calendar_color(self, path, login) -> Union[str, None]:
|
||||||
propfind_calendar_color = get_file_content("propfind_calendar_color.xml")
|
propfind_calendar_color = get_file_content("propfind_calendar_color.xml")
|
||||||
@@ -2976,6 +2976,14 @@ permissions: RrWwM
|
|||||||
user: owner1
|
user: owner1
|
||||||
collection: {user}/cal-m-lc(/.*)?
|
collection: {user}/cal-m-lc(/.*)?
|
||||||
permissions: RrWwm
|
permissions: RrWwm
|
||||||
|
[owner1-P]
|
||||||
|
user: owner1
|
||||||
|
collection: {user}/cal-P-uc(/.*)?
|
||||||
|
permissions: RrWwP
|
||||||
|
[owner1-p]
|
||||||
|
user: owner1
|
||||||
|
collection: {user}/cal-p-lc(/.*)?
|
||||||
|
permissions: RrWwp
|
||||||
[default]
|
[default]
|
||||||
user: .+
|
user: .+
|
||||||
collection: {user}(/.*)?
|
collection: {user}(/.*)?
|
||||||
@@ -2992,7 +3000,7 @@ permissions: RrWw""")
|
|||||||
"collection_by_map": "True",
|
"collection_by_map": "True",
|
||||||
"collection_by_token": "True"},
|
"collection_by_token": "True"},
|
||||||
"logging": {"request_header_on_debug": "False",
|
"logging": {"request_header_on_debug": "False",
|
||||||
"response_content_on_debug": "False",
|
"response_content_on_debug": "True",
|
||||||
"rights_rule_doesnt_match_on_debug": "True",
|
"rights_rule_doesnt_match_on_debug": "True",
|
||||||
"request_content_on_debug": "True"},
|
"request_content_on_debug": "True"},
|
||||||
"rights": {"type": "from_file"}})
|
"rights": {"type": "from_file"}})
|
||||||
@@ -3004,12 +3012,16 @@ permissions: RrWw""")
|
|||||||
path_owner1_t = "/owner1/cal-t-lc/"
|
path_owner1_t = "/owner1/cal-t-lc/"
|
||||||
path_owner1_M = "/owner1/cal-M-uc/"
|
path_owner1_M = "/owner1/cal-M-uc/"
|
||||||
path_owner1_m = "/owner1/cal-m-lc/"
|
path_owner1_m = "/owner1/cal-m-lc/"
|
||||||
|
path_owner1_P = "/owner1/cal-P-uc/"
|
||||||
|
path_owner1_p = "/owner1/cal-p-lc/"
|
||||||
|
|
||||||
logging.info("\n*** prepare")
|
logging.info("\n*** prepare")
|
||||||
self.mkcalendar(path_owner1_T, login="owner1:owner1pw")
|
self.mkcalendar(path_owner1_T, login="owner1:owner1pw")
|
||||||
self.mkcalendar(path_owner1_t, login="owner1:owner1pw")
|
self.mkcalendar(path_owner1_t, login="owner1:owner1pw")
|
||||||
self.mkcalendar(path_owner1_M, login="owner1:owner1pw")
|
self.mkcalendar(path_owner1_M, login="owner1:owner1pw")
|
||||||
self.mkcalendar(path_owner1_m, login="owner1:owner1pw")
|
self.mkcalendar(path_owner1_m, login="owner1:owner1pw")
|
||||||
|
self.mkcalendar(path_owner1_P, login="owner1:owner1pw")
|
||||||
|
self.mkcalendar(path_owner1_p, login="owner1:owner1pw")
|
||||||
|
|
||||||
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
|
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
|
||||||
logging.info("\n*** test: %s", db_type)
|
logging.info("\n*** test: %s", db_type)
|
||||||
@@ -3043,7 +3055,7 @@ permissions: RrWw""")
|
|||||||
json_dict['PathOrToken'] = path_user1.replace(".ics", "dm-lc" + db_type + ".ics")
|
json_dict['PathOrToken'] = path_user1.replace(".ics", "dm-lc" + db_type + ".ics")
|
||||||
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
logging.info("\n*** deletee map user1/owner1, globally disabled / granted m -> 200")
|
logging.info("\n*** delete map user1/owner1, globally disabled / granted m -> 200")
|
||||||
json_dict['PathMapped'] = path_owner1_m
|
json_dict['PathMapped'] = path_owner1_m
|
||||||
json_dict['PathOrToken'] = path_user1.replace(".ics", "dm-lc" + db_type + ".ics")
|
json_dict['PathOrToken'] = path_user1.replace(".ics", "dm-lc" + db_type + ".ics")
|
||||||
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
@@ -3112,6 +3124,171 @@ permissions: RrWw""")
|
|||||||
json_dict['PathMapped'] = path_owner1_t
|
json_dict['PathMapped'] = path_owner1_t
|
||||||
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token T (permit=True)")
|
||||||
|
privileges_T = self._propfind_privileges(path_owner1_T, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" in privileges_T
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token t (permit=True)")
|
||||||
|
privileges_t = self._propfind_privileges(path_owner1_t, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" not in privileges_t
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token * (permit=True)")
|
||||||
|
privileges_p = self._propfind_privileges(path_owner1_p, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" in privileges_p
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map M (permit=True)")
|
||||||
|
privileges_M = self._propfind_privileges(path_owner1_M, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" in privileges_M
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map m (permit=True)")
|
||||||
|
privileges_m = self._propfind_privileges(path_owner1_m, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" not in privileges_m
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map * (permit=True)")
|
||||||
|
privileges_t = self._propfind_privileges(path_owner1_t, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" in privileges_t
|
||||||
|
|
||||||
|
self.configure({"sharing": {"permit_create_token": "False", "permit_create_map": "False"}})
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token T (permit=False)")
|
||||||
|
privileges_T = self._propfind_privileges(path_owner1_T, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" in privileges_T
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token t (permit=False)")
|
||||||
|
privileges_t = self._propfind_privileges(path_owner1_t, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" not in privileges_t
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-token * (permit=False)")
|
||||||
|
privileges_t = self._propfind_privileges(path_owner1_t, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-token" not in privileges_t
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map M (permit=False)")
|
||||||
|
privileges_M = self._propfind_privileges(path_owner1_M, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" in privileges_M
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map m (permit=False)")
|
||||||
|
privileges_m = self._propfind_privileges(path_owner1_m, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" not in privileges_m
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly: RADICALE:share-map * (permit=False)")
|
||||||
|
privileges_t = self._propfind_privileges(path_owner1_t, login="owner1:owner1pw")
|
||||||
|
assert "RADICALE:share-map" not in privileges_t
|
||||||
|
|
||||||
|
# continue
|
||||||
|
self.configure({"sharing": {"permit_create_token": "True", "permit_create_map": "True"}})
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by owner")
|
||||||
|
privileges_P = self._propfind_privileges(path_owner1_P, login="owner1:owner1pw")
|
||||||
|
assert "D:write-properties" in privileges_P
|
||||||
|
|
||||||
|
privileges_p = self._propfind_privileges(path_owner1_p, login="owner1:owner1pw")
|
||||||
|
assert "D:write-properties" in privileges_p
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user")
|
||||||
|
|
||||||
|
logging.info("\n*** create map user1/owner1 P -> 200")
|
||||||
|
path_share = path_user1.replace(".ics", "P-uc" + db_type + ".ics")
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_P
|
||||||
|
json_dict['Hidden'] = False
|
||||||
|
json_dict['Enabled'] = True
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "r"
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "unhide", check=200, login="user1:user1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=P r (permit_properties_overlay=False)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "False"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" in privileges_P
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: r (permit_properties_overlay=True)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "True"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" in privileges_P
|
||||||
|
|
||||||
|
logging.info("\n*** update map with illegal combination pP")
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_P
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "rpP"
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** update map with illegal combination eE")
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_P
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "reE"
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** update map with only p")
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_p
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "rp"
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=P rp (permit_properties_overlay=False)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "False"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" not in privileges_P
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=P rp (permit_properties_overlay=True)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "True"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" not in privileges_P
|
||||||
|
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_P
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** create map user1/owner1 p -> 200")
|
||||||
|
path_share = path_user1.replace(".ics", "p-lc" + db_type + ".ics")
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_p
|
||||||
|
json_dict['Hidden'] = False
|
||||||
|
json_dict['Enabled'] = True
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "r"
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "unhide", check=200, login="user1:user1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=p r (permit_properties_overlay=False)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "False"}})
|
||||||
|
privileges_p = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" not in privileges_p
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=p r (permit_properties_overlay=True)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "True"}})
|
||||||
|
privileges_p = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" not in privileges_p
|
||||||
|
|
||||||
|
json_dict = {}
|
||||||
|
json_dict['PathMapped'] = path_owner1_p
|
||||||
|
json_dict['PathOrToken'] = path_share
|
||||||
|
json_dict['User'] = "user1"
|
||||||
|
json_dict['Permissions'] = "rP"
|
||||||
|
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner1:owner1pw", json_dict=json_dict)
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=p rP (permit_properties_overlay=False)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "False"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" in privileges_P
|
||||||
|
|
||||||
|
logging.info("\n*** check PROPFIND privileges list on collections directly by user: rights=p rP (permit_properties_overlay=True)")
|
||||||
|
self.configure({"sharing": {"permit_properties_overlay": "True"}})
|
||||||
|
privileges_P = self._propfind_privileges(path_share, login="user1:user1pw")
|
||||||
|
assert "D:write-properties" in privileges_P
|
||||||
|
|
||||||
def test_sharing_api_permissions_default(self) -> None:
|
def test_sharing_api_permissions_default(self) -> None:
|
||||||
"""sharing API usage tests related to global permissions."""
|
"""sharing API usage tests related to global permissions."""
|
||||||
self.configure({"auth": {"type": "htpasswd",
|
self.configure({"auth": {"type": "htpasswd",
|
||||||
@@ -3171,14 +3348,14 @@ permissions: RrWw""")
|
|||||||
assert answer_dict['Lines'] == 1
|
assert answer_dict['Lines'] == 1
|
||||||
assert answer_dict['Content'][0]['Permissions'] == "r"
|
assert answer_dict['Content'][0]['Permissions'] == "r"
|
||||||
|
|
||||||
# check PROPFIND/priviledges item as user
|
# check PROPFIND/privileges item as user
|
||||||
logging.info("\n*** PROPFIND/priviledges item as user")
|
logging.info("\n*** PROPFIND/privileges item as user")
|
||||||
priviledges_list = self._propfind_priviledges(path_user1_r, login="user1:user1pw")
|
privileges_list = self._propfind_privileges(path_user1_r, login="user1:user1pw")
|
||||||
assert "D:read" in priviledges_list
|
assert "D:read" in privileges_list
|
||||||
assert "D:write-content" not in priviledges_list
|
assert "D:write-content" not in privileges_list
|
||||||
assert "D:write-properties" not in priviledges_list
|
assert "D:write-properties" not in privileges_list
|
||||||
assert "D:write" not in priviledges_list
|
assert "D:write" not in privileges_list
|
||||||
assert "D:all" not in priviledges_list
|
assert "D:all" not in privileges_list
|
||||||
|
|
||||||
logging.info("\n*** create map user1/owner1 rw -> 200")
|
logging.info("\n*** create map user1/owner1 rw -> 200")
|
||||||
json_dict = {}
|
json_dict = {}
|
||||||
@@ -3203,14 +3380,14 @@ permissions: RrWw""")
|
|||||||
json_dict['PathOrToken'] = path_user1_rw
|
json_dict['PathOrToken'] = path_user1_rw
|
||||||
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
|
||||||
|
|
||||||
# check PROPFIND/priviledges item as user
|
# check PROPFIND/privileges item as user
|
||||||
logging.info("\n*** PROPFIND/priviledges item as user")
|
logging.info("\n*** PROPFIND/privileges item as user")
|
||||||
priviledges_list = self._propfind_priviledges(path_user1_rw, login="user1:user1pw")
|
privileges_list = self._propfind_privileges(path_user1_rw, login="user1:user1pw")
|
||||||
assert "D:read" in priviledges_list
|
assert "D:read" in privileges_list
|
||||||
assert "D:write-content" in priviledges_list
|
assert "D:write-content" in privileges_list
|
||||||
assert "D:write-properties" not in priviledges_list
|
assert "D:write-properties" not in privileges_list
|
||||||
assert "D:write" not in priviledges_list
|
assert "D:write" not in privileges_list
|
||||||
assert "D:all" not in priviledges_list
|
assert "D:all" not in privileges_list
|
||||||
|
|
||||||
logging.info("\n*** create map user1/owner1 with adjusted default permissions -> 200")
|
logging.info("\n*** create map user1/owner1 with adjusted default permissions -> 200")
|
||||||
self.configure({"sharing": {"default_permissions_create_map": "RrWw"}})
|
self.configure({"sharing": {"default_permissions_create_map": "RrWw"}})
|
||||||
@@ -3986,14 +4163,14 @@ permissions: RrWw""")
|
|||||||
json_dict['PathOrToken'] = path_shared_r
|
json_dict['PathOrToken'] = path_shared_r
|
||||||
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
|
||||||
|
|
||||||
# check PROPFIND/priviledges item as user
|
# check PROPFIND/privileges item as user
|
||||||
logging.info("\n*** PROPFIND/priviledges item as user -> calendar")
|
logging.info("\n*** PROPFIND/privileges item as user -> calendar")
|
||||||
priviledges_list = self._propfind_priviledges(path_shared_r, login="user:userpw")
|
privileges_list = self._propfind_privileges(path_shared_r, login="user:userpw")
|
||||||
assert "D:read" in priviledges_list
|
assert "D:read" in privileges_list
|
||||||
assert "D:write-content" not in priviledges_list
|
assert "D:write-content" not in privileges_list
|
||||||
assert "D:write-properties" in priviledges_list
|
assert "D:write-properties" in privileges_list
|
||||||
assert "D:write" not in priviledges_list
|
assert "D:write" not in privileges_list
|
||||||
assert "D:all" not in priviledges_list
|
assert "D:all" not in privileges_list
|
||||||
|
|
||||||
# verify PROPPATCH as user
|
# verify PROPPATCH as user
|
||||||
logging.info("\n*** PROPFIND collection user -> color #AAAAAA")
|
logging.info("\n*** PROPFIND collection user -> color #AAAAAA")
|
||||||
@@ -4478,6 +4655,8 @@ permissions: RrWw""")
|
|||||||
json_dict['PathMapped'] = path_mapped
|
json_dict['PathMapped'] = path_mapped
|
||||||
json_dict['PathOrToken'] = path_shared_r
|
json_dict['PathOrToken'] = path_shared_r
|
||||||
json_dict['Conversion'] = "bday"
|
json_dict['Conversion'] = "bday"
|
||||||
|
json_dict['Permissions'] = "rP"
|
||||||
|
json_dict['Enabled'] = True
|
||||||
json_dict['Enabled'] = True
|
json_dict['Enabled'] = True
|
||||||
json_dict['Hidden'] = False
|
json_dict['Hidden'] = False
|
||||||
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
|
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
|
||||||
@@ -4513,14 +4692,14 @@ permissions: RrWw""")
|
|||||||
assert "C:supported-calendar-component-set" in response
|
assert "C:supported-calendar-component-set" in response
|
||||||
assert "D:current-user-privilege-set" in response
|
assert "D:current-user-privilege-set" in response
|
||||||
|
|
||||||
# check PROPFIND/priviledges item as user
|
# check PROPFIND/privileges item as user
|
||||||
logging.info("\n*** PROPFIND/priviledges item as user -> calendar")
|
logging.info("\n*** PROPFIND/privileges item as user -> calendar")
|
||||||
priviledges_list = self._propfind_priviledges(path_shared_r, login="user:userpw")
|
privileges_list = self._propfind_privileges(path_shared_r, login="user:userpw")
|
||||||
assert "D:read" in priviledges_list
|
assert "D:read" in privileges_list
|
||||||
assert "D:write-content" not in priviledges_list
|
assert "D:write-content" not in privileges_list
|
||||||
assert "D:write-properties" in priviledges_list
|
assert "D:write-properties" in privileges_list
|
||||||
assert "D:write" not in priviledges_list
|
assert "D:write" not in privileges_list
|
||||||
assert "D:all" not in priviledges_list
|
assert "D:all" not in privileges_list
|
||||||
|
|
||||||
# verify content as user
|
# verify content as user
|
||||||
logging.info("\n*** GET collection user -> ok")
|
logging.info("\n*** GET collection user -> ok")
|
||||||
|
|||||||
Reference in New Issue
Block a user