sharing: extend DB with Conversion and Actions(reserved)
This commit is contained in:
@@ -31,7 +31,7 @@ from radicale.log import logger
|
||||
def propose_filename(collection: storage.BaseCollection, share: Union[dict, None] = None) -> str:
|
||||
"""Propose a filename for a collection."""
|
||||
share_bday_automap = False
|
||||
if share and share['ShareType'] == "bday":
|
||||
if share and share['Conversion'] == "bday":
|
||||
share_bday_automap = True
|
||||
if collection.tag == "VADDRESSBOOK" and not share_bday_automap:
|
||||
fallback_title = "Address book"
|
||||
@@ -112,7 +112,7 @@ class ApplicationPartGet(ApplicationBase):
|
||||
if not item.tag:
|
||||
return (httputils.NOT_ALLOWED if limited_access else
|
||||
httputils.DIRECTORY_LISTING)
|
||||
if share and share['ShareType'] == "bday":
|
||||
if share and share['Conversion'] == "bday":
|
||||
content_type = xmlutils.MIMETYPES["VCALENDAR"]
|
||||
else:
|
||||
content_type = xmlutils.MIMETYPES[item.tag]
|
||||
@@ -130,7 +130,7 @@ class ApplicationPartGet(ApplicationBase):
|
||||
"ETag": item.etag}
|
||||
if content_disposition:
|
||||
headers["Content-Disposition"] = content_disposition
|
||||
if isinstance(item, storage.BaseCollection) and self._sharing._enabled and share and share['ShareType'] == "bday":
|
||||
if isinstance(item, storage.BaseCollection) and share and share['Conversion'] == "bday":
|
||||
# convert VCF to ICS
|
||||
answer = item.serialize(vcf_to_ics=True)
|
||||
else:
|
||||
|
||||
@@ -124,7 +124,7 @@ def xml_propfind_response(
|
||||
break
|
||||
|
||||
share_bday_automap = False
|
||||
if share and share['ShareType'] == "bday":
|
||||
if share and share['Conversion'] == "bday":
|
||||
share_bday_automap = True
|
||||
|
||||
if share:
|
||||
@@ -546,7 +546,7 @@ class ApplicationPartPropfind(ApplicationBase):
|
||||
items_iter = itertools.chain([item], items_iter)
|
||||
for item, permission in list(self._collect_allowed_items(items_iter, user)):
|
||||
if self._sharing._enabled and share:
|
||||
if share['ShareType'] == "bday" and not isinstance(item, storage.BaseCollection):
|
||||
if share['Conversion'] == "bday" and not isinstance(item, storage.BaseCollection):
|
||||
if not item.convert_vcf_to_ics():
|
||||
continue
|
||||
allowed_items.append((item, permission, share['ShareType']))
|
||||
|
||||
@@ -34,7 +34,7 @@ from radicale.log import logger
|
||||
|
||||
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: Sequence[str] = ('ShareType', 'PathOrToken', 'PathMapped', 'Owner', 'User', 'Permissions', 'EnabledByOwner', 'EnabledByUser', 'HiddenByOwner', 'HiddenByUser', 'TimestampCreated', 'TimestampUpdated', 'Properties', 'Conversion', 'Actions')
|
||||
# ShareType: <token|map>
|
||||
# PathOrToken: <path|token> [PrimaryKey]
|
||||
# PathMapped: <path>
|
||||
@@ -48,6 +48,10 @@ DB_FIELDS_V1: Sequence[str] = ('ShareType', 'PathOrToken', 'PathMapped', 'Owner'
|
||||
# TimestampCreated: <unixtime> (when created)
|
||||
# TimestampUpdated: <unixtime> (last update)
|
||||
# Properties: Overlay of collection properties in JSON
|
||||
# Conversion: None|bday
|
||||
# bday: check VCARD(vcf) for BDAY and convert to reoccuring VEVENT(ics)
|
||||
# Actions: Actions structure in JSON
|
||||
# (future reserved for e.g. "filter", "filter_pre", "filter_post" or anything else, implemented on request)
|
||||
|
||||
DB_TYPES_V1: dict[str, type] = {
|
||||
"ShareType": str,
|
||||
@@ -62,7 +66,9 @@ DB_TYPES_V1: dict[str, type] = {
|
||||
"HiddenByUser": bool,
|
||||
"TimestampCreated": int,
|
||||
"TimestampUpdated": int,
|
||||
"Properties": dict
|
||||
"Properties": dict,
|
||||
"Conversion": str,
|
||||
"Actions": dict,
|
||||
}
|
||||
|
||||
DB_FIELDS_V1_USER_PERMITTED: Sequence[str] = ('EnabledByUser', 'HiddenByUser', 'Properties')
|
||||
@@ -106,7 +112,11 @@ API_TYPES_V1: dict[str, type] = {
|
||||
"Permissions": str,
|
||||
"Enabled": bool,
|
||||
"Hidden": bool,
|
||||
"Properties": dict}
|
||||
"Properties": dict,
|
||||
"Conversion": str,
|
||||
"Actions": dict,
|
||||
}
|
||||
|
||||
|
||||
TOKEN_PATTERN_V1: str = "v1/[a-zA-Z0-9_\\-]{44}"
|
||||
|
||||
@@ -116,6 +126,8 @@ USER_PATTERN: str = "([a-zA-Z0-9@]+)" # TODO: extend or find better source
|
||||
|
||||
OVERLAY_PROPERTIES_WHITELIST: Sequence[str] = ("C:calendar-description", "ICAL:calendar-color", "CR:addressbook-description", "INF:addressbook-color", "D:displayname")
|
||||
|
||||
CONVERSIONS_WHITELIST: Sequence[str] = ("bday")
|
||||
|
||||
|
||||
def load(configuration: "config.Configuration") -> "BaseSharing":
|
||||
"""Load the sharing database module chosen in configuration."""
|
||||
@@ -216,9 +228,11 @@ 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,
|
||||
Conversion: Union[str, None] = None,
|
||||
) -> list[dict]:
|
||||
""" retrieve sharing """
|
||||
return []
|
||||
|
||||
@@ -238,7 +252,10 @@ class BaseSharing:
|
||||
EnabledByOwner: bool = False, EnabledByUser: bool = False,
|
||||
HiddenByOwner: bool = True, HiddenByUser: bool = True,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: Union[dict, None] = None,
|
||||
) -> dict:
|
||||
""" create sharing """
|
||||
return {"status": "not-implemented"}
|
||||
|
||||
@@ -254,7 +271,10 @@ class BaseSharing:
|
||||
HiddenByOwner: Union[bool, None] = None,
|
||||
HiddenByUser: Union[bool, None] = None,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: Union[dict, None] = None,
|
||||
) -> dict:
|
||||
""" update sharing """
|
||||
return {"status": "not-implemented"}
|
||||
|
||||
@@ -446,7 +466,7 @@ class BaseSharing:
|
||||
ShareType="token",
|
||||
PathOrToken=match[1])
|
||||
if result is not None:
|
||||
logger.info("Sharing/%s: resolved %r->%r, user ->%r, permissions %r", "token", path, result['PathMapped'], result['Owner'], result['Permissions'])
|
||||
logger.info("Sharing/%s: resolved %r->%r, User=%r, Permissions=%r Conversion=%r", "token", path, result['PathMapped'], result['Owner'], result['Permissions'], result['Conversion'])
|
||||
return result
|
||||
else:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
@@ -487,7 +507,7 @@ class BaseSharing:
|
||||
logger.debug("TRACE/sharing/map: not found")
|
||||
return None
|
||||
|
||||
logger.info("Sharing/%s: resolved path %r->%r, user %r->%r, permissions %r", "map", path, result['PathMapped'], user, result['Owner'], result['Permissions'])
|
||||
logger.info("Sharing/%s: resolved path %r->%r, user %r->%r, Permissions=%r Conversion=%r", "map", path, result['PathMapped'], user, result['Owner'], result['Permissions'], result['Conversion'])
|
||||
return result
|
||||
else:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
@@ -524,7 +544,9 @@ class BaseSharing:
|
||||
logger.debug("TRACE/sharing/bday: not found")
|
||||
return None
|
||||
|
||||
logger.info("Sharing/%s: resolved path %r->%r, user %r->%r, permissions %r", "bday", path, result['PathMapped'], user, result['Owner'], result['Permissions'])
|
||||
if not result['Conversion']:
|
||||
result['Conversion'] = "bday"
|
||||
logger.info("Sharing/%s: resolved path %r->%r, user %r->%r, Permissions=%r Conversion=%r", "bday", path, result['PathMapped'], user, result['Owner'], result['Permissions'], result['Conversion'])
|
||||
return result
|
||||
else:
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
@@ -738,12 +760,14 @@ class BaseSharing:
|
||||
|
||||
# parameters default
|
||||
PathOrToken: Union[str, None] = None
|
||||
PathMapped: Union[str, None] = None
|
||||
User: Union[str, None] = None
|
||||
PathMapped: Union[str, None] = None
|
||||
User: Union[str, None] = None
|
||||
Permissions: Union[str, None] = None # no permissions by default
|
||||
Enabled: Union[bool, None] = None
|
||||
Hidden: Union[bool, None] = None
|
||||
Properties: Union[dict, None] = None
|
||||
Enabled: Union[bool, None] = None
|
||||
Hidden: Union[bool, None] = None
|
||||
Properties: Union[dict, None] = None
|
||||
Conversion: Union[str, None] = None
|
||||
Actions: Union[dict, None] = None # reserved so far
|
||||
|
||||
# parameters sanity check
|
||||
for key in request_data:
|
||||
@@ -805,6 +829,16 @@ class BaseSharing:
|
||||
return httputils.bad_request("Property not supported to overlay: %r" % entry)
|
||||
Properties = request_data['Properties']
|
||||
|
||||
if 'Conversion' in request_data:
|
||||
# verify against whitelist
|
||||
for entry in request_data['Conversion']:
|
||||
if entry not in CONVERSIONS_WHITELIST:
|
||||
return httputils.bad_request("Conversion not supported: %r" % entry)
|
||||
Conversion = request_data['Conversion']
|
||||
|
||||
if 'Actions' in request_data:
|
||||
return httputils.bad_request("Actions currently not supported (reserved for future needs)")
|
||||
|
||||
if 'Enabled' in request_data:
|
||||
Enabled = request_data['Enabled']
|
||||
else:
|
||||
@@ -946,7 +980,10 @@ class BaseSharing:
|
||||
HiddenByOwner=Hidden,
|
||||
HiddenByUser=HiddenByUser,
|
||||
Timestamp=Timestamp,
|
||||
Properties=Properties)
|
||||
Properties=Properties,
|
||||
Conversion=Conversion,
|
||||
Actions=Actions,
|
||||
)
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("TRACE/" + api_info + ": result=%r", result)
|
||||
@@ -992,6 +1029,7 @@ class BaseSharing:
|
||||
return httputils.NOT_ALLOWED
|
||||
|
||||
elif ShareType == "bday":
|
||||
Conversion = "bday"
|
||||
if self.permit_create_bday is False:
|
||||
if "b" not in access.permissions:
|
||||
logger.warning(api_info + ": access to PathMapped=%r not allowed for owner %r (permit=False but explicit grant misses 'b')", PathMapped, user)
|
||||
@@ -1030,7 +1068,10 @@ class BaseSharing:
|
||||
HiddenByOwner=Hidden,
|
||||
HiddenByUser=HiddenByUser,
|
||||
Timestamp=Timestamp,
|
||||
Properties=Properties)
|
||||
Properties=Properties,
|
||||
Conversion=Conversion,
|
||||
Actions=Actions,
|
||||
)
|
||||
|
||||
else:
|
||||
logger.warning(api_info + ": unsupported for ShareType=%r", ShareType)
|
||||
|
||||
@@ -131,8 +131,14 @@ class Sharing(sharing.BaseSharing):
|
||||
Permissions = row['Permissions']
|
||||
Hidden: bool = (row['HiddenByOwner'] or row['HiddenByUser'])
|
||||
Properties: Union[dict, None] = None
|
||||
Conversion: Union[str, None] = None
|
||||
Actions: Union[dict, None] = None
|
||||
if 'Properties' in row:
|
||||
Properties = row['Properties']
|
||||
if 'Conversion' in row:
|
||||
Conversion = row['Conversion']
|
||||
if 'Actions' in row:
|
||||
Actions = row['Actions']
|
||||
return {
|
||||
"mapped": True,
|
||||
"ShareType": ShareType,
|
||||
@@ -142,7 +148,10 @@ class Sharing(sharing.BaseSharing):
|
||||
"User": UserShare,
|
||||
"Hidden": Hidden,
|
||||
"Permissions": Permissions,
|
||||
"Properties": Properties}
|
||||
"Properties": Properties,
|
||||
"Conversion": Conversion,
|
||||
"Actions": Actions,
|
||||
}
|
||||
return None
|
||||
|
||||
def database_list_sharing(self,
|
||||
@@ -154,7 +163,9 @@ class Sharing(sharing.BaseSharing):
|
||||
EnabledByOwner: Union[bool, None] = None,
|
||||
EnabledByUser: Union[bool, None] = None,
|
||||
HiddenByOwner: Union[bool, None] = None,
|
||||
HiddenByUser: Union[bool, None] = None) -> list[dict]:
|
||||
HiddenByUser: Union[bool, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
) -> list[dict]:
|
||||
""" retrieve sharing """
|
||||
row: dict
|
||||
index = 0
|
||||
@@ -212,7 +223,10 @@ class Sharing(sharing.BaseSharing):
|
||||
EnabledByOwner: bool = False, EnabledByUser: bool = False,
|
||||
HiddenByOwner: bool = True, HiddenByUser: bool = True,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: Union[dict, None] = None,
|
||||
) -> dict:
|
||||
""" create sharing """
|
||||
row: dict
|
||||
|
||||
@@ -267,7 +281,10 @@ class Sharing(sharing.BaseSharing):
|
||||
"HiddenByUser": HiddenByUser,
|
||||
"TimestampCreated": Timestamp,
|
||||
"TimestampUpdated": Timestamp,
|
||||
"Properties": Properties}
|
||||
"Properties": Properties,
|
||||
"Conversion": Conversion,
|
||||
"Actions": Actions,
|
||||
}
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("TRACE/sharing/*/create: add row: %r", row)
|
||||
@@ -293,7 +310,10 @@ class Sharing(sharing.BaseSharing):
|
||||
HiddenByOwner: Union[bool, None] = None,
|
||||
HiddenByUser: Union[bool, None] = None,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: 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 EnabledByOwner=%s EnabledByUser=%s HiddenByOwner=%s HiddenByUser=%s", ShareType, PathOrToken, OwnerOrUser, PathMapped, Properties, EnabledByOwner, EnabledByUser, HiddenByOwner, HiddenByUser)
|
||||
@@ -336,6 +356,10 @@ class Sharing(sharing.BaseSharing):
|
||||
self._sharing_cache[index]["HiddenByUser"] = HiddenByUser
|
||||
if Properties is not None:
|
||||
self._sharing_cache[index]["Properties"] = Properties
|
||||
if Conversion is not None:
|
||||
self._sharing_cache[index]["Conversion"] = Conversion
|
||||
if Actions is not None:
|
||||
self._sharing_cache[index]["Actions"] = Actions
|
||||
# update timestamp
|
||||
self._sharing_cache[index]["TimestampUpdated"] = Timestamp
|
||||
|
||||
|
||||
@@ -124,8 +124,14 @@ class Sharing(sharing.BaseSharing):
|
||||
Permissions = row['Permissions']
|
||||
Hidden: bool = (row['HiddenByOwner'] or row['HiddenByUser'])
|
||||
Properties: Union[dict, None] = None
|
||||
Conversion: Union[str, None] = None
|
||||
Actions: Union[dict, None] = None
|
||||
if 'Properties' in row:
|
||||
Properties = row['Properties']
|
||||
if 'Conversion' in row:
|
||||
Conversion = row['Conversion']
|
||||
if 'Actions' in row:
|
||||
Actions = row['Actions']
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("TRACE/sharing: map %r to %r (Owner=%r User=%r Permissions=%r Hidden=%s Properties=%r)", PathOrToken, PathMapped, Owner, UserShare, Permissions, Hidden, Properties)
|
||||
return {
|
||||
@@ -137,7 +143,10 @@ class Sharing(sharing.BaseSharing):
|
||||
"User": UserShare,
|
||||
"Hidden": Hidden,
|
||||
"Permissions": Permissions,
|
||||
"Properties": Properties}
|
||||
"Properties": Properties,
|
||||
"Conversion": Conversion,
|
||||
"Actions": Actions,
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
@@ -150,7 +159,9 @@ class Sharing(sharing.BaseSharing):
|
||||
EnabledByOwner: Union[bool, None] = None,
|
||||
EnabledByUser: Union[bool, None] = None,
|
||||
HiddenByOwner: Union[bool, None] = None,
|
||||
HiddenByUser: Union[bool, None] = None) -> list[dict]:
|
||||
HiddenByUser: Union[bool, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
) -> list[dict]:
|
||||
""" retrieve sharing """
|
||||
result = []
|
||||
|
||||
@@ -221,7 +232,10 @@ class Sharing(sharing.BaseSharing):
|
||||
EnabledByOwner: bool = False, EnabledByUser: bool = False,
|
||||
HiddenByOwner: bool = True, HiddenByUser: bool = True,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: Union[dict, None] = None,
|
||||
) -> dict:
|
||||
""" create sharing """
|
||||
row: dict
|
||||
|
||||
@@ -245,7 +259,10 @@ class Sharing(sharing.BaseSharing):
|
||||
"HiddenByUser": HiddenByUser,
|
||||
"TimestampCreated": Timestamp,
|
||||
"TimestampUpdated": Timestamp,
|
||||
"Properties": Properties}
|
||||
"Properties": Properties,
|
||||
"Conversion": Conversion,
|
||||
"Actions": Actions,
|
||||
}
|
||||
|
||||
version = DB_VERSION
|
||||
|
||||
@@ -275,7 +292,10 @@ class Sharing(sharing.BaseSharing):
|
||||
HiddenByOwner: Union[bool, None] = None,
|
||||
HiddenByUser: Union[bool, None] = None,
|
||||
Timestamp: int = 0,
|
||||
Properties: Union[dict, None] = None) -> dict:
|
||||
Properties: Union[dict, None] = None,
|
||||
Conversion: Union[str, None] = None,
|
||||
Actions: Union[dict, None] = None,
|
||||
) -> dict:
|
||||
""" update sharing """
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("TRACE/sharing/%s/update: PathOrToken=%r OwnerOrUser=%r User=%r Properties=%r", ShareType, PathOrToken, OwnerOrUser, User, Properties)
|
||||
@@ -316,6 +336,10 @@ class Sharing(sharing.BaseSharing):
|
||||
row["HiddenByUser"] = HiddenByUser
|
||||
if Properties is not None:
|
||||
row["Properties"] = Properties
|
||||
if Conversion is not None:
|
||||
row["Conversion"] = Conversion
|
||||
if Actions is not None:
|
||||
row["Actions"] = Actions
|
||||
# update timestamp
|
||||
row["TimestampUpdated"] = Timestamp
|
||||
|
||||
|
||||
Reference in New Issue
Block a user