Files
Radicale/radicale/tests/test_sharing.py

7299 lines
370 KiB
Python

# This file is part of Radicale - CalDAV and CardDAV server
# Copyright © 2026-2026 Peter Bieringer <pb@bieringer.de>
# Copyright © 2026-2026 Max Berger <max@berger.name>
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
"""
Radicale tests related to sharing.
"""
import datetime
import json
import logging
import os
import re
import sys
import tempfile
import urllib
from typing import Dict, Sequence, Tuple, Union, cast
import pytest
import vobject
from radicale import pathutils, sharing, storage, xmlutils
from radicale.item import Item
from radicale.tests import BaseTest
from radicale.tests.helpers import get_file_content
class TestSharingApiSanity(BaseTest):
"""Tests with sharing."""
htpasswd_file_path: str
# Setup
def setup_method(self) -> None:
BaseTest.setup_method(self)
self.htpasswd_file_path = os.path.join(self.colpath, ".htpasswd")
self.htgroup_file_path = os.path.join(self.colpath, ".htgroup")
encoding: str = self.configuration.get("encoding", "stock")
htpasswd = ["owner:ownerpw", "user:userpw",
"owner1:owner1pw", "user1:user1pw",
"owner@domain.example:owner@pw", "user@domain.example:user@pw",
"owner.surename@domain.example:owner@pw", "user.surename@domain.example:user@pw",
"owner-surename@domain.example:owner@pw", "user-surename@domain.example:user@pw",
"owner_surename@domain.example:owner@pw", "user_surename@domain.example:user@pw",
"user1@domain.example:user1@pw", "user2@domain.example:user2@pw",
"user3:user3pw", "user4:user4",
"user1@domain.tld:user1@pw", "user2@domain.tld:user2@pw",
"us😀er:user😀pw",
"owner2:owner2pw", "user2:user2pw"]
htgroup = ["group1:user1",
"group2:user2",
"group3:user3",
"group4:user4",
"group12:user1 user2",
"group13:user1 user3",
"group23:user2 user3",
]
htpasswd_content = "\n".join(htpasswd)
htgroup_content = "\n".join(htgroup)
with open(self.htpasswd_file_path, "w", encoding=encoding) as f:
f.write(htpasswd_content)
with open(self.htgroup_file_path, "w", encoding=encoding) as f:
f.write(htgroup_content)
# Helper functions
def _sharing_api(self, sharing_type: str, action: str, check: int, login: Union[str, None], data: str, content_type: str, accept: Union[str, None], x_forwarded_for: Union[str, None] = None) -> Tuple[int, Dict[str, str], str]:
path_base = "/.sharing/v1/" + sharing_type + "/"
_, headers, answer = self.request("POST", path_base + action, check=check, login=login, data=data, content_type=content_type, accept=accept, x_forwarded_for=x_forwarded_for)
logging.info("received answer:\n%s", "\n".join(answer.splitlines()))
return _, headers, answer
def _sharing_api_form(self, sharing_type: str, action: str, check: int, login: Union[str, None], form_array: Sequence[str], accept: Union[str, None] = None, x_forwarded_for: Union[str, None] = None) -> Tuple[int, Dict[str, str], str]:
data = "&".join(form_array)
content_type = "application/x-www-form-urlencoded"
if accept is None:
accept = "text/plain"
_, headers, answer = self._sharing_api(sharing_type, action, check, login, data, content_type, accept, x_forwarded_for=x_forwarded_for)
return _, headers, answer
def _sharing_api_json(self, sharing_type: str, action: str, check: int, login: Union[str, None], json_dict: dict, accept: Union[str, None] = None, x_forwarded_for: Union[str, None] = None) -> Tuple[int, Dict[str, str], str]:
data = json.dumps(json_dict)
content_type = "application/json"
if accept is None:
accept = "application/json"
_, headers, answer = self._sharing_api(sharing_type, action, check, login, data, content_type, accept, x_forwarded_for=x_forwarded_for)
return _, headers, answer
def _propfind_allprop(self, path: str, login: str = "", check=207, x_forwarded_for: Union[str, None] = None) -> dict:
propfind_allprop = get_file_content("allprop.xml")
_, responses = self.propfind(path=path, data=propfind_allprop, login=login, check=check, x_forwarded_for=x_forwarded_for)
logging.info("response: %r", responses)
if check != 207:
return {}
response = responses[path]
assert not isinstance(response, int)
return response
def _propfind_privileges(self, path: str, login) -> list[str]:
response = self._propfind_allprop(path, login)
status, prop = response["D:current-user-privilege-set"]
logging.debug("prop: %r", prop)
privileges = prop.findall(xmlutils.make_clark("D:privilege"))
assert len(privileges) >= 1
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
return privileges_list
def _propfind_calendar_color(self, path, login) -> Union[str, None]:
propfind_calendar_color = get_file_content("propfind_calendar_color.xml")
_, responses = self.propfind(path=path, data=propfind_calendar_color, login=login)
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int)
status, prop = response["ICAL:calendar-color"]
logging.debug("ICAL:calendar-color: %r", prop.text)
assert status == 200
return prop.text
def _proppatch_calendar_color(self, path, login, color, check=207) -> None:
_, responses = self.proppatch(path=path, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">""" + color + """</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>""", login=login, check=check)
if check != 207:
return
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["ICAL:calendar-color"]
assert status == 200 and not prop.text
return
def _proppatch_calendar_color_remove(self, path, login) -> None:
_, responses = self.proppatch(path=path, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:remove>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/" />
</D:prop>
</D:remove>
</D:propertyupdate>""", login=login)
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["ICAL:calendar-color"]
assert status == 200 and not prop.text
return
def _propfind_calendar_description(self, path, login):
_, responses = self.propfind(path=path, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav" />
</D:prop>
</D:propfind>""", login=login)
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("C:calendar-description: %r", prop.text)
assert status == 200
return prop.text
def _proppatch_calendar_description(self, path, login, description) -> None:
_, responses = self.proppatch(path=path, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav">""" + description + """</C:calendar-description>
</D:prop>
</D:set>
</D:propertyupdate>""", login=login)
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["C:calendar-description"]
assert status == 200 and not prop.text
return
def _proppatch_calendar_description_remove(self, path, login) -> None:
_, responses = self.proppatch(path=path, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:remove>
<D:prop>
<C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav" />
</D:prop>
</D:remove>
</D:propertyupdate>""", login=login)
logging.info("response: %r", responses)
response = responses[path]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["C:calendar-description"]
assert status == 200 and not prop.text
return
# Test functions
def test_sharing_api_base_csv_custom(self) -> None:
self.database_path = os.path.join(self.colpath, "collection-db/test.csv")
self.configure({"sharing": {
"type": "csv",
"database_path": self.database_path,
"collection_by_map": "True",
"collection_by_token": "False"}
})
def test_sharing_api_base_no_auth_basic(self) -> None:
"""POST request at '/.sharing' without authentication."""
# disabled
for path in ["/.sharing", "/.sharing/"]:
_, headers, _ = self.request("POST", path, check=404)
path = "/.sharing/"
# no database is active
logging.info("\n*** check API hook base: map=True token=False")
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "False"}
})
_, headers, _ = self.request("POST", path, check=404)
logging.info("\n*** check API hook base: map=False token=True")
self.configure({"sharing": {
"collection_by_map": "False",
"collection_by_token": "True"}
})
_, headers, _ = self.request("POST", path, check=404)
logging.info("\n*** check API hook base: map=True token=True")
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "True"}
})
_, headers, _ = self.request("POST", path, check=404)
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# no database is active
logging.info("\n*** check API hook base: map=True token=False")
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "False"}
})
_, headers, _ = self.request("POST", path, check=401)
logging.info("\n*** check API hook base: map=False token=True")
self.configure({"sharing": {
"collection_by_map": "False",
"collection_by_token": "True"}
})
_, headers, _ = self.request("POST", path, check=401)
logging.info("\n*** check API hook base: map=True token=True")
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "True"}
})
_, headers, _ = self.request("POST", path, check=401)
def test_sharing_api_base_no_auth_delay(self) -> None:
delay = .3
delay_min = delay * 0.9 # no random jitter during test
delay_max = delay + 0.2 # no random jitter during test
if sys.platform == "darwin" or sys.platform == "win32": # no reliable sleep times
delay_max = delay_max * 1.5
for path in ["/.sharing", "/.sharing/"]:
time_begin = datetime.datetime.now()
_, headers, _ = self.request("POST", path, check=404)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta < delay_min # 404 should have no delay
path = "/.sharing/"
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# no database is active
logging.info("\n*** check API hook base: map=True token=False (incl. delay)")
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "False"},
"auth": {"delay": delay}})
time_begin = datetime.datetime.now()
_, headers, _ = self.request("POST", path, check=401)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta > delay_min
assert time_delta < delay_max
def test_sharing_api_base_with_auth(self) -> None:
"""POST request at '/.sharing' with authentication."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"collection_by_map": "True",
"collection_by_token": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
for path in ["/.sharing/", "/.sharing/v9/"]:
logging.info("\n*** check invalid API URI: %r", path)
_, headers, _ = self.request("POST", path, check=404, login="owner:ownerpw")
# path with valid API but no hook
for path in ["/.sharing/v1/"]:
logging.info("\n*** check valid API URI without hook: %r", path)
_, headers, _ = self.request("POST", path, check=404, login="owner:ownerpw")
# path with valid API and hook but not enabled "map"
self.configure({"sharing": {
"collection_by_map": "False",
"collection_by_token": "True"}
})
sharetype = "map"
for action in sharing.API_HOOKS_V1:
path = "/.sharing/v1/" + sharetype + "/" + action
logging.info("\n*** check valid API URI hook (but not enabled): %r", path)
_, headers, _ = self.request("POST", path, check=404, login="owner:ownerpw")
# path with valid API and hook but not enabled "token"
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "False",
"permit_create_map": "False",
"permit_create_token": "False"}
})
sharetype = "token"
for action in sharing.API_HOOKS_V1:
path = "/.sharing/v1/" + sharetype + "/" + action
logging.info("\n*** check valid API URI hook (but not enabled): %r", path)
_, headers, _ = self.request("POST", path, check=404, login="owner:ownerpw")
# check info hook
logging.info("\n*** check API hook: info/all (text)")
form_array = []
_, headers, answer = self._sharing_api_form("all", "info", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PermittedCreateCollectionByMap=False" in answer
assert "PermittedCreateCollectionByToken=False" in answer
assert "FeatureEnabledCollectionByMap=True" in answer
assert "FeatureEnabledCollectionByToken=False" in answer
logging.info("\n*** check API hook: info/all (json)")
json_dict = {}
_, headers, answer = self._sharing_api_json("all", "info", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['FeatureEnabledCollectionByMap'] is True, f'FeatureEnabledCollectionByMap {db_type}'
assert answer_dict['FeatureEnabledCollectionByToken'] is False, f'FeatureEnabledCollectionByToken {db_type}'
assert answer_dict['PermittedCreateCollectionByMap'] is False, f'PermittedCreateCollectionByMap {db_type}'
assert answer_dict['PermittedCreateCollectionByToken'] is False, f'PermittedCreateCollectionByToken {db_type}'
logging.info("\n*** check API hook: info/map")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "info", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['FeatureEnabledCollectionByMap'] is True
assert 'FeatureEnabledCollectionByToken' not in answer_dict
assert 'PermittedCreateCollectionByToken' not in answer_dict
logging.info("\n*** check API hook: info/token -> 404 (not enabled)")
json_dict = {}
_, headers, answer = self._sharing_api_json("token", "info", check=404, login="owner:ownerpw", json_dict=json_dict)
# path with valid API and hook and all enabled
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "True"}
})
for sharetype in sharing.SHARE_TYPES:
path = "/.sharing/v1/" + sharetype + "/" + action
# invalid API
logging.info("\n*** check hook -> 404 (invalid)")
_, headers, _ = self.request("POST", path + "NA", check=404, login="owner:ownerpw")
# valid API
logging.info("\n*** check hook -> 400 (valid but no data)")
_, headers, _ = self.request("POST", path, check=400, login="owner:ownerpw")
logging.info("\n*** check API hook: info/token -> 200")
json_dict = {}
_, headers, answer = self._sharing_api_json("token", "info", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['FeatureEnabledCollectionByToken'] is True
assert 'FeatureEnabledCollectionByMap' not in answer_dict
assert 'PermittedCreateCollectionByMap' not in answer_dict
# When turning on permission to create
self.configure({"sharing": {
"collection_by_map": "True",
"collection_by_token": "True",
"permit_create_map": "True",
"permit_properties_overlay": "True",
"permit_create_token": "True"}
})
logging.info("\n*** check API hook: info/all")
json_dict = {}
_, headers, answer = self._sharing_api_json("all", "info", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['FeatureEnabledCollectionByMap'] is True, f'FeatureEnabledCollectionByMap {db_type}'
assert answer_dict['FeatureEnabledCollectionByToken'] is True, f'FeatureEnabledCollectionByToken {db_type}'
assert answer_dict['PermittedCreateCollectionByMap'] is True, f'PermittedCreateCollectionByMap {db_type}'
assert answer_dict['PermittedCreateCollectionByToken'] is True, f'PermittedCreateCollectionByToken {db_type}'
assert answer_dict['SupportedConversions'] == list(sharing.CONVERSIONS_WHITELIST)
assert answer_dict['PermittedPropertiesOverlay'] is True
assert answer_dict['SupportedPropertiesOverlay'] == list(sharing.OVERLAY_PROPERTIES_WHITELIST)
assert answer_dict['SupportedActions'] == sharing.ACTIONS_WHITELIST_INFO
logging.info("\n*** check API hook: info/all (2)")
self.configure({"sharing": {"permit_properties_overlay": "False"}})
json_dict = {}
_, headers, answer = self._sharing_api_json("all", "info", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['PermittedPropertiesOverlay'] is False
def test_sharing_api_list_with_auth(self) -> None:
"""POST/list with authentication."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "true",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
self.mkcalendar("/owner/collectionL1/", login="owner:ownerpw")
self.mkcalendar("/owner/collectionL2/", login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
action = "list"
for sharing_type in sharing.SHARE_TYPES:
logging.info("\n*** list (without form) -> should fail")
path = "/.sharing/v1/" + sharing_type + "/" + action
_, headers, _ = self.request("POST", path, check=400, login="owner:ownerpw")
logging.info("\n*** list (form->csv)")
form_array = []
_, headers, answer = self._sharing_api_form(sharing_type, "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='not-found'" in answer
assert "Lines=0" in answer
logging.info("\n*** list (json->text)")
json_dict = {}
_, headers, answer = self._sharing_api_json(sharing_type, "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/plain")
assert "Status='not-found'" in answer
assert "Lines=0" in answer
logging.info("\n*** list (json->json)")
json_dict = {}
_, headers, answer = self._sharing_api_json(sharing_type, "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
assert answer_dict['Lines'] == 0
logging.info("\n*** create a token -> 200")
form_array = ["PathMapped=/owner/collectionL1/"]
_, headers, answer = self._sharing_api_form("token", "create", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PathOrToken='" in answer
# extract token
match = re.search("PathOrToken='(.+)'", answer)
if match:
token = match.group(1)
logging.info("received token %r", token)
else:
assert False
logging.info("\n*** create a map -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = "/owner/collectionL2/"
json_dict['PathOrToken'] = "/user/collectionL2-shared-by-owner/"
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list/all (form->csv)")
form_array = []
_, headers, answer = self._sharing_api_form("all", "list", check=200, login="owner:ownerpw", form_array=form_array, accept="text/csv")
assert "Status=" not in answer
assert "Line=" not in answer
assert "ShareType" in answer
assert "token" in answer
assert "map" in answer
logging.info("\n*** list/all (form->text)")
form_array = []
_, headers, answer = self._sharing_api_form("all", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=2" in answer
assert "Fields=" in answer
assert "Content[0]=" in answer
assert "Content[1]=" in answer
logging.info("\n*** delete token -> 200")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "delete", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** delete share -> 200")
form_array = []
form_array.append("PathOrToken=/user/collectionL2-shared-by-owner/")
form_array.append("PathMapped=/owner/collectionL2/")
_, headers, answer = self._sharing_api_form("map", "delete", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
def test_sharing_api_token_basic(self) -> None:
"""share-by-token API tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_base1 = "/owner/collection1.ics/"
path_base2 = "/owner/collection2.ics/"
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create token without PathMapped (form) -> 400")
form_array = []
_, headers, answer = self._sharing_api_form("token", "create", 400, login="owner:ownerpw", form_array=form_array)
logging.info("\n*** create token without PathMapped (json) -> 400")
json_dict = {}
_, headers, answer = self._sharing_api_json("token", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create token#1 without existing collection (form->text) -> 404")
form_array = ["PathMapped=" + path_base1]
_, headers, answer = self._sharing_api_form("token", "create", check=404, login="owner:ownerpw", form_array=form_array)
logging.info("\n*** create collection*")
self.mkcalendar(path_base1, login="owner:ownerpw")
self.mkcalendar(path_base2, login="owner:ownerpw")
logging.info("\n*** create token#1 with existing collection (form->text) but no trailing / -> 400")
form_array = ["PathMapped=" + path_base1.rstrip('/')]
_, headers, answer = self._sharing_api_form("token", "create", check=400, login="owner:ownerpw", form_array=form_array)
logging.info("\n*** create token#1 with existing collection (form->text) -> 200")
form_array = ["PathMapped=" + path_base1]
_, headers, answer = self._sharing_api_form("token", "create", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PathOrToken='" in answer
# extract token
match = re.search("PathOrToken='(.+)'", answer)
if match:
token1 = match.group(1)
logging.info("received token %r", token1)
else:
assert False
logging.info("\n*** create token#2 (json->text)")
json_dict = {'PathMapped': path_base2}
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/plain")
assert "Status='success'" in answer
assert "Token=" in answer
# extract token
match = re.search("Token='(.+)'", answer)
if match:
token2 = match.group(1)
logging.info("received token %r", token2)
else:
assert False
logging.info("\n*** lookup token#1 (form->text)")
form_array = ["PathOrToken=" + token1]
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
assert path_base1 in answer
logging.info("\n*** lookup token#2 (json->text")
json_dict = {'PathOrToken': token2}
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/plain")
assert "Status='success'" in answer
assert "Lines=1" in answer
assert path_base2 in answer
logging.info("\n*** lookup token#2 (json->json)")
json_dict = {'PathOrToken': token2}
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['PathMapped'] == path_base2
logging.info("\n*** lookup tokens (form->text)")
form_array = []
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=2" in answer
assert path_base1 in answer
assert path_base2 in answer
logging.info("\n*** lookup tokens (form->csv)")
form_array = []
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array, accept="text/csv")
assert "Status='success'" not in answer
assert "Lines=2" not in answer
assert ";".join(sharing.DB_FIELDS_V1) in answer
assert path_base1 in answer
assert path_base2 in answer
logging.info("\n*** delete token#1 (form->text)")
form_array = ["PathOrToken=" + token1]
_, headers, answer = self._sharing_api_form("token", "delete", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** lookup token#1 (form->text) -> should not be there anymore")
form_array = ["PathOrToken=" + token1]
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='not-found'" in answer
assert "Lines=0" in answer
logging.info("\n*** lookup tokens (form->text) -> still one should be there")
form_array = []
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
logging.info("\n*** disable token#2 as owner (form->text)")
form_array = ["PathOrToken=" + token2]
_, headers, answer = self._sharing_api_form("token", "disable", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** lookup token#2 (json->json) -> check for not enabled")
json_dict = {'PathOrToken': token2}
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['EnabledByOwner'] is False
logging.info("\n*** enable token#2 as owner (json->json)")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "enable", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** lookup token#2 (form->text) -> check for enabled")
form_array = []
form_array.append("PathOrToken=" + token2)
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
assert "True;True;True;True" in answer
logging.info("\n*** hide token#2 (form->text)")
form_array = []
form_array.append("PathOrToken=" + token2)
_, headers, answer = self._sharing_api_form("token", "hide", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** lookup token#2 (form->text) -> check for hidden")
form_array = []
form_array.append("PathOrToken=" + token2)
_, headers, answer = self._sharing_api_form("token", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
assert "True;True;True;True" in answer
logging.info("\n*** unhide token#2 (json->json)")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "unhide", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** lookup token#2 (json->json) -> check for not hidden")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['HiddenByOwner'] is False
logging.info("\n*** delete token#2 (json->json)")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** lookup token#2 (json->json) -> should not be there anymore")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
assert answer_dict['Lines'] == 0
logging.info("\n*** delete collection*")
self.delete(path_base1, login="owner:ownerpw")
self.delete(path_base2, login="owner:ownerpw")
def test_sharing_api_token_usage_proxy(self) -> None:
"""share-by-token API tests simulating a reverse proxy - real usage."""
script_name = "/radicale"
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "True",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"server": {"script_name": script_name},
"rights": {"type": "owner_only"}})
json_dict: dict
path_base = "/owner/calendar.ics/"
event = get_file_content("event1.ics")
path = path_base + "/event1.ics"
logging.info("\n*** prepare")
self.mkcalendar(path_base, login="owner:ownerpw")
self.put(path, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** test access to collection")
_, headers, answer = self.request("GET", path_base, check=200, login="owner:ownerpw")
assert "UID:event" in answer
logging.info("\n*** test access to item")
_, headers, answer = self.request("GET", path, check=200, login="owner:ownerpw")
assert "UID:event" in answer
logging.info("\n*** create token")
json_dict = {}
json_dict["PathMapped"] = path_base
json_dict["Enabled"] = True
json_dict["Hidden"] = False
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner:ownerpw", json_dict=json_dict, x_forwarded_for="127.0.0.2")
answer_dict = json.loads(answer)
assert "Status" in answer_dict
assert "PathOrToken" in answer_dict
Token = answer_dict["PathOrToken"]
logging.debug("Token: %r", Token)
assert Token.startswith(script_name) is True
path_shared = Token
# check PROPFIND item as owner without proxy
logging.info("\n*** PROPFIND item as owner via proxy -> calendar")
response = self._propfind_allprop(path_shared, login="owner:ownerpw", x_forwarded_for="127.0.0.2")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
# check PROPFIND item as owner without proxy
logging.info("\n*** PROPFIND item as owner -> calendar")
response = self._propfind_allprop(path_shared.removeprefix(script_name), login="owner:ownerpw")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
def test_sharing_api_token_usage_basic(self) -> None:
"""share-by-token API tests - real usage."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_base = "/owner/calendar.ics/"
path_base2 = "/owner/calendar2.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_base, login="owner:ownerpw")
event = get_file_content("event1.ics")
path = path_base + "/event1.ics"
self.put(path, event, login="owner:ownerpw")
self.mkcalendar(path_base2, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** test access to collection")
_, headers, answer = self.request("GET", path_base, check=200, login="owner:ownerpw")
assert "UID:event" in answer
logging.info("\n*** test access to item")
_, headers, answer = self.request("GET", path, check=200, login="owner:ownerpw")
assert "UID:event" in answer
logging.info("\n*** create token")
form_array = []
form_array.append("PathMapped=" + path_base)
_, headers, answer = self._sharing_api_form("token", "create", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PathOrToken=" in answer
# extract token
match = re.search("PathOrToken='(.+)'", answer)
if match:
token = match.group(1)
logging.info("received token %r", token)
else:
assert False
logging.info("\n*** create token#2")
form_array = []
form_array.append("PathMapped=" + path_base2)
_, headers, answer = self._sharing_api_form("token", "create", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PathOrToken=" in answer
# extract token
match = re.search("PathOrToken='(.+)'", answer)
if match:
token2 = match.group(1)
logging.info("received token %r", token2)
else:
assert False
logging.info("\n*** enable token (form->text)")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "enable", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** fetch collection using invalid token")
_, headers, answer = self.request("GET", "/.token/v1/invalidtoken/", check=403)
logging.info("\n*** fetch collection using token")
_, headers, answer = self.request("GET", token, check=200)
assert "UID:event" in answer
logging.info("\n*** disable token (form->text)")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "disable", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** fetch collection using disabled token")
_, headers, answer = self.request("GET", token, check=403)
logging.info("\n*** enable token (form->text)")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "enable", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** fetch collection using token")
_, headers, answer = self.request("GET", token, check=200)
assert "UID:event" in answer
logging.info("\n*** delete token#2 (json->json)")
json_dict = {}
json_dict['PathOrToken'] = token2
_, headers, answer = self._sharing_api_json("token", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['ApiVersion'] == 1
assert answer_dict['Status'] == "success"
logging.info("\n*** delete token (json->json)")
json_dict = {'PathOrToken': token}
_, headers, answer = self._sharing_api_json("token", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['ApiVersion'] == 1
assert answer_dict['Status'] == "success"
logging.info("\n*** delete token (form->text) -> no longer available")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "delete", check=404, login="owner:ownerpw", form_array=form_array)
logging.info("\n*** fetch collection using deleted token")
_, headers, answer = self.request("GET", token, check=403)
def test_sharing_api_token_usage_delay(self) -> None:
"""share-by-token API tests - real usage."""
delay = .3
delay_min = delay * 0.9 # no random jitter during test
delay_max = delay + 0.2 # no random jitter during test
if sys.platform == "darwin" or sys.platform == "win32": # no reliable sleep times
delay_max = delay_max * 1.5
self.configure({"auth": {"type": "htpasswd",
"delay": delay,
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_base = "/owner/calendar.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_base, login="owner:ownerpw")
event = get_file_content("event1.ics")
path = path_base + "/event1.ics"
self.put(path, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create token")
form_array = []
form_array.append("PathMapped=" + path_base)
form_array.append("Enabled=True")
_, headers, answer = self._sharing_api_form("token", "create", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "PathOrToken=" in answer
# extract token
match = re.search("PathOrToken='(.+)'", answer)
if match:
token = match.group(1)
logging.info("received token %r", token)
else:
assert False
logging.info("\n*** fetch collection using invalid token")
time_begin = datetime.datetime.now()
_, headers, answer = self.request("GET", "/.token/v1/invalidtoken/", check=403)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta > delay_min
assert time_delta < delay_max
logging.info("\n*** fetch collection using token")
time_begin = datetime.datetime.now()
_, headers, answer = self.request("GET", token, check=200)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta < delay_min # no delay
assert "UID:event" in answer
logging.info("\n*** disable token (form->text)")
form_array = ["PathOrToken=" + token]
_, headers, answer = self._sharing_api_form("token", "disable", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
logging.info("\n*** fetch collection using disabled token")
time_begin = datetime.datetime.now()
_, headers, answer = self.request("GET", token, check=403)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta > delay_min
assert time_delta < delay_max
logging.info("\n*** delete token (json->json)")
json_dict = {'PathOrToken': token}
_, headers, answer = self._sharing_api_json("token", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['ApiVersion'] == 1
assert answer_dict['Status'] == "success"
logging.info("\n*** fetch collection using deleted token with delay")
time_begin = datetime.datetime.now()
_, headers, answer = self.request("GET", token, check=403)
time_end = datetime.datetime.now()
time_delta = (time_end - time_begin).total_seconds()
assert time_delta > delay_min
assert time_delta < delay_max
def test_sharing_api_map_basic(self) -> None:
"""share-by-map API basic tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_owner = "/owner/calendar.ics/"
path_user = "/user/calendar-owner.ics/"
path_user2 = "/user/calendar-owner2.ics/"
self.mkcalendar(path_owner, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
self.configure({"sharing": {"permit_create_map": "False"}})
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create map without PathMapped (json) -> 400")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map without PathMapped but User (json) -> 400")
json_dict = {'User': "user"}
_, headers, answer = self._sharing_api_json("map", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map without PathMapped but User and PathOrToken (json) -> 400")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user
_, headers, answer = self._sharing_api_json("map", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map with PathMapped, User, PathOrToken without trailing / (json) -> 400")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user
json_dict['PathMapped'] = path_owner.rstrip('/')
_, headers, answer = self._sharing_api_json("map", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map with PathMapped without trailing /, User, PathOrToken (json) -> 400")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user.rstrip('/')
json_dict['PathMapped'] = path_owner
_, headers, answer = self._sharing_api_json("map", "create", 400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map with PathMapped, User, PathOrToken - not permitted (json) -> 403")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user
json_dict['PathMapped'] = path_owner
_, headers, answer = self._sharing_api_json("map", "create", 403, login="owner:ownerpw", json_dict=json_dict)
self.configure({"sharing": {"permit_create_map": "True"}})
logging.info("\n*** create map with PathMapped, User, PathOrToken (json) -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user
json_dict['PathMapped'] = path_owner
_, headers, answer = self._sharing_api_json("map", "create", 200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map with PathMapped, User, PathOrToken2 (json) -> 409")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_user2
json_dict['PathMapped'] = path_owner
_, headers, answer = self._sharing_api_json("map", "create", 409, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map with PathMapped, User, PathOrToken=PathOwner (json) -> 409")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_owner
json_dict['PathMapped'] = path_owner
_, headers, answer = self._sharing_api_json("map", "create", 409, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_basic_email(self) -> None:
"""share-by-map API basic tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
for (owner, user) in [
("owner@domain.example", "user@domain.example"),
("owner.surename@domain.example", "user.surename@domain.example"),
("owner-surename@domain.example", "user-surename@domain.example"),
("owner_surename@domain.example", "user_surename@domain.example"),
]:
path_owner = "/" + owner + "/calendar.ics/"
path_user_base = "/" + user + "/"
path_user = path_user_base + "calendar-owner.ics/"
self.mkcalendar(path_owner, login=owner + ":owner@pw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
self.configure({"sharing": {"permit_create_map": "True"}})
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create map with PathMapped, User, PathOrToken (json) -> 200")
json_dict = {}
json_dict['User'] = user
json_dict['PathOrToken'] = path_user
json_dict['PathMapped'] = path_owner
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", 200, login=owner + ":owner@pw", json_dict=json_dict)
logging.info("\n*** enable+unhide (json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_user
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "update", 200, login=user + ":user@pw", json_dict=json_dict)
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok")
_, responses = self.propfind(path_user_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login=user + ":user@pw", HTTP_DEPTH="1")
assert len(responses) == 2
logging.info("response: %r", responses)
response = responses[urllib.parse.quote(path_user)]
assert isinstance(response, dict)
def test_sharing_api_map_usage(self) -> None:
"""share-by-map API usage tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_header_on_debug": "True",
"response_content_on_debug": "True",
"request_content_on_debug": "False"},
"rights": {"type": "owner_only"}})
json_dict: dict
file_item1 = "event1.ics"
file_item2 = "event2.ics"
path_shared = "/user/calendarU-shared-by-owner.ics/"
path_shared_item1 = os.path.join(path_shared, file_item1)
path_shared_item2 = os.path.join(path_shared, file_item2)
path_mapped = "/owner/calendarU.ics/"
path_mapped_item1 = os.path.join(path_mapped, file_item1)
path_mapped_item2 = os.path.join(path_mapped, file_item2)
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content(file_item1)
self.put(path_mapped_item1, event, check=201, login="owner:ownerpw")
event = get_file_content(file_item2)
self.put(path_mapped_item2, event, check=201, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** test access to collection")
_, headers, answer = self.request("GET", path_mapped, check=200, login="owner:ownerpw")
assert "UID:event1" in answer
assert "UID:event2" in answer
logging.info("\n*** test access to item")
_, headers, answer = self.request("GET", path_mapped_item1, check=200, login="owner:ownerpw")
assert "UID:event1" in answer
logging.info("\n*** create map with PathMapped and User and PathOrToken (json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
json_dict['Properties'] = {"D:displayname": "Test"}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** lookup map without filter (json->json)")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['PathOrToken'] == path_shared
assert answer_dict['Content'][0]['PathMapped'] == path_mapped
assert answer_dict['Content'][0]['ShareType'] == "map"
assert answer_dict['Content'][0]['Owner'] == "owner"
assert answer_dict['Content'][0]['User'] == "user"
assert answer_dict['Content'][0]['EnabledByOwner'] is False
assert answer_dict['Content'][0]['EnabledByUser'] is False
assert answer_dict['Content'][0]['HiddenByOwner'] is True
assert answer_dict['Content'][0]['HiddenByUser'] is True
assert answer_dict['Content'][0]['Permissions'] == "r"
assert answer_dict['Content'][0]['Properties'] == {"D:displayname": "Test"}
logging.info("\n*** enable map by owner (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** enable map by user (json->json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** fetch collection (without credentials)")
_, headers, answer = self.request("GET", path_mapped, check=401)
logging.info("\n*** fetch collection (with credentials) as owner")
_, headers, answer = self.request("GET", path_mapped, check=200, login="owner:ownerpw")
assert "UID:event" in answer
assert 'Content-Disposition' in headers
# fallback title
assert 'Calendar.ics' in headers['Content-Disposition']
logging.info("\n*** fetch item (with credentials) as owner")
_, headers, answer = self.request("GET", path_mapped_item1, check=200, login="owner:ownerpw")
assert "UID:event" in answer
logging.info("\n*** fetch collection (with credentials) as user")
_, headers, answer = self.request("GET", path_mapped, check=403, login="user:userpw")
logging.info("\n*** fetch collection via map (with credentials) as user")
_, headers, answer = self.request("GET", path_shared, check=200, login="user:userpw")
assert "UID:event1" in answer
assert "UID:event2" in answer
assert 'Content-Disposition' in headers
# title from Properties
assert 'Test.ics' in headers['Content-Disposition']
logging.info("\n*** fetch item via map (with credentials) as user")
_, headers, answer = self.request("GET", path_shared_item1, check=200, login="user:userpw")
# only requested event has to be in the answer
assert "UID:event1" in answer
assert "UID:event2" not in answer
logging.info("\n*** fetch item via map (with credentials) as user")
_, headers, answer = self.request("GET", path_shared_item2, check=200, login="user:userpw")
# only requested event has to be in the answer
assert "UID:event2" in answer
assert "UID:event1" not in answer
logging.info("\n*** disable map by owner (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "disable", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** fetch collection via map (with credentials) as user -> n/a")
_, headers, answer = self.request("GET", path_shared, check=404, login="user:userpw")
logging.info("\n*** enable map by owner (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** fetch collection via map (with credentials) as user")
_, headers, answer = self.request("GET", path_shared, check=200, login="user:userpw")
logging.info("\n*** disable map by user (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "disable", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** fetch collection via map (with credentials) as user -> n/a")
_, headers, answer = self.request("GET", path_shared, check=404, login="user:userpw")
logging.info("\n*** delete map by user (json->json) -> fail")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "delete", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user related to user flags (json->json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
json_dict['Hidden'] = True
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list as user and check user flags (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['EnabledByUser'] is True
assert answer_dict['Content'][0]['HiddenByUser'] is True
logging.info("\n*** update map by user related to user flags (json->json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = False
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list as user and check EnabledByUser==False (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['EnabledByUser'] is False
logging.info("\n*** update map by owner related to owner flags (json->json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = False
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** list as user and check owner flags (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['EnabledByOwner'] is False
assert answer_dict['Content'][0]['HiddenByOwner'] is False
logging.info("\n*** update map by owner related to owner flag Enabled->True (json->json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** list as user and check owner flags (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['EnabledByOwner'] is True
logging.info("\n*** update map by owner related to Properties -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Properties'] = {"ICAL:calendar-color": "#CCCCCC"}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** list as user and check Properties (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Properties']["ICAL:calendar-color"] == "#CCCCCC"
logging.info("\n*** update map by user related to Properties -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Properties'] = {"ICAL:calendar-color": "#DDDDDD"}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list as user and check Properties (json->json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Properties']["ICAL:calendar-color"] == "#DDDDDD"
logging.info("\n*** delete map by owner (json->json) -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
def test_sharing_api_map_update_delete_permissions(self) -> None:
"""share-by-map API usage tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"request_content_on_debug": "False"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_shared = "/user/calendarUP-shared-by-owner.ics/"
path_shared2 = "/user/calendarUP-shared-by-owner2.ics/"
path_mapped = "/owner/calendarUP.ics/"
path_mapped2 = "/owner/calendarUP2.ics/"
path_mapped_o2 = "/owner2/calendarUP3.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
self.mkcalendar(path_mapped_o2, login="owner2:owner2pw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create map with PathMapped and User and PathOrToken (json)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** update map by owner: User (json->json) -> 200")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: PathMapped (json->json) -> 200")
json_dict = {}
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: PathOrToken (json->json) -> 404 (is primary key, therefore not found)")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared2
_, headers, answer = self._sharing_api_json("map", "update", check=404, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: PathMapped(owner2) (json->json) -> 403")
json_dict = {}
json_dict['PathMapped'] = path_mapped_o2
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: PathOrToken (json->json) -> 404")
json_dict = {}
json_dict['PathOrToken'] = path_mapped
_, headers, answer = self._sharing_api_json("map", "update", check=404, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: Permissions without PathOrToken (json->json) -> 400")
json_dict = {}
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: Permissions (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: Enabled (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: Hidden (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by owner: Properties (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Properties'] = {}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by user: Enabled user-mispatch (json->json) -> 403")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by owner: User (json->json) -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map by user: User (same) (json->json) -> 403")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: PathMapped (json->json) -> 403")
json_dict = {}
json_dict['PathMapped'] = path_shared
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: PathOrToken (json->json) -> 404")
json_dict = {}
json_dict['PathOrToken'] = path_mapped
_, headers, answer = self._sharing_api_json("map", "update", check=404, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: Permissions (json->json) -> 403")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: Enabled (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: Hidden (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** update map by user: Properties (json->json) -> 200")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Properties'] = {}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** delete map by user (json->json) -> 403")
json_dict = {}
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "delete", check=403, login="user:userpw", json_dict=json_dict)
logging.info("\n*** delete map by owner (json->json) -> ok")
json_dict = {}
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** delete map by owner 2nd time (json->json) -> 404")
json_dict = {}
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "delete", check=404, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_usercheck(self) -> None:
"""share-by-map API usage tests related to usercheck."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_share1 = "/user1/calendar-shared-by-owner1.ics/"
path_mapped1 = "/owner1/calendar1.ics/"
path_share2 = "/user2/calendar-shared-by-owner2.ics/"
path_mapped2 = "/owner2/calendar2.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_mapped1, login="%s:%s" % ("owner1", "owner1pw"))
event = get_file_content("event1.ics")
path = path_mapped1 + "/event1.ics"
self.put(path, event, login="%s:%s" % ("owner1", "owner1pw"))
self.mkcalendar(path_mapped2, login="%s:%s" % ("owner2", "owner2pw"))
event = get_file_content("event1.ics")
path = path_mapped2 + "/event1.ics"
self.put(path, event, login="%s:%s" % ("owner2", "owner2pw"))
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create map user1/owner1 as owner(wrong owner) -> fail")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_share1
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1:r -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_share1
json_dict['Permissions'] = "r"
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user1/owner1 (repeat) -> fail")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_share1
_, headers, answer = self._sharing_api_json("map", "create", check=409, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user2/owner2:rw -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_share2
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner2:owner2pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user2/owner1 -> 409 (conflict)")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_share1
_, headers, answer = self._sharing_api_json("map", "create", check=409, login="owner2:owner2pw", json_dict=json_dict)
logging.info("\n*** delete map user1 -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_share1
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** delete map user2 -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_share2
_, headers, answer = self._sharing_api_json("map", "delete", check=200, login="owner2:owner2pw", json_dict=json_dict)
def test_sharing_api_map_permissions(self) -> None:
"""share-by-map API usage tests related to permissions."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"request_content_on_debug": "False"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_shared_r = "/user/calendar-shared-by-owner-r.ics/"
path_shared_w = "/user1/calendar-shared-by-owner-w.ics/"
path_shared_rw = "/user2/calendar-shared-by-owner-rw.ics/"
path_mapped = "/owner/calendar.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content("event1.ics")
path = path_mapped + "/event1.ics"
self.put(path, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check
logging.info("\n*** fetch event as owner (init) -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
# create maps
logging.info("\n*** create map user/owner:r -> 400 (Enabled is not boolean)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = "True"
_, headers, answer = self._sharing_api_json("map", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user1/owner:w -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_w
json_dict['Permissions'] = "w"
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user2/owner:rw -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_rw
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# list created maps
logging.info("\n*** list (json->text)")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/csv")
# check permissions, no map is enabled by user -> 404
logging.info("\n*** fetch collection via map:r -> n/a")
_, headers, answer = self.request("GET", path_shared_r, check=404, login="user:userpw")
logging.info("\n*** fetch collection via map:w -> n/a")
_, headers, answer = self.request("GET", path_shared_w, check=404, login="user1:user1pw")
logging.info("\n*** fetch collection via map:rw -> n/a")
_, headers, answer = self.request("GET", path_shared_rw, check=404, login="user2:user2pw")
# enable maps by user
logging.info("\n*** enable map by user:r")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** enable map by user1:w")
json_dict = {}
json_dict['PathOrToken'] = path_shared_w
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
logging.info("\n*** enable map by user2:rw")
json_dict = {}
json_dict['PathOrToken'] = path_shared_rw
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user2:user2pw", json_dict=json_dict)
# list adjusted maps
logging.info("\n*** list (json->text)")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/csv")
# check permissions, no map is enabled by user -> 404
logging.info("\n*** fetch collection via map:r -> ok")
_, headers, answer = self.request("GET", path_shared_r, check=200, login="user:userpw")
logging.info("\n*** fetch collection via map:w -> fail")
_, headers, answer = self.request("GET", path_shared_w, check=403, login="user1:user1pw")
logging.info("\n*** fetch collection via map:rw -> ok")
_, headers, answer = self.request("GET", path_shared_rw, check=200, login="user2:user2pw")
# list adjusted maps
logging.info("\n*** list (json->text)")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/csv")
# PUT
logging.info("\n*** put to collection by user via map:r -> fail")
event = get_file_content("event2.ics")
path = path_shared_r + "/event2.ics"
self.put(path, event, check=403, login="user:userpw")
logging.info("\n*** put to collection by user1 via map:w -> ok")
event = get_file_content("event2.ics")
path = path_shared_w + "event2.ics"
self.put(path, event, check=201, login="user1:user1pw")
# check result
logging.info("\n*** fetch event via map:r -> ok")
_, headers, answer = self.request("GET", path_shared_r + "event2.ics", check=200, login="user:userpw")
logging.info("\n*** fetch event as owner -> ok")
_, headers, answer = self.request("GET", path_mapped + "event2.ics", check=200, login="owner:ownerpw")
logging.info("\n*** put to collection by user2 via map:rw -> ok")
event = get_file_content("event3.ics")
path = path_shared_rw + "event3.ics"
self.put(path, event, check=201, login="user2:user2pw")
# check result
logging.info("\n*** fetch event via map:r -> ok")
_, headers, answer = self.request("GET", path_shared_r + "event2.ics", check=200, login="user:userpw")
logging.info("\n*** fetch event via map:r -> ok")
_, headers, answer = self.request("GET", path_shared_r + "event3.ics", check=200, login="user:userpw")
logging.info("\n*** fetch event via map:rw -> ok")
_, headers, answer = self.request("GET", path_shared_rw + "event2.ics", check=200, login="user2:user2pw")
logging.info("\n*** fetch event via map:rw -> ok")
_, headers, answer = self.request("GET", path_shared_rw + "event3.ics", check=200, login="user2:user2pw")
logging.info("\n*** fetch event as owner -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
logging.info("\n*** fetch event as owner -> ok")
_, headers, answer = self.request("GET", path_mapped + "event2.ics", check=200, login="owner:ownerpw")
logging.info("\n*** fetch event as owner -> ok")
_, headers, answer = self.request("GET", path_mapped + "event3.ics", check=200, login="owner:ownerpw")
# DELETE
logging.info("\n*** DELETE from collection by user via map:r -> fail")
_, headers, answer = self.request("DELETE", path_shared_r + "event1.ics", check=403, login="user:userpw")
logging.info("\n*** DELETE from collection by user via map:rw -> ok")
_, headers, answer = self.request("DELETE", path_shared_rw + "event2.ics", check=200, login="user2:user2pw")
logging.info("\n*** DELETE from collection by user via map:w -> ok")
_, headers, answer = self.request("DELETE", path_shared_w + "event3.ics", check=200, login="user1:user1pw")
# check results
logging.info("\n*** fetch event as owner -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
logging.info("\n*** fetch event as owner -> fail")
_, headers, answer = self.request("GET", path_mapped + "event2.ics", check=404, login="owner:ownerpw")
logging.info("\n*** fetch event as owner -> fail")
_, headers, answer = self.request("GET", path_mapped + "event3.ics", check=404, login="owner:ownerpw")
def test_sharing_api_map_report_access(self) -> None:
"""share-by-map API usage tests related to report."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_shared = "/user/calendar2-shared-by-owner.ics/"
path_mapped = "/owner/calendar2.ics/"
path_shared_item = os.path.join(path_shared, "event1.ics")
path_mapped_item = os.path.join(path_mapped, "event1.ics")
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content("event1.ics")
self.put(path_mapped_item, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET
logging.info("\n*** GET event as owner (init) -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
# check REPORT as owner
logging.info("\n*** REPORT collection owner -> ok")
_, responses = self.report(path_mapped, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="owner:ownerpw")
assert len(responses) == 1
logging.info("response: %r", responses)
response = responses[path_mapped_item]
assert isinstance(response, dict)
status, prop = response["D:getetag"]
assert status == 200 and prop.text
# create map
logging.info("\n*** create map user/owner -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# check REPORT as user
logging.info("\n*** REPORT collection user -> 404")
_, responses = self.report(path_shared, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="user:userpw", check=404)
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check GET as user
logging.info("\n*** GET collection user -> ok")
_, headers, answer = self.request("GET", path_shared, check=200, login="user:userpw")
# check REPORT as user
logging.info("\n*** REPORT collection user -> ok")
_, responses = self.report(path_shared, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="user:userpw")
assert len(responses) == 1
logging.info("response: %r", responses)
response = responses[path_shared_item]
assert isinstance(response, dict)
status, prop = response["D:getetag"]
assert status == 200 and prop.text
# check GET as user without trailing /
logging.info("\n*** GET collection user (without trailing /) -> ok")
_, headers, answer = self.request("GET", path_shared.removesuffix("/"), check=200, login="user:userpw")
# check REPORT as user without trailing /
logging.info("\n*** REPORT collection user (without trailing /) -> ok")
_, responses = self.report(path_shared.removesuffix("/"), """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="user:userpw")
assert len(responses) == 1
logging.info("response: %r", responses)
response = responses[path_shared_item]
assert isinstance(response, dict)
status, prop = response["D:getetag"]
assert status == 200 and prop.text
def test_sharing_api_map_hidden(self) -> None:
"""share-by-map API usage tests related to report checking hidden."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_user_base = "/user/"
path_mapped_base = "/owner/"
path_user = path_user_base + "calendarRH.ics/"
path_mapped = path_mapped_base + "calendarRH.ics/"
path_mapped2 = path_mapped_base + "calendarRH2.ics/"
path_shared = path_user_base + "calendarRH-shared-by-owner.ics/"
path_mapped_item = os.path.join(path_mapped, "event1.ics")
path_user_item = os.path.join(path_user, "event2.ics")
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content("event1.ics")
self.put(path_mapped_item, event, login="owner:ownerpw")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
self.mkcalendar(path_user, login="user:userpw")
event = get_file_content("event2.ics")
self.put(path_user_item, event, login="user:userpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET
logging.info("\n*** GET event1 as owner -> 200")
_, headers, answer = self.request("GET", path_mapped_item, check=200, login="owner:ownerpw")
logging.info("\n*** GET event2 as user -> 200")
_, headers, answer = self.request("GET", path_user_item, check=200, login="user:userpw")
logging.info("\n*** GET collections as user -> 403")
_, headers, answer = self.request("GET", path_user_base, check=403, login="user:userpw")
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok")
_, responses = self.propfind(path_user_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
assert len(responses) == 2
logging.info("response: %r", responses)
response = responses[path_user]
assert isinstance(response, dict)
# create map
logging.info("\n*** create map user/owner -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="owner:ownerpw", HTTP_DEPTH="1")
assert len(responses) == 3
logging.info("response: %r", responses)
response = responses[path_mapped]
assert isinstance(response, dict)
response = responses[path_mapped2]
assert isinstance(response, dict)
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok")
_, responses = self.propfind(path_user_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
assert len(responses) == 2
logging.info("response: %r", responses)
response = responses[path_user]
assert isinstance(response, dict)
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok")
_, responses = self.propfind(path_user_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
assert len(responses) == 2
logging.info("response: %r", responses)
response = responses[path_user]
assert isinstance(response, dict)
# unhide map by user
logging.info("\n*** unhide map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "unhide", check=200, login="user:userpw", json_dict=json_dict)
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok (now 3 items)")
_, responses = self.propfind(path_user_base, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
assert len(responses) == 3
logging.info("response: %r", responses)
response = responses[path_user]
assert isinstance(response, dict)
response = responses[path_shared]
assert isinstance(response, dict)
def test_sharing_api_map_propfind_base(self) -> None:
"""share-by-map API usage tests related to propfind."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_shared = "/user/calendar-shared-by-owner.ics/"
path_mapped = "/owner/calendar.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content("event1.ics")
path = os.path.join(path_mapped, "event1.ics")
self.put(path, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET
logging.info("\n*** GET event as owner (init) -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# create map
logging.info("\n*** create map user/owner -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> 404")
_, responses = self.propfind(path_shared, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="user:userpw", check=404)
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check PROPFIND as user
logging.info("\n*** PROPFIND collection user -> ok")
_, responses = self.propfind(path_shared, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
<owner />
</prop>
</propfind>""", login="user:userpw", check=207)
logging.info("response: %r", responses)
response = responses[path_shared]
assert not isinstance(response, int) and len(response) == 2
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/user/"
status, prop = response["D:owner"]
assert status == 200 and len(prop) == 0
def test_sharing_api_map_proppatch_acl(self) -> None:
"""share-by-map API usage tests related to report."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_mapped = "/owner/calendarPP.ics/"
path_shared_r = "/user/calendarPP-shared-by-owner-r.ics/"
path_shared_w = "/user1/calendarPP-shared-by-owner-w.ics/"
path_shared_rw = "/user2/calendarPP-shared-by-owner-rw.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
event = get_file_content("event1.ics")
path = os.path.join(path_mapped, "event1.ics")
self.put(path, event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET
logging.info("\n*** GET event as owner (init) -> ok")
_, headers, answer = self.request("GET", path_mapped + "event1.ics", check=200, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# check PROPPATCH as owner
logging.info("\n*** PROPPATCH collection owner -> ok")
proppatch = get_file_content("proppatch_set_calendar_color.xml")
_, responses = self.proppatch(path_mapped, proppatch, login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["ICAL:calendar-color"]
assert status == 200 and not prop.text
# check PROPPATCH as user
logging.info("\n*** PROPPATCH collection as user -> 404")
proppatch = get_file_content("proppatch_remove_calendar_color.xml")
_, responses = self.proppatch(path_shared_r, proppatch, login="user:userpw", check=404)
_, responses = self.proppatch(path_shared_w, proppatch, login="user1:user1pw", check=404)
_, responses = self.proppatch(path_shared_rw, proppatch, login="user2:user2pw", check=404)
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user1/owner:w -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_w
json_dict['Permissions'] = "w"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user2/owner:rw -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_rw
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# check PROPPATCH as user
logging.info("\n*** PROPPATCH collection as user -> 403")
proppatch = get_file_content("proppatch_set_calendar_color.xml")
_, responses = self.proppatch(path_shared_r, proppatch, login="user:userpw", check=404)
_, responses = self.proppatch(path_shared_w, proppatch, login="user1:user1pw", check=404)
_, responses = self.proppatch(path_shared_rw, proppatch, login="user2:user2pw", check=404)
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** enable map by user1")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_w
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
logging.info("\n*** enable map by user2")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_rw
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user2:user2pw", json_dict=json_dict)
# check PROPPATCH as user
proppatch = get_file_content("proppatch_remove_calendar_color.xml")
logging.info("\n*** PROPPATCH collection as user:r -> 403")
_, responses = self.proppatch(path_shared_r, proppatch, login="user:userpw", check=403)
logging.info("\n*** PROPPATCH collection as user:w -> ok")
_, responses = self.proppatch(path_shared_w, proppatch, login="user1:user1pw")
logging.info("response: %r", responses)
logging.info("\n*** PROPPATCH collection as user:rw -> ok")
_, responses = self.proppatch(path_shared_rw, proppatch, login="user2:user2pw")
logging.info("response: %r", responses)
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
assert "ICAL:calendar-color" not in response
def test_sharing_api_map_move(self) -> None:
"""share-by-map API usage tests related to MOVE."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_user = "/user/calendarM.ics/"
path_mapped1 = "/owner/calendar1M.ics/"
path_mapped1r = "/owner/calendar1MR.ics/"
path_mapped2 = "/owner/calendar2M.ics/"
path_shared1_r = "/user/calendar1M-shared-by-owner-r.ics/"
path_shared1_rw = "/user/calendar1M-shared-by-owner-rw.ics/"
path_shared2_rw = "/user/calendar2M-shared-by-owner-rw.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped1, login="owner:ownerpw")
event = get_file_content("event1.ics")
self.put(os.path.join(path_mapped1, "event1.ics"), event, login="owner:ownerpw")
self.mkcalendar(path_mapped1r, login="owner:ownerpw")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
event = get_file_content("event2.ics")
self.put(os.path.join(path_mapped2, "event2.ics"), event, login="owner:ownerpw")
self.mkcalendar(path_user, login="user:userpw")
event = get_file_content("event3.ics")
self.put(os.path.join(path_user, "event3.ics"), event, login="user:userpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET as owner
logging.info("\n*** GET mapped1/event1 as owner (init) -> ok")
_, headers, answer = self.request("GET", os.path.join(path_mapped1, "event1.ics"), check=200, login="owner:ownerpw")
logging.info("\n*** GET mapped2/event2 as owner (init) -> ok")
_, headers, answer = self.request("GET", os.path.join(path_mapped2, "event2.ics"), check=200, login="owner:ownerpw")
# check MOVE as owner
logging.info("\n*** MOVE event1 to mapped2 as owner -> ok")
self.request("MOVE", os.path.join(path_mapped1, "event1.ics"), check=201,
login="owner:ownerpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_mapped2, "event1.ics"))
logging.info("\n*** GET mapped2/event1 as owner (after move) -> ok")
_, headers, answer = self.request("GET", os.path.join(path_mapped2, "event1.ics"), check=200, login="owner:ownerpw")
# check GET as user
logging.info("\n*** GET event1 as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared1_r, "event1.ics"), check=404, login="user:userpw")
logging.info("\n*** GET event2 as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared2_rw, "event2.ics"), check=404, login="user:userpw")
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped1r
json_dict['PathOrToken'] = path_shared1_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user/owner:rw -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1_rw
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map user/owner:rw -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared2_rw
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# check MOVE as user
logging.info("\n*** MOVE event1 of shared1 to shared2 as user -> 404 (not enabled)")
self.request("MOVE", os.path.join(path_shared1_r, "event1.ics"), check=404,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared2_rw, "event1.ics"))
logging.info("\n*** MOVE event1 of shared2 to shared1 as user -> 404 (not enabled)")
self.request("MOVE", os.path.join(path_shared2_rw, "event1.ics"), check=404,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared1_r, "event1.ics"))
# enable map by user
logging.info("\n*** enable map shared1_r by user")
json_dict = {}
json_dict['PathMapped'] = path_mapped1r
json_dict['PathOrToken'] = path_shared1_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** enable map shared1_rw by user")
json_dict = {}
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1_rw
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** enable map shared2_rw by user")
json_dict = {}
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared2_rw
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check GET as user
logging.info("\n*** GET event1 as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared1_r, "event1.ics"), check=404, login="user:userpw")
logging.info("\n*** GET event1 as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared1_rw, "event1.ics"), check=404, login="user:userpw")
logging.info("\n*** GET event1 as user -> ok")
_, headers, answer = self.request("GET", os.path.join(path_shared2_rw, "event1.ics"), check=200, login="user:userpw")
# check MOVE as user between shares
logging.info("\n*** MOVE event1 of shared1_r to shared2_rw as user -> 403 (not permitted to move from r)")
self.request("MOVE", os.path.join(path_shared1_r, "event1.ics"), check=403,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared2_rw, "event1.ics"))
logging.info("\n*** MOVE event1 of shared2_rw to shared1_r as user -> 403 (not permitted to move to r)")
self.request("MOVE", os.path.join(path_shared2_rw, "event1.ics"), check=403,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared1_r, "event1.ics"))
logging.info("\n*** MOVE event1 of shared1_rw to shared2_rw as user -> 404 (already moved by owner)")
self.request("MOVE", os.path.join(path_shared1_rw, "event1.ics"), check=404,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared2_rw, "event1.ics"))
logging.info("\n*** MOVE event1 of shared2_rw to shared1_rw as user -> 201")
self.request("MOVE", os.path.join(path_shared2_rw, "event1.ics"), check=201,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared1_rw, "event1.ics"))
# check GET as user
logging.info("\n*** GET event1 from r as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared1_r, "event1.ics"), check=404, login="user:userpw")
logging.info("\n*** GET event1 from 1/rw as user -> ok")
_, headers, answer = self.request("GET", os.path.join(path_shared1_rw, "event1.ics"), check=200, login="user:userpw")
logging.info("\n*** GET event1 from 2/rw as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared2_rw, "event1.ics"), check=404, login="user:userpw")
# check MOVE as user between shares and own calendar
logging.info("\n*** GET event3 as user -> 200")
_, headers, answer = self.request("GET", os.path.join(path_user, "event3.ics"), check=200, login="user:userpw")
logging.info("\n*** GET event3 as user from shared2_rw -> 404")
_, headers, answer = self.request("GET", os.path.join(path_shared2_rw, "event3.ics"), check=404, login="user:userpw")
logging.info("\n*** MOVE event3 of own to shared2_rw as user -> 201")
self.request("MOVE", os.path.join(path_user, "event3.ics"), check=201,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_shared2_rw, "event3.ics"))
logging.info("\n*** GET event3 as user from shared2_rw -> 200")
_, headers, answer = self.request("GET", os.path.join(path_shared2_rw, "event3.ics"), check=200, login="user:userpw")
logging.info("\n*** GET event3 as user -> 404")
_, headers, answer = self.request("GET", os.path.join(path_user, "event3.ics"), check=404, login="user:userpw")
logging.info("\n*** MOVE event3 to own from shared2_rw as user -> 201")
self.request("MOVE", os.path.join(path_shared2_rw, "event3.ics"), check=201,
login="user:userpw",
HTTP_DESTINATION="http://127.0.0.1/"+os.path.join(path_user, "event3.ics"))
def test_sharing_api_update(self) -> None:
"""sharing API usage tests related to update."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "False",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_mapped1 = "/owner/calendar1U.ics/"
path_mapped2 = "/owner/calendar2U.ics/"
path_shared1 = "/user/calendar1U-shared-by-owner.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped1, login="owner:ownerpw")
event = get_file_content("event1.ics")
self.put(os.path.join(path_mapped1, "event1.ics"), event, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check GET as owner
logging.info("\n*** GET mapped1 as owner (init) -> 200")
_, headers, answer = self.request("GET", path_mapped1, check=200, login="owner:ownerpw")
logging.info("\n*** GET shared1 as user (init) -> 404")
_, headers, answer = self.request("GET", path_shared1, check=404, login="user:userpw")
logging.info("\n*** create map user/owner:w -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1
json_dict['Permissions'] = "w"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** GET shared1 as user (still not enabled by user) -> 404")
_, headers, answer = self.request("GET", path_shared1, check=404, login="user:userpw")
# enable map by user
logging.info("\n*** enable map shared1 by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list/all (form->csv)")
form_array = []
_, headers, answer = self._sharing_api_form("map", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
# read collection
logging.info("\n*** GET shared1 as user (no read permissions set by owner) -> 403")
_, headers, answer = self.request("GET", path_shared1, check=403, login="user:userpw")
# update map
logging.info("\n*** update map user/owner:r -> ok")
json_dict = {}
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1
json_dict['Permissions'] = "r"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list/all (form->csv)")
form_array = []
_, headers, answer = self._sharing_api_form("map", "list", check=200, login="owner:ownerpw", form_array=form_array)
assert "Status='success'" in answer
assert "Lines=1" in answer
# read collection
logging.info("\n*** GET shared1 as user (read permissions set by owner) -> 200")
_, headers, answer = self.request("GET", path_shared1, check=200, login="user:userpw")
# update map
logging.info("\n*** update map user/owner:path_mapped2 -> ok")
json_dict = {}
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared1
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# read collection
logging.info("\n*** GET shared1 as user (path not matching) -> 404")
_, headers, answer = self.request("GET", path_shared1, check=404, login="user:userpw")
logging.info("\n*** create mapped2 collection")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
event = get_file_content("event2.ics")
self.put(os.path.join(path_mapped2, "event2.ics"), event, login="owner:ownerpw")
# read collection
logging.info("\n*** GET shared1 as user (path now matching) -> 200")
_, headers, answer = self.request("GET", path_shared1, check=200, login="user:userpw")
# cleanup
self.delete(path_mapped2, login="owner:ownerpw")
def test_sharing_api_list_filter(self) -> None:
"""sharing API usage tests related to update."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "False",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_user1 = "/user1/calendarLFu1.ics/"
path_user2 = "/user2/calendarLFu2.ics/"
path_user1_shared1 = "/user1/calendarLFo1-shared.ics/"
path_user1_shared2 = "/user1/calendarLFo2-shared.ics/"
path_user2_shared1 = "/user2/calendarLFo1-shared.ics/"
path_owner1 = "/owner1/calendarLFo1.ics/"
path_owner2 = "/owner2/calendarLFo2.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_owner1, login="owner1:owner1pw")
self.mkcalendar(path_owner2, login="owner2:owner2pw")
self.mkcalendar(path_user1, login="user1:user1pw")
self.mkcalendar(path_user2, login="user2:user2pw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# list current
logging.info("\n*** list owner1 -> empty")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
logging.info("\n*** list owner2 -> empty")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner2:owner2pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
logging.info("\n*** list user1 -> empty")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user1:user1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
logging.info("\n*** list user2 -> empty")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user2:user2pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "not-found"
# create map#1
logging.info("\n*** create map user1/owner1 -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user1_shared1
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list owner1")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
logging.info("\n*** list user1")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user1:user1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
# create map#2
logging.info("\n*** create map user1/owner2 -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner2
json_dict['PathOrToken'] = path_user1_shared2
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner2:owner2pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list user1 -> 2 entries")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user1:user1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 2
# create map#3
logging.info("\n*** create map user2/owner1 -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user2_shared1
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list owner1 -> 2 entries")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 2
logging.info("\n*** list user1 filter for PathMapped -> 1 entries")
json_dict = {}
json_dict['PathMapped'] = path_owner1
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user1:user1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
logging.info("\n*** list user1 filter for PathShared -> 1 entries")
json_dict = {}
json_dict['PathOrToken'] = path_user1_shared1
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user1:user1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
def test_sharing_api_create_conflict(self) -> None:
"""sharing API usage tests related to conflicts."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "False",
"rights_rule_doesnt_match_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
rights_file_path = os.path.join(self.colpath, "rights")
with open(rights_file_path, "w") as f:
f.write("""\
[default-collection]
user: .+
collection: .+
permissions: RrWw
[default]
user: .+
collection: {user}(/.*)?
permissions: RrWw""")
self.configure({"rights": {"file": rights_file_path}})
json_dict: dict
path_user1 = "/user1/calendarCCu1.ics/"
path_user2 = "/user2/calendarCCu2.ics/"
path_user1_shared1 = "/user1/calendarCCo1-shared.ics/"
path_user2_shared1 = "/user2/calendarCCo1-shared.ics/"
path_owner1 = "/owner1/calendarCCo1.ics/"
path_owner2 = "/owner2/calendarCCo2.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_owner1, login="owner1:owner1pw")
self.mkcalendar(path_owner2, login="owner2:owner2pw")
self.mkcalendar(path_user1, login="user1:user1pw")
self.mkcalendar(path_user2, login="user2:user2pw")
# create calendar a 2nd time
logging.info("\n*** mkcalendar user2 -> conflict")
self.mkcalendar(path_user2, login="user2:user2pw", check=409)
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# owner_only
self.configure({"rights": {"type": "owner_only"}})
# create map
logging.info("\n*** create map user1/owner1 -> ok")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user1_shared1
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** mkcalendar as user1 for user1/shared1 -> conflict")
self.mkcalendar(path_user1_shared1, login="user1:user1pw", check=409)
# create map
logging.info("\n*** create map user2/owner1 -> ok")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user2_shared1
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** mkcol as user2 for user2/shared1 -> conflict")
self.mkcalendar(path_user2_shared1, login="user2:user2pw", check=409)
# create map
logging.info("\n*** create map user2/owner2 -> 409")
json_dict = {}
json_dict['User'] = "user2"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user2
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=409, login="owner1:owner1pw", json_dict=json_dict)
# from_file
self.configure({"rights": {"type": "from_file"}})
logging.info("\n*** mkcalendar as user1 for user2/shared1 with rights from file -> conflict")
self.mkcalendar(path_user2_shared1, login="user1:user1pw", check=409)
logging.info("\n*** mkcol as user1 for user2/shared1 with rights from file -> conflict")
self.mkcol(path_user2_shared1, login="user1:user1pw", check=409)
def test_sharing_api_permissions_global(self) -> None:
"""sharing API usage tests related to global permissions."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "False",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_user1 = "/user1/calendarPGu1.ics/"
path_owner1 = "/owner1/calendarPGo1.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_owner1, login="owner1:owner1pw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# create map
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user1
logging.info("\n*** create map user1/owner1 but globally disabled -> 403")
self.configure({"sharing": {"permit_create_map": "False"}})
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1 but globally enabled -> 200")
self.configure({"sharing": {"permit_create_map": "True"}})
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
# create token
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1
logging.info("\n*** create token owner1 but globally disabled -> 403")
self.configure({"sharing": {"permit_create_token": "False"}})
_, headers, answer = self._sharing_api_json("token", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1 but globally enabled -> 200")
self.configure({"sharing": {"permit_create_token": "True"}})
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
def test_sharing_api_permissions_rights(self) -> None:
"""sharing API usage tests related to rights permissions."""
rights_file_path = os.path.join(self.colpath, "rights")
with open(rights_file_path, "w") as f:
f.write("""\
[default-collection]
user: .+
collection: {user}
permissions: RrWw
[owner1-T]
user: owner1
collection: {user}/cal-T-uc(/.*)?
permissions: RrWwT
[owner1-t]
user: owner1
collection: {user}/cal-t-lc(/.*)?
permissions: RrWwt
[owner1-M]
user: owner1
collection: {user}/cal-M-uc(/.*)?
permissions: RrWwM
[owner1-m]
user: owner1
collection: {user}/cal-m-lc(/.*)?
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]
user: .+
collection: {user}(/.*)?
permissions: RrWw""")
self.configure({"rights": {"file": rights_file_path}})
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"rights_rule_doesnt_match_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "from_file"}})
json_dict: dict
path_user1 = "/user1/calendarPGu1.ics/"
path_owner1_T = "/owner1/cal-T-uc/"
path_owner1_t = "/owner1/cal-t-lc/"
path_owner1_M = "/owner1/cal-M-uc/"
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")
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_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)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# create map
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathOrToken'] = path_user1
logging.info("\n*** create map user1/owner1, globally disabled")
self.configure({"sharing": {"permit_create_map": "False"}})
logging.info("\n*** create map user1/owner1, globally disabled / not granted M -> 403")
json_dict['PathMapped'] = path_owner1_M
json_dict['PathOrToken'] = path_user1.replace(".ics", "dM-uc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally disabled / not granted T -> 403")
json_dict['PathMapped'] = path_owner1_T
json_dict['PathOrToken'] = path_user1.replace(".ics", "dT-uc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally disabled / not granted t -> 403")
json_dict['PathMapped'] = path_owner1_t
json_dict['PathOrToken'] = path_user1.replace(".ics", "dt-lc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally disabled / granted m -> 200")
json_dict['PathMapped'] = path_owner1_m
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)
logging.info("\n*** delete map user1/owner1, globally disabled / granted m -> 200")
json_dict['PathMapped'] = path_owner1_m
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)
logging.info("\n*** create map user1/owner1, globally enabled")
self.configure({"sharing": {"permit_create_map": "True"}})
logging.info("\n*** create map user1/owner1, globally enabled / not granted M -> 403")
json_dict['PathMapped'] = path_owner1_M
json_dict['PathOrToken'] = path_user1.replace(".ics", "eM-uc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally enabled / ignore T -> 200")
json_dict['PathMapped'] = path_owner1_T
json_dict['PathOrToken'] = path_user1.replace(".ics", "eT-uc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally enabled / ignore t -> 200")
json_dict['PathMapped'] = path_owner1_t
json_dict['PathOrToken'] = path_user1.replace(".ics", "et-lc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1, globally enabled / ignore m -> 200")
json_dict['PathMapped'] = path_owner1_m
json_dict['PathOrToken'] = path_user1.replace(".ics", "em-lc" + db_type + ".ics")
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
# create token
json_dict = {}
logging.info("\n*** create token owner1, globally disabled")
self.configure({"sharing": {"permit_create_token": "False"}})
logging.info("\n*** create token owner1, globally disabled / not granted M -> 403")
json_dict['PathMapped'] = path_owner1_M
_, headers, answer = self._sharing_api_json("token", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally disabled / not granted m -> 403")
json_dict['PathMapped'] = path_owner1_m
_, headers, answer = self._sharing_api_json("token", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally disabled / not granted T -> 403")
json_dict['PathMapped'] = path_owner1_T
_, headers, answer = self._sharing_api_json("token", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally disabled / granted t -> 200")
json_dict['PathMapped'] = path_owner1_t
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally enabled")
self.configure({"sharing": {"permit_create_token": "True"}})
logging.info("\n*** create token owner1, globally enabled / ignore M -> 200")
json_dict['PathMapped'] = path_owner1_M
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally enabled / ignore m -> 200")
json_dict['PathMapped'] = path_owner1_m
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally enabled / not granted T -> 403")
json_dict['PathMapped'] = path_owner1_T
_, headers, answer = self._sharing_api_json("token", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create token owner1, globally enabled / ignore t -> 200")
json_dict['PathMapped'] = path_owner1_t
_, 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:
"""sharing API usage tests related to global permissions."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_owner1 = "/owner1/calendarPGo1.ics/"
path_owner1 = "/owner1/calendarPGo1.ics/"
path_owner2 = "/owner2/calendarPGo1.ics/"
path_owner1_rw = "/owner1/calendarPGo1rw.ics/"
path_owner1_RrWw = "/owner1/calendarPGo1RrWw.ics/"
path_user1_r = "/user1/calendarPGu1-r.ics/"
path_user1_rw = "/user1/calendarPGu1-rw.ics/"
path_user1_RrWw = "/user1/calendarPGu1-RrWw.ics/"
logging.info("\n*** prepare")
self.mkcalendar(path_owner1, login="owner1:owner1pw")
self.mkcalendar(path_owner1_rw, login="owner1:owner1pw")
self.mkcalendar(path_owner1_RrWw, login="owner1:owner1pw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# create map
self.configure({"sharing": {"default_permissions_create_map": "r"}})
logging.info("\n*** create map user1/owner1 with path of owner 2-> 403")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner2
json_dict['PathOrToken'] = path_user1_r
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=403, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** create map user1/owner1 r -> 200")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1
json_dict['PathOrToken'] = path_user1_r
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
# enable map by user
logging.info("\n*** enable map by user1")
json_dict = {}
json_dict['PathOrToken'] = path_user1_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
logging.info("\n*** list (json->json)")
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "r"
# check PROPFIND/privileges item as user
logging.info("\n*** PROPFIND/privileges item as user")
privileges_list = self._propfind_privileges(path_user1_r, login="user1:user1pw")
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" not in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
logging.info("\n*** create map user1/owner1 rw -> 200")
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1_rw
json_dict['PathOrToken'] = path_user1_rw
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** list (json->json)")
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "rw"
# enable map by user
logging.info("\n*** enable map by user1")
json_dict = {}
json_dict['PathOrToken'] = path_user1_rw
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user1:user1pw", json_dict=json_dict)
# check PROPFIND/privileges item as user
logging.info("\n*** PROPFIND/privileges item as user (rw, no P)")
privileges_list = self._propfind_privileges(path_user1_rw, login="user1:user1pw")
assert "D:read" in privileges_list
assert "D:write-content" in privileges_list
assert "D:write-properties" not in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
logging.info("\n*** create map user1/owner1 rwP-> 200")
json_dict = {}
json_dict['PathMapped'] = path_owner1_rw
json_dict['PathOrToken'] = path_user1_rw
json_dict['Permissions'] = "rwP"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** PROPFIND/privileges item as user (rwP)")
privileges_list = self._propfind_privileges(path_user1_rw, login="user1:user1pw")
assert "D:read" in privileges_list
assert "D:write-content" in privileges_list
assert "D:write-properties" in privileges_list
assert "D:write" in privileges_list
assert "D:all" not in privileges_list
logging.info("\n*** create map user1/owner1 with adjusted default permissions -> 200")
self.configure({"sharing": {"default_permissions_create_map": "RrWw"}})
json_dict = {}
json_dict['User'] = "user1"
json_dict['PathMapped'] = path_owner1_RrWw
json_dict['PathOrToken'] = path_user1_RrWw
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
logging.info("\n*** list (json->json)")
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "RrWw"
# create token
self.configure({"sharing": {"default_permissions_create_token": "r"}})
json_dict = {}
json_dict['PathMapped'] = path_owner1
logging.info("\n*** create token user1/owner1 r -> 200")
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
Token = answer_dict['PathOrToken']
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = Token
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "r"
logging.info("\n*** create token user1/owner1 rw -> 200")
json_dict['Permissions'] = "rw"
del json_dict['PathOrToken']
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
Token = answer_dict['PathOrToken']
logging.info("\n*** list (json->json)")
del json_dict['Permissions']
json_dict['PathOrToken'] = Token
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "rw"
logging.info("\n*** create token user1/owner1 with adjusted default permissions -> 200")
self.configure({"sharing": {"default_permissions_create_token": "RrWw"}})
del json_dict['PathOrToken']
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
Token = answer_dict['PathOrToken']
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = Token
_, headers, answer = self._sharing_api_json("token", "list", check=200, login="owner1:owner1pw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "RrWw"
def test_sharing_api_map_report_base(self) -> None:
"""share-by-map API usage tests related to report."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_mapped = "/owner/abook1.vcf/"
path_shared_r = "/user/abook-shared-by-owner.vcf/"
logging.info("\n*** prepare and test access")
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact = get_file_content("contact1.vcf")
path_mapped_item = path_mapped + "contact.vcf"
path_shared_item = path_shared_r + "contact.vcf"
self.put(path_mapped_item, contact, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check REPORT as owner
logging.info("\n*** REPORT collection owner -> ok")
_, responses = self.report(path_mapped, """\
<?xml version="1.0"?>
<CR:addressbook-multiget xmlns="DAV:" xmlns:CR="urn:ietf:params:xml:ns:carddav">
<prop>
<getetag />
<CR:address-data />
</prop>
<href>""" + path_mapped_item + """</href>
</CR:addressbook-multiget>""", login="owner:ownerpw")
assert len(responses) == 1
logging.info("response: %r", responses)
response = responses[path_mapped_item]
assert isinstance(response, dict)
status, prop = response["D:getetag"]
assert status == 200 and prop.text
# check REPORT as user
logging.info("\n*** REPORT collection user -> ok")
_, responses = self.report(path_shared_r, """\
<?xml version="1.0"?>
<CR:addressbook-multiget xmlns="DAV:" xmlns:CR="urn:ietf:params:xml:ns:carddav">
<prop>
<getetag />
<CR:address-data />
</prop>
<href>""" + path_shared_r + """</href>
</CR:addressbook-multiget>""", login="user:userpw")
assert len(responses) == 1
logging.info("response: %r", responses)
response = responses[path_shared_item]
assert isinstance(response, dict)
status, prop = response["D:getetag"]
assert status == 200 and prop.text
def test_sharing_api_map_propfind_overlay_api_base(self) -> None:
"""share-by-map API usage tests related to proppatch."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_mapped = "/owner/calendarPFO.ics/"
path_shared_r = "/user/calendarPFO-shared-by-owner-r.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# execute PROPPATCH as owner
logging.info("\n*** PROPPATCH collection owner -> ok")
_, responses = self.proppatch(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">#AAAAAA</I:calendar-color>
<C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav">ICAL-OWNER</C:calendar-description>
</D:prop>
</D:set>
</D:propertyupdate>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 2
status, prop = response["ICAL:calendar-color"]
assert status == 200 and not prop.text
status, prop = response["C:calendar-description"]
assert status == 200 and not prop.text
# verify PROPPATCH by owner
logging.info("\n*** PROPFIND collection owner -> ok")
propfind_calendar_color = get_file_content("propfind_multiple.xml")
_, responses = self.propfind(path_mapped, propfind_calendar_color, login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("calendar-description: %r", prop.text)
assert status == 200 and prop.text == "ICAL-OWNER"
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#AAAAAA"
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# verify PROPFIND as user
logging.info("\n*** PROPFIND collection owner -> ok")
propfind_calendar_color = get_file_content("propfind_multiple.xml")
_, responses = self.propfind(path_mapped, propfind_calendar_color, login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("calendar-description: %r", prop.text)
assert status == 200 and prop.text == "ICAL-OWNER"
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#AAAAAA"
# update map by user
logging.info("\n*** update map by user (json)")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
json_dict['Properties'] = {"C:calendar-description": "ICAL-USER", "ICAL:calendar-color": "#BBBBBB"}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
logging.info("\n*** list (json->csv)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/csv")
logging.info("\n*** list (json->txt)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/plain")
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
propfind_calendar_color = get_file_content("propfind_multiple.xml")
_, responses = self.propfind(path_shared_r, propfind_calendar_color, login="user:userpw")
logging.info("response: %r", responses)
response = responses[path_shared_r]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("calendar-description: %r", prop.text)
assert status == 200 and prop.text == "ICAL-USER"
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#BBBBBB"
# verify overlay not visible by owner
logging.info("\n*** PROPFIND collection owner -> ok")
propfind_calendar_color = get_file_content("propfind_multiple.xml")
_, responses = self.propfind(path_mapped, propfind_calendar_color, login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("calendar-description: %r", prop.text)
assert status == 200 and prop.text == "ICAL-OWNER"
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#AAAAAA"
# update map by user
logging.info("\n*** update map by user (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties='C:calendar-description'='ICAL-USER-NEW'")
form_array.append("Properties='ICAL:calendar-color'='#CCCCCC'")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
assert "Status='success'" in answer
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
propfind_calendar_color = get_file_content("propfind_multiple.xml")
_, responses = self.propfind(path_shared_r, propfind_calendar_color, login="user:userpw")
logging.info("response: %r", responses)
response = responses[path_shared_r]
assert not isinstance(response, int)
status, prop = response["C:calendar-description"]
logging.debug("calendar-description: %r", prop.text)
assert status == 200 and prop.text == "ICAL-USER-NEW"
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#CCCCCC"
# update map by user
logging.info("\n*** update properties with buggyy ones by user (form)")
form_array = ["User=" + "user"]
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties=BUGGYENTRY=BUGGYVALUE")
_, headers, answer = self._sharing_api_form("map", "update", check=400, login="user:userpw", form_array=form_array)
def test_sharing_api_map_propfind_overlay_api_delete(self) -> None:
"""share-by-map API usage tests related to proppatch."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
form_array: Sequence[str]
json_dict: dict
path_mapped = "/owner/calendarPFD.ics/"
path_shared_r = "/user/calendarPFD-shared-by-owner-r.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# set properties by user
logging.info("\n*** set properties by user (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties='ICAL:calendar-color'='#CCCCCC'")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# check that properties are existing in map
logging.info("\n*** list and check for properties (json->json)")
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == '#CCCCCC'
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
propfind_calendar_color = get_file_content("propfind_calendar_color.xml")
_, responses = self.propfind(path_shared_r, propfind_calendar_color, login="user:userpw")
logging.info("response: %r", responses)
response = responses[path_shared_r]
assert not isinstance(response, int)
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 200 and prop.text == "#CCCCCC"
# clear properties by user
logging.info("\n*** clear properties by user (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties=")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# check that properties are cleared
logging.info("\n*** list and check for cleared properties (json->json)")
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Properties'] is not None
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay no longer exists) -> ok")
propfind_calendar_color = get_file_content("propfind_calendar_color.xml")
_, responses = self.propfind(path_shared_r, propfind_calendar_color, login="user:userpw")
logging.info("response: %r", responses)
response = responses[path_shared_r]
assert not isinstance(response, int)
status, prop = response["ICAL:calendar-color"]
logging.debug("calendar-color: %r", prop.text)
assert status == 404
def test_sharing_api_map_propfind_overlay_api_permissions(self) -> None:
"""share-by-map API usage tests related to proppatch."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
path_mapped = "/owner/calendarPFOAP.ics/"
path_shared_r = "/user/calendarPFOAP-shared-by-owner-r.ics/"
logging.info("\n*** prepare and test access")
self.mkcalendar(path_mapped, login="owner:ownerpw")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# execute PROPPATCH as owner
logging.info("\n*** PROPPATCH collection owner -> ok")
self._proppatch_calendar_color(path_mapped, login="owner:ownerpw", color="#AAAAAA")
# verify PROPPATCH by owner
logging.info("\n*** PROPFIND collection owner (verify collection change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#AAAAAA"
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** list (json->json) db=" + db_type)
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "r"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** list after enable (json->json) db=" + db_type)
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "r"
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay, color back to owner) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#AAAAAA"
self.configure({"sharing": {"permit_properties_overlay": False}})
# update map by user
logging.info("\n*** update map by user (json) -> 403 (no overlay permitted)")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
json_dict['Properties'] = {"ICAL:calendar-color": "#BBBBBB"}
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
self.configure({"sharing": {"permit_properties_overlay": True}})
logging.info("\n*** update map by user (json) -> 200 (overlay permitted)")
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#BBBBBB"
# one property have to be visible
logging.info("\n*** list check for one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
# update map by owner
logging.info("\n*** update map by owner (disable property overlay)")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rp"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** list after update by owner (json->json) db=" + db_type)
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert answer_dict['Content'][0]['Permissions'] == "rp"
logging.info("\n*** update map by user (json) -> 403 (no overlay permitted by share permissions)")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
json_dict['Properties'] = {"ICAL:calendar-color": "#CCCCCC"}
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
# one property have to be visible
logging.info("\n*** list check for one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#BBBBBB"
# update map by owner
logging.info("\n*** update map by owner (disable property overlay)")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# one property have to be visible
logging.info("\n*** list check for one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
logging.info("\n*** update map by user (json) -> 200 (overlay permitted by share permissions)")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
json_dict['Properties'] = {"ICAL:calendar-color": "#CCCCCC"}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#CCCCCC"
self.configure({"sharing": {"permit_properties_overlay": True}})
# update map by owner
logging.info("\n*** update map by owner (disable property overlay)")
json_dict = {}
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rp"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# one property have to be visible
logging.info("\n*** list check for one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#CCCCCC"
logging.info("\n*** update map by user (json) -> 403 (overlay permitted but denied by share permissions)")
json_dict = {}
json_dict['PathOrToken'] = path_shared_r
json_dict['Properties'] = {"ICAL:calendar-color": "#EEEEEE"}
_, headers, answer = self._sharing_api_json("map", "update", check=403, login="user:userpw", json_dict=json_dict)
def test_sharing_api_map_propfind_overlay_proppatch(self) -> None:
"""share-by-map API usage tests related to proppatch."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/user/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# execute PROPPATCH as owner
logging.info("\n*** PROPPATCH collection owner -> ok")
self._proppatch_calendar_color(path_mapped, login="owner:ownerpw", color="#AAAAAA")
# verify PROPPATCH by owner
logging.info("\n*** PROPFIND collection owner (verify collection change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#AAAAAA"
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# check PROPFIND/privileges item as user
logging.info("\n*** PROPFIND/privileges item as user -> calendar")
privileges_list = self._propfind_privileges(path_shared_r, login="user:userpw")
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# verify PROPPATCH as user
logging.info("\n*** PROPFIND collection user -> color #AAAAAA")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#AAAAAA"
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#BBBBBB")
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
logging.info("\n*** list (json->csv)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/csv")
logging.info("\n*** list (json->txt)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict, accept="text/plain")
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#BBBBBB"
# verify overlay not visible by owner
logging.info("\n*** PROPFIND collection owner (no collection change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#AAAAAA"
# execute PROPPATCH as user (delete color)
logging.info("\n*** PROPPATCH collection user (delete color) -> ok")
self._proppatch_calendar_color_remove(path_shared_r, login="user:userpw")
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay, color back to owner) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#AAAAAA"
# update map by owner
logging.info("\n*** update map by owner (disable property overlay)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rwe"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user (set color/collection) -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#DDDDDD")
# verify overlay as user
logging.info("\n*** PROPFIND collection user (collection color changed) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#DDDDDD"
# verify overlay visible by owner
logging.info("\n*** PROPFIND collection owner (visible change by user) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#DDDDDD"
# update map by owner
logging.info("\n*** update map by owner (enable property overlay)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rwE"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user (set color rw, enforce overlay enabled by default) -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#EEEEEE")
# verify overlay visible by owner
logging.info("\n*** PROPFIND collection owner (invisible change by user) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#DDDDDD"
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay: color) -> ok")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#EEEEEE"
# update map by owner
logging.info("\n*** update map by owner (enable property overlay)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rwe"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user (set color rwe, enforce overlay enabled by default) -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#EEEE00")
# verify overlay visible by owner
logging.info("\n*** PROPFIND collection owner (visible change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#EEEE00"
self.configure({"sharing": {"enforce_properties_overlay": False}})
# update map by owner
logging.info("\n*** update map by owner (enable property overlay)")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rw"
json_dict['User'] = "user"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** PROPPATCH collection user (set color rwe but enforce disabled) -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#FFFFFF")
# verify visible by owner
logging.info("\n*** PROPFIND collection owner (visible change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#FFFFFF"
def test_sharing_api_map_propfind_overlay_partial(self) -> None:
"""share-by-map API usage tests related to partial overlay."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_create_token": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/user/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
# execute PROPPATCH color as owner
logging.info("\n*** PROPPATCH color collection owner -> ok")
self._proppatch_calendar_color(path_mapped, login="owner:ownerpw", color="#AAAAAA")
# verify PROPPATCH color by owner
logging.info("\n*** PROPFIND color collection owner (verify collection change) -> ok")
color = self._propfind_calendar_color(path_mapped, login="owner:ownerpw")
assert color == "#AAAAAA"
# execute PROPPATCH description as owner
logging.info("\n*** PROPPATCH description collection owner -> ok")
self._proppatch_calendar_description(path_mapped, login="owner:ownerpw", description="OWNER")
# verify PROPPATCH description by owner
logging.info("\n*** PROPFIND description collection owner (verify collection change) -> ok")
description = self._propfind_calendar_description(path_mapped, login="owner:ownerpw")
assert description == "OWNER"
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# verify PROPPATCH as user
logging.info("\n*** PROPFIND color collection user -> #AAAAAA")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#AAAAAA"
logging.info("\n*** PROPFIND description collection collection user -> ok")
description = self._propfind_calendar_description(path_shared_r, login="user:userpw")
assert description == "OWNER"
# execute PROPPATCH color as user
logging.info("\n*** PROPPATCH color collection user -> ok")
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#BBBBBB")
# one property has to be visible
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
# execute PROPPATCH description as user
logging.info("\n*** PROPPATCH description collection user -> ok")
self._proppatch_calendar_description(path_shared_r, login="user:userpw", description="USER")
# both properties have to be visible
logging.info("\n*** list check for both properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
assert 'C:calendar-description' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['C:calendar-description'] == "USER"
# verify PROPPATCH as user
logging.info("\n*** PROPFIND color collection user -> #BBBBBB")
color = self._propfind_calendar_color(path_shared_r, login="user:userpw")
assert color == "#BBBBBB"
logging.info("\n*** PROPFIND description collection collection user -> ok")
description = self._propfind_calendar_description(path_shared_r, login="user:userpw")
assert description == "USER"
# execute PROPPATCH DELETE description as user
logging.info("\n*** PROPPATCH DELETE description collection user -> ok")
self._proppatch_calendar_description_remove(path_shared_r, login="user:userpw")
# one property has to survive
logging.info("\n*** list check for still one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#BBBBBB"
# set properties by user using API
logging.info("\n*** set properties by user color overwrite (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties='ICAL:calendar-color'='#CCCCCC'")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# one property has to survive
logging.info("\n*** list check for still one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#CCCCCC"
assert 'C:calendar-description' not in answer_dict['Content'][0]['Properties']
# set property by user using API
logging.info("\n*** set properties by user description extension (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties='C:calendar-description'='USER-OWNER'")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# both properties have to be visible
logging.info("\n*** list check for both properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['ICAL:calendar-color'] == "#CCCCCC"
assert 'C:calendar-description' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['C:calendar-description'] == "USER-OWNER"
# delete property by user using API
logging.info("\n*** delete property by user color (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties='ICAL:calendar-color'=''")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# only one property has to be visible
logging.info("\n*** list check for single properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' not in answer_dict['Content'][0]['Properties']
assert 'C:calendar-description' in answer_dict['Content'][0]['Properties']
assert answer_dict['Content'][0]['Properties']['C:calendar-description'] == "USER-OWNER"
# clear all propertie by user using API
logging.info("\n*** delete property by user color (form)")
form_array = []
form_array.append("PathOrToken=" + path_shared_r)
form_array.append("Properties=")
_, headers, answer = self._sharing_api_form("map", "update", check=200, login="user:userpw", form_array=form_array)
# no property has to be visible
logging.info("\n*** list check empty properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' not in answer_dict['Content'][0]['Properties']
assert 'C:calendar-description' not in answer_dict['Content'][0]['Properties']
# set properties by user using API
logging.info("\n*** set properties by user (json)")
json_dict = {}
json_dict["PathOrToken"] = path_shared_r
json_dict["Properties"] = {'C:calendar-description': 'USER-OWNER', 'ICAL:calendar-color': '#DDDDDD'}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
# both properties have to be visible
logging.info("\n*** list check for both properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert 'C:calendar-description' in answer_dict['Content'][0]['Properties']
# delete on property by user using API
logging.info("\n*** delete property by user color (json)")
json_dict = {}
json_dict["PathOrToken"] = path_shared_r
json_dict["Properties"] = {'C:calendar-description': ''}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
# one property have to be visible
logging.info("\n*** list check for one property (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' in answer_dict['Content'][0]['Properties']
assert 'C:calendar-description' not in answer_dict['Content'][0]['Properties']
# delete all propertie by user using API
logging.info("\n*** delete all properties by user (json)")
json_dict = {}
json_dict["PathOrToken"] = path_shared_r
json_dict["Properties"] = {}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="user:userpw", json_dict=json_dict)
# no property has to be visible
logging.info("\n*** list check empty properties (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
assert 'ICAL:calendar-color' not in answer_dict['Content'][0]['Properties']
assert 'C:calendar-description' not in answer_dict['Content'][0]['Properties']
def test_sharing_api_map_vcf_bday_basic(self) -> None:
"""share-by-map with conversion=bday basic tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"response_header_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
path_shared_r = "/user/calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact = get_file_content("contact1.vcf")
path = path_mapped + "/contact1.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact2-with-bday.vcf")
path = path_mapped + "/contact2-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact3-with-bday.vcf")
path = path_mapped + "/contact3-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<propname />
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
assert "CR:supported-address-data" in response
# execute GET as owner
logging.info("\n*** GET VCF collection owner -> ok")
_, headers, answer = self.request("GET", path_mapped, login="owner:ownerpw")
assert "contact1" in answer
assert "contact2" in answer
# title from fallback
assert 'Content-Disposition' in headers
assert 'Address%20book.vcf' in headers['Content-Disposition']
# create map with unsupported permissions
logging.info("\n*** create map(bday) user/owner:r -> fail")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rw"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
_, headers, answer = self._sharing_api_json("map", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
# create map
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# update map with (denied) conversion change
logging.info("\n*** update map(bday), try to change Conversion -> fail")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "none"
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
# update map with same conversion
logging.info("\n*** update map(bday) with equal Conversion -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# enable map by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# list by user
logging.info("\n*** list by user")
json_dict = {}
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="user:userpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
row = answer_dict['Content'][0]
assert row['ShareType'] == "map"
assert row['Conversion'] == "bday"
# check PROPFIND collection as user
logging.info("\n*** PROPFIND collection as user -> calendar")
response = self._propfind_allprop(path_shared_r, login="user:userpw")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "D:sync-token" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
logging.info("\n*** PROPFIND item as user -> calendar")
response = self._propfind_allprop(path_shared_r + "contact2-with-bday.ics", login="user:userpw")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "D:sync-token" not in response
assert "D:current-user-privilege-set" in response
logging.info("\n*** PROPFIND item as user with unsupported bday conversion -> not found")
response = self._propfind_allprop(path_shared_r + "contact1.ics", login="user:userpw", check=404)
# check PROPFIND/privileges collection as user
logging.info("\n*** PROPFIND/privileges collection as user -> calendar")
privileges_list = self._propfind_privileges(path_shared_r, login="user:userpw")
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# verify content as user
logging.info("\n*** GET collection user -> ok")
_, headers, answer = self.request("GET", path_shared_r, login="user:userpw")
assert "BEGIN:VCARD" not in answer
assert "BEGIN:VCALENDAR" in answer
assert "RRULE:FREQ=YEARLY" in answer
assert "DTSTART;VALUE=DATE:19700101" in answer
assert "DTEND;VALUE=DATE:19700102" in answer
assert "TRANSP:TRANSPARENT" in answer
assert "DESCRIPTION:BDAY=1970-01-01" in answer
assert "CATEGORIES:Birthday" in answer
# content type must be adjusted
assert 'Content-Type' in headers
assert 'text/calendar' in headers['Content-Type']
# title from Properties
assert 'Content-Disposition' in headers
assert 'Test-BDAY.ics' in headers['Content-Disposition']
# verify report as user
logging.info("\n*** REPORT collection user -> ok")
_, responses = self.report(path_shared_r, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag/>
</D:prop>
</C:calendar-query>""", login="user:userpw")
logging.debug("resonses: %r", responses)
assert path_shared_r + "contact2-with-bday.ics" in responses
assert path_shared_r + "contact3-with-bday.ics" in responses
assert path_shared_r + "contact1.ics" not in responses
# get elements as user
logging.info("\n*** REPORT collection entries user -> ok")
_, responses = self.report(path_shared_r, """\
<?xml version="1.0"?>
<C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<prop>
<getetag />
<C:calendar-data />
</prop>
<href>""" + path_shared_r + "contact2-with-bday.ics" + """</href>
<href>""" + path_shared_r + "contact3-with-bday.ics" + """</href>
</C:calendar-multiget>""", login="user:userpw")
logging.debug("resonses: %r", responses)
assert path_shared_r + "contact2-with-bday.ics" in responses
assert path_shared_r + "contact3-with-bday.ics" in responses
assert path_shared_r + "contact1.ics" not in responses
# verify content as user
logging.info("\n*** GET item as user -> ok")
_, headers, answer = self.request("GET", path_shared_r + "contact2-with-bday.ics", login="user:userpw")
logging.debug("resonse: %r", answer)
assert "BEGIN:VCARD" not in answer
assert "BEGIN:VCALENDAR" in answer
assert "RRULE:FREQ=YEARLY" in answer
assert "DTSTART;VALUE=DATE:19700101" in answer
assert "DTEND;VALUE=DATE:19700102" in answer
assert "TRANSP:TRANSPARENT" in answer
assert "DESCRIPTION:BDAY=1970-01-01" in answer
assert "CATEGORIES:Birthday" in answer
assert "CLASS:PRIVATE" in answer
assert "STATUS:CONFIRMED" in answer
# content type must be adjusted
assert 'Content-Type' in headers
assert 'text/calendar' in headers['Content-Type']
# title from Properties
assert 'Content-Disposition' not in headers
# get a single item which is not exsting on conversion
logging.info("\n*** GET item as user -> ok")
_, headers, answer = self.request("GET", path_shared_r + "contact1.ics", login="user:userpw", check=404)
# timerange filter elements as user
logging.info("\n*** REPORT collection entries with timerange user -> ok")
_, responses = self.report(path_shared_r, """\
<?xml version="1.0"?>
<C:calendar-query xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<prop>
<getcontenttype />
<getetag />
<C:calendar-data />
</prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20251124T000000Z" end="20260715T000000Z" />
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>""", login="user:userpw")
logging.debug("resonses: %r", responses)
assert path_shared_r + "contact2-with-bday.ics" in responses
assert path_shared_r + "contact3-with-bday.ics" in responses
assert path_shared_r + "contact1.ics" not in responses
logging.info("\n*** PROPFIND collection entries user -> ok")
_, responses = self.propfind(path_shared_r, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:">
<prop>
<getcontenttype />
<resourcetype />
<getetag />
</prop>
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
logging.debug("resonses: %r", responses)
assert path_shared_r + "contact2-with-bday.ics" in responses
assert path_shared_r + "contact3-with-bday.ics" in responses
assert path_shared_r + "contact1.ics" not in responses
response = responses[path_shared_r + "contact2-with-bday.ics"]
assert not isinstance(response, int)
status, prop = response["D:getcontenttype"]
assert "text/calendar" in str(prop.text)
# delete item as user
logging.info("\n*** DELETE item as user -> 403")
_, responses = self.delete(path_shared_r + "contact2-with-bday.ics", login="user:userpw", check=403)
def test_sharing_api_map_vcf_bday_template(self) -> None:
"""share-by-map with conversion=bday template tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"response_header_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
path_shared_r = "/user/calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact2 = get_file_content("contact2-with-bday.vcf")
path2 = path_mapped + "/contact2-with-bday.vcf"
path_shared_2 = path_shared_r + "/contact2-with-bday.vcf"
self.put(path2, contact2, login="owner:ownerpw")
contact3 = get_file_content("contact3-with-bday.vcf")
path3 = path_mapped + "/contact3-with-bday.vcf"
path_shared_3 = path_shared_r + "/contact3-with-bday.vcf"
self.put(path3, contact3, login="owner:ownerpw")
# create map
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
"conversion_bday_alarm_trigger_template": "-15H;BDAY tomorrow$9H;BDAY today",
}})
# verify content as user
logging.info("\n*** GET collection user format:default -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (Birthday)"}})
logging.info("\n*** GET collection user format with fn+Birthday -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{n:f} {n:g} {n:a}|{fn}|{nickname}] (Birthday)"}})
logging.info("\n*** GET collection user format:n -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:FamilyTest GivenTest AdditionalsTest (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{nickname}|{n:f} {n:g} {n:a}|{fn}] (Birthday)"}})
logging.info("\n*** GET collection user format:nickname -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-NICKNAME (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "{nickname} (Birthday)"}})
logging.info("\n*** GET collection user format:nickname not resolvable -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:!nickname! (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{nickname}|{nickname}|{fn}] (Birthday)"}})
logging.info("\n*** GET collection user format:nickname with fn fallback -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:Test-FN-C3 (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{nickname}|{nickname}|{n:g} {n:f}] (Birthday)"}})
logging.info("\n*** GET collection user format:nickname with n fallback -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:Given3Test Family3Test (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{nickname}|{nickname}|{n:f}, {n:g}] (Birthday)"}})
logging.info("\n*** GET collection user format:nickname with n fallback -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:Family3Test\\, Given3Test (Birthday)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{nickname}|{nickname}|{n:f} {n:g} {n:a}] (Birthday)"}})
logging.info("\n*** GET collection user format:nickname with n fallback -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:Family3Test Given3Test !n:a! (Birthday)" in answer
assert "DESCRIPTION:BDAY=1990-01-01" in answer
self.configure({"sharing": {"conversion_bday_description_template": "Birthday={year}{month}{day}"}})
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday=19900101" in answer
self.configure({"sharing": {"conversion_bday_description_template": "year={year} month={month} day={day}\nfn='{fn}'\nn:g='{n:g}'\nn:f='{n:f}'\nn:a='{n:a}'"}})
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:year=1990 month=01 day=01" in answer
assert "DESCRIPTION:BDAY tomorrow" in answer
assert "DESCRIPTION:BDAY today" in answer
assert "TRIGGER:-PT15H" in answer
assert "TRIGGER:PT9H" in answer
self.configure({"sharing": {"conversion_bday_alarm_trigger_template": "-12H;Birthday tomorrow of {fn}$12H;Birthday today of {n:g} {n:f}"}})
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday tomorrow of Test-FN-C3" in answer
assert "DESCRIPTION:Birthday today of Given3Test Family3Test" in answer
assert "TRIGGER:-PT12H" in answer
assert "TRIGGER:PT12H" in answer
self.configure({"sharing": {"conversion_bday_categories": "Birthday,Geburtstag"}})
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday tomorrow of Test-FN-C3" in answer
assert "DESCRIPTION:Birthday today of Given3Test Family3Test" in answer
assert "CATEGORIES:Birthday,Geburtstag" in answer
self.configure({
"sharing": {"conversion_bday_description_template": "",
"conversion_bday_alarm_trigger_template": "",
}
})
logging.info("\n*** GET collection user format: no description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION" not in answer
logging.info("\n*** configuration test: conversion_bday_summary_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_description_template": "year={year} month={month} day={day}\nfn='{fn}'\nn:g='{n:g}'\nn:f='{n:f}'\nn:a='{notsupported}'",
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_description_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_summary_template": "[{nickname}|{nickname}|{n:f} {n:g} {n:a}] {notsupportedplaceholder}",
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_summary_template empty")
try:
self.configure({"sharing": {
"conversion_bday_summary_template": "",
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_description_template empty")
try:
self.configure({"sharing": {
"conversion_bday_description_template": "",
}})
except RuntimeError:
pass
else:
pass
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "-12T;Birthday tomorrow of {fn}"
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "BROKEN;Birthday tomorrow of {fn}"
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "-+BROKEN;Birthday tomorrow of {fn}"
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "+0;Birthday tomorrow of {fn}"
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "-12H"
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_alarm_trigger_template not supported")
try:
self.configure({"sharing": {
"conversion_bday_alarm_trigger_template": "-12H;"
}})
except RuntimeError:
pass
else:
raise
# update template
logging.info("\n*** update map(bday) user/owner:r with invalid config -> 400")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_alarm_trigger_template": "-12H;"
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
self.configure({
"sharing": {"conversion_bday_description_template": sharing.SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT,
"conversion_bday_alarm_trigger_template": "",
}
})
# update template
logging.info("\n*** update map(bday) user/owner:r with valid description template -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template": "{fn} ({year})",
"conversion_bday_description_template": "Birthday={year}-{month}-{day}"
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday=1990-01-01" in answer
assert "SUMMARY:Test-FN-C3 (1990)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r with valid empty description template -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_description_template": ""
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday=" not in answer
assert "DESCRIPTION:BDAY=" not in answer
assert "SUMMARY:Test-FN-C3 (1990)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r DEL description template -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_description_template": sharing.SHARING_ACTIONS_DELETE_VALUE
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday=" not in answer
assert "DESCRIPTION:BDAY=" in answer
assert "SUMMARY:Test-FN-C3 (1990)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r DEL summary template -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template": sharing.SHARING_ACTIONS_DELETE_VALUE
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: description -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "DESCRIPTION:Birthday=" not in answer
assert "DESCRIPTION:BDAY=" in answer
assert "SUMMARY:Family3Test Given3Test !n:a! (Birthday)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r with valid age max -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": 5,
"conversion_bday_summary_template": "{fn} ({year}/[{age}|MAX-in-the-past])",
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: summary with age:5 -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "Test-FN-C3 (1990/MAX-in-the-past)" in answer
assert "Test-FN-C3 (1990/0)" not in answer
assert "Test-FN-C3 (1990/5)" not in answer
assert "Test-FN-C3 (1990/6)" not in answer
# update template
logging.info("\n*** update map(bday) user/owner:r with valid age max -> 200")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": sharing.SHARING_ACTIONS_DELETE_VALUE
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** GET collection user format: summary with age default -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "Test-FN-C3 (1990/0)" in answer
assert "Test-FN-C3 (1990/5)" in answer
assert "Test-FN-C3 (1990/6)" in answer
def test_sharing_bday_conversion_empty_fn(self) -> None:
"""BDAY-to-ICS conversion of a VCARD with an empty FN property.
vCard mandates FN, but some clients emit it empty. The fallback for
an empty ``{fn}`` must be assigned to the ``{fn}`` placeholder, not to
``{nickname}``: otherwise a present NICKNAME gets clobbered with the
``!fn!`` marker and the ``{fn}`` fallback never triggers.
"""
vcard = vobject.readOne(
"BEGIN:VCARD\r\n"
"VERSION:3.0\r\n"
"UID:contact-empty-fn\r\n"
"N:FamilyTest;GivenTest;;;\r\n"
"FN:\r\n"
"NICKNAME:Test-NICKNAME\r\n"
"BDAY:1990-05-06\r\n"
"END:VCARD\r\n")
class _StubCollection:
path = "test"
item = Item(collection=cast(storage.BaseCollection, _StubCollection()),
vobject_item=vcard, href="contact-empty-fn.vcf")
converted = item.convert_vcf_to_ics(ShareActions={"config": {
"conversion_bday_summary_template": "{nickname} (BDAY)",
"conversion_bday_description_template": "[{fn}|no-fn]"}})
assert converted is not None
serialized = converted.serialize()
# NICKNAME must survive, not be overwritten by the '!fn!' marker
assert "SUMMARY:Test-NICKNAME (BDAY)" in serialized
# empty {fn} must expose the '!fn!' marker so the fallback resolves
assert "DESCRIPTION:no-fn" in serialized
def test_sharing_api_map_vcf_bday_age_template(self) -> None:
"""share-by-map with conversion=bday template tests with age."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"response_header_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
path_shared_r = "/user/calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact2 = get_file_content("contact2-with-bday.vcf")
path2 = path_mapped + "/contact2-with-bday.vcf"
path_shared_2 = path_shared_r + "/contact2-with-bday.vcf"
self.put(path2, contact2, login="owner:ownerpw")
# create map
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
}})
logging.info("\n*** configuration test: conversion_bday_age_max < 0")
try:
self.configure({"sharing": {
"conversion_bday_age_max": -1,
}})
except RuntimeError:
pass
else:
raise
logging.info("\n*** configuration test: conversion_bday_age_max > MAX")
try:
self.configure({"sharing": {
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX_LIMIT + 1),
}})
except RuntimeError:
pass
else:
raise
# verify content as user
logging.info("\n*** GET collection user format:default -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] ([{age}. |]Birthday)",
"conversion_bday_age_max": 99,
}})
logging.info("\n*** GET collection user format:text (summary) (age:99) -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (0. Birthday)" in answer
assert "SUMMARY:Test-FN (1. Birthday)" in answer
assert "SUMMARY:Test-FN (99. Birthday)" in answer
assert "SUMMARY:Test-FN (100. Birthday)" not in answer
logging.info("\n*** GET collection user format:text (no trigger) (age:99) -> ok")
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "AGE={age}",
}})
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:AGE=0" in answer
assert "DESCRIPTION:AGE=1" in answer
assert "DESCRIPTION:AGE=99" in answer
assert "DESCRIPTION:AGE=100" not in answer
logging.info("\n*** GET collection user format:text (with trigger) -> ok")
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
"conversion_bday_alarm_trigger_template": "-15H;alarm {fn} [{age}. |]birthday",
}})
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:BDAY=1970-01-01" in answer
assert "DESCRIPTION:alarm Test-FN 0. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 1. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 99. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
# update template
logging.info("\n*** update map(bday) user/owner:r (age:98) -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": 98,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** GET collection user format:text (age:98) -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:BDAY=1970-01-01" in answer
assert "DESCRIPTION:alarm Test-FN 0. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 1. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 98. birthday" in answer
assert "DESCRIPTION:alarm Test-FN 99. birthday" not in answer
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
logging.info("\n*** GET collection user limit age (age:98) -> ok")
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "AGE=[{age}|MAX-in-the-past]",
"conversion_bday_alarm_trigger_template": "",
}})
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:AGE=0" in answer
assert "DESCRIPTION:AGE=1" in answer
assert "DESCRIPTION:AGE=98" in answer
assert "DESCRIPTION:AGE=99" not in answer
assert "DESCRIPTION:AGE=100" not in answer
# update template
logging.info("\n*** update map(bday) user/owner:r (age:5) -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": 5,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** GET collection user format:text (age:5) -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:AGE=MAX-in-the-past" in answer
assert "DESCRIPTION:alarm Test-FN 0. birthday" not in answer
assert "DESCRIPTION:alarm Test-FN 1. birthday" not in answer
assert "DESCRIPTION:alarm Test-FN 98. birthday" not in answer
assert "DESCRIPTION:alarm Test-FN 99. birthday" not in answer
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
logging.info("\n*** GET collection user limit age (age:5) -> ok")
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "AGE=[{age}|MAX-in-the-past|MAX-in-the-past]",
"conversion_bday_alarm_trigger_template": "",
}})
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY)" in answer
assert "DESCRIPTION:AGE=MAX-in-the-past" in answer
assert "DESCRIPTION:AGE=0" not in answer
assert "DESCRIPTION:AGE=1" not in answer
assert "DESCRIPTION:AGE=98" not in answer
assert "DESCRIPTION:AGE=99" not in answer
assert "DESCRIPTION:AGE=100" not in answer
# update template with invalid data test
logging.info("\n*** update map(bday) user/owner:r -> age_max not negative")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": -1,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
# update template with invalid data test
logging.info("\n*** update map(bday) user/owner:r -> age_max exceeds MAX")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX_LIMIT + 1),
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
# update template with valid data test
logging.info("\n*** update map(bday) user/owner:r -> age_max ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": 0,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# update template with valid data test
logging.info("\n*** update map(bday) user/owner:r -> age_max < MAX")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": sharing.SHARING_BDAY_AGE_MAX_LIMIT,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map(bday) user/owner:r -> unsupported level 1")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"level1": {
"level2": "test",
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map(bday) user/owner:r -> unsupported level 2")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"level2": "test",
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_vcf_no_year_bday_age_template(self) -> None:
"""share-by-map with conversion=bday template tests with age and VCF without year."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"response_header_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
path_shared_r = "/user/calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact2 = get_file_content("contact4-with-bday-no-year.vcf")
path2 = path_mapped + "/contact4-with-bday-no-year.vcf"
path_shared_2 = path_shared_r + "/contact4-with-bday-no-year.vcf"
self.put(path2, contact2, login="owner:ownerpw")
# create map
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
}})
# verify content as user
logging.info("\n*** GET collection user format:default -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] ([{age}. |]Birthday)",
}})
logging.info("\n*** GET collection user format:text -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "(0. Birthday)" not in answer
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "AGE={age} BDAY={year}-{month}-{day}",
}})
logging.info("\n*** GET collection user format:text -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
assert "DESCRIPTION:AGE=0" not in answer
assert "DESCRIPTION:AGE=??" in answer
self.configure({"sharing": {
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
"conversion_bday_alarm_trigger_template": "-15H;alarm {fn} [{age}. |]birthday",
}})
logging.info("\n*** GET collection user format:text -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
assert "DESCRIPTION:BDAY=????-01-01" in answer
assert "DESCRIPTION:alarm Test bday without year Thunderbird CardBook 0. birthday" not in answer
assert "DESCRIPTION:alarm Test bday without year Thunderbird CardBook ??. birthday" in answer
def test_sharing_api_map_vcf_bday_per_share_template(self) -> None:
"""share-by-map with conversion=bday template per share tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"response_header_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped_2 = "/owner/adressbook2-" + db_type + ".vcf/"
path_mapped_3 = "/owner/adressbook3-" + db_type + ".vcf/"
path_shared_2r = "/user/calendar-bday-abook2-shared-by-owner-r-" + db_type + ".ics/"
path_shared_3r = "/user/calendar-bday-abook3-shared-by-owner-r-" + db_type + ".ics/"
self.create_addressbook(path_mapped_2, login="owner:ownerpw")
self.create_addressbook(path_mapped_3, login="owner:ownerpw")
contact2 = get_file_content("contact2-with-bday.vcf")
path2 = path_mapped_2 + "/contact2-with-bday.vcf"
path_shared_2 = path_shared_2r + "/contact2-with-bday.vcf"
self.put(path2, contact2, login="owner:ownerpw")
contact3 = get_file_content("contact3-with-bday.vcf")
path3 = path_mapped_3 + "/contact3-with-bday.vcf"
path_shared_3 = path_shared_3r + "/contact3-with-bday.vcf"
self.put(path3, contact3, login="owner:ownerpw")
# create map
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_2
json_dict['PathOrToken'] = path_shared_2r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-BDAY2"}
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template": "{fn} (BDAY2)",
"conversion_bday_description_template": "BDAY2={year}-{month}-{day}",
}}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_3
json_dict['PathOrToken'] = path_shared_3r
json_dict['Conversion'] = "bday"
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Properties'] = {"D:displayname": "Test-Birthday3"}
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template": "{fn} (Birthday3)",
"conversion_bday_description_template": "Birthday3={year}-{month}-{day}",
}}
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_2
json_dict['PathOrToken'] = path_shared_2r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_3
json_dict['PathOrToken'] = path_shared_3r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# verify content as user
logging.info("\n*** GET collection user template 2 -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY2)" in answer
logging.info("\n*** GET collection user template 3 -> ok")
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
assert "SUMMARY:Test-FN-C3 (Birthday3)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_2
json_dict['PathOrToken'] = path_shared_2r
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template": "{fn} (BDAY2x)",
"conversion_bday_description_template": "BDAY2x={year}-{month}-{day}",
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# verify content as user
logging.info("\n*** GET collection user template 2 -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (BDAY2x)" in answer
# update template
logging.info("\n*** update map(bday) user/owner:r with wrong Action -> problem")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped_2
json_dict['PathOrToken'] = path_shared_2r
json_dict['Actions'] = {"config": {
"conversion_bday_summary_template_UNSUPPORTED": "{fn} (BDAY2x)",
"conversion_bday_description_template_UNSUPPORTED": "BDAY2x={year}-{month}-{day}",
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_vcf_bday_complex(self) -> None:
"""share-by-map with conversion=bday complex tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
path_user = "/user/"
path_shared_bday = path_user + "calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
path_shared_map = path_user + "abook-shared-by-owner-r-" + db_type + ".vcf/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact = get_file_content("contact1.vcf")
path = path_mapped + "/contact1.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact2-with-bday.vcf")
path = path_mapped + "/contact2-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact3-with-bday.vcf")
path = path_mapped + "/contact3-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<propname />
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
assert "CR:supported-address-data" in response
# execute GET as owner
logging.info("\n*** GET VCF collection owner -> ok")
_, answer = self.get(path_mapped, login="owner:ownerpw")
assert "contact1" in answer
assert "contact2" in answer
# create bday
logging.info("\n*** create map(bday) user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_bday
json_dict['Conversion'] = "bday"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable bday by user
logging.info("\n*** enable map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_bday
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# unhide bday by user
logging.info("\n*** unhide map(bday) by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_bday
_, headers, answer = self._sharing_api_json("map", "unhide", check=200, login="user:userpw", json_dict=json_dict)
# create map
logging.info("\n*** create map user/owner:r -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_map
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_map
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# unhide map by user
logging.info("\n*** unhide map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_map
_, headers, answer = self._sharing_api_json("map", "unhide", check=200, login="user:userpw", json_dict=json_dict)
# check PROPFIND item as user
logging.info("\n*** PROPFIND all as user")
_, responses = self.propfind(path_user, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<RADICALE:displayname />
<ICAL:calendar-color />
<ns3:addressbook-color />
<C:calendar-description />
<C:supported-calendar-component-set />
<CR:addressbook-description />
<CS:source />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
logging.debug("responses: %r", responses)
response = responses[path_shared_map]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_shared_map, response)
assert "C:supported-calendar-component-set" in response
response = responses[path_shared_bday]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_shared_bday, response)
assert "C:supported-calendar-component-set" in response
# check PROPFIND item as user
logging.info("\n*** PROPFIND all as user (reduced)")
_, responses = self.propfind(path_user, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<C:calendar-description />
<CR:addressbook-description />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="user:userpw", HTTP_DEPTH="1")
# logging.debug("responses: %r", responses)
response = responses[path_shared_bday]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_shared_bday, response)
assert "D:getcontentlength" in response
assert "RADICALE:getcontentcount" in response
status, prop = response["D:getcontentlength"]
assert int(str(prop.text)) > 600
status, prop = response["RADICALE:getcontentcount"]
assert int(str(prop.text)) == 2
def test_sharing_api_map_vcf_bday_self(self) -> None:
"""share-by-map with conversion=bday to self tests (without and with simulated proxy)."""
script_name = "/radicale"
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_map": "True"},
"logging": {"request_header_on_debug": "False",
"response_header_on_debug": "True",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"server": {"script_name": script_name},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
path_owner = "/owner/"
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = path_owner + "abook-" + db_type + ".vcf/"
path_shared = path_owner + "cal-bday-abook-" + db_type + ".ics/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact = get_file_content("contact1.vcf")
path = path_mapped + "/contact1.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact2-with-bday.vcf")
path = path_mapped + "/contact2-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact3-with-bday.vcf")
path = path_mapped + "/contact3-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<propname />
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
assert "CR:supported-address-data" in response
logging.info("\n*** PROPFIND collection owner via proxy -> ok")
_, responses = self.propfind(script_name + path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<propname />
</propfind>""", login="owner:ownerpw", x_forwarded_for="127.0.0.2")
logging.info("response: %r", responses)
response = responses[script_name + path_mapped]
assert not isinstance(response, int)
assert "CR:supported-address-data" in response
# execute GET as owner
logging.info("\n*** GET VCF collection owner -> ok")
_, answer = self.get(path_mapped, login="owner:ownerpw")
assert "contact1" in answer
assert "contact2" in answer
assert "Family3Test" in answer
# create map
logging.info("\n*** create bday owner to itself -> ok")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared
json_dict['Conversion'] = "bday"
json_dict['Enabled'] = False
json_dict['Hidden'] = True
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
# check PROPFIND item as owner
logging.info("\n*** PROPFIND all as owner")
_, responses = self.propfind(path_owner, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<RADICALE:displayname />
<ICAL:calendar-color />
<ns3:addressbook-color />
<C:calendar-description />
<C:supported-calendar-component-set />
<CR:addressbook-description />
<CS:source />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="owner:ownerpw", HTTP_DEPTH="1")
# logging.debug("responses: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_mapped, response)
assert "C:supported-calendar-component-set" in response
assert path_shared not in responses
assert "D:resourcetype" in response
status, resourcetype = response["D:resourcetype"]
resourcetypes = resourcetype.find(xmlutils.make_clark("CR:addressbook"))
assert resourcetypes is not None
assert "{urn:ietf:params:xml:ns:carddav}addressbook" in resourcetypes.tag
assert "{urn:ietf:params:xml:ns:caldav}calendar" not in resourcetypes.tag
logging.info("\n*** PROPFIND all as owner via proxy")
_, responses = self.propfind(script_name + path_owner, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<RADICALE:displayname />
<ICAL:calendar-color />
<ns3:addressbook-color />
<C:calendar-description />
<C:supported-calendar-component-set />
<CR:addressbook-description />
<CS:source />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="owner:ownerpw", HTTP_DEPTH="1", x_forwarded_for="127.0.0.2")
# logging.debug("responses: %r", responses)
response = responses[script_name + path_mapped]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_mapped, response)
assert "C:supported-calendar-component-set" in response
assert path_shared not in responses
assert "D:resourcetype" in response
status, resourcetype = response["D:resourcetype"]
resourcetypes = resourcetype.find(xmlutils.make_clark("CR:addressbook"))
assert resourcetypes is not None
assert "{urn:ietf:params:xml:ns:carddav}addressbook" in resourcetypes.tag
assert "{urn:ietf:params:xml:ns:caldav}calendar" not in resourcetypes.tag
# enable + unhide
logging.info("\n*** enable+unhide bday owner to itself -> ok")
json_dict = {}
json_dict['PathOrToken'] = path_shared
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# check PROPFIND item as owner
logging.info("\n*** PROPFIND item as owner")
_, responses = self.propfind(path_owner, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<RADICALE:displayname />
<ICAL:calendar-color />
<ns3:addressbook-color />
<C:calendar-description />
<C:supported-calendar-component-set />
<CR:addressbook-description />
<CS:source />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="owner:ownerpw", HTTP_DEPTH="1")
# logging.debug("responses: %r", responses)
response = responses[path_shared]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_mapped, response)
assert "C:supported-calendar-component-set" in response
assert path_shared in responses
status, resourcetype = response["D:resourcetype"]
resourcetypes = resourcetype.find(xmlutils.make_clark("C:calendar"))
assert resourcetypes is not None
assert "{urn:ietf:params:xml:ns:carddav}addressbook" not in resourcetypes.tag
assert "{urn:ietf:params:xml:ns:caldav}calendar" in resourcetypes.tag
status, sup_cal_comp_set = response["C:supported-calendar-component-set"]
sup_cal_comp_sets = sup_cal_comp_set.findall(xmlutils.make_clark("C:comp"))
comp_attr = []
for comp in sup_cal_comp_sets:
comp_attr.append(comp.attrib)
logging.debug("comp: %r", comp.attrib)
assert {'name': 'VEVENT'} in comp_attr
assert {'name': 'VTODO'} not in comp_attr
assert {'name': 'VJOURNAL'} not in comp_attr
# check PROPFIND item as owner via proxy
logging.info("\n*** PROPFIND collection as owner via proxy")
_, responses = self.propfind(script_name + path_owner, """\
<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/" xmlns:RADICALE="http://radicale.org/ns/" xmlns:ns3="http://inf-it.com/ns/ab/">
<prop>
<resourcetype />
<RADICALE:displayname />
<ICAL:calendar-color />
<ns3:addressbook-color />
<C:calendar-description />
<C:supported-calendar-component-set />
<CR:addressbook-description />
<CS:source />
<RADICALE:getcontentcount />
<getcontentlength />
</prop>
</propfind>""", login="owner:ownerpw", HTTP_DEPTH="1", x_forwarded_for="127.0.0.2")
# logging.debug("responses: %r", responses)
response = responses[script_name + path_shared]
assert not isinstance(response, int)
logging.debug("response %r: %r", path_mapped, response)
assert "C:supported-calendar-component-set" in response
assert script_name + path_shared in responses
status, resourcetype = response["D:resourcetype"]
resourcetypes = resourcetype.find(xmlutils.make_clark("C:calendar"))
assert resourcetypes is not None
assert "{urn:ietf:params:xml:ns:carddav}addressbook" not in resourcetypes.tag
assert "{urn:ietf:params:xml:ns:caldav}calendar" in resourcetypes.tag
status, sup_cal_comp_set = response["C:supported-calendar-component-set"]
sup_cal_comp_sets = sup_cal_comp_set.findall(xmlutils.make_clark("C:comp"))
comp_attr = []
for comp in sup_cal_comp_sets:
comp_attr.append(comp.attrib)
logging.debug("comp: %r", comp.attrib)
assert {'name': 'VEVENT'} in comp_attr
assert {'name': 'VTODO'} not in comp_attr
assert {'name': 'VJOURNAL'} not in comp_attr
# check PROPFIND collection as owner
logging.info("\n*** PROPFIND collection as owner -> calendar")
response = self._propfind_allprop(path_shared, login="owner:ownerpw")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "D:sync-token" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
# check PROPFIND collection as owner via proxy
logging.info("\n*** PROPFIND collection as owner via proxy -> calendar")
response = self._propfind_allprop(script_name + path_shared, login="owner:ownerpw", x_forwarded_for="127.0.0.2")
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "D:sync-token" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
# verify content as owner
logging.info("\n*** GET collection owner -> ok")
_, headers, answer = self.request("GET", path_shared, login="owner:ownerpw")
assert 'Content-Type' in headers
assert 'text/calendar' in headers['Content-Type']
# title from default
assert 'Content-Disposition' in headers
assert 'Calendar.ics' in headers['Content-Disposition']
# verify content as owner via proxy
logging.info("\n*** GET collection owner via proxy -> ok")
_, headers, answer = self.request("GET", script_name + path_shared, login="owner:ownerpw", x_forwarded_for="127.0.0.2")
assert 'Content-Type' in headers
assert 'text/calendar' in headers['Content-Type']
# title from default
assert 'Content-Disposition' in headers
assert 'Calendar.ics' in headers['Content-Disposition']
# verify report as owner
logging.info("\n*** REPORT collection owner -> ok")
_, responses = self.report(path_shared, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag/>
</D:prop>
</C:calendar-query>""", login="owner:ownerpw")
logging.debug("resonses: %r", responses)
assert path_shared + "contact2-with-bday.ics" in responses
assert path_shared + "contact3-with-bday.ics" in responses
assert path_shared + "contact1.ics" not in responses
# verify report as owner via proxy
logging.info("\n*** REPORT collection owner -> ok")
_, responses = self.report(script_name + path_shared, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag/>
</D:prop>
</C:calendar-query>""", login="owner:ownerpw", x_forwarded_for="127.0.0.2")
logging.debug("resonses: %r", responses)
assert script_name + path_shared + "contact2-with-bday.ics" in responses
assert script_name + path_shared + "contact3-with-bday.ics" in responses
assert script_name + path_shared + "contact1.ics" not in responses
def test_sharing_api_token_vcf_bday(self) -> None:
"""share-by-bday to a token tests."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"conversion_bday_summary_template": "{fn} (BDAY)",
"permit_create_token": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_header_on_debug": "True",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
self.configure({"sharing": {"permit_properties_overlay": "True"}})
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
self.create_addressbook(path_mapped, login="owner:ownerpw")
contact = get_file_content("contact1.vcf")
path = path_mapped + "/contact1.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact2-with-bday.vcf")
path = path_mapped + "/contact2-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
contact = get_file_content("contact3-with-bday.vcf")
path = path_mapped + "/contact3-with-bday.vcf"
self.put(path, contact, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<propname />
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int)
assert "CR:supported-address-data" in response
# execute GET as owner
logging.info("\n*** GET VCF collection owner -> ok")
_, answer = self.get(path_mapped, login="owner:ownerpw")
assert "contact1" in answer
assert "contact2" in answer
assert "Family3Test" in answer
# create map
logging.info("\n*** create token with bday conversion (default permissions) -> ok")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathMapped'] = path_mapped
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Conversion'] = "bday"
_, headers, answer = self._sharing_api_json("token", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert "Status" in answer_dict
assert "PathOrToken" in answer_dict
Token = answer_dict["PathOrToken"]
path_shared = Token
# execute GET with token
logging.info("\n*** GET bday with token")
_, answer = self.get(path_shared)
assert "VCARD" not in answer
assert "Test-FN-C3 (BDAY)" in answer # contact2
assert "Test-FN (BDAY)" in answer # contact1
# verify content as owner
logging.info("\n*** GET collection owner -> ok")
_, headers, answer = self.request("GET", path_shared)
assert 'Content-Type' in headers
assert 'text/calendar' in headers['Content-Type']
# title from default
assert 'Content-Disposition' in headers
assert 'Calendar.ics' in headers['Content-Disposition']
# create map of ics with conversion -> fail
logging.info("\n*** create token with bday conversion but unsupported permissions -> fail")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathMapped'] = path_mapped
json_dict['Enabled'] = True
json_dict['Permissions'] = "rw"
json_dict['Hidden'] = False
json_dict['Conversion'] = "bday"
_, headers, answer = self._sharing_api_json("token", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
# check PROPFIND item with token
logging.info("\n*** PROPFIND item with token -> calendar")
response = self._propfind_allprop(path_shared)
logging.debug("response: %r", response)
assert "CR:supported-address-data" not in response
assert "D:sync-token" not in response
assert "C:supported-calendar-component-set" in response
assert "D:current-user-privilege-set" in response
status, props = response["D:current-user-privilege-set"]
privileges = props.findall(xmlutils.make_clark("D:privilege"))
assert len(privileges) >= 1
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" not in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# execute PROPPATCH color as user
logging.info("\n*** PROPPATCH color collection with token -> permission denied")
color = "#BBBBBB"
_, responses = self.proppatch(path=path_shared, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">""" + color + """</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>""", check=403)
# update map
logging.info("\n*** update token with bday conversion ("r" permissions) -> ok")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "r"
_, headers, answer = self._sharing_api_json("token", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** PROPFIND item with token -> calendar")
response = self._propfind_allprop(path_shared)
logging.debug("response: %r", response)
status, props = response["D:current-user-privilege-set"]
privileges = props.findall(xmlutils.make_clark("D:privilege"))
assert len(privileges) >= 1
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# execute PROPPATCH color as user
logging.info("\n*** PROPPATCH color collection with token -> ok")
color = "#BBBBBB"
_, responses = self.proppatch(path=path_shared, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">""" + color + """</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>""", check=207)
self.configure({"sharing": {"permit_properties_overlay": "False"}})
logging.info("\n*** PROPFIND item with token (r) -> calendar")
response = self._propfind_allprop(path_shared)
logging.debug("response: %r", response)
status, props = response["D:current-user-privilege-set"]
privileges = props.findall(xmlutils.make_clark("D:privilege"))
assert len(privileges) >= 1
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" not in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# execute PROPPATCH color as user
logging.info("\n*** PROPPATCH color collection with token -> ok")
color = "#BBBBBB"
_, responses = self.proppatch(path=path_shared, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">""" + color + """</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>""", check=403)
# update map to "rP"
logging.info("\n*** update token with bday conversion ('rP' permissions) -> ok")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "rP"
_, headers, answer = self._sharing_api_json("token", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** PROPFIND item with token -> calendar")
response = self._propfind_allprop(path_shared)
logging.debug("response: %r", response)
status, props = response["D:current-user-privilege-set"]
privileges = props.findall(xmlutils.make_clark("D:privilege"))
assert len(privileges) >= 1
privileges_list = [xmlutils.make_human_tag(privilege.findall("*")[0].tag) for privilege in privileges]
assert "D:read" in privileges_list
assert "D:write-content" not in privileges_list
assert "D:write-properties" in privileges_list
assert "D:write" not in privileges_list
assert "D:all" not in privileges_list
# execute PROPPATCH color as user
logging.info("\n*** PROPPATCH color collection with token -> ok")
color = "#BBBBBB"
_, responses = self.proppatch(path=path_shared, data="""\
<?xml version="1.0" encoding="utf-8"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">""" + color + """</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>""", check=207)
# update map to "rPe"
logging.info("\n*** update token with bday conversion ('rPe' permissions) -> not supported")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "rPe"
_, headers, answer = self._sharing_api_json("token", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
# update map to "rPE"
logging.info("\n*** update token with bday conversion ('rPE' permissions) -> not supported")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
json_dict['Permissions'] = "rPE"
_, headers, answer = self._sharing_api_json("token", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
# update map Conversion
logging.info("\n*** update token remove Conversion -> not supported")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathOrToken'] = path_shared
json_dict['Conversion'] = ""
_, headers, answer = self._sharing_api_json("token", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_token_ics_bday(self) -> None:
"""share-by-token ics with bday conversion (has to fail)."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_token": True,
"permit_properties_overlay": "True",
"enforce_properties_overlay": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_header_on_debug": "True",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendar-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# create map
logging.info("\n*** create token of calendar with bday conversion -> fail")
json_dict = {}
json_dict['User'] = "owner"
json_dict['PathMapped'] = path_mapped
json_dict['Enabled'] = True
json_dict['Hidden'] = False
json_dict['Conversion'] = "bday"
_, headers, answer = self._sharing_api_json("token", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_properies_overlay_unicode(self) -> None:
"""share-by-map API usage tests related to properties overlay using unicode."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/user/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# check PROPFIND as owner
logging.info("\n*** PROPFIND collection owner -> ok")
_, responses = self.propfind(path_mapped, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<current-user-principal />
</prop>
</propfind>""", login="owner:ownerpw")
logging.info("response: %r", responses)
response = responses[path_mapped]
assert not isinstance(response, int) and len(response) == 1
status, prop = response["D:current-user-principal"]
assert status == 200 and len(prop) == 1
element = prop.find(xmlutils.make_clark("D:href"))
assert element is not None and element.text == "/owner/"
description_owner = "Test-Uni😀code-Single'Quote-UmÄlaut-Double\"Quote"
description_user = 'Test-Uni😁code-Single\'Quote-Sßz-Double"quote'
# execute PROPPATCH as owner
logging.info("\n*** PROPPATCH collection owner -> ok")
self._proppatch_calendar_description(path_mapped, login="owner:ownerpw", description=description_owner)
# verify PROPPATCH by owner
logging.info("\n*** PROPFIND collection owner (verify collection change) -> ok")
description = self._propfind_calendar_description(path_mapped, login="owner:ownerpw")
assert description == description_owner
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
# verify PROPFIND as user
logging.info("\n*** PROPFIND collection user")
description = self._propfind_calendar_description(path_shared_r, login="user:userpw")
assert description == description_owner
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user -> ok")
self._proppatch_calendar_description(path_shared_r, login="user:userpw", description=description_user)
self._proppatch_calendar_color(path_shared_r, login="user:userpw", color="#FFFFFF")
logging.info("\n*** list (json->json)")
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "list", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
assert answer_dict['Lines'] == 1
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
description = self._propfind_calendar_description(path_shared_r, login="user:userpw")
assert description == description_user
# verify overlay not visible by owner
logging.info("\n*** PROPFIND collection owner (no collection change) -> ok")
description = self._propfind_calendar_description(path_mapped, login="owner:ownerpw")
assert description == description_owner
# check properties file
collection_props_path = os.path.join(self.colpath, "collection-root", path_mapped.removeprefix('/'), ".Radicale.props")
logging.info("collection_props path: %r", collection_props_path)
with open(collection_props_path) as f:
props = json.load(f)
logging.info("collection_props: %r", props)
assert props['C:calendar-description'] == description_owner
# reconfigure to trigger restart and reparsing of database
self.configure({"auth": {"type": "htpasswd"}})
# verify overlay as user
logging.info("\n*** PROPFIND collection user (overlay) -> ok")
description = self._propfind_calendar_description(path_shared_r, login="user:userpw")
assert description == description_user
@pytest.mark.skipif(not pathutils.path_supports_unicode(tempfile.mkdtemp()), reason="TEMP is not supporting unicode")
def test_sharing_api_map_user_unicode(self) -> None:
"""share-by-map API usage tests related to properties overlay using unicode."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/us😀er/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "us😀er"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "us😀er"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="us😀er:user😀pw", json_dict=json_dict)
@pytest.mark.skipif(not pathutils.path_supports_unicode(tempfile.mkdtemp()), reason="TEMP is not supporting unicode")
def test_sharing_api_map_path_unicode(self) -> None:
"""share-by-map API usage tests related to properties overlay using unicode."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendar😀PFP-" + db_type + ".ics/"
path_shared_r = "/user/calendar😁PFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
answer_dict = json.loads(answer)
assert answer_dict['Status'] == "success"
# enable map by user
logging.info("\n*** enable map by user")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
def test_sharing_api_map_strict_user_unicode(self) -> None:
"""share-by-map API usage tests related to properties overlay using unicode in user."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"server": {"validate_user_value": "strict"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/user/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# create map
logging.info("\n*** create map user/owner:rP -> failed")
json_dict = {}
json_dict['User'] = "us😁er"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_strict_path_unicode(self) -> None:
"""share-by-map API usage tests related to properties overlay using unicode in user."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "True",
"collection_by_map": "True",
"collection_by_token": "True"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"server": {"validate_path_value": "strict"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
logging.info("\n*** create collection, already rejected in early state")
path_mapped = "/owner/calendar😀PFP-" + db_type + ".ics/"
path_shared_r = "/user/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw", check=400)
logging.info("\n*** create collection")
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_shared_r = "/user/calendar😁PFP-shared-by-owner-r-" + db_type + ".ics/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
# create map
logging.info("\n*** create map user/owner:rP -> ok")
json_dict = {}
json_dict['User'] = "user"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "rP"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=400, login="owner:ownerpw", json_dict=json_dict)
def test_sharing_api_map_user_group_by_domain(self) -> None:
"""share-by-map API usage tests related user group by domain."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "False",
"collection_by_map": "True",
"collection_by_token": "False"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped = "/owner/calendarPFP-" + db_type + ".ics/"
path_mapped2 = "/owner/calendarPFP2-" + db_type + ".ics/"
path_shared_r = "/{user}/calendarPFP-shared-by-owner-r-" + db_type + ".ics/"
path_shared2_r = "/{user}/calendarPFP2-shared-by-owner-r-" + db_type + ".ics/"
path_shared_r_base = "/{user}/"
self.mkcalendar(path_mapped, login="owner:ownerpw")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
# create map
logging.info("\n*** create map @domain/owner:rP -> success")
json_dict = {}
json_dict['User'] = "@domain.example"
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
# create map
logging.info("\n*** create map @domain/owner:rP -> success")
json_dict = {}
json_dict['User'] = "@domain.example"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared2_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
# verify PROPFIND as user1
logging.info("\n*** PROPFIND collection user1@domain.example")
path_shared_r_user = path_shared_r.replace("{user}", "user1@domain.example")
_, responses = self.propfind(path_shared_r_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1@domain.example:user1@pw")
assert path_shared_r_user.replace('@', '%40') in responses
# verify PROPFIND as user2
logging.info("\n*** PROPFIND collection user2@domain.example")
path_shared_r_user = path_shared_r.replace("{user}", "user2@domain.example")
_, responses = self.propfind(path_shared_r_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user2@domain.example:user2@pw")
assert path_shared_r_user.replace('@', '%40') in responses
# verify PROPFIND as user1
logging.info("\n*** PROPFIND collection user1@domain.tld")
path_shared_r_user = path_shared_r.replace("{user}", "user1@domain.tld")
_, responses = self.propfind(path_shared_r_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1@domain.tld:user1@pw", check=404)
# verify PROPFIND as user2
logging.info("\n*** PROPFIND collection user2@domain.tld")
path_shared_r_user = path_shared_r.replace("{user}", "user2@domain.tld")
_, responses = self.propfind(path_shared_r_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user2@domain.tld:user2@pw", check=404)
# verify PROPFIND as user1 in list
logging.info("\n*** PROPFIND collection DEPTH=1 user1@domain.example")
path_shared_r_base_user = path_shared_r_base.replace("{user}", "user1@domain.example")
path_shared_r_user = path_shared_r.replace("{user}", "user1@domain.example")
path_shared2_r_user = path_shared_r.replace("{user}", "user1@domain.example")
_, responses = self.propfind(path_shared_r_base_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1@domain.example:user1@pw", HTTP_DEPTH="1")
assert path_shared_r_base_user.replace('@', '%40') in responses
assert path_shared_r_user.replace('@', '%40') in responses
assert path_shared2_r_user.replace('@', '%40') in responses
# execute PROPPATCH as user
logging.info("\n*** PROPPATCH collection user1@domain.example -> forbidden")
self._proppatch_calendar_color(path_shared_r_user, login="user1@domain.example:user1@pw", color="#FFFFFF", check=403)
# verify PROPFIND as user1 not in list
logging.info("\n*** PROPFIND collection DEPTH=1 user1@domain.tld")
path_shared_r_base_user = path_shared_r_base.replace("{user}", "user1@domain.tld")
path_shared_r_user = path_shared_r.replace("{user}", "user1@domain.tld")
_, responses = self.propfind(path_shared_r_base_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1@domain.tld:user1@pw", HTTP_DEPTH="1")
assert path_shared_r_base_user.replace('@', '%40') in responses
assert path_shared_r_user.replace('@', '%40') not in responses
logging.info("\n*** PROPPATCH collection user1@domain.tld -> not found")
self._proppatch_calendar_color(path_shared_r_user, login="user1@domain.tld:user1@pw", color="#FFFFFF", check=404)
def test_sharing_api_map_user_group_by_local(self) -> None:
"""share-by-map API usage tests related user group by local."""
self.configure({"auth": {"type": "htpasswd",
"htpasswd_filename": self.htpasswd_file_path,
"htpasswd_encryption": "plain"},
"group": {"type": "htgroup",
"htgroup_filename": self.htgroup_file_path},
"sharing": {
"type": "csv",
"permit_create_map": "True",
"permit_create_token": "False",
"collection_by_map": "True",
"collection_by_token": "False"},
"logging": {"request_header_on_debug": "False",
"response_content_on_debug": "True",
"request_content_on_debug": "True"},
"rights": {"type": "owner_only"}})
json_dict: dict
logging.info("\n*** prepare and test access")
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
logging.info("\n*** test: %s", db_type)
self.configure({"sharing": {"type": db_type}})
path_mapped1 = "/owner/calendarUGBL1-" + db_type + ".ics/"
path_mapped2 = "/owner/calendarUGBL2-" + db_type + ".ics/"
path_mapped3 = "/owner/calendarUGBL3-" + db_type + ".ics/"
path_shared1_r = "/{user}/calendarUGBL1-shared-by-owner-r-" + db_type + ".ics/"
path_shared2_r = "/{user}/calendarUGBL2-shared-by-owner-r-" + db_type + ".ics/"
path_shared3_r = "/{user}/calendarUGBL3-shared-by-owner-r-" + db_type + ".ics/"
path_shared_r_base = "/{user}/"
self.mkcalendar(path_mapped1, login="owner:ownerpw")
self.mkcalendar(path_mapped2, login="owner:ownerpw")
self.mkcalendar(path_mapped3, login="owner:ownerpw")
# create map
logging.info("\n*** create map :group1/owner -> success")
json_dict = {}
json_dict['User'] = ":group1"
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map :group2/owner -> success")
json_dict = {}
json_dict['User'] = ":group2"
json_dict['PathMapped'] = path_mapped2
json_dict['PathOrToken'] = path_shared2_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** create map :group1,group2/owner -> success")
json_dict = {}
json_dict['User'] = ":group1,group2"
json_dict['PathMapped'] = path_mapped3
json_dict['PathOrToken'] = path_shared3_r
json_dict['Permissions'] = "r"
json_dict['Enabled'] = True
json_dict['Hidden'] = False
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
# verify PROPFIND as user1 in list
logging.info("\n*** PROPFIND collection DEPTH=1 user1")
path_shared_r_base_user = path_shared_r_base.replace("{user}", "user1")
path_shared1_r_user = path_shared1_r.replace("{user}", "user1")
path_shared2_r_user = path_shared2_r.replace("{user}", "user1")
path_shared3_r_user = path_shared3_r.replace("{user}", "user1")
_, responses = self.propfind(path_shared_r_base_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1:user1pw", HTTP_DEPTH="1")
assert path_shared_r_base_user in responses
assert path_shared1_r_user in responses
assert path_shared2_r_user not in responses
assert path_shared3_r_user in responses
# verify PROPFIND as user2 in list
logging.info("\n*** PROPFIND collection DEPTH=1 user2")
path_shared_r_base_user = path_shared_r_base.replace("{user}", "user2")
path_shared1_r_user = path_shared1_r.replace("{user}", "user2")
path_shared2_r_user = path_shared2_r.replace("{user}", "user2")
path_shared3_r_user = path_shared3_r.replace("{user}", "user2")
_, responses = self.propfind(path_shared_r_base_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user2:user2pw", HTTP_DEPTH="1")
assert path_shared_r_base_user in responses
assert path_shared1_r_user not in responses
assert path_shared2_r_user in responses
assert path_shared3_r_user in responses
# try upload item as user1 -> fail (w permission missing)
logging.info("\n*** PUT to shared1 as user1 -> 403")
path_shared1_r_user = path_shared1_r.replace("{user}", "user1")
event = get_file_content("event1.ics")
self.put(path_shared1_r_user, event, login="user1:user1pw", check=403)
# update permissions
logging.info("\n*** update map :group1/owner -> success")
json_dict = {}
json_dict['PathMapped'] = path_mapped1
json_dict['PathOrToken'] = path_shared1_r
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
logging.info("\n*** update map :group3/owner -> success")
json_dict = {}
json_dict['PathMapped'] = path_mapped3
json_dict['PathOrToken'] = path_shared3_r
json_dict['Permissions'] = "rw"
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
# upload item as user1 -> success
logging.info("\n*** PUT to shared1 as user1 -> 201")
path_shared1_r_user = path_shared1_r.replace("{user}", "user1")
event = get_file_content("event1.ics")
self.put(path_shared1_r_user, event, login="user1:user1pw")
# propfind as user1 -> success
logging.info("\n*** PROPFIND collection user1")
path_shared1_r_user = path_shared1_r.replace("{user}", "user1")
_, responses = self.propfind(path_shared1_r_user, """\
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav" />
</propfind>""", login="user1:user1pw")
assert path_shared1_r_user in responses
# report as user1 -> success
logging.info("\n*** REPORT collection user1")
path_shared1_r_user = path_shared1_r.replace("{user}", "user1")
item_shared1_r_user = path_shared1_r.replace("{user}", "user1") + "event1.ics"
_, responses = self.report(path_shared1_r_user, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="user1:user1pw")
assert item_shared1_r_user in responses
# report as user2 -> success
logging.info("\n*** REPORT collection user2")
path_shared1_r_user = path_shared1_r.replace("{user}", "user2")
item_shared1_r_user = path_shared1_r.replace("{user}", "user2") + "event1.ics"
_, responses = self.report(path_shared1_r_user, """\
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag />
</D:prop>
</C:calendar-query>""", login="user2:user2pw", check=404)
# get item as user1 -> success
logging.info("\n*** GET from shared1 as user1")
item_shared1_r_user = path_shared1_r.replace("{user}", "user1") + "event1.ics"
self.get(item_shared1_r_user, login="user1:user1pw")
# get item as user2 -> 404
logging.info("\n*** GET from shared3 as user2")
item_shared3_r_user = path_shared1_r.replace("{user}", "user2") + "event1.ics"
self.get(item_shared3_r_user, login="user2:user2pw", check=404)
# move item as user1 -> success
logging.info("\n*** MOVE item shared1 to shared3 as user1")
item_shared1_r_user = path_shared1_r.replace("{user}", "user1") + "event1.ics"
item_shared3_r_user = path_shared3_r.replace("{user}", "user1") + "event1.ics"
self.request("MOVE", item_shared1_r_user, login="user1:user1pw", HTTP_DESTINATION="http://127.0.0.1"+item_shared3_r_user)
# get item as user2 -> 200
logging.info("\n*** GET from shared3 as user2")
item_shared3_r_user = path_shared3_r.replace("{user}", "user2") + "event1.ics"
self.get(item_shared3_r_user, login="user2:user2pw")
# delete item as user1 -> 404
logging.info("\n*** DELETE from shared1 as user1 -> 404")
item_shared1_r_user = path_shared1_r.replace("{user}", "user1") + "event1.ics"
self.delete(item_shared1_r_user, login="user1:user1pw", check=404)
# delete item as user1
logging.info("\n*** DELETE from shared3 as user1 -> 200")
item_shared3_r_user = path_shared3_r.replace("{user}", "user1") + "event1.ics"
self.delete(item_shared3_r_user, login="user1:user1pw")
# try proppatch -> 403
logging.info("\n*** PROPPATCH shared3 as user1 -> 403")
path_shared3_r_user = path_shared3_r.replace("{user}", "user1")
self._proppatch_calendar_color(path_shared3_r_user, login="user1:user1pw", color="#FFFFFF", check=403)