sharing: review logging, doc/test cosmetics/fixes

This commit is contained in:
Peter Bieringer
2026-03-07 05:53:01 +01:00
parent 7878e612fb
commit 6c1af39dff
3 changed files with 54 additions and 55 deletions

View File

@@ -239,9 +239,10 @@ Shows what kind of ShareTypes are supported
* Output: text/plain|application/json
* Examples
* form->text
```
```bash
curl -u user:pass -H "accept: text/plain" -d "" http://localhost:5232/.sharing/v1/all/info
ApiVersion=1
Status='success'
@@ -269,8 +270,7 @@ curl -u user:pass --silent -H "accept: application/json" -d "" http://localhost:
##### API Hook "(token|map)/create"
* Authorization
Authenticated user is `Owner`
* Authenticated user is `Owner`
###### API Hook "token/create"
@@ -455,6 +455,7 @@ Delete a share selected by `PathOrToken`.
* Output: text/plain|application/json
* Examples:
* form->text
```bash
@@ -493,7 +494,9 @@ Execute delete+create in case `PathOrToken` needs to be changed.
* Output: text/plain|application/json
* form->text
* Examples:
* form->text
```bash
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/update

View File

@@ -506,13 +506,13 @@ class BaseSharing:
# convert JSON boolean
if key in request_data:
if type(request_data[key]) is not bool:
logger.error(api_info + ": unsupported (non-boolean) " + key + ": " + request_data[key])
logger.warning(api_info + ": unsupported (non-boolean) " + key + ": " + request_data[key])
return httputils.bad_request("Invalid non-boolean value for " + key + ": " + request_data[key])
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/" + api_info + " (json): %r", f"{request_data}")
elif 'application/x-www-form-urlencoded' in content_type:
input_format = "form"
output_format = "text" # default
output_format = "plain" # default
request_parsed = parse_qs(request_body)
# convert arrays into single value
request_data = {}
@@ -534,7 +534,7 @@ class BaseSharing:
try:
request_data[key] = config._convert_to_bool(request_parsed[key][0])
except ValueError:
logger.error(api_info + ": unsupported (non-boolean) " + key + ": " + request_parsed[key][0])
logger.warning(api_info + ": unsupported (non-boolean) " + key + ": " + request_parsed[key][0])
return httputils.bad_request("Invalid non-boolean value for " + key + ": " + request_parsed[key][0])
else:
request_data[key] = request_parsed[key][0]
@@ -552,7 +552,7 @@ class BaseSharing:
elif 'text/csv' in accept:
output_format = "csv"
elif 'text/plain' in accept:
output_format = "text"
output_format = "plain"
else:
# default from input type
pass
@@ -562,7 +562,7 @@ class BaseSharing:
return httputils.bad_request("CSV output format is only allowed for list action")
elif output_format == "json":
pass
elif output_format == "text":
elif output_format == "plain":
pass
else:
return httputils.bad_request("Output format not supported")
@@ -588,23 +588,23 @@ class BaseSharing:
elif key == "PathOrToken":
if ShareType == "token":
if not re.search('^' + TOKEN_PATTERN_V1 + '$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
logger.warning(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for PathOrToken")
elif ShareType == "map":
if not re.search('^' + PATH_PATTERN + '$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
logger.warning(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for PathOrToken")
elif not request_data[key].endswith("/"):
return httputils.bad_request("PathOrToken not ending with /")
elif key == "PathMapped":
if not re.search('^' + PATH_PATTERN + '$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
logger.warning(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for PathMapped")
elif not request_data[key].endswith("/"):
return httputils.bad_request("PathMapped not ending with /")
elif key == "User":
if not re.search('^' + USER_PATTERN + '$', request_data[key]):
logger.error(api_info + ": unsupported " + key)
logger.warning(api_info + ": unsupported " + key)
return httputils.bad_request("Invalid value for User")
# check for optional parameters
@@ -617,7 +617,7 @@ class BaseSharing:
# ignored
pass
elif action not in ['list', 'create']:
logger.error(api_info + ": missing PathOrToken")
logger.warning(api_info + ": missing PathOrToken")
return httputils.bad_request("Missing PathOrToken")
else:
# PathOrToken is optional
@@ -625,7 +625,7 @@ class BaseSharing:
else:
if action == "create" and ShareType == "token":
# not supported
logger.error(api_info + ": PathOrToken found but not supported")
logger.warning(api_info + ": PathOrToken found but not supported")
return httputils.bad_request("PathOrToken not supported")
PathOrToken = request_data['PathOrToken']
@@ -701,7 +701,7 @@ class BaseSharing:
logger.debug("TRACE/" + api_info + ": start")
if PathMapped is None:
logger.error(api_info + ": missing PathMapped")
logger.warning(api_info + ": missing PathMapped")
return httputils.bad_request("Missing PathMapped")
# check whether collection exists
@@ -742,16 +742,16 @@ class BaseSharing:
# check access Permissions
access = Access(self._rights, user, PathMapped)
if not access.check("r"):
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r", PathMapped, user)
return httputils.NOT_ALLOWED
if self.permit_create_token is False:
if "t" not in access.permissions:
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=False but explict grant misses 't')", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=False but explict grant misses 't')", PathMapped, user)
return httputils.NOT_ALLOWED
else:
if "T" in access.permissions:
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=True but denied by 'T')", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=True but denied by 'T')", PathMapped, user)
return httputils.NOT_ALLOWED
if User is not None:
@@ -793,7 +793,7 @@ class BaseSharing:
# retrieve existing share
share = self.database_get_sharing(ShareType=ShareType, PathOrToken=PathOrToken, OnlyEnabled=False)
if share is not None:
logger.error(api_info + ": share already exists PathOrToken=%r", PathOrToken)
logger.warning(api_info + ": share already exists PathOrToken=%r", PathOrToken)
return httputils.CONFLICT
if User is None:
@@ -804,21 +804,21 @@ class BaseSharing:
# check access Permissions
access = Access(self._rights, user, PathMapped, None) # PathMapped is mandatory
if not access.check("r") and "i" not in access.permissions:
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r", PathMapped, user)
return httputils.NOT_ALLOWED
if self.permit_create_map is False:
if "m" not in access.permissions:
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=False but explicit grant misses 'm')", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=False but explicit grant misses 'm')", PathMapped, user)
return httputils.NOT_ALLOWED
else:
if "M" in access.permissions:
logger.error(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=True but denied by 'M')", PathMapped, user)
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=True but denied by 'M')", PathMapped, user)
return httputils.NOT_ALLOWED
access = Access(self._rights, User, PathOrToken)
if not access.check("r"):
logger.error(api_info + ": access to PathOrToken=%r not allowed for User=%r", PathOrToken, User)
logger.warning(api_info + ": access to PathOrToken=%r not allowed for User=%r", PathOrToken, User)
return httputils.NOT_ALLOWED
# check whether share is already existing as real collection
@@ -827,7 +827,7 @@ class BaseSharing:
if not item:
pass
else:
logger.error(api_info + ": PathOrToken=%r already exists as real collection for User=%r", PathOrToken, User)
logger.warning(api_info + ": PathOrToken=%r already exists as real collection for User=%r", PathOrToken, User)
return httputils.CONFLICT
if logger.isEnabledFor(logging.DEBUG):
@@ -848,7 +848,7 @@ class BaseSharing:
Properties=Properties)
else:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
logger.warning(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/" + api_info + ": result=%r", result)
@@ -860,13 +860,14 @@ class BaseSharing:
elif result['status'] == "success":
answer['Status'] = "success"
else:
return httputils.bad_request("Internal failure")
logger.warning(api_info + ": %r by user %r not successful", PathMapped, request_data['User'])
return httputils.bad_request("Internal Error")
if ShareType == "token":
PathOrToken = token
answer['PathOrToken'] = token
logger.info(api_info + " success: PathMapped=%r Permissions=%r PathOrToken=%r", PathMapped, Permissions, token)
logger.info(api_info + " success: PathMapped=%r Permissions=%r PathOrToken=%r", PathMapped, Permissions, PathOrToken)
# action: update
elif action == "update":
@@ -874,7 +875,7 @@ class BaseSharing:
logger.debug("TRACE/" + api_info + ": start")
if ShareType not in ["token", "map"]:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
logger.warning(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if PathOrToken is None:
@@ -892,7 +893,7 @@ class BaseSharing:
# 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)
logger.warning(api_info + ": access to %r not allowed for user %r", PathMapped, user)
return httputils.NOT_ALLOWED
result = self.database_update_sharing(
@@ -910,22 +911,22 @@ class BaseSharing:
elif user == share['User']:
# User is only allowed to update Properties
if PathMapped 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))
logger.warning(api_info + ": 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 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)
logger.warning(api_info + ": %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)
logger.info(api_info + ": %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)
logger.info(api_info + ": %r overlay denied, but granted by permission 'P'", PathOrToken)
else:
logger.info("Update on shared %r: overlay denied by option", PathOrToken)
logger.warning(api_info + ": %r overlay denied by option", PathOrToken)
return httputils.NOT_ALLOWED
return httputils.NOT_ALLOWED
@@ -940,7 +941,7 @@ class BaseSharing:
else:
# neither owner nor user matches
logger.warning("Update sharing of %r not permitted for user %r", PathOrToken, user)
logger.warning(api_info + ": sharing of %r not permitted for user %r", PathOrToken, user)
return httputils.NOT_ALLOWED
# result handling
@@ -952,11 +953,8 @@ class BaseSharing:
answer['Status'] = "success"
pass
else:
if ShareType == "token":
logger.info("Update of sharing-by-token: %r not successful", request_data['PathOrToken'])
elif ShareType == "map":
logger.info("Update of sharing-by-map: %r not successful", request_data['PathOrToken'])
return httputils.bad_request("Invalid share type")
logger.warning(api_info + ": %r not successful", request_data['PathOrToken'])
return httputils.bad_request("Internal Error")
# action: delete
elif action == "delete":
@@ -964,7 +962,7 @@ class BaseSharing:
logger.debug("TRACE/" + api_info + ": start")
if ShareType not in ["token", "map"]:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
logger.warning(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if PathOrToken is None:
@@ -983,7 +981,7 @@ class BaseSharing:
PathOrToken=PathOrToken) # verification above that it is not None
else:
# only owner is permitted to delete a share
logger.warning("Delete sharing of %r not permitted for user %r", PathOrToken, user)
logger.warning(api_info + ": %r not permitted for user %r", PathOrToken, user)
return httputils.NOT_ALLOWED
# result handling
@@ -995,11 +993,8 @@ class BaseSharing:
answer['Status'] = "success"
pass
else:
if ShareType == "token":
logger.info("Delete sharing-by-token: %r of user %r not successful", request_data['PathOrToken'], request_data['User'])
elif ShareType == "map":
logger.info("Delete sharing-by-map: %r of user %r not successful", request_data['PathOrToken'], request_data['User'])
return httputils.bad_request("Invalid share type")
logger.warning(api_info + ": %r by user %r not successful", request_data['PathOrToken'], request_data['User'])
return httputils.bad_request("Internal Error")
# action: info
elif action == "info":
@@ -1018,7 +1013,7 @@ class BaseSharing:
logger.debug("TRACE/sharing/API/POST/" + action)
if ShareType not in ["token", "map"]:
logger.error(api_info + ": unsupported for ShareType=%r", ShareType)
logger.warning(api_info + ": unsupported for ShareType=%r", ShareType)
return httputils.bad_request("Invalid share type")
if PathOrToken is None:
@@ -1071,7 +1066,7 @@ class BaseSharing:
else:
# neither owner nor user matches
logger.warning("Toggle sharing of %r not permitted for user %r", PathOrToken, user)
logger.warning(api_info + ": %r by user %r not permitted", PathOrToken, user)
return httputils.NOT_ALLOWED
if result:
@@ -1083,21 +1078,21 @@ class BaseSharing:
answer['Status'] = "success"
pass
else:
logger.error("Toggle sharing: %r of user %s not successful", request_data['PathOrToken'], user)
logger.warning(api_info + ": %r by user %s not successful", request_data['PathOrToken'], user)
return httputils.bad_request("Internal Error")
else:
# default
logger.error(api_info + ": unsupported action=%r", action)
logger.warning(api_info + ": unsupported action=%r", action)
return httputils.bad_request("Invalid action")
# output handler
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/sharing/API/POST output format: %r", output_format)
logger.debug("TRACE/sharing/API/POST answer: %r", answer)
if output_format == "csv" or output_format == "text":
if output_format == "csv" or output_format == "plain":
answer_array = []
if output_format == "text":
if output_format == "plain":
for key in answer:
if key != 'Content':
if API_TYPES_V1[key] is bool or API_TYPES_V1[key] is int:
@@ -1109,7 +1104,7 @@ class BaseSharing:
writer = DictWriter(csv, fieldnames=DB_FIELDS_V1, delimiter=';')
if output_format == "csv":
writer.writeheader()
elif output_format == "text":
elif output_format == "plain":
writer.writeheader()
for entry in answer['Content']:
# TODO: Argument 1 to "writerow" of "DictWriter" has incompatible type "str"; expected "Mapping[str, Any]" [arg-type]

View File

@@ -191,6 +191,7 @@ class TestSharingApiSanity(BaseTest):
"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)):