15 KiB
Collection Sharing
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.
Sharing Implementation
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.
CxDAV requests
CxDav request "(DELETE|GET|HEAD|PUT)"
- Actions
- map
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Activate
permissions_filterbyPermissions
CxDav request "REPORT"
- Actions
- map
- back-map response
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Activate
permissions_filterbyPermissions
CxDav request "PROPFIND" without HTTP_DEPTH=1
- Actions
- map
- back-map response
- overwrite
Propertiesif provided
- Lookup by
path(provided in request)user(authenticated)
- Replace
userbyOwnerpathbyPathMapped- Overlay
Propertiesif provided- Activate
permissions_filterbyPermissions
CxDav request "PROPFIND" with HTTP_DEPTH=1
- Actions
- extend list
- Lookup for active shares for
userin sharing database - Extend list if conditions are met
permissions_filterbyPermissions
CxDav request "PROPPATCH"
- 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)
CxDav request "(MKCALENDAR|MKCOL)"
- Action
- check for conflicts
- Lookup by
user(authenticated)
- Verify for non-existence as
PathOrTokenin sharing databasepath(provided in request)
CxDav request "(MOVE)"
- 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)
Sharing Configuration Store
Types of supported sharing configuration:
- csv (>= 3.7.0)
- files (>= 3.7.0)
Sharing Configuration Entry Data
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
Sharing Configuration Entry Storage
CSV
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.
Files
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).
Sharing Access
Sharing Access via Maps
Map-based sharing can be accessed as usual after authentication and authorization.
Permission Control
permit_create_map- supported rights permissions:
Mm
- supported rights permissions:
Workflow
- 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)
Sharing Access via Tokens
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
Permission Control
permit_create_token- supported rights permissions:
Tt
- supported rights permissions:
Workflow
- create token as owner
- enable token as owner (can be combined with "create")
- handover URI with token to client
Sharing Configuration Management API
Sharing Configuration Management API version 1
Type: POST API
Base-URI: /.sharing/v1/<ShareType>/<Hook>
See also test cases in radicale/tests/test_sharing.py
Data Format
Input Data Format
Parsing be controlled by CONTENT_TYPE
- application/x-www-form-urlencoded
- application/json
Output Data Format
Can be selected by HTTP_ACCEPT - default is equal to provided CONTENT_TYPE
- text/plain
- text/csv (only for "list")
- application/json
Accepted Input Data Fields
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
API Hooks
API Hook "info"
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
}
API Hook "(token|map)/create"
- Authorization
- Authenticated user is
Owner
- Authenticated user is
API Hook "token/create"
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="}
API Hook "map/create"
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"}
API Hook "(map|token|all)/list"
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]
}
API Hook "(map|token)/delete"
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"}
API Hook "(token|map)/update"
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"}
API Hooks "(map|token)/(enable|disable|hide|unhide)"
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"}
Properties Overlay
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
Properties Overlay Control Options
permit_properties_overlay- supported share permissions:
Pp
- supported share permissions:
enforce_properties_overlay- supported share permissions:
Ee
- supported share permissions: