Radicale 3.2.0__tar.gz → 3.2.2__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.2.0 → radicale-3.2.2}/CHANGELOG.md +21 -0
- {radicale-3.2.0 → radicale-3.2.2}/DOCUMENTATION.md +61 -1
- {radicale-3.2.0 → radicale-3.2.2}/PKG-INFO +3 -3
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/PKG-INFO +3 -3
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/SOURCES.txt +9 -1
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/requires.txt +2 -2
- {radicale-3.2.0 → radicale-3.2.2}/config +39 -3
- {radicale-3.2.0 → radicale-3.2.2}/radicale/__init__.py +9 -3
- {radicale-3.2.0 → radicale-3.2.2}/radicale/__main__.py +11 -4
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/__init__.py +21 -4
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/base.py +8 -2
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/put.py +10 -5
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/report.py +40 -19
- {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/__init__.py +8 -1
- radicale-3.2.2/radicale/auth/denyall.py +30 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/config.py +43 -2
- {radicale-3.2.0 → radicale-3.2.2}/radicale/httputils.py +6 -2
- {radicale-3.2.0 → radicale-3.2.2}/radicale/log.py +11 -4
- {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/from_file.py +4 -3
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/base.py +3 -1
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/get.py +8 -3
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/verify.py +15 -5
- radicale-3.2.2/radicale/web/internal_data/css/icons/delete.svg +1 -0
- radicale-3.2.2/radicale/web/internal_data/css/icons/download.svg +1 -0
- radicale-3.2.2/radicale/web/internal_data/css/icons/edit.svg +1 -0
- radicale-3.2.2/radicale/web/internal_data/css/icons/new.svg +1 -0
- radicale-3.2.2/radicale/web/internal_data/css/icons/upload.svg +1 -0
- radicale-3.2.2/radicale/web/internal_data/css/loading.svg +72 -0
- radicale-3.2.2/radicale/web/internal_data/css/logo.svg +10 -0
- {radicale-3.2.0 → radicale-3.2.2}/setup.py +9 -3
- {radicale-3.2.0 → radicale-3.2.2}/COPYING.md +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/MANIFEST.in +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/README.md +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/dependency_links.txt +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/entry_points.txt +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/top_level.txt +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/delete.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/get.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/head.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/mkcalendar.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/mkcol.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/move.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/options.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/post.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/propfind.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/app/proppatch.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/htpasswd.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/http_x_remote_user.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/none.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/remote_user.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/none.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/rabbitmq/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/item/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/item/filter.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/pathutils.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/py.typed +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/authenticated.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/owner_only.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/owner_write.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/server.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/cache.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/create_collection.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/delete.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/discover.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/history.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/lock.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/meta.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/move.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/sync.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/upload.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem_nolock.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/types.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/utils.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/__init__.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/css/icon.png +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/css/main.css +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/fn.js +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/index.html +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/web/none.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale/xmlutils.py +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/radicale.wsgi +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/rights +0 -0
- {radicale-3.2.0 → radicale-3.2.2}/setup.cfg +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.dev
|
|
4
|
+
|
|
5
|
+
## 3.2.2
|
|
6
|
+
* Enhancement: add support for auth.type=denyall (will be default for security reasons in upcoming releases)
|
|
7
|
+
* Enhancement: display warning in case only default config is active
|
|
8
|
+
* Enhancement: display warning in case no user authentication is active
|
|
9
|
+
* Enhancement: add option to skip broken item to avoid triggering exception (default: enabled)
|
|
10
|
+
* Enhancement: add support for predefined collections for new users
|
|
11
|
+
* Enhancement: add options to enable several parts in debug log like backtrace, request_header, request_content, response_content (default: disabled)
|
|
12
|
+
* Enhancement: rights/from_file: display resulting permission of a match in debug log
|
|
13
|
+
* Enhancement: add Apache config file example (see contrib directory)
|
|
14
|
+
* Fix: "verify-collection" skips non-collection directories, logging improved
|
|
15
|
+
|
|
16
|
+
## 3.2.1
|
|
17
|
+
|
|
18
|
+
* Enhancement: add option for logging bad PUT request content
|
|
19
|
+
* Enhancement: extend logging with step where bad PUT request failed
|
|
20
|
+
* Fix: support for recurrence "full day"
|
|
21
|
+
* Fix: list of web_files related to HTML pages
|
|
22
|
+
* Test: update/adjustments for workflows (pytest>=7, typeguard<4.3)
|
|
23
|
+
|
|
3
24
|
## 3.2.0
|
|
4
25
|
|
|
5
26
|
* Enhancement: add hook support for event changes+deletion hooks (initial support: "rabbitmq")
|
|
@@ -216,6 +216,8 @@ requirements.
|
|
|
216
216
|
|
|
217
217
|
#### Linux with systemd system-wide
|
|
218
218
|
|
|
219
|
+
Recommendation: check support by [Linux Distribution Packages](#linux-distribution-packages) instead of manual setup / initial configuration.
|
|
220
|
+
|
|
219
221
|
Create the **radicale** user and group for the Radicale service. (Run
|
|
220
222
|
`useradd --system --user-group --home-dir / --shell /sbin/nologin radicale` as root.)
|
|
221
223
|
The storage folder must be writable by **radicale**. (Run
|
|
@@ -863,6 +865,12 @@ Delete sync-token that are older than the specified time. (seconds)
|
|
|
863
865
|
|
|
864
866
|
Default: `2592000`
|
|
865
867
|
|
|
868
|
+
#### skip_broken_item
|
|
869
|
+
|
|
870
|
+
Skip broken item instead of triggering an exception
|
|
871
|
+
|
|
872
|
+
Default: `True`
|
|
873
|
+
|
|
866
874
|
##### hook
|
|
867
875
|
|
|
868
876
|
Command that is run after changes to storage. Take a look at the
|
|
@@ -870,6 +878,26 @@ Command that is run after changes to storage. Take a look at the
|
|
|
870
878
|
|
|
871
879
|
Default:
|
|
872
880
|
|
|
881
|
+
##### predefined_collections
|
|
882
|
+
|
|
883
|
+
Create predefined user collections
|
|
884
|
+
|
|
885
|
+
Example:
|
|
886
|
+
|
|
887
|
+
{
|
|
888
|
+
"def-addressbook": {
|
|
889
|
+
"D:displayname": "Personal Address Book",
|
|
890
|
+
"tag": "VADDRESSBOOK"
|
|
891
|
+
},
|
|
892
|
+
"def-calendar": {
|
|
893
|
+
"C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO",
|
|
894
|
+
"D:displayname": "Personal Calendar",
|
|
895
|
+
"tag": "VCALENDAR"
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
Default:
|
|
900
|
+
|
|
873
901
|
#### web
|
|
874
902
|
|
|
875
903
|
##### type
|
|
@@ -902,6 +930,36 @@ Don't include passwords in logs.
|
|
|
902
930
|
|
|
903
931
|
Default: `True`
|
|
904
932
|
|
|
933
|
+
##### bad_put_request_content
|
|
934
|
+
|
|
935
|
+
Log bad PUT request content (for further diagnostics)
|
|
936
|
+
|
|
937
|
+
Default: `False`
|
|
938
|
+
|
|
939
|
+
##### backtrace_on_debug
|
|
940
|
+
|
|
941
|
+
Log backtrace on level=debug
|
|
942
|
+
|
|
943
|
+
Default: `False`
|
|
944
|
+
|
|
945
|
+
##### request_header_on_debug
|
|
946
|
+
|
|
947
|
+
Log request on level=debug
|
|
948
|
+
|
|
949
|
+
Default: `False`
|
|
950
|
+
|
|
951
|
+
##### request_content_on_debug
|
|
952
|
+
|
|
953
|
+
Log request on level=debug
|
|
954
|
+
|
|
955
|
+
Default: `False`
|
|
956
|
+
|
|
957
|
+
##### response_content_on_debug = True
|
|
958
|
+
|
|
959
|
+
Log response on level=debug
|
|
960
|
+
|
|
961
|
+
Default: `False`
|
|
962
|
+
|
|
905
963
|
#### headers
|
|
906
964
|
|
|
907
965
|
In this section additional HTTP headers that are sent to clients can be
|
|
@@ -1079,6 +1137,8 @@ Delete the collections by running something like:
|
|
|
1079
1137
|
curl -u user -X DELETE 'http://localhost:5232/user/calendar'
|
|
1080
1138
|
```
|
|
1081
1139
|
|
|
1140
|
+
Note: requires config/option `permit_delete_collection = True`
|
|
1141
|
+
|
|
1082
1142
|
### Authentication and Rights
|
|
1083
1143
|
|
|
1084
1144
|
This section describes the format of the rights file for the `from_file`
|
|
@@ -1509,7 +1569,7 @@ Radicale has been packaged for:
|
|
|
1509
1569
|
* [Debian](http://packages.debian.org/radicale) by Jonas Smedegaard
|
|
1510
1570
|
* [Gentoo](https://packages.gentoo.org/packages/www-apps/radicale)
|
|
1511
1571
|
by René Neumann, Maxim Koltsov and Manuel Rüger
|
|
1512
|
-
* [Fedora/
|
|
1572
|
+
* [Fedora/EnterpriseLinux](https://src.fedoraproject.org/rpms/radicale) by Jorti
|
|
1513
1573
|
and Peter Bieringer
|
|
1514
1574
|
* [Mageia](http://madb.mageia.org/package/show/application/0/name/radicale)
|
|
1515
1575
|
by Jani Välimaa
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: Radicale
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: CalDAV and CardDAV Server
|
|
5
5
|
Home-page: https://radicale.org/
|
|
6
6
|
Author: Guillaume Ayoub
|
|
@@ -34,8 +34,8 @@ Requires-Dist: python-dateutil>=2.7.3
|
|
|
34
34
|
Requires-Dist: pika>=1.1.0
|
|
35
35
|
Requires-Dist: setuptools; python_version < "3.9"
|
|
36
36
|
Provides-Extra: test
|
|
37
|
-
Requires-Dist: pytest
|
|
38
|
-
Requires-Dist: typeguard<3; extra == "test"
|
|
37
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
38
|
+
Requires-Dist: typeguard<4.3; extra == "test"
|
|
39
39
|
Requires-Dist: waitress; extra == "test"
|
|
40
40
|
Requires-Dist: bcrypt; extra == "test"
|
|
41
41
|
Provides-Extra: bcrypt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: Radicale
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: CalDAV and CardDAV Server
|
|
5
5
|
Home-page: https://radicale.org/
|
|
6
6
|
Author: Guillaume Ayoub
|
|
@@ -34,8 +34,8 @@ Requires-Dist: python-dateutil>=2.7.3
|
|
|
34
34
|
Requires-Dist: pika>=1.1.0
|
|
35
35
|
Requires-Dist: setuptools; python_version < "3.9"
|
|
36
36
|
Provides-Extra: test
|
|
37
|
-
Requires-Dist: pytest
|
|
38
|
-
Requires-Dist: typeguard<3; extra == "test"
|
|
37
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
38
|
+
Requires-Dist: typeguard<4.3; extra == "test"
|
|
39
39
|
Requires-Dist: waitress; extra == "test"
|
|
40
40
|
Requires-Dist: bcrypt; extra == "test"
|
|
41
41
|
Provides-Extra: bcrypt
|
|
@@ -40,6 +40,7 @@ radicale/app/proppatch.py
|
|
|
40
40
|
radicale/app/put.py
|
|
41
41
|
radicale/app/report.py
|
|
42
42
|
radicale/auth/__init__.py
|
|
43
|
+
radicale/auth/denyall.py
|
|
43
44
|
radicale/auth/htpasswd.py
|
|
44
45
|
radicale/auth/http_x_remote_user.py
|
|
45
46
|
radicale/auth/none.py
|
|
@@ -76,4 +77,11 @@ radicale/web/none.py
|
|
|
76
77
|
radicale/web/internal_data/fn.js
|
|
77
78
|
radicale/web/internal_data/index.html
|
|
78
79
|
radicale/web/internal_data/css/icon.png
|
|
79
|
-
radicale/web/internal_data/css/
|
|
80
|
+
radicale/web/internal_data/css/loading.svg
|
|
81
|
+
radicale/web/internal_data/css/logo.svg
|
|
82
|
+
radicale/web/internal_data/css/main.css
|
|
83
|
+
radicale/web/internal_data/css/icons/delete.svg
|
|
84
|
+
radicale/web/internal_data/css/icons/download.svg
|
|
85
|
+
radicale/web/internal_data/css/icons/edit.svg
|
|
86
|
+
radicale/web/internal_data/css/icons/new.svg
|
|
87
|
+
radicale/web/internal_data/css/icons/upload.svg
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
[auth]
|
|
54
54
|
|
|
55
55
|
# Authentication method
|
|
56
|
-
# Value: none | htpasswd | remote_user | http_x_remote_user
|
|
56
|
+
# Value: none | htpasswd | remote_user | http_x_remote_user | denyall
|
|
57
57
|
#type = none
|
|
58
58
|
|
|
59
59
|
# Htpasswd filename
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
# Message displayed in the client when a password is needed
|
|
71
71
|
#realm = Radicale - Password Required
|
|
72
72
|
|
|
73
|
-
#
|
|
73
|
+
# Convert username to lowercase, must be true for case-insensitive auth providers
|
|
74
74
|
#lc_username = False
|
|
75
75
|
|
|
76
76
|
|
|
@@ -99,10 +99,31 @@
|
|
|
99
99
|
# Delete sync token that are older (seconds)
|
|
100
100
|
#max_sync_token_age = 2592000
|
|
101
101
|
|
|
102
|
+
# Skip broken item instead of triggering an exception
|
|
103
|
+
#skip_broken_item = True
|
|
104
|
+
|
|
102
105
|
# Command that is run after changes to storage
|
|
103
106
|
# Example: ([ -d .git ] || git init) && git add -A && (git diff --cached --quiet || git commit -m "Changes by \"%(user)s\"")
|
|
104
107
|
#hook =
|
|
105
108
|
|
|
109
|
+
# Create predefined user collections
|
|
110
|
+
#
|
|
111
|
+
# json format:
|
|
112
|
+
#
|
|
113
|
+
# {
|
|
114
|
+
# "def-addressbook": {
|
|
115
|
+
# "D:displayname": "Personal Address Book",
|
|
116
|
+
# "tag": "VADDRESSBOOK"
|
|
117
|
+
# },
|
|
118
|
+
# "def-calendar": {
|
|
119
|
+
# "C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO",
|
|
120
|
+
# "D:displayname": "Personal Calendar",
|
|
121
|
+
# "tag": "VCALENDAR"
|
|
122
|
+
# }
|
|
123
|
+
# }
|
|
124
|
+
#
|
|
125
|
+
#predefined_collections =
|
|
126
|
+
|
|
106
127
|
|
|
107
128
|
[web]
|
|
108
129
|
|
|
@@ -120,6 +141,21 @@
|
|
|
120
141
|
# Don't include passwords in logs
|
|
121
142
|
#mask_passwords = True
|
|
122
143
|
|
|
144
|
+
# Log bad PUT request content
|
|
145
|
+
#bad_put_request_content = False
|
|
146
|
+
|
|
147
|
+
# Log backtrace on level=debug
|
|
148
|
+
#backtrace_on_debug = False
|
|
149
|
+
|
|
150
|
+
# Log request header on level=debug
|
|
151
|
+
#request_header_on_debug = False
|
|
152
|
+
|
|
153
|
+
# Log request content on level=debug
|
|
154
|
+
#request_content_on_debug = False
|
|
155
|
+
|
|
156
|
+
# Log response content on level=debug
|
|
157
|
+
#response_content_on_debug = False
|
|
158
|
+
|
|
123
159
|
|
|
124
160
|
[headers]
|
|
125
161
|
|
|
@@ -133,4 +169,4 @@
|
|
|
133
169
|
#type = none
|
|
134
170
|
#rabbitmq_endpoint =
|
|
135
171
|
#rabbitmq_topic =
|
|
136
|
-
#rabbitmq_queue_type = classic
|
|
172
|
+
#rabbitmq_queue_type = classic
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Copyright © 2008 Nicolas Kandel
|
|
3
3
|
# Copyright © 2008 Pascal Halter
|
|
4
4
|
# Copyright © 2008-2017 Guillaume Ayoub
|
|
5
|
-
# Copyright © 2017-
|
|
5
|
+
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
|
6
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
6
7
|
#
|
|
7
8
|
# This library is free software: you can redistribute it and/or modify
|
|
8
9
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -51,11 +52,16 @@ def _get_application_instance(config_path: str, wsgi_errors: types.ErrorStream
|
|
|
51
52
|
configuration = config.load(config.parse_compound_paths(
|
|
52
53
|
config.DEFAULT_CONFIG_PATH,
|
|
53
54
|
config_path))
|
|
54
|
-
log.set_level(cast(str, configuration.get("logging", "level")))
|
|
55
|
+
log.set_level(cast(str, configuration.get("logging", "level")), configuration.get("logging", "backtrace_on_debug"))
|
|
55
56
|
# Log configuration after logger is configured
|
|
57
|
+
default_config_active = True
|
|
56
58
|
for source, miss in configuration.sources():
|
|
57
|
-
logger.info("%s %s", "Skipped missing" if miss
|
|
59
|
+
logger.info("%s %s", "Skipped missing/unreadable" if miss
|
|
58
60
|
else "Loaded", source)
|
|
61
|
+
if not miss and source != "default config":
|
|
62
|
+
default_config_active = False
|
|
63
|
+
if default_config_active:
|
|
64
|
+
logger.warn("%s", "No config file found/readable - only default config is active")
|
|
59
65
|
_application_instance = Application(configuration)
|
|
60
66
|
if _application_config_path != config_path:
|
|
61
67
|
raise ValueError("RADICALE_CONFIG must not change: %r != %r" %
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# This file is part of Radicale - CalDAV and CardDAV server
|
|
2
2
|
# Copyright © 2011-2017 Guillaume Ayoub
|
|
3
|
-
# Copyright © 2017-
|
|
3
|
+
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
|
4
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
4
5
|
#
|
|
5
6
|
# This library is free software: you can redistribute it and/or modify
|
|
6
7
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -141,7 +142,7 @@ def run() -> None:
|
|
|
141
142
|
# Preliminary configure logging
|
|
142
143
|
with contextlib.suppress(ValueError):
|
|
143
144
|
log.set_level(config.DEFAULT_CONFIG_SCHEMA["logging"]["level"]["type"](
|
|
144
|
-
vars(args_ns).get("c:logging:level", "")))
|
|
145
|
+
vars(args_ns).get("c:logging:level", "")), True)
|
|
145
146
|
|
|
146
147
|
# Update Radicale configuration according to arguments
|
|
147
148
|
arguments_config: types.MUTABLE_CONFIG = {}
|
|
@@ -164,11 +165,17 @@ def run() -> None:
|
|
|
164
165
|
sys.exit(1)
|
|
165
166
|
|
|
166
167
|
# Configure logging
|
|
167
|
-
log.set_level(cast(str, configuration.get("logging", "level")))
|
|
168
|
+
log.set_level(cast(str, configuration.get("logging", "level")), configuration.get("logging", "backtrace_on_debug"))
|
|
168
169
|
|
|
169
170
|
# Log configuration after logger is configured
|
|
171
|
+
default_config_active = True
|
|
170
172
|
for source, miss in configuration.sources():
|
|
171
|
-
logger.info("%s %s", "Skipped missing" if miss else "Loaded", source)
|
|
173
|
+
logger.info("%s %s", "Skipped missing/unreadable" if miss else "Loaded", source)
|
|
174
|
+
if not miss and source != "default config":
|
|
175
|
+
default_config_active = False
|
|
176
|
+
|
|
177
|
+
if default_config_active:
|
|
178
|
+
logger.warn("%s", "No config file found/readable - only default config is active")
|
|
172
179
|
|
|
173
180
|
if args_ns.verify_storage:
|
|
174
181
|
logger.info("Verifying storage")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Copyright © 2008 Pascal Halter
|
|
4
4
|
# Copyright © 2008-2017 Guillaume Ayoub
|
|
5
5
|
# Copyright © 2017-2019 Unrud <unrud@outlook.com>
|
|
6
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
6
7
|
#
|
|
7
8
|
# This library is free software: you can redistribute it and/or modify
|
|
8
9
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -80,6 +81,9 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
80
81
|
"""
|
|
81
82
|
super().__init__(configuration)
|
|
82
83
|
self._mask_passwords = configuration.get("logging", "mask_passwords")
|
|
84
|
+
self._bad_put_request_content = configuration.get("logging", "bad_put_request_content")
|
|
85
|
+
self._request_header_on_debug = configuration.get("logging", "request_header_on_debug")
|
|
86
|
+
self._response_content_on_debug = configuration.get("logging", "response_content_on_debug")
|
|
83
87
|
self._auth_delay = configuration.get("auth", "delay")
|
|
84
88
|
self._internal_server = configuration.get("server", "_internal_server")
|
|
85
89
|
self._max_content_length = configuration.get(
|
|
@@ -139,7 +143,10 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
139
143
|
answers = []
|
|
140
144
|
if answer is not None:
|
|
141
145
|
if isinstance(answer, str):
|
|
142
|
-
|
|
146
|
+
if self._response_content_on_debug:
|
|
147
|
+
logger.debug("Response content:\n%s", answer)
|
|
148
|
+
else:
|
|
149
|
+
logger.debug("Response content: suppressed by config/option [auth] response_content_on_debug")
|
|
143
150
|
headers["Content-Type"] += "; charset=%s" % self._encoding
|
|
144
151
|
answer = answer.encode(self._encoding)
|
|
145
152
|
accept_encoding = [
|
|
@@ -185,8 +192,11 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
185
192
|
logger.info("%s request for %r%s received from %s%s",
|
|
186
193
|
request_method, unsafe_path, depthinfo,
|
|
187
194
|
remote_host, remote_useragent)
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
if self._request_header_on_debug:
|
|
196
|
+
logger.debug("Request header:\n%s",
|
|
197
|
+
pprint.pformat(self._scrub_headers(environ)))
|
|
198
|
+
else:
|
|
199
|
+
logger.debug("Request header: suppressed by config/option [auth] request_header_on_debug")
|
|
190
200
|
|
|
191
201
|
# SCRIPT_NAME is already removed from PATH_INFO, according to the
|
|
192
202
|
# WSGI specification.
|
|
@@ -268,7 +278,14 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
|
|
268
278
|
if "W" in self._rights.authorization(user, principal_path):
|
|
269
279
|
with self._storage.acquire_lock("w", user):
|
|
270
280
|
try:
|
|
271
|
-
self._storage.create_collection(principal_path)
|
|
281
|
+
new_coll = self._storage.create_collection(principal_path)
|
|
282
|
+
if new_coll:
|
|
283
|
+
jsn_coll = self.configuration.get("storage", "predefined_collections")
|
|
284
|
+
for (name_coll, props) in jsn_coll.items():
|
|
285
|
+
try:
|
|
286
|
+
self._storage.create_collection(principal_path + name_coll, props=props)
|
|
287
|
+
except ValueError as e:
|
|
288
|
+
logger.warning("Failed to create predefined collection %r: %s", name_coll, e)
|
|
272
289
|
except ValueError as e:
|
|
273
290
|
logger.warning("Failed to create principal "
|
|
274
291
|
"collection %r: %s", user, e)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# This file is part of Radicale - CalDAV and CardDAV server
|
|
2
2
|
# Copyright © 2020 Unrud <unrud@outlook.com>
|
|
3
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
3
4
|
#
|
|
4
5
|
# This library is free software: you can redistribute it and/or modify
|
|
5
6
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -48,6 +49,8 @@ class ApplicationBase:
|
|
|
48
49
|
self._rights = rights.load(configuration)
|
|
49
50
|
self._web = web.load(configuration)
|
|
50
51
|
self._encoding = configuration.get("encoding", "request")
|
|
52
|
+
self._log_bad_put_request_content = configuration.get("logging", "bad_put_request_content")
|
|
53
|
+
self._response_content_on_debug = configuration.get("logging", "response_content_on_debug")
|
|
51
54
|
self._hook = hook.load(configuration)
|
|
52
55
|
|
|
53
56
|
def _read_xml_request_body(self, environ: types.WSGIEnviron
|
|
@@ -69,8 +72,11 @@ class ApplicationBase:
|
|
|
69
72
|
|
|
70
73
|
def _xml_response(self, xml_content: ET.Element) -> bytes:
|
|
71
74
|
if logger.isEnabledFor(logging.DEBUG):
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
if self._response_content_on_debug:
|
|
76
|
+
logger.debug("Response content:\n%s",
|
|
77
|
+
xmlutils.pretty_xml(xml_content))
|
|
78
|
+
else:
|
|
79
|
+
logger.debug("Response content: suppressed by config/option [auth] response_content_on_debug")
|
|
74
80
|
f = io.BytesIO()
|
|
75
81
|
ET.ElementTree(xml_content).write(f, encoding=self._encoding,
|
|
76
82
|
xml_declaration=True)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Copyright © 2008 Pascal Halter
|
|
4
4
|
# Copyright © 2008-2017 Guillaume Ayoub
|
|
5
5
|
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
|
6
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
6
7
|
#
|
|
7
8
|
# This library is free software: you can redistribute it and/or modify
|
|
8
9
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -133,7 +134,7 @@ class ApplicationPartPut(ApplicationBase):
|
|
|
133
134
|
try:
|
|
134
135
|
content = httputils.read_request_body(self.configuration, environ)
|
|
135
136
|
except RuntimeError as e:
|
|
136
|
-
logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
|
|
137
|
+
logger.warning("Bad PUT request on %r (read_request_body): %s", path, e, exc_info=True)
|
|
137
138
|
return httputils.BAD_REQUEST
|
|
138
139
|
except socket.timeout:
|
|
139
140
|
logger.debug("Client timed out", exc_info=True)
|
|
@@ -145,7 +146,11 @@ class ApplicationPartPut(ApplicationBase):
|
|
|
145
146
|
vobject_items = radicale_item.read_components(content or "")
|
|
146
147
|
except Exception as e:
|
|
147
148
|
logger.warning(
|
|
148
|
-
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
|
149
|
+
"Bad PUT request on %r (read_components): %s", path, e, exc_info=True)
|
|
150
|
+
if self._log_bad_put_request_content:
|
|
151
|
+
logger.warning("Bad PUT request content of %r:\n%s", path, content)
|
|
152
|
+
else:
|
|
153
|
+
logger.debug("Bad PUT request content: suppressed by config/option [auth] bad_put_request_content")
|
|
149
154
|
return httputils.BAD_REQUEST
|
|
150
155
|
(prepared_items, prepared_tag, prepared_write_whole_collection,
|
|
151
156
|
prepared_props, prepared_exc_info) = prepare(
|
|
@@ -199,7 +204,7 @@ class ApplicationPartPut(ApplicationBase):
|
|
|
199
204
|
props = prepared_props
|
|
200
205
|
if prepared_exc_info:
|
|
201
206
|
logger.warning(
|
|
202
|
-
"Bad PUT request on %r: %s", path, prepared_exc_info[1],
|
|
207
|
+
"Bad PUT request on %r (prepare): %s", path, prepared_exc_info[1],
|
|
203
208
|
exc_info=prepared_exc_info)
|
|
204
209
|
return httputils.BAD_REQUEST
|
|
205
210
|
|
|
@@ -216,7 +221,7 @@ class ApplicationPartPut(ApplicationBase):
|
|
|
216
221
|
self._hook.notify(hook_notification_item)
|
|
217
222
|
except ValueError as e:
|
|
218
223
|
logger.warning(
|
|
219
|
-
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
|
224
|
+
"Bad PUT request on %r (create_collection): %s", path, e, exc_info=True)
|
|
220
225
|
return httputils.BAD_REQUEST
|
|
221
226
|
else:
|
|
222
227
|
assert not isinstance(item, storage.BaseCollection)
|
|
@@ -238,7 +243,7 @@ class ApplicationPartPut(ApplicationBase):
|
|
|
238
243
|
self._hook.notify(hook_notification_item)
|
|
239
244
|
except ValueError as e:
|
|
240
245
|
logger.warning(
|
|
241
|
-
"Bad PUT request on %r: %s", path, e, exc_info=True)
|
|
246
|
+
"Bad PUT request on %r (upload): %s", path, e, exc_info=True)
|
|
242
247
|
return httputils.BAD_REQUEST
|
|
243
248
|
|
|
244
249
|
headers = {"ETag": etag}
|
|
@@ -24,8 +24,8 @@ import posixpath
|
|
|
24
24
|
import socket
|
|
25
25
|
import xml.etree.ElementTree as ET
|
|
26
26
|
from http import client
|
|
27
|
-
from typing import (Callable, Iterable, Iterator, List, Optional,
|
|
28
|
-
Tuple, Union)
|
|
27
|
+
from typing import (Any, Callable, Iterable, Iterator, List, Optional,
|
|
28
|
+
Sequence, Tuple, Union)
|
|
29
29
|
from urllib.parse import unquote, urlparse
|
|
30
30
|
|
|
31
31
|
import vobject.base
|
|
@@ -196,14 +196,17 @@ def _expand(
|
|
|
196
196
|
start: datetime.datetime,
|
|
197
197
|
end: datetime.datetime,
|
|
198
198
|
) -> ET.Element:
|
|
199
|
-
|
|
200
|
-
if hasattr(item.vobject_item.vevent, 'rrule'):
|
|
201
|
-
rruleset = item.vobject_item.vevent.getrruleset()
|
|
199
|
+
dt_format = '%Y%m%dT%H%M%SZ'
|
|
202
200
|
|
|
203
|
-
|
|
201
|
+
if type(item.vobject_item.vevent.dtstart.value) is datetime.date:
|
|
202
|
+
# If an event comes to us with a dt_start specified as a date
|
|
203
|
+
# then in the response we return the date, not datetime
|
|
204
|
+
dt_format = '%Y%m%d'
|
|
205
|
+
|
|
206
|
+
expanded_item, rruleset = _make_vobject_expanded_item(item, dt_format)
|
|
204
207
|
|
|
205
208
|
if rruleset:
|
|
206
|
-
recurrences = rruleset.between(start, end)
|
|
209
|
+
recurrences = rruleset.between(start, end, inc=True)
|
|
207
210
|
|
|
208
211
|
expanded: vobject.base.Component = copy.copy(expanded_item.vobject_item)
|
|
209
212
|
is_expanded_filled: bool = False
|
|
@@ -214,7 +217,7 @@ def _expand(
|
|
|
214
217
|
vevent = copy.deepcopy(expanded.vevent)
|
|
215
218
|
vevent.recurrence_id = ContentLine(
|
|
216
219
|
name='RECURRENCE-ID',
|
|
217
|
-
value=recurrence_utc.strftime(
|
|
220
|
+
value=recurrence_utc.strftime(dt_format), params={}
|
|
218
221
|
)
|
|
219
222
|
|
|
220
223
|
if is_expanded_filled is False:
|
|
@@ -231,8 +234,9 @@ def _expand(
|
|
|
231
234
|
|
|
232
235
|
|
|
233
236
|
def _make_vobject_expanded_item(
|
|
234
|
-
item: radicale_item.Item
|
|
235
|
-
|
|
237
|
+
item: radicale_item.Item,
|
|
238
|
+
dt_format: str,
|
|
239
|
+
) -> Tuple[radicale_item.Item, Optional[Any]]:
|
|
236
240
|
# https://www.rfc-editor.org/rfc/rfc4791#section-9.6.5
|
|
237
241
|
# The returned calendar components MUST NOT use recurrence
|
|
238
242
|
# properties (i.e., EXDATE, EXRULE, RDATE, and RRULE) and MUST NOT
|
|
@@ -243,17 +247,34 @@ def _make_vobject_expanded_item(
|
|
|
243
247
|
item = copy.copy(item)
|
|
244
248
|
vevent = item.vobject_item.vevent
|
|
245
249
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
+
if type(vevent.dtstart.value) is datetime.date:
|
|
251
|
+
start_utc = datetime.datetime.fromordinal(
|
|
252
|
+
vevent.dtstart.value.toordinal()
|
|
253
|
+
).replace(tzinfo=datetime.timezone.utc)
|
|
254
|
+
else:
|
|
255
|
+
start_utc = vevent.dtstart.value.astimezone(datetime.timezone.utc)
|
|
256
|
+
|
|
257
|
+
vevent.dtstart = ContentLine(name='DTSTART', value=start_utc, params=[])
|
|
250
258
|
|
|
251
259
|
dt_end = getattr(vevent, 'dtend', None)
|
|
252
260
|
if dt_end is not None:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
if type(vevent.dtend.value) is datetime.date:
|
|
262
|
+
end_utc = datetime.datetime.fromordinal(
|
|
263
|
+
dt_end.value.toordinal()
|
|
264
|
+
).replace(tzinfo=datetime.timezone.utc)
|
|
265
|
+
else:
|
|
266
|
+
end_utc = dt_end.value.astimezone(datetime.timezone.utc)
|
|
267
|
+
|
|
268
|
+
vevent.dtend = ContentLine(name='DTEND', value=end_utc, params={})
|
|
269
|
+
|
|
270
|
+
rruleset = None
|
|
271
|
+
if hasattr(item.vobject_item.vevent, 'rrule'):
|
|
272
|
+
rruleset = vevent.getrruleset()
|
|
273
|
+
|
|
274
|
+
# There is something strage behavour during serialization native datetime, so converting manualy
|
|
275
|
+
vevent.dtstart.value = vevent.dtstart.value.strftime(dt_format)
|
|
276
|
+
if dt_end is not None:
|
|
277
|
+
vevent.dtend.value = vevent.dtend.value.strftime(dt_format)
|
|
257
278
|
|
|
258
279
|
timezones_to_remove = []
|
|
259
280
|
for component in item.vobject_item.components():
|
|
@@ -271,7 +292,7 @@ def _make_vobject_expanded_item(
|
|
|
271
292
|
except AttributeError:
|
|
272
293
|
pass
|
|
273
294
|
|
|
274
|
-
return item
|
|
295
|
+
return item, rruleset
|
|
275
296
|
|
|
276
297
|
|
|
277
298
|
def xml_item_response(base_prefix: str, href: str,
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# Copyright © 2008 Nicolas Kandel
|
|
3
3
|
# Copyright © 2008 Pascal Halter
|
|
4
4
|
# Copyright © 2008-2017 Guillaume Ayoub
|
|
5
|
-
# Copyright © 2017-
|
|
5
|
+
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
|
6
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
6
7
|
#
|
|
7
8
|
# This library is free software: you can redistribute it and/or modify
|
|
8
9
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -31,13 +32,19 @@ Take a look at the class ``BaseAuth`` if you want to implement your own.
|
|
|
31
32
|
from typing import Sequence, Tuple, Union
|
|
32
33
|
|
|
33
34
|
from radicale import config, types, utils
|
|
35
|
+
from radicale.log import logger
|
|
34
36
|
|
|
35
37
|
INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
|
|
38
|
+
"denyall",
|
|
36
39
|
"htpasswd")
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
def load(configuration: "config.Configuration") -> "BaseAuth":
|
|
40
43
|
"""Load the authentication module chosen in configuration."""
|
|
44
|
+
if configuration.get("auth", "type") == "none":
|
|
45
|
+
logger.warning("No user authentication is selected: '[auth] type=none' (insecure)")
|
|
46
|
+
if configuration.get("auth", "type") == "denyall":
|
|
47
|
+
logger.warning("All access is blocked by: '[auth] type=denyall'")
|
|
41
48
|
return utils.load_plugin(INTERNAL_TYPES, "auth", "Auth", BaseAuth,
|
|
42
49
|
configuration)
|
|
43
50
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# This file is part of Radicale - CalDAV and CardDAV server
|
|
2
|
+
# Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
|
|
3
|
+
#
|
|
4
|
+
# This library is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
A dummy backend that denies any username and password.
|
|
19
|
+
|
|
20
|
+
Used as default for security reasons.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from radicale import auth
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Auth(auth.BaseAuth):
|
|
28
|
+
|
|
29
|
+
def _login(self, login: str, password: str) -> str:
|
|
30
|
+
return ""
|