Static collection sharing without permissions filter using soft-links (Unix-only) is supported since storage type multifilesystem was implemented, see (Wiki: Sharing Collections)[https://github.com/Kozea/Radicale/wiki/Sharing-Collections]
With 3.7.0 major extension was implemented using internal mapping configuration stored in a database and a management API.
Implemenation of sharing collections is done in case entry exists in sharing database by replacing provided data on request and adjust if required data in responses.
Permissions are filtered by provided Permissions.
- Actions
- map
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Activate
permissions_filterbyPermissions
- Actions
- map
- back-map response
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Activate
permissions_filterbyPermissions
- Actions
- map
- back-map response
- overwrite
Propertiesif provided
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Overlay
Propertiesif provided- Activate
permissions_filterbyPermissions
- Actions
- extend list
- Lookup for active shares for
userin sharing database - Extend list if conditions are met
permissions_filterbyPermissions
- Actions
- map
- adjust properties of a collection
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Activate
permissions_filterbyPermissions- Depending on
permissions_filter, global options andPermissions- adjust properties of collection
- adjust whitelisted properties in
Propertiesfor overlay (see OVERLAY_PROPERTIES_WHITELIST)
- Action
- check for conflicts
- Lookup by
user(authenticated)
- Verify for non-existence as
PathOrTokenin sharing databasepath(provided in request)
- Action
- map source
- map destination
- Lookup by
path(provided in request)user(authenticated)to_path(provided in request)to_user(same asuser)
- Replace
userbyOwner(ofpath)pathbyPathMapped(of path)to_userbyOwner(ofto_path)to_pathbyPathMapped(ofto_path)- Activate
permissions_filterbyPermissions(ofto_path)to_permissions_filterbyPermissions(ofto_path)
Types of supported sharing configuration:
- csv (>= 3.7.0)
- files (>= 3.7.0)
ShareType: type of sharetoken: token-based share (do not require user authentication)map: map-based share (requires user authentication)
PathOrToken: token or "virtual" collection, has to be unique (PRIMARY KEY)PathMapped: target collectionOwner: owner of the shareUser: user of the sharePermissions: effective permission of the shareEnabledByOwner: control by ownerEnabledByUser: control by userHiddenByOwner: control by ownerHiddenByUser: control by userTimestampCreated: unixtime of creationTimestampUpdated: unixtime of last updateProperties: overlay properties (limited set whitelisted)
Enabled*: owner AND user have to enable a share to become usable
Hidden*: owner AND user have to disable a share to become visible in PROPFIND
One CSV file containing one row per sharing config, separated by ; and containing header with columns from above.
If given, properties are stored in JSON format in CSV.
File-based configuration store is using encoded PathOrToken as filename for each config. File contains the data stored as "dict" in binary Python "pickle" format (same is also used for item cache files).
Map-based sharing can be accessed as usual after authentication and authorization.
permit_create_map- supported rights permissions:
Mm
- supported rights permissions:
- create map as owner
- enable map as owner (can be combined with "create")
- enable map as user (explicit required to avoid sudden available share)
In case share should be visible using PROPFIND
- unhide map as owner (can be combined with "create")
- unhide map as user (explicit required to avoid sudden visible share)
Token-based sharing can be accessed after retrieving the token via
Token-URI: /.token/<Token>
Note: requests to not enabled or not even defined tokens will resul tin 401 Not Authorized
permit_create_token- supported rights permissions:
Tt
- supported rights permissions:
- create token as owner
- enable token as owner (can be combined with "create")
- handover URI with token to client
Type: POST API
Base-URI: /.sharing/v1/<ShareType>/<Hook>
See also test cases in radicale/tests/test_sharing.py
Parsing be controlled by CONTENT_TYPE
- application/x-www-form-urlencoded
- application/json
Can be selected by HTTP_ACCEPT - default is equal to provided CONTENT_TYPE
- text/plain
- text/csv (only for "list")
- application/json
PathOrToken: token or "virtual" collectionPathMapped: target collectionOwner: owner of the shareUser: user of the sharePermissions: effective permission of the shareEnabled: owner/user selected by authenticationHidden: owner/user selected by authenticationProperties: properties to overlay
Shows what kind of ShareTypes are supported
-
Output: text/plain|application/json
-
Examples
-
form->text
curl -u user:pass -H "accept: text/plain" -d "" http://localhost:5232/.sharing/v1/all/info
ApiVersion=1
Status='success'
FeatureEnabledCollectionByMap=True
PermittedCreateCollectionByMap=True
FeatureEnabledCollectionByToken=True
PermittedCreateCollectionByToken=True - json->json, parsed with
jq
bash
curl -u user:pass --silent -H "accept: application/json" -d "" http://localhost:5232/.sharing/v1/all/info | jq
{
"ApiVersion": 1,
"Status": "success",
"FeatureEnabledCollectionByMap": true,
"PermittedCreateCollectionByMap": true,
"FeatureEnabledCollectionByToken": true,
"PermittedCreateCollectionByToken": true
}
- Authorization
- Authenticated user is
Owner
- Authenticated user is
Create a share by mapping a collection of an Owner to a token.
-
Authorization
-
PathMappedis existing and a collection -
Authenticated user as
Ownerhas at least read access toPathMapped -
Global permitted by
permit_create_token = Trueorrightspermissiont -
Global denied by
permit_create_token = FalseorrightspermissionT -
Input
| Parameter | Type | Requirement |
|---|---|---|
| PathMapped | str | mandatory |
| User | str | optional(default:owner) |
| Permissions | str | optional(default:r) |
| Enabled | bool | optional(owner/default:False) |
| Hidden | bool | optional(owner/default:True) |
| Properties | str | optional |
- Output: text/plain|application/json
| Parameter | Type | Value | | - | - | | PathOrToken | str | (autogenerated token) |
- Examples:
- form->text
curl -u user:pass -d "PathMapped=/user/testcalendar1/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/token/create
ApiVersion=1
Status='success'
PathOrToken='v1/VQR7AmsVRi2ZlFj_JwGpFx-ES5Goyku-gP_YkLh1zUw='- json->json
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathMapped": "/user/testcalendar1/", "Enabled": true, "Hidden": false}' http://localhost:5232/.sharing/v1/token/create
{"ApiVersion": 1, "Status": "success", "PathOrToken": "v1/aMsmGqOsRwSH-2-6tEa8EMr4RMYzMU7WvPmjnp5qDnw="}Create a share by mapping a collection of an Owner to an User.
-
Authorization
-
PathMappedis existing and a collection -
Authenticated user as
Ownerhas at least read access toPathMapped -
Provided
Userhas at least read access toPathOrToken -
Global permitted by
permit_create_map = Trueorrightspermissionm -
Global denied by
permit_create_map = FalseorrightspermissionM -
Input
| Parameter | Type | Requirement | | - | - | | PathOrToken | str | mandatory | | PathMapped | str | mandatory | | User | str | mandatory | | Permissions | str | optional(default:r) | | Enabled | bool | optional(owner/default:False) | | Hidden | bool | optional(owner/default:True) | | Properties | optional |
-
Output: text/plain|application/json
-
Examples:
-
form->text
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" -d "PathMapped=/owner/testcalendar1/" -d "User=user" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/create
ApiVersion=1
Status='success'- json->json
curl -u owner:pass -H "Content-Type: application/json" -d '{ "PathOrToken": "/user/cal1-from-owner/", "PathMapped": "/owner/testcalendar1/", "User" : "user", "Enabled": true, "Hidden": false}' http://localhost:5232/.sharing/v1/map/create
{"ApiVersion": 1, "Status": "success"}List shares (optional with filter) either owned or assigned as user.
-
Authorization
-
Authenticated user as
OwnerorUser -
Input
| Parameter | Type | Used for |
|---|---|---|
| PathOrToken | str | optional |
| PathMapped | str | optional |
-
Output: text/plain|text/csv|application/json
-
Examples
-
form->text ("all")
curl -u user:pass -d "" http://localhost:5232/.sharing/v1/map/list://localhost:5232/.sharing/v1/map/list
ApiVersion=1
Lines=1
Status='success'
Fields="ShareType;PathOrToken;PathMapped;Owner;User;Permissions;EnabledByOwner;EnabledByUser;HiddenByOwner;HiddenByUser;TimestampCreated;TimestampUpdated;Properties"
Content[0]="map;/user/cal1-from-owner/;/owner/testcalendar1/;owner;user;r;True;True;False;False;1772748001;1772748163;- form->csv ("map" only)
curl -H "accept: text/csv" -u user:pass -d "" http://localhost:5232/.sharing/v1/map/list://localhost:5232/.sharing/v1/map/list
ShareType;PathOrToken;PathMapped;Owner;User;Permissions;EnabledByOwner;EnabledByUser;HiddenByOwner;HiddenByUser;TimestampCreated;TimestampUpdated;Properties
map;/user/cal1-from-owner/;/owner/testcalendar1/;owner;user;r;True;False;False;True;1772747277;1772747277;- json->json ("all"), parsed with
jq
curl -s -H "Content-Type: application/json" -u user:pass -d "{}" http://localhost:5232/.sharing/v1/all/list | jq
{
"ApiVersion": 1,
"Lines": 2,
"Status": "success",
"Content": [
{
"ShareType": "map",
"PathOrToken": "/user/cal1-from-owner/",
"PathMapped": "/owner/testcalendar1/",
"Owner": "owner",
"User": "user",
"Permissions": "r",
"EnabledByOwner": true,
"EnabledByUser": false,
"HiddenByOwner": false,
"HiddenByUser": true,
"TimestampCreated": 1772747277,
"TimestampUpdated": 1772747277,
"Properties": ""
},
{
"ShareType": "token",
"PathOrToken": "v1/DUSl_J5rRlWx3fy8YRXpH22FFllplkOTpcSwfGtpvkc=",
"PathMapped": "/user/testcalendar1/",
"Owner": "user",
"User": "user",
"Permissions": "r",
"EnabledByOwner": true,
"EnabledByUser": false,
"HiddenByOwner": false,
"HiddenByUser": true,
"TimestampCreated": 1772747371,
"TimestampUpdated": 1772747371,
"Properties": ""
}
7]
}Delete a share selected by PathOrToken.
-
Authorization
-
Authenticated user is
Owner -
Share is existing and owned
-
Input
| Parameter | Type | Used for | as Owner | as User |
|---|---|---|---|---|
| PathOrToken | str | selection | mandatory | not-permitted |
-
Output: text/plain|application/json
-
Examples:
-
form->text
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/delete
ApiVersion=1
Status='success'- json->json
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathOrToken": "v1/DUSl_J5rRlWx3fy8YRXpH22FFllplkOTpcSwfGtpvkc="}' http://localhost:5232/.sharing/v1/token/delete
{"ApiVersion": 1, "Status": "success"}Update a share selected by PathOrToken.
Execute delete+create in case PathOrToken needs to be changed.
-
Authorization
- Authenticated user is
OwnerorUser
- Authenticated user is
-
Input
| Parameter | Type | Used for | Owner | User |
|---|---|---|---|---|
| PathOrToken | str | selection | mandatory | mandatory |
| PathMapped | str | adjust | optional | not-permitted |
| User | str | adjust | optional | not-permitted |
| Permissions | str | adjust | optional | not-permitted |
| Enabled | bool | adjust | optional(owner) | optional(user) |
| Hidden | bool | adjust | optional(owner) | optional(user) |
| Properties | str | adjust | optional | optional |
-
Output: text/plain|application/json
-
Examples:
- form->text
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/update
ApiVersion=1
Status='success'- json->json
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathOrToken": "/user/cal1-from-owner/", "Enabled": true, "Hidden": false}' http://localhost:5232/.sharing/v1/map/update
{"ApiVersion": 1, "Status": "success"}Toggle enable|disable|hide|unhide of Owner or User of a share selected by PathOrToken
-
Authorization
- Authenticated user is
OwnerorUser PathOrTokenis existing and either owned or assigned to user
- Authenticated user is
-
Input
| Parameter | Type | Used for | Owner | User |
|---|---|---|---|---|
| PathOrToken | selection | mandatory | mandatory |
-
Output: text/plain|application/json
-
form->text
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/enable
ApiVersion=1
Status='success'
```bash
* json->json
```bash
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathOrToken": "/user/cal1-from-owner/"}' http://localhost:5232/.sharing/v1/map/unhide
{"ApiVersion": 1, "Status": "success"}Owner or user can define per share a set of properties to overlay on PROPFIND response during create or update via API.
Whitelisted ones are defined in OVERLAY_PROPERTIES_WHITELIST in radicale/sharing/__init__.py:
C:calendar-descriptionICAL:calendar-colorCR:addressbook-descriptionINF:addressbook-color
permit_properties_overlay- supported share permissions:
Pp
- supported share permissions:
enforce_properties_overlay- supported share permissions:
Ee
- supported share permissions:
- sharing / permit_properties_overlay = True
- Prepare XML statements
## PROPFIND color
xml_pfc='<?xml version="1.0"?>
<propfind xmlns="DAV:" xmlns:ICAL="http://apple.com/ns/ical/">
<prop>
<ICAL:calendar-color />
</prop>
</propfind>'
## PROPPATCH color
xml_ppc='<?xml version="1.0"?>
<D:propertyupdate xmlns:D="DAV:">
<D:set>
<D:prop>
<I:calendar-color xmlns:I="http://apple.com/ns/ical/">#DDDDDD</I:calendar-color>
</D:prop>
</D:set>
</D:propertyupdate>'- Tests
## Retrieve collection color of owner (no color set)
curl -u owner:pass -d "$xml_pfc" -X PROPFIND http://localhost:5232/owner/testcalendar1/
## Create read-only share for user
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" -d "PathMapped=/owner/testcalendar1/" -d "User=user" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/create
## Accept (enable+unhide) share by user
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/update
## Retrieve collection color of share by user (no color set)
curl -u user:pass -d "$xml_pfc" -X PROPFIND http://localhost:5232/user/cal1-from-owner/
## Set property overlay by user
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d 'Properties="ICAL:calendar-color"="#CCCCCC"' http://localhost:5232/.sharing/v1/map/update
## Retrieve collection color of share by user (color set)
curl -u user:pass -d "$xml_pfc" -X PROPFIND http://localhost:5232/user/cal1-from-owner/
## Delete property overlay by user
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d 'Properties=' http://localhost:5232/.sharing/v1/map/update
## Retrieve collection color of share by user (no color set)
curl -u user:pass -d "$xml_pfc" -X PROPFIND http://localhost:5232/user/cal1-from-owner/
## Add property overlay by user using PROPPATCH
curl -u user:pass -d "$xml_ppc" -X PROPPATCH http://localhost:5232/user/cal1-from-owner/
## Retrieve collection color of share by user (color set)
curl -u user:pass -d "$xml_pfc" -X PROPFIND http://localhost:5232/user/cal1-from-owner/