sharing: text/plain provides now also Fields in output, doc update
This commit is contained in:
72
SHARING.md
72
SHARING.md
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
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]
|
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.
|
With _3.7.0_ major extension was implemented using internal mapping configuration stored in a database and a management API.
|
||||||
|
|
||||||
## Sharing Implementation
|
## Sharing Implementation
|
||||||
|
|
||||||
@@ -143,24 +143,18 @@ Types of supported sharing configuration:
|
|||||||
|
|
||||||
#### CSV
|
#### CSV
|
||||||
|
|
||||||
(_>= 3.7.0_)
|
|
||||||
|
|
||||||
One CSV file containing one row per sharing config, separated by `;` and containing header with columns from above.
|
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.
|
If given, properties are stored in JSON format in CSV.
|
||||||
|
|
||||||
#### Files
|
#### 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).
|
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
|
||||||
|
|
||||||
### Sharing Access via Maps
|
### Sharing Access via Maps
|
||||||
|
|
||||||
(_>= 3.7.0_)
|
|
||||||
|
|
||||||
Map-based sharing can be accessed as usual after authentication and authorization.
|
Map-based sharing can be accessed as usual after authentication and authorization.
|
||||||
|
|
||||||
#### Permission Control
|
#### Permission Control
|
||||||
@@ -181,8 +175,6 @@ In case share should be visible using PROPFIND
|
|||||||
|
|
||||||
### Sharing Access via Tokens
|
### Sharing Access via Tokens
|
||||||
|
|
||||||
(_>= 3.7.0_)
|
|
||||||
|
|
||||||
Token-based sharing can be accessed after retrieving the token via
|
Token-based sharing can be accessed after retrieving the token via
|
||||||
|
|
||||||
Token-URI: `/.token/<Token>`
|
Token-URI: `/.token/<Token>`
|
||||||
@@ -204,8 +196,6 @@ Note: requests to not enabled or not even defined tokens will resul tin _401 Not
|
|||||||
|
|
||||||
### Sharing Configuration Management API version 1
|
### Sharing Configuration Management API version 1
|
||||||
|
|
||||||
(_>= 3.7.0_)
|
|
||||||
|
|
||||||
Type: POST API
|
Type: POST API
|
||||||
|
|
||||||
Base-URI: `/.sharing/v1/<ShareType>/<Hook>`
|
Base-URI: `/.sharing/v1/<ShareType>/<Hook>`
|
||||||
@@ -218,16 +208,16 @@ See also test cases in `radicale/tests/test_sharing.py`
|
|||||||
|
|
||||||
Parsing be controlled by `CONTENT_TYPE`
|
Parsing be controlled by `CONTENT_TYPE`
|
||||||
|
|
||||||
* application/x-www-form-urlencoded (_>= 3.7.0_)
|
* application/x-www-form-urlencoded
|
||||||
* application/json (_>= 3.7.0_)
|
* application/json
|
||||||
|
|
||||||
##### Output Data Format
|
##### Output Data Format
|
||||||
|
|
||||||
Can be selected by `HTTP_ACCEPT`
|
Can be selected by `HTTP_ACCEPT` - default is equal to provided `CONTENT_TYPE`
|
||||||
|
|
||||||
* text/plain (_>= 3.7.0_)
|
* text/plain
|
||||||
* text/csv (_>= 3.7.0_) - only for "list"
|
* text/csv (only for "list")
|
||||||
* application/json (_>= 3.7.0_)
|
* application/json
|
||||||
|
|
||||||
##### Accepted Input Data Fields
|
##### Accepted Input Data Fields
|
||||||
|
|
||||||
@@ -264,6 +254,7 @@ PermittedCreateCollectionByToken=True
|
|||||||
* json->json, parsed with `jq`
|
* json->json, parsed with `jq`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
bash
|
||||||
curl -u user:pass --silent -H "accept: application/json" -d "" http://localhost:5232/.sharing/v1/all/info | jq
|
curl -u user:pass --silent -H "accept: application/json" -d "" http://localhost:5232/.sharing/v1/all/info | jq
|
||||||
{
|
{
|
||||||
"ApiVersion": 1,
|
"ApiVersion": 1,
|
||||||
@@ -311,7 +302,7 @@ Create a share by mapping a collection of an `Owner` to a token.
|
|||||||
* Examples:
|
* Examples:
|
||||||
* form->text
|
* form->text
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -u user:pass -d "PathMapped=/user/testcalendar1/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/token/create
|
curl -u user:pass -d "PathMapped=/user/testcalendar1/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/token/create
|
||||||
ApiVersion=1
|
ApiVersion=1
|
||||||
Status='success'
|
Status='success'
|
||||||
@@ -320,7 +311,7 @@ PathOrToken='v1/VQR7AmsVRi2ZlFj_JwGpFx-ES5Goyku-gP_YkLh1zUw='
|
|||||||
|
|
||||||
* json->json
|
* json->json
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathMapped": "/user/testcalendar1/", "Enabled": true, "Hidden": false}' http://localhost:5232/.sharing/v1/token/create
|
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="}
|
{"ApiVersion": 1, "Status": "success", "PathOrToken": "v1/aMsmGqOsRwSH-2-6tEa8EMr4RMYzMU7WvPmjnp5qDnw="}
|
||||||
```
|
```
|
||||||
@@ -353,7 +344,7 @@ Create a share by mapping a collection of an `Owner` to an `User`.
|
|||||||
* Examples:
|
* Examples:
|
||||||
* form->text
|
* form->text
|
||||||
|
|
||||||
```
|
```bash
|
||||||
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
|
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
|
ApiVersion=1
|
||||||
Status='success'
|
Status='success'
|
||||||
@@ -361,7 +352,7 @@ Status='success'
|
|||||||
|
|
||||||
* json->json
|
* json->json
|
||||||
|
|
||||||
```
|
```bash
|
||||||
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
|
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"}
|
{"ApiVersion": 1, "Status": "success"}
|
||||||
```
|
```
|
||||||
@@ -383,9 +374,21 @@ List shares (optional with filter) either owned or assigned as user.
|
|||||||
* Output: text/plain|text/csv|application/json
|
* Output: text/plain|text/csv|application/json
|
||||||
|
|
||||||
* Examples
|
* Examples
|
||||||
|
|
||||||
|
* form->text ("all")
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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)
|
* form->csv ("map" only)
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -H "accept: text/csv" -u user:pass -d "" http://localhost:5232/.sharing/v1/map/list://localhost:5232/.sharing/v1/map/list
|
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
|
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;
|
map;/user/cal1-from-owner/;/owner/testcalendar1/;owner;user;r;True;False;False;True;1772747277;1772747277;
|
||||||
@@ -393,7 +396,7 @@ map;/user/cal1-from-owner/;/owner/testcalendar1/;owner;user;r;True;False;False;T
|
|||||||
|
|
||||||
* json->json ("all"), parsed with `jq`
|
* json->json ("all"), parsed with `jq`
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -s -H "Content-Type: application/json" -u user:pass -d "{}" http://localhost:5232/.sharing/v1/all/list | jq
|
curl -s -H "Content-Type: application/json" -u user:pass -d "{}" http://localhost:5232/.sharing/v1/all/list | jq
|
||||||
{
|
{
|
||||||
"ApiVersion": 1,
|
"ApiVersion": 1,
|
||||||
@@ -454,7 +457,7 @@ Delete a share selected by `PathOrToken`.
|
|||||||
* Examples:
|
* Examples:
|
||||||
* form->text
|
* form->text
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/delete
|
curl -u owner:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/delete
|
||||||
ApiVersion=1
|
ApiVersion=1
|
||||||
Status='success'
|
Status='success'
|
||||||
@@ -462,7 +465,7 @@ Status='success'
|
|||||||
|
|
||||||
* json->json
|
* json->json
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -u user:pass -H "Content-Type: application/json" -d '{ "PathOrToken": "v1/DUSl_J5rRlWx3fy8YRXpH22FFllplkOTpcSwfGtpvkc="}' http://localhost:5232/.sharing/v1/token/delete
|
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"}
|
{"ApiVersion": 1, "Status": "success"}
|
||||||
```
|
```
|
||||||
@@ -491,7 +494,8 @@ Execute delete+create in case `PathOrToken` needs to be changed.
|
|||||||
* Output: text/plain|application/json
|
* Output: text/plain|application/json
|
||||||
|
|
||||||
* form->text
|
* form->text
|
||||||
```
|
|
||||||
|
```bash
|
||||||
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" -d "Enabled=True" -d "Hidden=False" http://localhost:5232/.sharing/v1/map/update
|
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
|
ApiVersion=1
|
||||||
Status='success'
|
Status='success'
|
||||||
@@ -499,7 +503,7 @@ Status='success'
|
|||||||
|
|
||||||
* json->json
|
* json->json
|
||||||
|
|
||||||
```
|
```bash
|
||||||
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
|
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"}
|
{"ApiVersion": 1, "Status": "success"}
|
||||||
```
|
```
|
||||||
@@ -522,15 +526,15 @@ Toggle enable|disable|hide|unhide of `Owner` or `User` of a share selected by `P
|
|||||||
|
|
||||||
* form->text
|
* form->text
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/enable
|
curl -u user:pass -d "PathOrToken=/user/cal1-from-owner/" http://localhost:5232/.sharing/v1/map/enable
|
||||||
ApiVersion=1
|
ApiVersion=1
|
||||||
Status='success'
|
Status='success'
|
||||||
```
|
```bash
|
||||||
|
|
||||||
* json->json
|
* 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
|
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"}
|
{"ApiVersion": 1, "Status": "success"}
|
||||||
```
|
```
|
||||||
@@ -541,10 +545,10 @@ Owner or user can define per share a set of properties to overlay on PROPFIND re
|
|||||||
|
|
||||||
Whitelisted ones are defined in `OVERLAY_PROPERTIES_WHITELIST` in `radicale/sharing/__init__.py`:
|
Whitelisted ones are defined in `OVERLAY_PROPERTIES_WHITELIST` in `radicale/sharing/__init__.py`:
|
||||||
|
|
||||||
* `C:calendar-description` (_>= 3.7.0_)
|
* `C:calendar-description`
|
||||||
* `ICAL:calendar-color` (_>= 3.7.0_)
|
* `ICAL:calendar-color`
|
||||||
* `CR:addressbook-description` (_>= 3.7.0_)
|
* `CR:addressbook-description`
|
||||||
* `INF:addressbook-color` (_>= 3.7.0_)
|
* `INF:addressbook-color`
|
||||||
|
|
||||||
### Properties Overlay Control Options
|
### Properties Overlay Control Options
|
||||||
|
|
||||||
|
|||||||
@@ -1091,6 +1091,8 @@ class BaseSharing:
|
|||||||
writer = DictWriter(csv, fieldnames=DB_FIELDS_V1, delimiter=';')
|
writer = DictWriter(csv, fieldnames=DB_FIELDS_V1, delimiter=';')
|
||||||
if output_format == "csv":
|
if output_format == "csv":
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
|
elif output_format == "txt":
|
||||||
|
writer.writeheader()
|
||||||
for entry in answer['Content']:
|
for entry in answer['Content']:
|
||||||
# TODO: Argument 1 to "writerow" of "DictWriter" has incompatible type "str"; expected "Mapping[str, Any]" [arg-type]
|
# TODO: Argument 1 to "writerow" of "DictWriter" has incompatible type "str"; expected "Mapping[str, Any]" [arg-type]
|
||||||
writer.writerow(entry) # type: ignore[arg-type]
|
writer.writerow(entry) # type: ignore[arg-type]
|
||||||
@@ -1100,7 +1102,10 @@ class BaseSharing:
|
|||||||
index = 0
|
index = 0
|
||||||
for line in csv.getvalue().splitlines():
|
for line in csv.getvalue().splitlines():
|
||||||
# create a shell array with content lines
|
# create a shell array with content lines
|
||||||
answer_array.append('Content[' + str(index) + ']="' + line.replace('"', '\\"') + '"')
|
if index == 0:
|
||||||
|
answer_array.append('Fields="' + line + '"')
|
||||||
|
else:
|
||||||
|
answer_array.append('Content[' + str(index - 1) + ']="' + line.replace('"', '\\"') + '"')
|
||||||
index += 1
|
index += 1
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "text/csv"
|
"Content-Type": "text/csv"
|
||||||
|
|||||||
@@ -362,9 +362,21 @@ class TestSharingApiSanity(BaseTest):
|
|||||||
|
|
||||||
logging.info("\n*** list/all (form->csv)")
|
logging.info("\n*** list/all (form->csv)")
|
||||||
form_array = []
|
form_array = []
|
||||||
|
_, headers, answer = self._sharing_api_form("all", "list", check=200, login="owner:ownerpw", form_array=form_array, accept="text/csv")
|
||||||
|
assert "Status=" not in answer
|
||||||
|
assert "Line=" not in answer
|
||||||
|
assert "ShareType" in answer
|
||||||
|
assert "token" in answer
|
||||||
|
assert "map" in answer
|
||||||
|
|
||||||
|
logging.info("\n*** list/all (form->text)")
|
||||||
|
form_array = []
|
||||||
_, headers, answer = self._sharing_api_form("all", "list", check=200, login="owner:ownerpw", form_array=form_array)
|
_, headers, answer = self._sharing_api_form("all", "list", check=200, login="owner:ownerpw", form_array=form_array)
|
||||||
assert "Status='success'" in answer
|
assert "Status='success'" in answer
|
||||||
assert "Lines=2" in answer
|
assert "Lines=2" in answer
|
||||||
|
assert "Fields=" in answer
|
||||||
|
assert "Content[0]=" in answer
|
||||||
|
assert "Content[1]=" in answer
|
||||||
|
|
||||||
logging.info("\n*** delete token -> 200")
|
logging.info("\n*** delete token -> 200")
|
||||||
form_array = ["PathOrToken=" + token]
|
form_array = ["PathOrToken=" + token]
|
||||||
|
|||||||
Reference in New Issue
Block a user