Merge pull request #2033 from pbiering/sharing-bday-add-desc
Sharing bday add description, doc updates, bugfixes
This commit is contained in:
215
SHARING.md
215
SHARING.md
@@ -1,121 +1,29 @@
|
|||||||
|
|
||||||
# Collection Sharing
|
# 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]
|
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
|
||||||
|
* added internal mapping with configuration stored in a database
|
||||||
|
* added management API
|
||||||
|
* WebUI extension using the management API
|
||||||
|
|
||||||
## Sharing Implementation
|
## 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.
|
Implemenation of sharing collections is done by using a database to lookup the URI and in case entry exists by mapping to target URI and replacing provided data on request and adjust if required data in response.
|
||||||
|
|
||||||
Permissions are filtered by provided `Permissions`.
|
Permissions are filtered by provided `Permissions`.
|
||||||
|
|
||||||
### CxDAV requests
|
## Sharing Configuration
|
||||||
|
|
||||||
#### CxDav request "(DELETE|GET|HEAD|PUT)"
|
New section `[sharing]` controls sharing configuration, see [DOCUMENTATION:Sharing](DOCUMENTATION.md#sharing) for details
|
||||||
|
|
||||||
* Actions
|
|
||||||
* map
|
|
||||||
* Lookup by
|
|
||||||
* `path` (provided in request)
|
|
||||||
* `user` (authenticated)
|
|
||||||
* Replace
|
|
||||||
* `user` by `Owner`
|
|
||||||
* `path` by `PathMapped`
|
|
||||||
* Activate
|
|
||||||
* `permissions_filter` by `Permissions`
|
|
||||||
|
|
||||||
#### CxDav request "REPORT"
|
|
||||||
|
|
||||||
* Actions
|
|
||||||
* map
|
|
||||||
* back-map response
|
|
||||||
* Lookup by
|
|
||||||
* `path` (provided in request)
|
|
||||||
* `user` (authenticated)
|
|
||||||
* Replace
|
|
||||||
* `user` by `Owner`
|
|
||||||
* `path` by `PathMapped`
|
|
||||||
* Activate
|
|
||||||
* `permissions_filter` by `Permissions`
|
|
||||||
|
|
||||||
#### CxDav request "PROPFIND" without HTTP_DEPTH=1
|
|
||||||
|
|
||||||
* Actions
|
|
||||||
* map
|
|
||||||
* back-map response
|
|
||||||
* overwrite `Properties` if provided
|
|
||||||
* Lookup by
|
|
||||||
* `path` (provided in request)
|
|
||||||
* `user` (authenticated)
|
|
||||||
* Replace
|
|
||||||
* `user` by `Owner`
|
|
||||||
* `path` by `PathMapped`
|
|
||||||
* Overlay
|
|
||||||
* `Properties` if provided
|
|
||||||
* Activate
|
|
||||||
* `permissions_filter` by `Permissions`
|
|
||||||
|
|
||||||
#### CxDav request "PROPFIND" with HTTP_DEPTH=1
|
|
||||||
|
|
||||||
* Actions
|
|
||||||
* extend list
|
|
||||||
* Lookup for active shares for `user` in sharing database
|
|
||||||
* Extend list if conditions are met
|
|
||||||
* `permissions_filter` by `Permissions`
|
|
||||||
|
|
||||||
#### CxDav request "PROPPATCH"
|
|
||||||
|
|
||||||
* Actions
|
|
||||||
* map
|
|
||||||
* adjust properties of a collection
|
|
||||||
* Lookup by
|
|
||||||
* `path` (provided in request)
|
|
||||||
* `user` (authenticated)
|
|
||||||
* Replace
|
|
||||||
* `user` by `Owner`
|
|
||||||
* `path` by `PathMapped`
|
|
||||||
* Activate
|
|
||||||
* `permissions_filter` by `Permissions`
|
|
||||||
* Depending on `permissions_filter`, global options and `Permissions`
|
|
||||||
* adjust properties of collection
|
|
||||||
* adjust whitelisted properties in `Properties` for overlay (see OVERLAY_PROPERTIES_WHITELIST)
|
|
||||||
|
|
||||||
#### CxDav request "(MKCALENDAR|MKCOL)"
|
|
||||||
|
|
||||||
* Action
|
|
||||||
* check for conflicts
|
|
||||||
* Lookup by
|
|
||||||
* `user` (authenticated)
|
|
||||||
* Verify for non-existence as `PathOrToken` in sharing database
|
|
||||||
* `path` (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 as `user`)
|
|
||||||
* Replace
|
|
||||||
* `user` by `Owner` (of `path`)
|
|
||||||
* `path` by `PathMapped` (of path)
|
|
||||||
* `to_user` by `Owner` (of `to_path`)
|
|
||||||
* `to_path` by `PathMapped` (of `to_path`)
|
|
||||||
* Activate
|
|
||||||
* `permissions_filter` by `Permissions` (of `to_path`)
|
|
||||||
* `to_permissions_filter` by `Permissions` (of `to_path`)
|
|
||||||
|
|
||||||
## Sharing Configuration Store
|
## Sharing Configuration Store
|
||||||
|
|
||||||
Types of supported sharing configuration:
|
Types of supported sharing configuration:
|
||||||
|
|
||||||
* csv (_>= 3.7.0_)
|
* *csv* (_>= 3.7.0_)
|
||||||
* files (_>= 3.7.0_)
|
* *files* (_>= 3.7.0_)
|
||||||
|
|
||||||
### Sharing Configuration Entry Data
|
### Sharing Configuration Entry Data
|
||||||
|
|
||||||
@@ -152,6 +60,107 @@ 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).
|
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 Request Handling
|
||||||
|
|
||||||
|
### CxDAV requests
|
||||||
|
|
||||||
|
#### CxDav request "(DELETE|GET|HEAD|PUT)"
|
||||||
|
|
||||||
|
* Actions
|
||||||
|
* map
|
||||||
|
* Lookup by
|
||||||
|
* `path` (provided in request)
|
||||||
|
* `user` (authenticated)
|
||||||
|
* Replace
|
||||||
|
* `path` by `PathMapped`
|
||||||
|
* `user` by `Owner`
|
||||||
|
* Activate
|
||||||
|
* `permissions_filter` by `Permissions`
|
||||||
|
|
||||||
|
#### CxDav request "REPORT"
|
||||||
|
|
||||||
|
* Actions
|
||||||
|
* map
|
||||||
|
* back-map response
|
||||||
|
* Lookup by
|
||||||
|
* `path` (provided in request)
|
||||||
|
* `user` (authenticated)
|
||||||
|
* Replace
|
||||||
|
* `path` by `PathMapped`
|
||||||
|
* `user` by `Owner`
|
||||||
|
* Activate
|
||||||
|
* `permissions_filter` by `Permissions`
|
||||||
|
|
||||||
|
#### CxDav request "PROPFIND" without HTTP_DEPTH=1
|
||||||
|
|
||||||
|
* Actions
|
||||||
|
* map
|
||||||
|
* back-map response
|
||||||
|
* overwrite `Properties` if provided
|
||||||
|
* Lookup by
|
||||||
|
* `path` (provided in request)
|
||||||
|
* `user` (authenticated)
|
||||||
|
* Replace
|
||||||
|
* `path` by `PathMapped`
|
||||||
|
* `user` by `Owner`
|
||||||
|
* Overlay
|
||||||
|
* `Properties` if provided
|
||||||
|
* Activate
|
||||||
|
* `permissions_filter` by `Permissions`
|
||||||
|
|
||||||
|
#### CxDav request "PROPFIND" with HTTP_DEPTH=1
|
||||||
|
|
||||||
|
* Actions
|
||||||
|
* extend list
|
||||||
|
* Lookup for active shares for `user` in sharing database
|
||||||
|
* Extend list if conditions are met
|
||||||
|
* `permissions_filter` by `Permissions`
|
||||||
|
|
||||||
|
#### CxDav request "PROPPATCH"
|
||||||
|
|
||||||
|
* Actions
|
||||||
|
* map
|
||||||
|
* adjust properties of a collection
|
||||||
|
* Lookup by
|
||||||
|
* `path` (provided in request)
|
||||||
|
* `user` (authenticated)
|
||||||
|
* Replace
|
||||||
|
* `path` by `PathMapped`
|
||||||
|
* `user` by `Owner`
|
||||||
|
* Activate
|
||||||
|
* `permissions_filter` by `Permissions`
|
||||||
|
* Depending on `permissions_filter`, global options and `Permissions`
|
||||||
|
* adjust properties of collection
|
||||||
|
* adjust whitelisted properties in `Properties` for overlay (see OVERLAY_PROPERTIES_WHITELIST)
|
||||||
|
|
||||||
|
#### CxDav request "(MKCALENDAR|MKCOL)"
|
||||||
|
|
||||||
|
* Action
|
||||||
|
* check for conflicts
|
||||||
|
* Lookup by
|
||||||
|
* `user` (authenticated)
|
||||||
|
* Verify for non-existence as `PathOrToken` in sharing database
|
||||||
|
* `path` (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 as `user`)
|
||||||
|
* Replace
|
||||||
|
* `path` by `PathMapped` (of path)
|
||||||
|
* `user` by `Owner` (of `path`)
|
||||||
|
* `to_path` by `PathMapped` (of `to_path`)
|
||||||
|
* `to_user` by `Owner` (of `to_path`)
|
||||||
|
* Activate
|
||||||
|
* `permissions_filter` by `Permissions` (of `to_path`)
|
||||||
|
* `to_permissions_filter` by `Permissions` (of `to_path`)
|
||||||
|
|
||||||
## Sharing Access
|
## Sharing Access
|
||||||
|
|
||||||
### Sharing Access via Maps
|
### Sharing Access via Maps
|
||||||
@@ -579,7 +588,7 @@ Whitelisted ones are defined in `OVERLAY_PROPERTIES_WHITELIST` in `radicale/shar
|
|||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
|
||||||
* sharing / permit_properties_overlay = True
|
* `permit_properties_overlay = True`
|
||||||
|
|
||||||
#### Test sequence
|
#### Test sequence
|
||||||
|
|
||||||
|
|||||||
@@ -549,8 +549,12 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|||||||
else:
|
else:
|
||||||
profiler_active = True
|
profiler_active = True
|
||||||
|
|
||||||
|
try:
|
||||||
status, headers, answer, xml_request = function(
|
status, headers, answer, xml_request = function(
|
||||||
environ, base_prefix, path, user, remote_host, remote_useragent)
|
environ, base_prefix, path, user, remote_host, remote_useragent)
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error("PermissionError: %s", e)
|
||||||
|
status, headers, answer, xml_request = httputils.INTERNAL_SERVER_ERROR
|
||||||
|
|
||||||
# Profiling
|
# Profiling
|
||||||
if self._profiling_per_request:
|
if self._profiling_per_request:
|
||||||
|
|||||||
@@ -537,6 +537,7 @@ class Item:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
bdayS = match[1] + match[2] + match[3]
|
bdayS = match[1] + match[2] + match[3]
|
||||||
|
bdaySdesc = match[1] + "-" + match[2] + "-" + match[3]
|
||||||
bdayY = int(match[1])
|
bdayY = int(match[1])
|
||||||
bdayM = int(match[2])
|
bdayM = int(match[2])
|
||||||
bdayD = int(match[3])
|
bdayD = int(match[3])
|
||||||
@@ -594,6 +595,9 @@ class Item:
|
|||||||
# add transparency
|
# add transparency
|
||||||
item_ics.vevent.add('transp').value = "TRANSPARENT"
|
item_ics.vevent.add('transp').value = "TRANSPARENT"
|
||||||
|
|
||||||
|
# add description
|
||||||
|
item_ics.vevent.add('description').value = "BDAY=" + bdaySdesc
|
||||||
|
|
||||||
href = self.href
|
href = self.href
|
||||||
if href is not None:
|
if href is not None:
|
||||||
href = href.rstrip(".vcf") + ".ics"
|
href = href.rstrip(".vcf") + ".ics"
|
||||||
|
|||||||
@@ -266,7 +266,9 @@ class Sharing(sharing.BaseSharing):
|
|||||||
"HiddenByOwner": HiddenByOwner,
|
"HiddenByOwner": HiddenByOwner,
|
||||||
"HiddenByUser": HiddenByUser,
|
"HiddenByUser": HiddenByUser,
|
||||||
"TimestampCreated": Timestamp,
|
"TimestampCreated": Timestamp,
|
||||||
"TimestampUpdated": Timestamp}
|
"TimestampUpdated": Timestamp,
|
||||||
|
"Properties": Properties}
|
||||||
|
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("TRACE/sharing/*/create: add row: %r", row)
|
logger.debug("TRACE/sharing/*/create: add row: %r", row)
|
||||||
self._sharing_cache.append(row)
|
self._sharing_cache.append(row)
|
||||||
|
|||||||
@@ -244,7 +244,8 @@ class Sharing(sharing.BaseSharing):
|
|||||||
"HiddenByOwner": HiddenByOwner,
|
"HiddenByOwner": HiddenByOwner,
|
||||||
"HiddenByUser": HiddenByUser,
|
"HiddenByUser": HiddenByUser,
|
||||||
"TimestampCreated": Timestamp,
|
"TimestampCreated": Timestamp,
|
||||||
"TimestampUpdated": Timestamp}
|
"TimestampUpdated": Timestamp,
|
||||||
|
"Properties": Properties}
|
||||||
|
|
||||||
version = DB_VERSION
|
version = DB_VERSION
|
||||||
|
|
||||||
|
|||||||
@@ -942,6 +942,7 @@ class TestSharingApiSanity(BaseTest):
|
|||||||
json_dict['User'] = "user"
|
json_dict['User'] = "user"
|
||||||
json_dict['PathMapped'] = path_mapped
|
json_dict['PathMapped'] = path_mapped
|
||||||
json_dict['PathOrToken'] = path_shared
|
json_dict['PathOrToken'] = path_shared
|
||||||
|
json_dict['Properties'] = {"D:displayname": "Test"}
|
||||||
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
|
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
|
||||||
answer_dict = json.loads(answer)
|
answer_dict = json.loads(answer)
|
||||||
assert answer_dict['Status'] == "success"
|
assert answer_dict['Status'] == "success"
|
||||||
@@ -962,6 +963,7 @@ class TestSharingApiSanity(BaseTest):
|
|||||||
assert answer_dict['Content'][0]['HiddenByOwner'] is True
|
assert answer_dict['Content'][0]['HiddenByOwner'] is True
|
||||||
assert answer_dict['Content'][0]['HiddenByUser'] is True
|
assert answer_dict['Content'][0]['HiddenByUser'] is True
|
||||||
assert answer_dict['Content'][0]['Permissions'] == "r"
|
assert answer_dict['Content'][0]['Permissions'] == "r"
|
||||||
|
assert answer_dict['Content'][0]['Properties'] == {"D:displayname": "Test"}
|
||||||
|
|
||||||
logging.info("\n*** enable map by owner (json->json) -> 200")
|
logging.info("\n*** enable map by owner (json->json) -> 200")
|
||||||
json_dict = {}
|
json_dict = {}
|
||||||
@@ -4288,6 +4290,7 @@ permissions: RrWw""")
|
|||||||
assert "DTSTART;VALUE=DATE:19700101" in answer
|
assert "DTSTART;VALUE=DATE:19700101" in answer
|
||||||
assert "DTEND;VALUE=DATE:19700102" in answer
|
assert "DTEND;VALUE=DATE:19700102" in answer
|
||||||
assert "TRANSP:TRANSPARENT" in answer
|
assert "TRANSP:TRANSPARENT" in answer
|
||||||
|
assert "DESCRIPTION:BDAY=1970-01-01" in answer
|
||||||
|
|
||||||
# verify report as user
|
# verify report as user
|
||||||
logging.info("\n*** REPORT collection user -> ok")
|
logging.info("\n*** REPORT collection user -> ok")
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ setup(
|
|||||||
license="GNU GPL v3",
|
license="GNU GPL v3",
|
||||||
platforms="Any",
|
platforms="Any",
|
||||||
packages=find_packages(
|
packages=find_packages(
|
||||||
exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
|
exclude=["*.tests", "*.tests.*", "tests.*", "tests", "integ_tests"]),
|
||||||
package_data={"radicale": [*web_files, "py.typed"]},
|
package_data={"radicale": [*web_files, "py.typed"]},
|
||||||
entry_points={"console_scripts": ["radicale = radicale.__main__:run"]},
|
entry_points={"console_scripts": ["radicale = radicale.__main__:run"]},
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
|
|||||||
Reference in New Issue
Block a user