review update+delete

This commit is contained in:
Peter Bieringer
2026-03-03 08:34:05 +01:00
parent f9399dc17e
commit 3267cc5228
5 changed files with 399 additions and 155 deletions

View File

@@ -241,31 +241,36 @@ map,/user/cal1-from-owner/,/owner/cal1/,owner,user,r,False,False,True,True,17719
```
##### API Hook "*/delete"
##### API Hook "(map|token)/delete"
Delete a share selected by `PathOrToken`
Delete a share selected by `PathOrToken`.
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathOrToken | yes | |
| Parameter | Type | Owner | User |
| - | - | - | - |
| PathOrToken | selector | mandatory | not-permitted |
* Output: result status
##### API Hook "*/update"
##### API Hook "(token|map)/update"
Update a share selected by `PathOrToken`
Update a share selected by `PathOrToken`.
Execute delete+create in case `PathOrToken` needs to be changed.
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathOrToken | yes | n/a |
| PathMapped | no | |
| OwnerOrUser | yes | n/a |
| User | no | |
| Properties | no | |
| Parameter | Type | Owner | User |
| - | - | - | - |
| PathOrToken | selector | mandatory | mandatory |
| Owner | by authentication | not-permitted | not-permitted |
| PathMapped | adjustable | optional | not-permitted |
| User | adjustable | optional | not-permitted |
| Permissions | adjustable | optional | not-permitted |
| Enabled | adjustable | optional(owner) | optional(user) |
| Hidden | adjustable | optional(owner) | optional(user) |
| Properties | adjustable | optional | optional |
* Output: result status

View File

@@ -36,6 +36,7 @@ INTERNAL_TYPES: Sequence[str] = ("csv", "files", "none")
DB_FIELDS_V1: Sequence[str] = ('ShareType', 'PathOrToken', 'PathMapped', 'Owner', 'User', 'Permissions', 'EnabledByOwner', 'EnabledByUser', 'HiddenByOwner', 'HiddenByUser', 'TimestampCreated', 'TimestampUpdated', 'Properties')
DB_FIELDS_V1_BOOL: Sequence[str] = ('EnabledByOwner', 'EnabledByUser', 'HiddenByOwner', 'HiddenByUser')
DB_FIELDS_V1_INT: Sequence[str] = ('TimestampCreated', 'TimestampUpdated')
DB_FIELDS_V1_USER_PERMITTED: Sequence[str] = ('EnabledByUser', 'HiddenByUser', 'Properties')
# ShareType: <token|map>
# PathOrToken: <path|token> [PrimaryKey]
# PathMapped: <path>
@@ -48,6 +49,7 @@ DB_FIELDS_V1_INT: Sequence[str] = ('TimestampCreated', 'TimestampUpdated')
# HiddenByUser: True|False (share exposure controlled by user) - check skipped if Owner==User
# TimestampCreated: <unixtime> (when created)
# TimestampUpdated: <unixtime> (last update)
# Properties: Overlay of collection properties
SHARE_TYPES: Sequence[str] = ('token', 'map', 'all')
SHARE_TYPES_V1: Sequence[str] = ('token', 'map')
@@ -164,15 +166,16 @@ class BaseSharing:
PathMapped: Union[str, None] = None,
User: Union[str, None] = None,
EnabledByOwner: Union[bool, None] = None,
EnabledByUser: Union[bool, None] = None,
HiddenByOwner: Union[bool, None] = None,
HiddenByUser: Union[bool, None] = None) -> list[dict]:
EnabledByUser: Union[bool, None] = None,
HiddenByOwner: Union[bool, None] = None,
HiddenByUser: Union[bool, None] = None) -> list[dict]:
""" retrieve sharing """
return []
def get_sharing(self,
ShareType: str,
PathOrToken: str,
OnlyEnabled: bool = True,
User: Union[str, None] = None) -> Union[dict, None]:
""" retrieve sharing target and attributes by map """
return {"status": "not-implemented"}
@@ -192,12 +195,14 @@ class BaseSharing:
def update_sharing(self,
ShareType: str,
PathOrToken: str,
OwnerOrUser: str,
OwnerOrUser: Union[str, None] = None,
User: Union[str, None] = None,
PathMapped: Union[str, None] = None,
Permissions: Union[str, None] = None,
EnabledByOwner: Union[bool, None] = None,
EnabledByUser: Union[bool, None] = None,
HiddenByOwner: Union[bool, None] = None,
HiddenByUser: Union[bool, None] = None,
Timestamp: int = 0,
Properties: Union[dict, None] = None) -> dict:
""" update sharing """
@@ -205,9 +210,7 @@ class BaseSharing:
def delete_sharing(self,
ShareType: str,
PathOrToken: str,
Owner: str,
PathMapped: Union[str, None] = None) -> dict:
PathOrToken: str) -> dict:
""" delete sharing """
return {"status": "not-implemented"}
@@ -504,6 +507,12 @@ class BaseSharing:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/API: converted Properties from form into dict: %r", properties_dict)
request_data[key] = properties_dict
elif key == "Enabled" or key == "Hidden":
try:
request_data[key] = config._convert_to_bool(request_parsed[key])
except ValueError:
logger.error(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for " + key)
else:
request_data[key] = request_parsed[key][0]
if logger.isEnabledFor(logging.DEBUG):
@@ -566,10 +575,6 @@ class BaseSharing:
return httputils.bad_request("Invalid value for PathMapped")
elif not request_data[key].endswith("/"):
return httputils.bad_request("PathMapped not ending with /")
elif key == "Enabled" or key == "Hidden":
if not re.search('^(False|True)$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for " + key)
elif key == "User":
if not re.search('^' + USER_PATTERN + '$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
@@ -577,7 +582,7 @@ class BaseSharing:
# check for mandatory parameters
if 'PathMapped' not in request_data:
if action in ['info', 'list', 'update']:
if action in ['info', 'list', 'update', 'delete']:
# ignored
pass
else:
@@ -617,6 +622,16 @@ class BaseSharing:
return httputils.bad_request("Property not supported to overlay: %r" % entry)
Properties = request_data['Properties']
if 'Enabled' in request_data:
Enabled = request_data['Enabled']
else:
Enabled = None
if 'Hidden' in request_data:
Hidden = request_data['Hidden']
else:
Hidden = None
if ShareType == "map":
if action == 'info':
# ignored
@@ -809,66 +824,72 @@ class BaseSharing:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/" + api_info + ": start")
if PathOrToken is None:
return httputils.bad_request("Missing PathOrToken")
if ShareType not in ["token", "map"]:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if PathOrToken is None:
return httputils.bad_request("Missing PathOrToken")
# check for permissions to update
share = self.get_sharing(ShareType=ShareType, PathOrToken=PathOrToken)
share = self.get_sharing(ShareType=ShareType, PathOrToken=PathOrToken, OnlyEnabled=False)
if share is None:
return httputils.NOT_FOUND
if share['Owner'] is not None and user == share['Owner']:
# unconditional update as owner
if user == share['Owner']:
if PathMapped is not None:
# check access Permissions
access = Access(self._rights, user, str(PathMapped), None)
if not access.check("r") and "i" not in access.permissions:
logger.warning("Update sharing: access to PathMapped %r not allowed for user %r", PathMapped, user)
return httputils.NOT_ALLOWED
result = self.update_sharing(
ShareType=ShareType,
PathMapped=PathMapped,
Permissions=Permissions,
EnabledByOwner=EnabledByOwner,
HiddenByOwner=HiddenByOwner,
EnabledByOwner=Enabled,
HiddenByOwner=Hidden,
PathOrToken=str(PathOrToken), # verification above that it is not None
OwnerOrUser=user,
User=User,
Timestamp=Timestamp,
Properties=Properties)
elif share['User'] is not None and Owner == share['User']:
elif user == share['User']:
# User is only allowed to update Properties
if PathMapped is not None or EnabledByOwner is not None or HiddenByOwner is not None:
logger.info("Update sharing: access to %r not allowed for user %r to adjust anything beside Properties", PathOrToken, user)
if PathMapped is not None or EnabledByOwner is not None or HiddenByOwner is not None or Permissions is not None or User is not None:
logger.info("Update sharing: access to %r not allowed for user %r to adjust anything beside: %s", PathOrToken, user, " ".join(DB_FIELDS_V1_USER_PERMITTED))
return httputils.NOT_ALLOWED
if Properties is None:
logger.info("Update sharing: access to %r as user %r misses Properties", PathOrToken, user)
return httputils.NOT_ALLOWED
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/API/update: permit_properties_overlay=%s Permissions=%r", self.permit_properties_overlay, share['Permissions'])
if self.permit_properties_overlay:
if share['Permissions'] is not None and "p" in str(share['Permissions']):
logger.info("Update on shared %r: overlay permitted, but denied by permission 'p'", PathOrToken)
return httputils.NOT_ALLOWED
if Properties is not None:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/API/update: permit_properties_overlay=%s Permissions=%r", self.permit_properties_overlay, share['Permissions'])
if self.permit_properties_overlay:
if share['Permissions'] is not None and "p" in str(share['Permissions']):
logger.info("Update on shared %r: overlay permitted, but denied by permission 'p'", PathOrToken)
return httputils.NOT_ALLOWED
else:
logger.info("Update on shared %r: overlay permitted by option", PathOrToken)
else:
logger.info("Update on shared %r: overlay permitted by option", PathOrToken)
else:
if share['Permissions'] is not None and "P" in str(share['Permissions']):
logger.info("Update on shared %r: overlay denied, but granted by permission 'P'", PathOrToken)
else:
logger.info("Update on shared %r: overlay denied by option", PathOrToken)
if share['Permissions'] is not None and "P" in str(share['Permissions']):
logger.info("Update on shared %r: overlay denied, but granted by permission 'P'", PathOrToken)
else:
logger.info("Update on shared %r: overlay denied by option", PathOrToken)
return httputils.NOT_ALLOWED
return httputils.NOT_ALLOWED
return httputils.NOT_ALLOWED
# limited update as user
result = self.update_sharing(
ShareType=ShareType,
PathMapped=PathMapped,
PathOrToken=str(PathOrToken), # verification above that it is not None
OwnerOrUser=user,
EnabledByUser=Enabled,
HiddenByUser=Hidden,
Timestamp=Timestamp,
Properties=Properties)
else:
# neither owner nor user matches
logger.warning("Update sharing of %r not permitted for user %r", PathOrToken, user)
return httputils.NOT_ALLOWED
# result handling
@@ -891,25 +912,26 @@ class BaseSharing:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/" + api_info + ": start")
if ShareType not in ["token", "map"]:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if PathOrToken is None:
return httputils.bad_request("Missing PathOrToken")
if ShareType == "token":
# check whether share exists
share = self.get_sharing(ShareType=ShareType, PathOrToken=PathOrToken, OnlyEnabled=False)
if share is None:
return httputils.NOT_FOUND
if user == share['Owner']:
result = self.delete_sharing(
ShareType=ShareType,
PathOrToken=str(PathOrToken), # verification above that it is not None
Owner=Owner)
elif ShareType == "map":
result = self.delete_sharing(
ShareType=ShareType,
PathOrToken=str(PathOrToken), # verification above that it is not None
PathMapped=PathMapped,
Owner=Owner)
PathOrToken=str(PathOrToken)) # verification above that it is not None
else:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
# only owner is permitted to delete a share
logger.warning("Delete sharing of %r not permitted for user %r", PathOrToken, user)
return httputils.NOT_ALLOWED
# result handling
if result['status'] == "not-found":

View File

@@ -91,6 +91,7 @@ class Sharing(sharing.BaseSharing):
def get_sharing(self,
ShareType: str,
PathOrToken: str,
OnlyEnabled: bool = True,
User: Union[str, None] = None) -> Union[dict, None]:
""" retrieve sharing target and attributes by map """
# Lookup
@@ -103,25 +104,26 @@ class Sharing(sharing.BaseSharing):
if index == 0:
# skip fieldnames
pass
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing: check row: %r", row)
if row['ShareType'] != ShareType:
pass
elif row['PathOrToken'] != PathOrToken:
pass
elif User is not None and row['User'] != User:
pass
elif row['EnabledByOwner'] is not True:
pass
elif row['ShareType'] == "map":
if row['EnabledByUser'] is not True:
else:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing: check row: %r", row)
if row['ShareType'] != ShareType:
pass
elif row['PathOrToken'] != PathOrToken:
pass
elif User is not None and row['User'] != User:
pass
elif OnlyEnabled is True and row['EnabledByOwner'] is not True:
pass
elif row['ShareType'] == "map":
if OnlyEnabled is True and row['EnabledByUser'] is not True:
pass
else:
found = True
break
else:
found = True
break
else:
found = True
break
index += 1
if found:
@@ -269,17 +271,19 @@ class Sharing(sharing.BaseSharing):
def update_sharing(self,
ShareType: str,
PathOrToken: str,
OwnerOrUser: str,
OwnerOrUser: Union[str, None] = None,
User: Union[str, None] = None,
PathMapped: Union[str, None] = None,
Permissions: Union[str, None] = None,
EnabledByOwner: Union[bool, None] = None,
EnabledByUser: Union[bool, None] = None,
HiddenByOwner: Union[bool, None] = None,
HiddenByUser: Union[bool, None] = None,
Timestamp: int = 0,
Properties: Union[dict, None] = None) -> dict:
""" update sharing """
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: PathOrToken=%r OwnerOrUser=%r PathMapped=%r Properties=%r", ShareType, PathOrToken, OwnerOrUser, PathMapped, Properties)
logger.debug("TRACE/sharing/%s/update: PathOrToken=%r OwnerOrUser=%r PathMapped=%r Properties=%r EnabledByOwner=%s EnabledByUser=%s HiddenByOwner=%s HiddenByUser=%s", ShareType, PathOrToken, OwnerOrUser, PathMapped, Properties, EnabledByOwner, EnabledByUser, HiddenByOwner, HiddenByUser)
# lookup token
found = False
@@ -301,24 +305,6 @@ class Sharing(sharing.BaseSharing):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: found index=%d", ShareType, index)
if row['Owner'] != OwnerOrUser:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: OwnerOrUser=%r not matching Owner=%r -> check now for matching User=%r", ShareType, OwnerOrUser, row['Owner'], row['User'])
if row['User'] == OwnerOrUser and PathMapped is None and Permissions is None and EnabledByOwner is None and HiddenByOwner is None and Properties is not None:
# user is only permitted to update Properties
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: OwnerOrUser=%r PathOrToken=%r index=%d is permitted to update Properties", ShareType, OwnerOrUser, PathOrToken, index)
pass
else:
return {"status": "permission-denied"}
if User is not None and row['User'] != User:
return {"status": "permission-denied"}
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: OwnerOrUser=%r PathOrToken=%r index=%d", ShareType, OwnerOrUser, PathOrToken, index)
logger.debug("TRACE/sharing/%s/update: orig row[%d]=%r", ShareType, index, row)
# CSV: remove+adjust+readd
if PathMapped is not None:
row["PathMapped"] = PathMapped
@@ -328,8 +314,12 @@ class Sharing(sharing.BaseSharing):
row["User"] = User
if EnabledByOwner is not None:
row["EnabledByOwner"] = EnabledByOwner
if EnabledByUser is not None:
row["EnabledByUser"] = EnabledByUser
if HiddenByOwner is not None:
row["HiddenByOwner"] = HiddenByOwner
if HiddenByUser is not None:
row["HiddenByUser"] = HiddenByUser
if Properties is not None:
row["Properties"] = Properties
# update timestamp
@@ -353,11 +343,10 @@ class Sharing(sharing.BaseSharing):
def delete_sharing(self,
ShareType: str,
PathOrToken: str, Owner: str,
PathMapped: Union[str, None] = None) -> dict:
PathOrToken: str) -> dict:
""" delete sharing """
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/delete: PathOrToken=%r Owner=%r PathMapped=%r", ShareType, PathOrToken, Owner, PathMapped)
logger.debug("TRACE/sharing/%s/delete: PathOrToken=%r", ShareType, PathOrToken)
# lookup token
found = False
@@ -373,28 +362,18 @@ class Sharing(sharing.BaseSharing):
elif row['PathOrToken'] != PathOrToken:
pass
else:
if ShareType == "map":
# extra filter
if row['PathMapped'] != PathMapped:
pass
else:
found = True
break
else:
found = True
break
found = True
break
index += 1
if found:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/delete: found index=%d", ShareType, index)
if row['Owner'] != Owner:
return {"status": "permission-denied"}
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/delete: Owner=%r PathOrToken=%r index=%d", ShareType, Owner, PathOrToken, index)
logger.debug("TRACE/sharing/%s/delete: PathOrToken=%r Owner=%r index=%d", ShareType, PathOrToken, row['Owner'], index)
self._sharing_cache.pop(index)
with self._storage.acquire_lock("w", Owner, path=self._sharing_db_file):
with self._storage.acquire_lock("w", row['Owner'], path=self._sharing_db_file):
if self._write_csv(self._sharing_db_file):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing_by_token: write CSV done")

View File

@@ -89,6 +89,7 @@ class Sharing(sharing.BaseSharing):
def get_sharing(self,
ShareType: str,
PathOrToken: str,
OnlyEnabled: bool = True,
User: Union[str, None] = None) -> Union[dict, None]:
""" retrieve sharing target and attributes by map """
# Lookup
@@ -111,9 +112,9 @@ class Sharing(sharing.BaseSharing):
if User is not None and row['User'] != User:
return None
elif row['EnabledByOwner'] is not True:
elif OnlyEnabled is True and row['EnabledByOwner'] is not True:
return None
elif row['ShareType'] == "map":
elif OnlyEnabled is True and row['ShareType'] == "map":
if row['EnabledByUser'] is not True:
return None
@@ -264,12 +265,14 @@ class Sharing(sharing.BaseSharing):
def update_sharing(self,
ShareType: str,
PathOrToken: str,
OwnerOrUser: str,
OwnerOrUser: Union[str, None] = None,
User: Union[str, None] = None,
PathMapped: Union[str, None] = None,
Permissions: Union[str, None] = None,
EnabledByOwner: Union[bool, None] = None,
EnabledByUser: Union[bool, None] = None,
HiddenByOwner: Union[bool, None] = None,
HiddenByUser: Union[bool, None] = None,
Timestamp: int = 0,
Properties: Union[dict, None] = None) -> dict:
""" update sharing """
@@ -293,20 +296,6 @@ class Sharing(sharing.BaseSharing):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: check: %r", ShareType, row)
if row['Owner'] != OwnerOrUser:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: OwnerOrUser=%r not matching Owner=%r -> check now for matching User=%r", ShareType, OwnerOrUser, row['Owner'], row['User'])
if row['User'] == OwnerOrUser and PathMapped is None and Permissions is None and EnabledByOwner is None and HiddenByOwner is None and Properties is not None:
# user is only permitted to update Properties
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: OwnerOrUser=%r PathOrToken=%r is permitted to update Properties", ShareType, OwnerOrUser, PathOrToken)
pass
else:
return {"status": "permission-denied"}
if User is not None and row['User'] != User:
return {"status": "permission-denied"}
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/update: orig row=%r", ShareType, row)
@@ -318,8 +307,12 @@ class Sharing(sharing.BaseSharing):
row["User"] = User
if EnabledByOwner is not None:
row["EnabledByOwner"] = EnabledByOwner
if EnabledByUser is not None:
row["EnabledByUser"] = EnabledByUser
if HiddenByOwner is not None:
row["HiddenByOwner"] = HiddenByOwner
if HiddenByUser is not None:
row["HiddenByUser"] = HiddenByUser
if Properties is not None:
row["Properties"] = Properties
# update timestamp
@@ -340,11 +333,10 @@ class Sharing(sharing.BaseSharing):
def delete_sharing(self,
ShareType: str,
PathOrToken: str, Owner: str,
PathMapped: Union[str, None] = None) -> dict:
PathOrToken: str) -> dict:
""" delete sharing """
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/%s/delete: PathOrToken=%r Owner=%r", ShareType, PathOrToken, Owner)
logger.debug("TRACE/sharing/%s/delete: PathOrToken=%r", ShareType, PathOrToken)
sharing_config_file = os.path.join(self._sharing_db_path_ShareType[ShareType], self._encode_path(PathOrToken))
@@ -352,7 +344,7 @@ class Sharing(sharing.BaseSharing):
return {"status": "not-found"}
# read content
with self._storage.acquire_lock("r", Owner, path=sharing_config_file):
with self._storage.acquire_lock("r", path=sharing_config_file):
# read file
with open(sharing_config_file, "rb") as fb:
(version, row) = pickle.load(fb)
@@ -360,10 +352,6 @@ class Sharing(sharing.BaseSharing):
if version != DB_VERSION:
return {"status": "error"}
# verify owner
if row['Owner'] != Owner:
return {"status": "permission-denied"}
try:
os.remove(sharing_config_file)
except Exception as e:

View File

@@ -705,6 +705,7 @@ class TestSharingApiSanity(BaseTest):
"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",
@@ -874,6 +875,106 @@ class TestSharingApiSanity(BaseTest):
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"
@@ -883,6 +984,164 @@ class TestSharingApiSanity(BaseTest):
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_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: 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"
def test_sharing_api_map_usercheck(self) -> None:
"""share-by-map API usage tests related to usercheck."""
self.configure({"auth": {"type": "htpasswd",
@@ -2668,7 +2927,6 @@ permissions: RrWw""")
# update map by user
logging.info("\n*** update map by user (json)")
json_dict = {}
json_dict['User'] = "user"
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)
@@ -2718,7 +2976,7 @@ permissions: RrWw""")
# update map by user
logging.info("\n*** update map by user (form)")
form_array = ["User=" + "user"]
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'")
@@ -2853,7 +3111,6 @@ permissions: RrWw""")
# update map by user
logging.info("\n*** update map by user (json) -> 403 (no overlay permitted)")
json_dict = {}
json_dict['User'] = "user"
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)
@@ -2871,7 +3128,6 @@ permissions: RrWw""")
# 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'] = "rp"
@@ -2891,7 +3147,6 @@ permissions: RrWw""")
logging.info("\n*** update map by user (json) -> 403 (no overlay permitted by share permissions)")
json_dict = {}
json_dict['User'] = "user"
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)
@@ -2904,7 +3159,6 @@ permissions: RrWw""")
# 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'] = "rP"
@@ -2913,7 +3167,6 @@ permissions: RrWw""")
logging.info("\n*** update map by user (json) -> 200 (overlay permitted by share permissions)")
json_dict = {}
json_dict['User'] = "user"
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)
@@ -2928,16 +3181,13 @@ permissions: RrWw""")
# 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'] = "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*** update map by user (json) -> 403 (overlay permitted but denied by share permissions)")
json_dict = {}
json_dict['User'] = "user"
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)