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.
Files changed (88) hide show
  1. {radicale-3.2.0 → radicale-3.2.2}/CHANGELOG.md +21 -0
  2. {radicale-3.2.0 → radicale-3.2.2}/DOCUMENTATION.md +61 -1
  3. {radicale-3.2.0 → radicale-3.2.2}/PKG-INFO +3 -3
  4. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/PKG-INFO +3 -3
  5. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/SOURCES.txt +9 -1
  6. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/requires.txt +2 -2
  7. {radicale-3.2.0 → radicale-3.2.2}/config +39 -3
  8. {radicale-3.2.0 → radicale-3.2.2}/radicale/__init__.py +9 -3
  9. {radicale-3.2.0 → radicale-3.2.2}/radicale/__main__.py +11 -4
  10. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/__init__.py +21 -4
  11. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/base.py +8 -2
  12. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/put.py +10 -5
  13. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/report.py +40 -19
  14. {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/__init__.py +8 -1
  15. radicale-3.2.2/radicale/auth/denyall.py +30 -0
  16. {radicale-3.2.0 → radicale-3.2.2}/radicale/config.py +43 -2
  17. {radicale-3.2.0 → radicale-3.2.2}/radicale/httputils.py +6 -2
  18. {radicale-3.2.0 → radicale-3.2.2}/radicale/log.py +11 -4
  19. {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/from_file.py +4 -3
  20. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/base.py +3 -1
  21. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/get.py +8 -3
  22. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/verify.py +15 -5
  23. radicale-3.2.2/radicale/web/internal_data/css/icons/delete.svg +1 -0
  24. radicale-3.2.2/radicale/web/internal_data/css/icons/download.svg +1 -0
  25. radicale-3.2.2/radicale/web/internal_data/css/icons/edit.svg +1 -0
  26. radicale-3.2.2/radicale/web/internal_data/css/icons/new.svg +1 -0
  27. radicale-3.2.2/radicale/web/internal_data/css/icons/upload.svg +1 -0
  28. radicale-3.2.2/radicale/web/internal_data/css/loading.svg +72 -0
  29. radicale-3.2.2/radicale/web/internal_data/css/logo.svg +10 -0
  30. {radicale-3.2.0 → radicale-3.2.2}/setup.py +9 -3
  31. {radicale-3.2.0 → radicale-3.2.2}/COPYING.md +0 -0
  32. {radicale-3.2.0 → radicale-3.2.2}/MANIFEST.in +0 -0
  33. {radicale-3.2.0 → radicale-3.2.2}/README.md +0 -0
  34. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/dependency_links.txt +0 -0
  35. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/entry_points.txt +0 -0
  36. {radicale-3.2.0 → radicale-3.2.2}/Radicale.egg-info/top_level.txt +0 -0
  37. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/delete.py +0 -0
  38. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/get.py +0 -0
  39. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/head.py +0 -0
  40. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/mkcalendar.py +0 -0
  41. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/mkcol.py +0 -0
  42. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/move.py +0 -0
  43. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/options.py +0 -0
  44. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/post.py +0 -0
  45. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/propfind.py +0 -0
  46. {radicale-3.2.0 → radicale-3.2.2}/radicale/app/proppatch.py +0 -0
  47. {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/htpasswd.py +0 -0
  48. {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/http_x_remote_user.py +0 -0
  49. {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/none.py +0 -0
  50. {radicale-3.2.0 → radicale-3.2.2}/radicale/auth/remote_user.py +0 -0
  51. {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/__init__.py +0 -0
  52. {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/none.py +0 -0
  53. {radicale-3.2.0 → radicale-3.2.2}/radicale/hook/rabbitmq/__init__.py +0 -0
  54. {radicale-3.2.0 → radicale-3.2.2}/radicale/item/__init__.py +0 -0
  55. {radicale-3.2.0 → radicale-3.2.2}/radicale/item/filter.py +0 -0
  56. {radicale-3.2.0 → radicale-3.2.2}/radicale/pathutils.py +0 -0
  57. {radicale-3.2.0 → radicale-3.2.2}/radicale/py.typed +0 -0
  58. {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/__init__.py +0 -0
  59. {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/authenticated.py +0 -0
  60. {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/owner_only.py +0 -0
  61. {radicale-3.2.0 → radicale-3.2.2}/radicale/rights/owner_write.py +0 -0
  62. {radicale-3.2.0 → radicale-3.2.2}/radicale/server.py +0 -0
  63. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/__init__.py +0 -0
  64. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/__init__.py +0 -0
  65. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/cache.py +0 -0
  66. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/create_collection.py +0 -0
  67. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/delete.py +0 -0
  68. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/discover.py +0 -0
  69. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/history.py +0 -0
  70. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/lock.py +0 -0
  71. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/meta.py +0 -0
  72. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/move.py +0 -0
  73. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/sync.py +0 -0
  74. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem/upload.py +0 -0
  75. {radicale-3.2.0 → radicale-3.2.2}/radicale/storage/multifilesystem_nolock.py +0 -0
  76. {radicale-3.2.0 → radicale-3.2.2}/radicale/types.py +0 -0
  77. {radicale-3.2.0 → radicale-3.2.2}/radicale/utils.py +0 -0
  78. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/__init__.py +0 -0
  79. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal.py +0 -0
  80. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/css/icon.png +0 -0
  81. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/css/main.css +0 -0
  82. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/fn.js +0 -0
  83. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/internal_data/index.html +0 -0
  84. {radicale-3.2.0 → radicale-3.2.2}/radicale/web/none.py +0 -0
  85. {radicale-3.2.0 → radicale-3.2.2}/radicale/xmlutils.py +0 -0
  86. {radicale-3.2.0 → radicale-3.2.2}/radicale.wsgi +0 -0
  87. {radicale-3.2.0 → radicale-3.2.2}/rights +0 -0
  88. {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/RHEL/CentOS](https://src.fedoraproject.org/rpms/radicale) by Jorti
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.0
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<7; extra == "test"
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.0
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<7; extra == "test"
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/main.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
@@ -11,7 +11,7 @@ setuptools
11
11
  bcrypt
12
12
 
13
13
  [test]
14
- pytest<7
15
- typeguard<3
14
+ pytest>=7
15
+ typeguard<4.3
16
16
  waitress
17
17
  bcrypt
@@ -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
- # Сonvert username to lowercase, must be true for case-insensitive auth providers
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-2019 Unrud <unrud@outlook.com>
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-2019 Unrud <unrud@outlook.com>
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
- logger.debug("Response content:\n%s", answer)
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
- logger.debug("Request headers:\n%s",
189
- pprint.pformat(self._scrub_headers(environ)))
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
- logger.debug("Response content:\n%s",
73
- xmlutils.pretty_xml(xml_content))
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, Sequence,
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
- rruleset = None
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
- expanded_item = _make_vobject_expanded_item(item)
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('%Y%m%dT%H%M%SZ'), params={}
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
- ) -> radicale_item.Item:
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
- start_utc = vevent.dtstart.value.astimezone(datetime.timezone.utc)
247
- vevent.dtstart = ContentLine(
248
- name='DTSTART',
249
- value=start_utc.strftime('%Y%m%dT%H%M%SZ'), params={})
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
- end_utc = dt_end.value.astimezone(datetime.timezone.utc)
254
- vevent.dtend = ContentLine(
255
- name='DTEND',
256
- value=end_utc.strftime('%Y%m%dT%H%M%SZ'), params={})
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-2018 Unrud <unrud@outlook.com>
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 ""