Radicale 3.8.2__tar.gz → 3.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {radicale-3.8.2 → radicale-3.9.0}/CHANGELOG.md +28 -0
- {radicale-3.8.2 → radicale-3.9.0}/DOCUMENTATION.md +100 -15
- {radicale-3.8.2 → radicale-3.9.0}/PKG-INFO +9 -3
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/PKG-INFO +9 -3
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/SOURCES.txt +2 -1
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/requires.txt +9 -2
- {radicale-3.8.2 → radicale-3.9.0}/config +6 -2
- {radicale-3.8.2 → radicale-3.9.0}/pyproject.toml +20 -10
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/__init__.py +12 -10
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/base.py +6 -4
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/delete.py +7 -11
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/get.py +13 -17
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/head.py +3 -3
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/mkcalendar.py +5 -4
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/mkcol.py +5 -4
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/move.py +8 -14
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/options.py +1 -1
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/post.py +3 -3
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/propfind.py +36 -27
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/proppatch.py +11 -8
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/put.py +9 -10
- {radicale-3.8.2 → radicale-3.9.0}/radicale/app/report.py +11 -11
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/__init__.py +48 -18
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/dovecot.py +3 -3
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/ldap.py +17 -16
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/oauth2.py +2 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/pam.py +20 -13
- {radicale-3.8.2 → radicale-3.9.0}/radicale/config.py +33 -6
- {radicale-3.8.2 → radicale-3.9.0}/radicale/group/htgroup.py +1 -1
- {radicale-3.8.2 → radicale-3.9.0}/radicale/item/filter.py +33 -36
- {radicale-3.8.2 → radicale-3.9.0}/radicale/rights/__init__.py +8 -6
- {radicale-3.8.2 → radicale-3.9.0}/radicale/rights/authenticated.py +3 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/rights/from_file.py +3 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/rights/owner_only.py +3 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/rights/owner_write.py +3 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/server.py +107 -1
- {radicale-3.8.2 → radicale-3.9.0}/radicale/sharing/__init__.py +61 -20
- {radicale-3.8.2 → radicale-3.9.0}/radicale/sharing/files.py +2 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/lock.py +2 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/tests/custom/rights.py +3 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/tests/custom/web.py +2 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/utils.py +2 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/__init__.py +2 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal.py +1 -0
- radicale-3.9.0/radicale/web/internal_data/css/icons/check-circle.svg +1 -0
- radicale-3.9.0/radicale/web/internal_data/css/icons/minus-circle.svg +1 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/main.css +104 -7
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/index.html +8 -41
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/api/api.js +2 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/api/sharing.js +1 -1
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/models/collection.js +3 -1
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/CollectionsScene.js +395 -62
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/CreateEditCollectionScene.js +6 -2
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/CreateEditShareScene.js +34 -10
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/ShareCollectionScene.js +0 -16
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/misc.js +1 -15
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/none.py +1 -1
- radicale-3.8.2/radicale/web/internal_data/js/scenes/IncomingSharingScene.js +0 -195
- {radicale-3.8.2 → radicale-3.9.0}/COPYING.md +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/MANIFEST.in +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/README.md +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/dependency_links.txt +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/entry_points.txt +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/Radicale.egg-info/top_level.txt +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/__main__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/denyall.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/htpasswd.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/http_remote_user.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/http_x_remote_user.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/imap.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/none.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/auth/remote_user.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/group/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/group/from_auth.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/group/none.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/hook/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/hook/email/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/hook/none.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/hook/rabbitmq/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/httputils.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/item/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/log.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/pathutils.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/py.typed +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/sharing/common.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/sharing/csv.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/sharing/none.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/base.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/cache.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/create_collection.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/delete.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/discover.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/get.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/history.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/meta.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/move.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/sync.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/upload.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem/verify.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/storage/multifilesystem_nolock.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/tests/custom/__init__.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/tests/custom/auth.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/tests/custom/storage_simple_sync.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/types.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icon.png +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/delete.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/download.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/edit.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/eye.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/key.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/new.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/repeat.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/share.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/icons/upload.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/loading.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/css/logo.svg +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/api/common.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/config.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/constants.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/main.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/DeleteConfirmationScene.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/LoadingScene.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/LoginScene.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/UploadCollectionScene.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/scenes/scene_manager.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/collection_utils.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/collections_cache.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/error.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/form_validator.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/permissions.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/web/internal_data/js/utils/url_text.js +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale/xmlutils.py +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/radicale.wsgi +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/rights +0 -0
- {radicale-3.8.2 → radicale-3.9.0}/setup.cfg +0 -0
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.9.0
|
|
4
|
+
* Add: [server] type: serve with hypercorn, which additionally speaks HTTP/2 over cleartext (H2C) on the same socket (`pip install radicale[h2]`)
|
|
5
|
+
* Adjustment WebUI: show incoming shares as cards
|
|
6
|
+
* Fix: typo in /.mobileconfig
|
|
7
|
+
* Fix: empty text-match filter no longer fails the REPORT
|
|
8
|
+
* Improvement: sharing/api: reject unsupported overlay properties depending on collection type and conversion
|
|
9
|
+
* Improvement: sharing/bday: remove default "(BDAY)" suffix from overlayed "displayname"
|
|
10
|
+
* Fix: WebUI/Sharing: property overrides on transformed shares
|
|
11
|
+
* Fix: PUT request: accept If-Match: * for an existing item
|
|
12
|
+
* Add: new permission 'l' for locking properties by blocking write access to properties (PROPPATCH) and suppressing write-properties (PROPFIND)
|
|
13
|
+
* Add: storage-hook supports now placeholder 'actor' (useful for shared collection)
|
|
14
|
+
* Fix: REPORT with calendar-data parses every item with vobject just to count VEVENTs (10× slower than needed)
|
|
15
|
+
* Fix: Honor zero-offset VALARM triggers in time-range filters
|
|
16
|
+
* Fix: time-range filter on VTODO with only CREATED and COMPLETED
|
|
17
|
+
* Fix: addressbook-query prop-filter defaults to test="anyof"
|
|
18
|
+
* Fix: REPORT expand: non-recurring events with a TZID were time-range filtered by their local time read as UTC
|
|
19
|
+
* Fix: sharing: broken support for custom database_path
|
|
20
|
+
* Fix: auth/login-cache: add lock around remove expired entry
|
|
21
|
+
* Fix: sharing/delete collection: fix uncatched collection URI without trailing '/'
|
|
22
|
+
* Fix: auth/sharing+rights: refactoring "user_groups" handling to be thread-safe
|
|
23
|
+
* Fix: auth/login-cache: partially missing thread-safety
|
|
24
|
+
* Extension: contrib/Apache reference config supports now also HTTP2
|
|
25
|
+
* Extension: HTTP version added to request log
|
|
26
|
+
|
|
27
|
+
## 3.8.3
|
|
28
|
+
* Fix: [group] htgroup: fix not working htgroup_cache=True
|
|
29
|
+
* Fix: auth: clear rights/user_groups before conditional set depending on group_type/auth_type
|
|
30
|
+
|
|
3
31
|
## 3.8.2
|
|
4
32
|
* Add: tests: sharing API plus CalDAV/CardDAV workflow and corrupt CSV handling (covers #2259)
|
|
5
33
|
* Add: tests: mocked LDAP, IMAP, OAuth2 and PAM authentication servers (covers #2259)
|
|
@@ -306,13 +306,13 @@ To enable and manage the service run:
|
|
|
306
306
|
|
|
307
307
|
```bash
|
|
308
308
|
# Enable the service
|
|
309
|
-
|
|
309
|
+
systemctl enable radicale
|
|
310
310
|
# Start the service
|
|
311
|
-
|
|
311
|
+
systemctl start radicale
|
|
312
312
|
# Check the status of the service
|
|
313
|
-
|
|
313
|
+
systemctl status radicale
|
|
314
314
|
# View all log messages
|
|
315
|
-
|
|
315
|
+
journalctl --unit radicale.service
|
|
316
316
|
```
|
|
317
317
|
|
|
318
318
|
#### Linux with systemd as a user
|
|
@@ -340,13 +340,13 @@ To enable and manage the service run:
|
|
|
340
340
|
|
|
341
341
|
```bash
|
|
342
342
|
# Enable the service
|
|
343
|
-
|
|
343
|
+
systemctl --user enable radicale
|
|
344
344
|
# Start the service
|
|
345
|
-
|
|
345
|
+
systemctl --user start radicale
|
|
346
346
|
# Check the status of the service
|
|
347
|
-
|
|
347
|
+
systemctl --user status radicale
|
|
348
348
|
# View all log messages
|
|
349
|
-
|
|
349
|
+
journalctl --user --unit radicale.service
|
|
350
350
|
```
|
|
351
351
|
|
|
352
352
|
#### Windows with "NSSM - the Non-Sucking Service Manager"
|
|
@@ -467,6 +467,34 @@ Be reminded that Radicale's default configuration enforces limits on the
|
|
|
467
467
|
maximum number of parallel connections, the maximum file size and the rate of
|
|
468
468
|
incorrect authentication attempts. Connections are terminated after a timeout.
|
|
469
469
|
|
|
470
|
+
#### HTTP/2 to the backend
|
|
471
|
+
|
|
472
|
+
The examples above forward to Radicale over HTTP/1.x. The built-in server
|
|
473
|
+
answers HTTP/1.0 and closes after each request; with `type = hypercorn` (see
|
|
474
|
+
[Configuration/Server](#server)) the hop keeps connections alive instead, and it
|
|
475
|
+
can speak **HTTP/2 over cleartext (H2C)** on the same socket: the connection
|
|
476
|
+
starts with the HTTP/2 client preface, so a proxy configured to use HTTP/2 for
|
|
477
|
+
this upstream gets it without terminating TLS itself.
|
|
478
|
+
|
|
479
|
+
The reason to prefer it is request smuggling. Smuggling needs a connection that
|
|
480
|
+
carries more than one request, and two peers that disagree on where the first
|
|
481
|
+
one ends: most commonly a `Content-Length` header pulling against a
|
|
482
|
+
`Transfer-Encoding` header, or duplicate `Transfer-Encoding` headers that the
|
|
483
|
+
two peers combine or drop differently. HTTP/2 leaves nothing to disagree
|
|
484
|
+
about — every frame carries its own length, a request ends at an explicit
|
|
485
|
+
end-of-stream, and a `content-length` that contradicts the data is a stream
|
|
486
|
+
error (RFC 9113). The hop that changes is the last one, so no HTTP/1.1
|
|
487
|
+
downgrade is introduced, and the client-side hop is unchanged: this removes
|
|
488
|
+
the ambiguity on one hop, not on the path end to end.
|
|
489
|
+
|
|
490
|
+
With `ssl = True` `hypercorn` serves HTTP/2 over TLS by ALPN too, so the
|
|
491
|
+
encrypted hop described under [Secure connection between Radicale and the
|
|
492
|
+
reverse proxy](#secure-connection-between-radicale-and-the-reverse-proxy) can
|
|
493
|
+
use it; the proxy still has to be told to use HTTP/2 for that upstream.
|
|
494
|
+
|
|
495
|
+
`type = builtin` stays the default, so a proxy that forwards to it keeps working
|
|
496
|
+
exactly as before.
|
|
497
|
+
|
|
470
498
|
#### Manage user accounts with the reverse proxy
|
|
471
499
|
|
|
472
500
|
Set the configuration option `type` in the `auth` section to
|
|
@@ -798,9 +826,60 @@ The following describes all configuration sections and options.
|
|
|
798
826
|
The configuration options in this section are only relevant in standalone
|
|
799
827
|
mode; they are ignored, when Radicale runs on WSGI.
|
|
800
828
|
|
|
829
|
+
##### type
|
|
830
|
+
|
|
831
|
+
_(>= 3.9.0)_
|
|
832
|
+
|
|
833
|
+
The server to run: `builtin` (the default) or `hypercorn`.
|
|
834
|
+
|
|
835
|
+
The built-in server answers HTTP/1.0 and closes after each request. `hypercorn`
|
|
836
|
+
keeps connections alive and also serves **HTTP/2 over cleartext (H2C)** on the
|
|
837
|
+
same socket, which is what a reverse proxy in front of Radicale needs to speak
|
|
838
|
+
HTTP/2 without terminating TLS itself; it is an optional dependency, installed
|
|
839
|
+
with the `h2` extra using `pip install radicale[h2]` or by underlying
|
|
840
|
+
OS (package name: 'python3-hypercorn'), supported version: hypercorn>=0.17.
|
|
841
|
+
Asking for it when it is not installed fails with an error
|
|
842
|
+
naming the extra instead of falling back to the built-in server.
|
|
843
|
+
|
|
844
|
+
With `hypercorn` the TLS options are used as they are (`ssl`, `certificate`,
|
|
845
|
+
`key`, `certificate_authority`, `ciphersuite`) and `max_content_length` is
|
|
846
|
+
handed over to it — without that, hypercorn's own WSGI body limit of 16 Mbyte
|
|
847
|
+
would answer a larger request with `400` before Radicale sees it. Two details
|
|
848
|
+
change: `protocol` is **ignored** (hypercorn sets its own TLS versions, TLSv1.2
|
|
849
|
+
at the minimum) and an unset `ciphersuite` means hypercorn's default cipher
|
|
850
|
+
list rather than the system one. `max_connections` and `timeout` configure the
|
|
851
|
+
built-in server only, and a request that is still in flight is given three
|
|
852
|
+
seconds when the server stops.
|
|
853
|
+
|
|
854
|
+
Default: `builtin`
|
|
855
|
+
|
|
856
|
+
Note: while `builtin` has internal support for handling IPv4 and IPv6 binding
|
|
857
|
+
separate, `hypercorn` hasn't as of now. Following config runs into error
|
|
858
|
+
message
|
|
859
|
+
|
|
860
|
+
```ini
|
|
861
|
+
[server]
|
|
862
|
+
hosts = [::]:5232,0.0.0.0:5232
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
while having (here OS Linux):
|
|
866
|
+
|
|
867
|
+
``` bash
|
|
868
|
+
# Sysctl status:
|
|
869
|
+
sysctl net.ipv6.bindv6only
|
|
870
|
+
net.ipv6.bindv6only = 0
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Solutions:
|
|
874
|
+
- reduce `hosts` config to IPv4 or IPv6
|
|
875
|
+
- adjust toggle on OS Linux by `sysctl -w net.ipv6.bindv6only=1` (don't forget to make this permanent)
|
|
876
|
+
|
|
801
877
|
##### hosts
|
|
802
878
|
|
|
803
|
-
A comma separated list of addresses that the server will bind to.
|
|
879
|
+
A comma separated list of addresses that the server will bind to. A host name
|
|
880
|
+
is bound on every address it resolves to, as with the built-in server; with
|
|
881
|
+
`hypercorn` a failure to bind one of them stops the server, while the built-in
|
|
882
|
+
one logs it and serves the others.
|
|
804
883
|
|
|
805
884
|
Default: `localhost:5232`
|
|
806
885
|
|
|
@@ -1705,11 +1784,12 @@ tutorial for an example.
|
|
|
1705
1784
|
Default: (unset)
|
|
1706
1785
|
|
|
1707
1786
|
Supported placeholders:
|
|
1708
|
-
- `%(user)s`: logged-in user
|
|
1787
|
+
- `%(user)s`: logged-in (or in case of sharing: mapped) user
|
|
1709
1788
|
- `%(cwd)s`: current working directory _(>= 3.5.1)_
|
|
1710
1789
|
- `%(path)s`: full path of item _(>= 3.5.1)_
|
|
1711
1790
|
- `%(to_path)s`: full path of destination item (only set on MOVE request) _(>= 3.5.5)_
|
|
1712
1791
|
- `%(request)s`: request method _(>= 3.5.5)_
|
|
1792
|
+
- `%(actor)s`: logged-in user _(>= 3.9.0)_
|
|
1713
1793
|
|
|
1714
1794
|
The command will be executed with base directory defined in `filesystem_folder` (see above)
|
|
1715
1795
|
|
|
@@ -1939,7 +2019,7 @@ Log storage cache actions on `level = debug`
|
|
|
1939
2019
|
|
|
1940
2020
|
Default: `False`
|
|
1941
2021
|
|
|
1942
|
-
#####
|
|
2022
|
+
##### profiling
|
|
1943
2023
|
|
|
1944
2024
|
_(>= 3.5.10)_
|
|
1945
2025
|
|
|
@@ -2076,7 +2156,7 @@ _(>= 3.5.5)_
|
|
|
2076
2156
|
|
|
2077
2157
|
Port on SMTP server to connect to.
|
|
2078
2158
|
|
|
2079
|
-
Default:
|
|
2159
|
+
Default: `25`
|
|
2080
2160
|
|
|
2081
2161
|
##### smtp_security
|
|
2082
2162
|
|
|
@@ -2397,7 +2477,7 @@ _(>= 3.7.0)_
|
|
|
2397
2477
|
|
|
2398
2478
|
Default permissions for create token-based sharing
|
|
2399
2479
|
|
|
2400
|
-
Default: `r`
|
|
2480
|
+
Default: `r` _(< 3.7.1)_ / `rp` _(>= 3.7.1)_
|
|
2401
2481
|
|
|
2402
2482
|
Supported: `rfwEePp`
|
|
2403
2483
|
|
|
@@ -2580,7 +2660,7 @@ address books. Use Radicale's web interface or a client with support for it
|
|
|
2580
2660
|
To create a new calendar run something like:
|
|
2581
2661
|
|
|
2582
2662
|
```bash
|
|
2583
|
-
|
|
2663
|
+
curl -u user -X MKCOL 'http://localhost:5232/user/calendar' --data \
|
|
2584
2664
|
'<?xml version="1.0" encoding="UTF-8" ?>
|
|
2585
2665
|
<create xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:I="http://apple.com/ns/ical/">
|
|
2586
2666
|
<set>
|
|
@@ -2605,7 +2685,7 @@ $ curl -u user -X MKCOL 'http://localhost:5232/user/calendar' --data \
|
|
|
2605
2685
|
To create a new address book run something like:
|
|
2606
2686
|
|
|
2607
2687
|
```bash
|
|
2608
|
-
|
|
2688
|
+
curl -u user -X MKCOL 'http://localhost:5232/user/addressbook' --data \
|
|
2609
2689
|
'<?xml version="1.0" encoding="UTF-8" ?>
|
|
2610
2690
|
<create xmlns="DAV:" xmlns:CR="urn:ietf:params:xml:ns:carddav">
|
|
2611
2691
|
<set>
|
|
@@ -2718,6 +2798,11 @@ The following `permissions` are recognized:
|
|
|
2718
2798
|
* **t:** deny create of token-based sharing of collection in case `permit_create_token=True` _(>= 3.7.0)_
|
|
2719
2799
|
* **M:** permit create of map-based sharing of collection in case `permit_create_map= False` _(>= 3.7.0)_
|
|
2720
2800
|
* **m:** deny create of map-based sharing of collection in case `permit_create_map=True` _(>= 3.7.0)_
|
|
2801
|
+
* **P:** permit properties overlay in case permit_properties_overlay=False _(>= 3.7.0)_
|
|
2802
|
+
* **p:** deny properties overlay in case permit_properties_overlay=True _(>= 3.7.0)_
|
|
2803
|
+
* **E:** enable enforce properties overlay in case enforce_properties_overlay=False _(>= 3.7.0)_
|
|
2804
|
+
* **e:** disable enforce of properties overlay in case enforce_properties_overlay=True _(>= 3.7.0)_
|
|
2805
|
+
* **l:** lock properties (disable write-properties) _(>= 3.8.4)_
|
|
2721
2806
|
|
|
2722
2807
|
### Storage
|
|
2723
2808
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Radicale
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.0
|
|
4
4
|
Summary: CalDAV and CardDAV Server
|
|
5
5
|
Author-email: Guillaume Ayoub <guillaume.ayoub@kozea.fr>, Unrud <unrud@outlook.com>, Peter Bieringer <pb@bieringer.de>
|
|
6
6
|
License: GNU GPL v3
|
|
@@ -39,6 +39,9 @@ Requires-Dist: bcrypt; extra == "test"
|
|
|
39
39
|
Requires-Dist: argon2-cffi; extra == "test"
|
|
40
40
|
Requires-Dist: pam; extra == "test"
|
|
41
41
|
Requires-Dist: ldap3; extra == "test"
|
|
42
|
+
Requires-Dist: hypercorn>=0.17; extra == "test"
|
|
43
|
+
Provides-Extra: h2
|
|
44
|
+
Requires-Dist: hypercorn>=0.17; extra == "h2"
|
|
42
45
|
Provides-Extra: bcrypt
|
|
43
46
|
Requires-Dist: bcrypt; extra == "bcrypt"
|
|
44
47
|
Provides-Extra: argon2
|
|
@@ -56,8 +59,11 @@ Requires-Dist: pytest-playwright; extra == "dev"
|
|
|
56
59
|
Requires-Dist: html5validator; extra == "dev"
|
|
57
60
|
Provides-Extra: caldav-test
|
|
58
61
|
Requires-Dist: pytest>=7; extra == "caldav-test"
|
|
59
|
-
Requires-Dist: caldav
|
|
60
|
-
Requires-Dist: caldav-server-tester
|
|
62
|
+
Requires-Dist: caldav==3.5.0; python_version >= "3.10" and extra == "caldav-test"
|
|
63
|
+
Requires-Dist: caldav-server-tester==1.4.0; python_version >= "3.10" and extra == "caldav-test"
|
|
64
|
+
Requires-Dist: icalendar==7.3.0; python_version >= "3.10" and extra == "caldav-test"
|
|
65
|
+
Requires-Dist: icalendar-searcher==1.2.0; python_version >= "3.10" and extra == "caldav-test"
|
|
66
|
+
Requires-Dist: recurring-ical-events==3.8.2; python_version >= "3.10" and extra == "caldav-test"
|
|
61
67
|
Dynamic: license-file
|
|
62
68
|
|
|
63
69
|
# Radicale
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Radicale
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.0
|
|
4
4
|
Summary: CalDAV and CardDAV Server
|
|
5
5
|
Author-email: Guillaume Ayoub <guillaume.ayoub@kozea.fr>, Unrud <unrud@outlook.com>, Peter Bieringer <pb@bieringer.de>
|
|
6
6
|
License: GNU GPL v3
|
|
@@ -39,6 +39,9 @@ Requires-Dist: bcrypt; extra == "test"
|
|
|
39
39
|
Requires-Dist: argon2-cffi; extra == "test"
|
|
40
40
|
Requires-Dist: pam; extra == "test"
|
|
41
41
|
Requires-Dist: ldap3; extra == "test"
|
|
42
|
+
Requires-Dist: hypercorn>=0.17; extra == "test"
|
|
43
|
+
Provides-Extra: h2
|
|
44
|
+
Requires-Dist: hypercorn>=0.17; extra == "h2"
|
|
42
45
|
Provides-Extra: bcrypt
|
|
43
46
|
Requires-Dist: bcrypt; extra == "bcrypt"
|
|
44
47
|
Provides-Extra: argon2
|
|
@@ -56,8 +59,11 @@ Requires-Dist: pytest-playwright; extra == "dev"
|
|
|
56
59
|
Requires-Dist: html5validator; extra == "dev"
|
|
57
60
|
Provides-Extra: caldav-test
|
|
58
61
|
Requires-Dist: pytest>=7; extra == "caldav-test"
|
|
59
|
-
Requires-Dist: caldav
|
|
60
|
-
Requires-Dist: caldav-server-tester
|
|
62
|
+
Requires-Dist: caldav==3.5.0; python_version >= "3.10" and extra == "caldav-test"
|
|
63
|
+
Requires-Dist: caldav-server-tester==1.4.0; python_version >= "3.10" and extra == "caldav-test"
|
|
64
|
+
Requires-Dist: icalendar==7.3.0; python_version >= "3.10" and extra == "caldav-test"
|
|
65
|
+
Requires-Dist: icalendar-searcher==1.2.0; python_version >= "3.10" and extra == "caldav-test"
|
|
66
|
+
Requires-Dist: recurring-ical-events==3.8.2; python_version >= "3.10" and extra == "caldav-test"
|
|
61
67
|
Dynamic: license-file
|
|
62
68
|
|
|
63
69
|
# Radicale
|
|
@@ -99,11 +99,13 @@ radicale/web/internal_data/css/icon.png
|
|
|
99
99
|
radicale/web/internal_data/css/loading.svg
|
|
100
100
|
radicale/web/internal_data/css/logo.svg
|
|
101
101
|
radicale/web/internal_data/css/main.css
|
|
102
|
+
radicale/web/internal_data/css/icons/check-circle.svg
|
|
102
103
|
radicale/web/internal_data/css/icons/delete.svg
|
|
103
104
|
radicale/web/internal_data/css/icons/download.svg
|
|
104
105
|
radicale/web/internal_data/css/icons/edit.svg
|
|
105
106
|
radicale/web/internal_data/css/icons/eye.svg
|
|
106
107
|
radicale/web/internal_data/css/icons/key.svg
|
|
108
|
+
radicale/web/internal_data/css/icons/minus-circle.svg
|
|
107
109
|
radicale/web/internal_data/css/icons/new.svg
|
|
108
110
|
radicale/web/internal_data/css/icons/repeat.svg
|
|
109
111
|
radicale/web/internal_data/css/icons/share.svg
|
|
@@ -119,7 +121,6 @@ radicale/web/internal_data/js/scenes/CollectionsScene.js
|
|
|
119
121
|
radicale/web/internal_data/js/scenes/CreateEditCollectionScene.js
|
|
120
122
|
radicale/web/internal_data/js/scenes/CreateEditShareScene.js
|
|
121
123
|
radicale/web/internal_data/js/scenes/DeleteConfirmationScene.js
|
|
122
|
-
radicale/web/internal_data/js/scenes/IncomingSharingScene.js
|
|
123
124
|
radicale/web/internal_data/js/scenes/LoadingScene.js
|
|
124
125
|
radicale/web/internal_data/js/scenes/LoginScene.js
|
|
125
126
|
radicale/web/internal_data/js/scenes/ShareCollectionScene.js
|
|
@@ -14,8 +14,11 @@ bcrypt
|
|
|
14
14
|
pytest>=7
|
|
15
15
|
|
|
16
16
|
[caldav_test:python_version >= "3.10"]
|
|
17
|
-
caldav
|
|
18
|
-
caldav-server-tester
|
|
17
|
+
caldav==3.5.0
|
|
18
|
+
caldav-server-tester==1.4.0
|
|
19
|
+
icalendar==7.3.0
|
|
20
|
+
icalendar-searcher==1.2.0
|
|
21
|
+
recurring-ical-events==3.8.2
|
|
19
22
|
|
|
20
23
|
[dev]
|
|
21
24
|
flake8
|
|
@@ -25,6 +28,9 @@ pytest
|
|
|
25
28
|
pytest-playwright
|
|
26
29
|
html5validator
|
|
27
30
|
|
|
31
|
+
[h2]
|
|
32
|
+
hypercorn>=0.17
|
|
33
|
+
|
|
28
34
|
[ldap]
|
|
29
35
|
ldap3
|
|
30
36
|
|
|
@@ -38,3 +44,4 @@ bcrypt
|
|
|
38
44
|
argon2-cffi
|
|
39
45
|
pam
|
|
40
46
|
ldap3
|
|
47
|
+
hypercorn>=0.17
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
[server]
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
# Server to run
|
|
15
|
+
# Value: builtin (HTTP/1.0) | hypercorn (HTTP/1 + HTTP/2 incl. H2C)
|
|
16
|
+
#type = builtin
|
|
17
|
+
|
|
18
|
+
# Server IP addresses or hostnames separated by a comma the server will bind to
|
|
15
19
|
# IPv4 syntax: address:port
|
|
16
20
|
# IPv6 syntax: [address]:port
|
|
17
21
|
# Hostname syntax (using "getaddrinfo" to resolve to IPv4/IPv6 adress(es)): hostname:port
|
|
@@ -178,7 +182,7 @@
|
|
|
178
182
|
#oauth2_client_secret =
|
|
179
183
|
|
|
180
184
|
# PAM service
|
|
181
|
-
#
|
|
185
|
+
#pam_service = radicale
|
|
182
186
|
|
|
183
187
|
# PAM group user should be member of
|
|
184
188
|
#pam_group_membership =
|
|
@@ -3,7 +3,7 @@ name = "Radicale"
|
|
|
3
3
|
# When the version is updated, a new section in the CHANGELOG.md file must be
|
|
4
4
|
# added too.
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
version = "3.
|
|
6
|
+
version = "3.9.0"
|
|
7
7
|
authors = [{name = "Guillaume Ayoub", email = "guillaume.ayoub@kozea.fr"}, {name = "Unrud", email = "unrud@outlook.com"}, {name = "Peter Bieringer", email = "pb@bieringer.de"}]
|
|
8
8
|
license = {text = "GNU GPL v3"}
|
|
9
9
|
description = "CalDAV and CardDAV Server"
|
|
@@ -40,13 +40,25 @@ dependencies = [
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
[project.optional-dependencies]
|
|
43
|
-
test = ["pytest>=7", "waitress", "bcrypt", "argon2-cffi", "pam", "ldap3"]
|
|
43
|
+
test = ["pytest>=7", "waitress", "bcrypt", "argon2-cffi", "pam", "ldap3", "hypercorn>=0.17"]
|
|
44
|
+
h2 = ["hypercorn>=0.17"]
|
|
44
45
|
bcrypt = ["bcrypt"]
|
|
45
46
|
argon2 = ["argon2-cffi"]
|
|
46
47
|
ldap = ["ldap3"]
|
|
47
48
|
pam = ["pam"]
|
|
48
49
|
dev = ["flake8", "isort", "mypy", "pytest", "pytest-playwright", "html5validator"]
|
|
49
|
-
|
|
50
|
+
# Exact pins: the observed feature report depends on the client libraries
|
|
51
|
+
# (feature model, search matching, recurrence expansion), not only on
|
|
52
|
+
# caldav-server-tester, so they are all frozen together. This is the single
|
|
53
|
+
# place to bump them, see caldav_compat_tests/README.md.
|
|
54
|
+
caldav_test = [
|
|
55
|
+
"pytest>=7",
|
|
56
|
+
"caldav==3.5.0; python_version >= '3.10'",
|
|
57
|
+
"caldav-server-tester==1.4.0; python_version >= '3.10'",
|
|
58
|
+
"icalendar==7.3.0; python_version >= '3.10'",
|
|
59
|
+
"icalendar-searcher==1.2.0; python_version >= '3.10'",
|
|
60
|
+
"recurring-ical-events==3.8.2; python_version >= '3.10'",
|
|
61
|
+
]
|
|
50
62
|
|
|
51
63
|
[project.scripts]
|
|
52
64
|
radicale = "radicale.__main__:run"
|
|
@@ -226,7 +238,7 @@ commands = [["mypy", "--install-types", "--non-interactive", "."]]
|
|
|
226
238
|
skip_install = true
|
|
227
239
|
|
|
228
240
|
[tool.tox.env.integ_test]
|
|
229
|
-
description = "Server+WebUI: run 'playwright' integration tests (with 'chromium')"
|
|
241
|
+
description = "Server+WebUI: run 'playwright' integration tests (with 'chromium', may require additional system libs, check with 'ldd `find .tox/ -name chrome-headless-shell` | grep found')"
|
|
230
242
|
deps = ["pytest", "pytest-playwright"]
|
|
231
243
|
# Store Playwright browsers inside .tox for better caching
|
|
232
244
|
setenv = { PLAYWRIGHT_BROWSERS_PATH = "{toxworkdir}/.playwright" }
|
|
@@ -235,12 +247,10 @@ commands = [["pytest", "integ_tests"]]
|
|
|
235
247
|
|
|
236
248
|
[tool.tox.env.caldav_compat_test]
|
|
237
249
|
description = "Server: run caldav compatibility tests"
|
|
250
|
+
# versions are pinned in project.optional-dependencies.caldav_test
|
|
238
251
|
extras = ["caldav_test"]
|
|
239
|
-
deps = [
|
|
240
|
-
|
|
241
|
-
"caldav_server_tester==1.2.0", # synchronize version with .github/workflows/test.yml#caldav-compat-test
|
|
242
|
-
"pytest"
|
|
243
|
-
]
|
|
252
|
+
deps = "{[project.optional-dependencies]caldav_test}"
|
|
253
|
+
allowlist_externals = [ "pytest" ]
|
|
244
254
|
commands = [["pytest", "caldav_compat_tests"]]
|
|
245
255
|
|
|
246
256
|
[tool.tox.env.js]
|
|
@@ -252,7 +262,7 @@ commands = [["npx", "-y", "-p", "typescript@7.0.2", "tsc", "-p", "radicale/web/j
|
|
|
252
262
|
skip_install = true
|
|
253
263
|
|
|
254
264
|
[tool.tox.env.html5validator]
|
|
255
|
-
description = "WebUI: validate HTML"
|
|
265
|
+
description = "WebUI: validate HTML (requires 'java', e.g. 'java-latest-openjdk-headless')"
|
|
256
266
|
deps = ["html5validator-2"]
|
|
257
267
|
commands = [["html5validator", "--root", "radicale/web/internal_data/", "--also-check-css"]]
|
|
258
268
|
|
|
@@ -472,9 +472,10 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
472
472
|
https_info = " " + environ.get("SSL_PROTOCOL", "") + " " + environ.get("SSL_CIPHER", "")
|
|
473
473
|
else:
|
|
474
474
|
https_info = ""
|
|
475
|
-
|
|
475
|
+
http_proto = " " + environ["SERVER_PROTOCOL"]
|
|
476
|
+
logger.info("%s request for %r%s received from %s%s%s%s",
|
|
476
477
|
request_method, unsafe_path, depthinfo,
|
|
477
|
-
remote_host, remote_useragent_txt, https_info)
|
|
478
|
+
remote_host, remote_useragent_txt, http_proto, https_info)
|
|
478
479
|
if self._request_header_on_debug:
|
|
479
480
|
if logger.isEnabledFor(logging.DEBUG):
|
|
480
481
|
logger.debug("Request header:\n%s",
|
|
@@ -554,11 +555,12 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
554
555
|
self.configuration, environ, base64.b64decode(
|
|
555
556
|
authorization.encode("ascii"))).split(":", 1)
|
|
556
557
|
|
|
558
|
+
user_groups: set = set([])
|
|
557
559
|
if login and not app_base._check_user_format(self._storage, login, self._validate_user_value, self._urldecode_username):
|
|
558
560
|
info = "not compliant to %r" % self._validate_user_value
|
|
559
561
|
user = ""
|
|
560
562
|
else:
|
|
561
|
-
(user, info) = self._auth.login(login, password, context) or ("", "") if login else ("", "")
|
|
563
|
+
(user, info, user_groups) = self._auth.login(login, password, context) or ("", "", set([])) if login else ("", "", set([]))
|
|
562
564
|
request_info: dict = {
|
|
563
565
|
"method": request_method,
|
|
564
566
|
"login": login, # not 'user' in this step
|
|
@@ -603,14 +605,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
603
605
|
|
|
604
606
|
if user:
|
|
605
607
|
group_type = self.configuration.get("group", "type")
|
|
606
|
-
if group_type in ["htgroup"]:
|
|
607
|
-
|
|
608
|
+
if group_type in ["htgroup"] and login:
|
|
609
|
+
user_groups = self._group.groups(login)
|
|
610
|
+
logger.debug("Groups received from %r: %r", group_type, ",".join(user_groups))
|
|
608
611
|
elif group_type in ["from_auth"]:
|
|
609
612
|
auth_type = self.configuration.get("auth", "type")
|
|
610
613
|
if auth_type in ["ldap", "pam"]:
|
|
611
614
|
try:
|
|
612
|
-
logger.debug("Groups received from %r: %r", auth_type, ",".join(
|
|
613
|
-
self._rights._user_groups = self._auth._groups
|
|
615
|
+
logger.debug("Groups received from %r: %r", auth_type, ",".join(user_groups))
|
|
614
616
|
except AttributeError:
|
|
615
617
|
pass
|
|
616
618
|
|
|
@@ -621,8 +623,8 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
621
623
|
principal = next(iter(self._storage.discover(
|
|
622
624
|
principal_path, depth="1")), None)
|
|
623
625
|
if not principal:
|
|
624
|
-
if "W" in self._rights.authorization(user, principal_path):
|
|
625
|
-
with self._storage.acquire_lock("w", user):
|
|
626
|
+
if "W" in self._rights.authorization(user, user_groups, principal_path):
|
|
627
|
+
with self._storage.acquire_lock("w", user, actor=user):
|
|
626
628
|
try:
|
|
627
629
|
new_coll, _, _ = self._storage.create_collection(principal_path)
|
|
628
630
|
if new_coll:
|
|
@@ -677,7 +679,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
677
679
|
status, headers, answer, xml_request = httputils.METHOD_NOT_ALLOWED
|
|
678
680
|
else:
|
|
679
681
|
status, headers, answer, xml_request = function(
|
|
680
|
-
environ, base_prefix, path, user, request_info)
|
|
682
|
+
environ, base_prefix, path, user, user_groups, request_info)
|
|
681
683
|
except PermissionError as e:
|
|
682
684
|
logger.error("PermissionError: %s", e)
|
|
683
685
|
status, headers, answer, xml_request = httputils.INTERNAL_SERVER_ERROR
|
|
@@ -267,6 +267,7 @@ class Access:
|
|
|
267
267
|
"""Helper class to check access rights of an item"""
|
|
268
268
|
|
|
269
269
|
user: str
|
|
270
|
+
user_groups: set
|
|
270
271
|
path: str
|
|
271
272
|
parent_path: str
|
|
272
273
|
permissions: str
|
|
@@ -274,13 +275,14 @@ class Access:
|
|
|
274
275
|
_parent_permissions: Optional[str]
|
|
275
276
|
_permissions_filter: Union[str, None] = None
|
|
276
277
|
|
|
277
|
-
def __init__(self, rights: rights.BaseRights, user: str, path: str, permissions_filter: Union[str, None] = None
|
|
278
|
+
def __init__(self, rights: rights.BaseRights, user: str, user_groups: set, path: str, permissions_filter: Union[str, None] = None
|
|
278
279
|
) -> None:
|
|
279
280
|
self._rights = rights
|
|
280
281
|
self.user = user
|
|
282
|
+
self.user_groups = user_groups
|
|
281
283
|
self.path = path
|
|
282
284
|
self.parent_path = pathutils.parent_path(path)
|
|
283
|
-
self.permissions = self._rights.authorization(self.user, self.path)
|
|
285
|
+
self.permissions = self._rights.authorization(self.user, self.user_groups, self.path)
|
|
284
286
|
if permissions_filter is not None:
|
|
285
287
|
self._permissions_filter = permissions_filter
|
|
286
288
|
self.permissions = intersect(
|
|
@@ -293,7 +295,7 @@ class Access:
|
|
|
293
295
|
return self.permissions
|
|
294
296
|
if self._parent_permissions is None:
|
|
295
297
|
self._parent_permissions = self._rights.authorization(
|
|
296
|
-
self.user, self.parent_path)
|
|
298
|
+
self.user, self.user_groups, self.parent_path)
|
|
297
299
|
if self._permissions_filter is not None:
|
|
298
300
|
self._parent_permissions = intersect(
|
|
299
301
|
_with_implied_freebusy(self._parent_permissions),
|
|
@@ -302,7 +304,7 @@ class Access:
|
|
|
302
304
|
|
|
303
305
|
def check(self, permission: str,
|
|
304
306
|
item: Optional[types.CollectionOrItem] = None) -> bool:
|
|
305
|
-
if permission not in
|
|
307
|
+
if permission not in rights.INTERNAL_PERMISSIONS_ITEM:
|
|
306
308
|
raise ValueError("Invalid permission argument: %r" % permission)
|
|
307
309
|
if not item:
|
|
308
310
|
permissions = permission + permission.upper()
|
|
@@ -23,7 +23,7 @@ from http import client
|
|
|
23
23
|
from typing import Optional, Union
|
|
24
24
|
from urllib.parse import quote
|
|
25
25
|
|
|
26
|
-
from radicale import httputils,
|
|
26
|
+
from radicale import httputils, storage, types, xmlutils
|
|
27
27
|
from radicale.app.base import Access, ApplicationBase
|
|
28
28
|
from radicale.hook import HookNotificationItem, HookNotificationItemTypes
|
|
29
29
|
from radicale.log import logger
|
|
@@ -66,27 +66,23 @@ def xml_delete(base_prefix: str, path: str, collection: storage.BaseCollection,
|
|
|
66
66
|
class ApplicationPartDelete(ApplicationBase):
|
|
67
67
|
|
|
68
68
|
def do_DELETE(self, environ: types.WSGIEnviron, base_prefix: str,
|
|
69
|
-
path: str, user: str, request_info: dict) -> types.WSGIResponse:
|
|
69
|
+
path: str, user: str, user_groups: set, request_info: dict) -> types.WSGIResponse:
|
|
70
70
|
"""Manage DELETE request."""
|
|
71
71
|
actor = user
|
|
72
72
|
permissions_filter = None
|
|
73
73
|
share = None
|
|
74
74
|
if self._sharing._enabled:
|
|
75
75
|
# Sharing by token or map (if enabled)
|
|
76
|
-
share = self._sharing.sharing_collection_resolver(path, user)
|
|
77
|
-
user_lookup = user
|
|
78
|
-
if self._rights._user_groups is not None and len(self._rights._user_groups) > 0:
|
|
79
|
-
user_lookup += sharing.SHARING_SEPARATOR_GROUP + ','.join(self._rights._user_groups)
|
|
80
|
-
share = self._sharing.sharing_collection_resolver(path, user_lookup)
|
|
76
|
+
share = self._sharing.sharing_collection_resolver(path, user, user_groups)
|
|
81
77
|
if share:
|
|
82
|
-
if
|
|
83
|
-
logger.notice("delete of a mapped collection is never permitted: %r -> %r", share['PathOrToken'],
|
|
78
|
+
if share['PathMapped'].endswith('/'):
|
|
79
|
+
logger.notice("delete of a mapped collection is never permitted: %r -> %r", share['PathOrToken'], share['PathMapped'])
|
|
84
80
|
return httputils.NOT_ALLOWED
|
|
85
81
|
# overwrite and run through extended permission check
|
|
86
82
|
path = share['PathMapped']
|
|
87
83
|
user = share['Owner']
|
|
88
84
|
permissions_filter = share['Permissions']
|
|
89
|
-
access = Access(self._rights, user, path, permissions_filter)
|
|
85
|
+
access = Access(self._rights, user, user_groups, path, permissions_filter)
|
|
90
86
|
if not access.check("w"):
|
|
91
87
|
return httputils.NOT_ALLOWED
|
|
92
88
|
if self._sharing._enabled:
|
|
@@ -95,7 +91,7 @@ class ApplicationPartDelete(ApplicationBase):
|
|
|
95
91
|
if collections_share_list is not None and len(collections_share_list) > 0:
|
|
96
92
|
logger.notice("delete of a mapped collection in use (%d) is not permitted: %r", len(collections_share_list), path)
|
|
97
93
|
return httputils.CONFLICT
|
|
98
|
-
with self._storage.acquire_lock("w", user, path=path, request="DELETE"):
|
|
94
|
+
with self._storage.acquire_lock("w", user, path=path, request="DELETE", actor=actor):
|
|
99
95
|
item = next(iter(self._storage.discover(path)), None)
|
|
100
96
|
if not item:
|
|
101
97
|
return httputils.NOT_FOUND
|