sharing: add global and conditional permissions support

This commit is contained in:
Peter Bieringer
2026-02-26 08:14:49 +01:00
parent 3ad1b1a12a
commit 1cb622e903
5 changed files with 111 additions and 5 deletions

View File

@@ -2102,6 +2102,40 @@ Share collection by map
Default: `false`
##### permit_create_token
_(>= 3.7.0)_
Permit create of token-based sharing
Default: `false`
* If `False` it can be explicitly granted by `permissions: t`
* If `True` it can be explicitly forbidden by `permissions: T`
##### permit_create_map
_(>= 3.7.0)_
Permit create of map-based sharing
Default: `false`
* If `False` it can be explicitly granted by `permissions: m`
* If `True` it can be explicitly forbidden by `permissions: M`
##### default_permissions_create_token
Default permissions for create token-based sharing
Default: `r`
##### default_permissions_create_map
Default permissions for map-based sharing
Default: `r`
## Supported Clients
Radicale has been tested with:
@@ -2326,6 +2360,10 @@ The following `permissions` are recognized:
* **d:** deny deleting a collection in case `permit_delete_collection=True` _(>= 3.3.0)_
* **O:** allow overwriting a collection in case `permit_overwrite_collection=False` _(>= 3.3.0)_
* **o:** deny overwriting a collection in case `permit_overwrite_collection=True` _(>= 3.3.0)_
* **T:** permit create of token-based sharing of collection in case `permit_create_token=False` _(>= 3.7.0)_
* **t:** deny create of token-based sharing of collection in case `permit_create_token=True` _(>= 3.7.0)_
* **M:** permit create of map-based sharing of collection in case `permit_create_map= False` _(>= 3.7.0)_
* **m:** deny create of map-based sharing of collection in case `permit_create_map=True` _(>= 3.7.0)_
### Storage

16
config
View File

@@ -317,6 +317,22 @@
# Share collection by map
#collection_by_map = false
# Permit create of token-based sharing
# If False it can be explicitly granted by permissions: t
# If True it can be explicitly forbidden by permissions: T
#permit_create_token = false
# Permit create of map-based sharing
# If False it can be explicitly granted by permissions: m
# If True it can be explicitly forbidden by permissions: M
#permit_create_map = false
# Default permissions for token-based sharing
#default_permissions_create_token = r
# Default permissions for map-based sharing
#default_permissions_create_map = r
[web]

View File

@@ -482,7 +482,23 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
("collection_by_map", {
"value": "false",
"help": "enable sharing of collection by map",
"type": bool})])),
"type": bool}),
("permit_create_token", {
"value": "false",
"help": "permit create of token-based sharing",
"type": bool}),
("permit_create_map", {
"value": "false",
"help": "permit create of map-based sharing",
"type": bool}),
("default_permissions_create_token", {
"value": "r",
"help": "default permissions for token-based sharing",
"type": rights_permission}),
("default_permissions_create_map", {
"value": "r",
"help": "default permissions for map-based sharing",
"type": rights_permission})])),
("hook", OrderedDict([
("type", {
"value": "none",

View File

@@ -31,6 +31,10 @@ Permissions:
- d: deny deleting a collection in case permit_delete_collection=True (>= 3.3.0)
- O: allow overwriting a collection in case permit_overwrite_collection=False (>= 3.3.0)
- o: deny overwriting a collection in case permit_overwrite_collection=True (>= 3.3.0)
- T: permit create of token-based sharing of collection in case permit_create_token=False (>= 3.7.0)
- t: deny create of token-based sharing of collection in case permit_create_token=True (>= 3.7.0)
- M: permit create of map-based sharing of collection in case permit_create_map= False (>= 3.7.0)
- m: deny create of map-based sharing of collection in case permit_create_map=True (>= 3.7.0)
Take a look at the class ``BaseRights`` if you want to implement your own.

View File

@@ -102,8 +102,16 @@ class BaseSharing:
# Sharing
self.sharing_collection_by_map = configuration.get("sharing", "collection_by_map")
self.sharing_collection_by_token = configuration.get("sharing", "collection_by_token")
self.permit_create_token = configuration.get("sharing", "permit_create_token")
self.permit_create_map = configuration.get("sharing", "permit_create_map")
self.default_permissions_create_token = configuration.get("sharing", "default_permissions_create_token")
self.default_permissions_create_map = configuration.get("sharing", "default_permissions_create_map")
logger.info("sharing.collection_by_map : %s", self.sharing_collection_by_map)
logger.info("sharing.collection_by_token: %s", self.sharing_collection_by_token)
logger.info("sharing.permit_create_token: %s", self.permit_create_token)
logger.info("sharing.permit_create_map : %s", self.permit_create_map)
logger.info("sharing.default_permissions_create_token: %r", self.default_permissions_create_token)
logger.info("sharing.default_permissions_create_map : %r", self.default_permissions_create_map)
if ((self.sharing_collection_by_map is False) and (self.sharing_collection_by_token is False)):
logger.info("sharing disabled as no feature is enabled")
@@ -634,7 +642,13 @@ class BaseSharing:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("TRACE/" + api_info + ": start")
if 'Permissions' not in request_data:
Permissions = "r"
if ShareType == "token":
Permissions = self.default_permissions_create_token
elif ShareType == "map":
Permissions = self.default_permissions_create_map
else:
# default
Permissions = "r"
if 'Enabled' in request_data:
EnabledByOwner = config._convert_to_bool(request_data['Enabled'])
@@ -652,10 +666,19 @@ class BaseSharing:
if ShareType == "token":
# check access Permissions
access = Access(self._rights, user, str(PathMapped)) # PathMapped is mandatory
if not access.check("r") and "i" not in access.permissions:
if not access.check("r"):
logger.info("Add sharing-by-token: access to %r not allowed for user %r", PathMapped, user)
return httputils.NOT_ALLOWED
if self.permit_create_token is False:
if "t" not in access.permissions:
logger.info("Add sharing-by-token: access to %r not allowed for user %r (permit=False but explict grant misses 't')", PathMapped, user)
return httputils.NOT_ALLOWED
else:
if "T" in access.permissions:
logger.info("Add sharing-by-token: access to %r not allowed for user %r (permit=True but denied by 'T')", PathMapped, user)
return httputils.NOT_ALLOWED
# v1: create uuid token with 2x 32 bytes = 256 bit
token = "v1/" + str(base64.urlsafe_b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), 'utf-8')
@@ -690,9 +713,18 @@ class BaseSharing:
logger.info("Add sharing-by-map: access to path(mapped) %r not allowed for owner %r", PathMapped, Owner)
return httputils.NOT_ALLOWED
if self.permit_create_map is False:
if "m" not in access.permissions:
logger.info("Add sharing-by-map: access to %r not allowed for user %r (permit=False but explicit grant misses 'm')", PathMapped, user)
return httputils.NOT_ALLOWED
else:
if "M" in access.permissions:
logger.info("Add sharing-by-map: access to %r not allowed for user %r (permit=True but denied by 'M')", PathMapped, user)
return httputils.NOT_ALLOWED
access = Access(self._rights, str(User), PathOrToken)
if not access.check("r") and "i" not in access.permissions:
logger.info("Add sharing-by-map: access to path %r not allowed for user %r", PathOrToken, user)
if not access.check("r"):
logger.info("Add sharing-by-map: access to path %r not allowed for user %r", PathOrToken, User)
return httputils.NOT_ALLOWED
# check whether share is already existing as real collection