Files
Radicale/SHARING.md
2026-03-03 19:47:36 +01:00

322 lines
7.9 KiB
Markdown

# 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 a major extension was implemented using internal mapping configuration stored in a database and a management API.
## Sharing Configuration Store
Types of supported sharing configuration:
* csv (_>= 3.7.0_)
* files (_>= 3.7.0_)
### Sharing Configuration Entry Data
* `ShareType`: type of share
* `token`: 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 collection
* `Owner`: owner of the share
* `User`: user of the share
* `Permissions`: effective permission of the share
* `EnabledByOwner`: control by owner
* `EnabledByUser`: control by user
* `HiddenByOwner`: control by owner
* `HiddenByUser`: control by user
* `TimestampCreated`: unixtime of creation
* `TimestampUpdated`: unixtime of last update
* `Properties`: 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
(_>= 3.7.0_)
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
(_>= 3.7.0_)
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
(_>= 3.7.0_)
Map-based sharing can be accessed as usual after authentication and authorization.
#### Permission Control
* `permit_create_map`
* supported *rights* permissions: `Mm`
#### 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
(_>= 3.7.0_)
Token-based sharing can be accessed after retrieving the token via
Token-URI: `/.token/<Token>`
#### Permission Control
* `permit_create_token`
* supported *rights* permissions: `Tt`
#### 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
(_>= 3.7.0_)
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 (_>= 3.7.0_)
* application/json (_>= 3.7.0_)
##### Output Data Format
Can be selected by `HTTP_ACCEPT`
* text/plain (_>= 3.7.0_)
* text/csv (_>= 3.7.0_) - only for "list"
* application/json (_>= 3.7.0_)
##### Accepted Input Data Fields
* `PathOrToken`: token or "virtual" collection
* `PathMapped`: target collection
* `Owner`: owner of the share
* `User`: user of the share
* `Permissions`: effective permission of the share
* `Enabled`: owner/user selected by authentication
* `Hidden`: owner/user selected by authentication
* `Properties`: properties to overlay
#### API Hooks
##### API Hook "info"
Shows what kind of ShareTypes are supported
* Example: 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
```
* Example: JSON
```
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 "*/create"
Create a share
###### API Hook "token/create"
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathMapped | yes | |
| Permissions | no | r
* Output
| Parameter | Value |
| - | - |
| PathOrToken | (autogenerated token) |
* Example: TEXT
```
curl -u user:pass -d "PathMapped=/user/testcalendar1/" http://localhost:5232/.sharing/v1/token/create
ApiVersion=1
Status=success
PathOrToken=v1/VQR7AmsVRi2ZlFj_JwGpFx-ES5Goyku-gP_YkLh1zUw=
```
###### API Hook "map/create"
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathOrToken | yes | |
| PathMapped | yes | |
| Permissions | no | r
| User | yes | |
* Output: result status
* Example: TEXT
```
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" -d "PathMapped=/owner/cal1/" -d "User=user" http://localhost:5232/.sharing/v1/map/create
ApiVersion=1
Status=success
```
##### API Hook "*/list"
List shares (optional with filter)
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathMapped | no | (all) |
| Owner | no | (owned ones) |
| User | no | (filtered) |
* Output: plain/csv/json
* Example: CSV
```
curl -H "accept: text/csv" -u owner:pass -d "" http://localhost:5232/.sharing/v1/map/list
ShareType,PathOrToken,PathMapped,Owner,User,Permissions,EnabledByOwner,EnabledByUser,HiddenByOwner,HiddenByUser,TimestampCreated,TimestampUpdated
map,/user/cal1-from-owner/,/owner/cal1/,owner,user,r,False,False,True,True,1771962120,1771962120
```
##### API Hook "(map|token)/delete"
Delete a share selected by `PathOrToken`.
* Input
| Parameter | Type | Owner | User |
| - | - | - | - |
| PathOrToken | selector | mandatory | not-permitted |
* Output: result status
##### API Hook "(token|map)/update"
Update a share selected by `PathOrToken`.
Execute delete+create in case `PathOrToken` needs to be changed.
* Input
| Parameter | Type | Owner | User |
| - | - | - | - |
| PathOrToken | selector | mandatory | mandatory |
| Owner | by authentication | not-permitted | not-permitted |
| PathMapped | adjustable | optional | not-permitted |
| User | adjustable | optional | not-permitted |
| Permissions | adjustable | optional | not-permitted |
| Enabled | adjustable | optional(owner) | optional(user) |
| Hidden | adjustable | optional(owner) | optional(user) |
| Properties | adjustable | optional | optional |
* Output: result status
##### API Hooks "*/(enable|disable|hide|unhide)"
Enable|disable|hide|unhide a share selected by `PathOrToken`
* Input
| Parameter | Mandatory | Default |
| - | - | - |
| PathOrToken | yes | n/a |
* Output: result status
* Example: TEXT (enable)
```
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" -d "PathMapped=/owner/cal1/" -d "User=user" http://localhost:5232/.sharing/v1/map/enable
ApiVersion=1
Status=success
```
* Example: JSON (unhide)
```
curl -u owner:pass -d '{"PathOrToken": "/user/cal1-from-owner/", "PathMapped": "/owner/cal1/", "User": "user"} 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-description` (_>= 3.7.0_)
* `ICAL:calendar-color` (_>= 3.7.0_)
* `CR:addressbook-description` (_>= 3.7.0_)
* `INF:addressbook-color` (_>= 3.7.0_)
### Properties Overlay Control Options
* `permit_properties_overlay`
* supported *share* permissions: `Pp`
* `enforce_properties_overlay`
* supported *share* permissions: `Ee`