Merge pull request #1918 from pbiering/improve-synctoken-log
Improve synctoken log
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## 3.5.9.dev
|
||||
* Extend: [auth] add support for type http_remote_user
|
||||
* Extend: logging of invalid sync-token with user, path, remote host and useragent
|
||||
|
||||
## 3.5.8
|
||||
* Extend: [auth] re-factor & overhaul LDAP authentication, especially for Python's ldap module
|
||||
|
||||
@@ -371,7 +371,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
|
||||
|
||||
if not login or user:
|
||||
status, headers, answer = function(
|
||||
environ, base_prefix, path, user)
|
||||
environ, base_prefix, path, user, remote_host, remote_useragent)
|
||||
if (status, headers, answer) == httputils.NOT_ALLOWED:
|
||||
logger.info("Access to %r denied for %s", path,
|
||||
repr(user) if user else "anonymous user")
|
||||
|
||||
@@ -55,7 +55,7 @@ def xml_delete(base_prefix: str, path: str, collection: storage.BaseCollection,
|
||||
class ApplicationPartDelete(ApplicationBase):
|
||||
|
||||
def do_DELETE(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage DELETE request."""
|
||||
access = Access(self._rights, user, path)
|
||||
if not access.check("w"):
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -58,7 +59,7 @@ class ApplicationPartGet(ApplicationBase):
|
||||
return value
|
||||
|
||||
def do_GET(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
|
||||
user: str) -> types.WSGIResponse:
|
||||
user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage GET request."""
|
||||
# Redirect to /.web if the root path is requested
|
||||
if not pathutils.strip_path(path):
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +26,7 @@ from radicale.app.get import ApplicationPartGet
|
||||
class ApplicationPartHead(ApplicationPartGet, ApplicationBase):
|
||||
|
||||
def do_HEAD(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
|
||||
user: str) -> types.WSGIResponse:
|
||||
user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage HEAD request."""
|
||||
# Body is dropped in `Application.__call__` for HEAD requests
|
||||
return self.do_GET(environ, base_prefix, path, user)
|
||||
return self.do_GET(environ, base_prefix, path, user, remote_host, remote_useragent)
|
||||
|
||||
@@ -33,7 +33,7 @@ from radicale.log import logger
|
||||
class ApplicationPartMkcalendar(ApplicationBase):
|
||||
|
||||
def do_MKCALENDAR(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage MKCALENDAR request."""
|
||||
if "w" not in self._rights.authorization(user, path):
|
||||
return httputils.NOT_ALLOWED
|
||||
|
||||
@@ -33,7 +33,7 @@ from radicale.log import logger
|
||||
class ApplicationPartMkcol(ApplicationBase):
|
||||
|
||||
def do_MKCOL(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage MKCOL request."""
|
||||
permissions = self._rights.authorization(user, path)
|
||||
if not rights.intersect(permissions, "Ww"):
|
||||
|
||||
@@ -48,7 +48,7 @@ def get_server_netloc(environ: types.WSGIEnviron, force_port: bool = False):
|
||||
class ApplicationPartMove(ApplicationBase):
|
||||
|
||||
def do_MOVE(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage MOVE request."""
|
||||
raw_dest = environ.get("HTTP_DESTINATION", "")
|
||||
to_url = urlparse(raw_dest)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -26,7 +27,7 @@ from radicale.app.base import ApplicationBase
|
||||
class ApplicationPartOptions(ApplicationBase):
|
||||
|
||||
def do_OPTIONS(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage OPTIONS request."""
|
||||
headers = {
|
||||
"Allow": ", ".join(
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2020 Tom Hacohen <tom@stosb.com>
|
||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2020-2020 Tom Hacohen <tom@stosb.com>
|
||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +26,7 @@ from radicale.app.base import ApplicationBase
|
||||
class ApplicationPartPost(ApplicationBase):
|
||||
|
||||
def do_POST(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage POST request."""
|
||||
if path == "/.web" or path.startswith("/.web/"):
|
||||
return self._web.post(environ, base_prefix, path, user)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Copyright © 2008 Nicolas Kandel
|
||||
# Copyright © 2008 Pascal Halter
|
||||
# Copyright © 2008-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2017-2021 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -376,7 +377,7 @@ class ApplicationPartPropfind(ApplicationBase):
|
||||
yield item, permission
|
||||
|
||||
def do_PROPFIND(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage PROPFIND request."""
|
||||
access = Access(self._rights, user, path)
|
||||
if not access.check("r"):
|
||||
|
||||
@@ -73,7 +73,7 @@ def xml_proppatch(base_prefix: str, path: str,
|
||||
class ApplicationPartProppatch(ApplicationBase):
|
||||
|
||||
def do_PROPPATCH(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage PROPPATCH request."""
|
||||
access = Access(self._rights, user, path)
|
||||
if not access.check("w"):
|
||||
|
||||
@@ -142,7 +142,7 @@ def prepare(vobject_items: List[vobject.base.Component], path: str,
|
||||
class ApplicationPartPut(ApplicationBase):
|
||||
|
||||
def do_PUT(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage PUT request."""
|
||||
access = Access(self._rights, user, path)
|
||||
if not access.check("w"):
|
||||
|
||||
@@ -149,7 +149,7 @@ def free_busy_report(base_prefix: str, path: str, xml_request: Optional[ET.Eleme
|
||||
def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
||||
collection: storage.BaseCollection, encoding: str,
|
||||
unlock_storage_fn: Callable[[], None],
|
||||
max_occurrence: int = 0,
|
||||
max_occurrence: int = 0, user: str = "", remote_addr: str = "", remote_useragent: str = ""
|
||||
) -> Tuple[int, ET.Element]:
|
||||
"""Read and answer REPORT requests that return XML.
|
||||
|
||||
@@ -213,8 +213,8 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
||||
sync_token, names = collection.sync(old_sync_token)
|
||||
except ValueError as e:
|
||||
# Invalid sync token
|
||||
logger.warning("Client provided invalid sync token %r: %s",
|
||||
old_sync_token, e, exc_info=True)
|
||||
logger.warning("Client provided invalid sync token for path %r (user %r from %s%s): %s",
|
||||
path, user, remote_addr, remote_useragent, e, exc_info=True)
|
||||
# client.CONFLICT doesn't work with some clients (e.g. InfCloud)
|
||||
return (client.FORBIDDEN,
|
||||
xmlutils.webdav_error("D:valid-sync-token"))
|
||||
@@ -776,7 +776,7 @@ def test_filter(collection_tag: str, item: radicale_item.Item,
|
||||
class ApplicationPartReport(ApplicationBase):
|
||||
|
||||
def do_REPORT(self, environ: types.WSGIEnviron, base_prefix: str,
|
||||
path: str, user: str) -> types.WSGIResponse:
|
||||
path: str, user: str, remote_host: str, remote_useragent: str) -> types.WSGIResponse:
|
||||
"""Manage REPORT request."""
|
||||
access = Access(self._rights, user, path)
|
||||
if not access.check("r"):
|
||||
@@ -820,7 +820,7 @@ class ApplicationPartReport(ApplicationBase):
|
||||
try:
|
||||
status, xml_answer = xml_report(
|
||||
base_prefix, path, xml_content, collection, self._encoding,
|
||||
lock_stack.close, max_occurrence)
|
||||
lock_stack.close, max_occurrence, user, remote_host, remote_useragent)
|
||||
except ValueError as e:
|
||||
logger.warning(
|
||||
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is part of Radicale - CalDAV and CardDAV server
|
||||
# Copyright © 2012-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2017-2023 Unrud <unrud@outlook.com>
|
||||
# Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -79,6 +80,8 @@ class BaseTest:
|
||||
if http_if_match is not None and not isinstance(http_if_match, str):
|
||||
raise TypeError("http_if_match argument must be %r, not %r" %
|
||||
(str, type(http_if_match)))
|
||||
remote_useragent = kwargs.pop("remote_useragent", None)
|
||||
remote_host = kwargs.pop("remote_host", None)
|
||||
environ: Dict[str, Any] = {k.upper(): v for k, v in kwargs.items()}
|
||||
for k, v in environ.items():
|
||||
if not isinstance(v, str):
|
||||
@@ -90,6 +93,10 @@ class BaseTest:
|
||||
login.encode(encoding)).decode()
|
||||
if http_if_match:
|
||||
environ["HTTP_IF_MATCH"] = http_if_match
|
||||
if remote_useragent:
|
||||
environ["HTTP_USER_AGENT"] = remote_useragent
|
||||
if remote_host:
|
||||
environ["REMOTE_ADDR"] = remote_host
|
||||
environ["REQUEST_METHOD"] = method.upper()
|
||||
environ["PATH_INFO"] = path
|
||||
if data is not None:
|
||||
|
||||
@@ -1687,7 +1687,7 @@ permissions: RrWw""")
|
||||
</C:free-busy-query>""", 400, is_xml=False)
|
||||
|
||||
def _report_sync_token(
|
||||
self, calendar_path: str, sync_token: Optional[str] = None
|
||||
self, calendar_path: str, sync_token: Optional[str] = None, **kwargs
|
||||
) -> Tuple[str, RESPONSES]:
|
||||
sync_token_xml = (
|
||||
"<sync-token><![CDATA[%s]]></sync-token>" % sync_token
|
||||
@@ -1699,7 +1699,7 @@ permissions: RrWw""")
|
||||
<getetag />
|
||||
</prop>
|
||||
%s
|
||||
</sync-collection>""" % sync_token_xml)
|
||||
</sync-collection>""" % sync_token_xml, **kwargs)
|
||||
xml = DefusedET.fromstring(answer)
|
||||
if status in (403, 409):
|
||||
assert xml.tag == xmlutils.make_clark("D:error")
|
||||
@@ -1847,6 +1847,15 @@ permissions: RrWw""")
|
||||
calendar_path, "http://radicale.org/ns/sync/INVALID")
|
||||
assert not sync_token
|
||||
|
||||
def test_report_sync_collection_invalid_sync_token_with_user(self) -> None:
|
||||
"""Test sync-collection report with an invalid sync token and user+host+useragent"""
|
||||
self.configure({"auth": {"type": "none"}})
|
||||
calendar_path = "/calendar.ics/"
|
||||
self.mkcalendar(calendar_path)
|
||||
sync_token, _ = self._report_sync_token(
|
||||
calendar_path, "http://radicale.org/ns/sync/INVALID", login="testuser:", remote_host="192.0.2.1", remote_useragent="Testclient/1.0")
|
||||
assert not sync_token
|
||||
|
||||
def test_propfind_sync_token(self) -> None:
|
||||
"""Retrieve the sync-token with a propfind request"""
|
||||
calendar_path = "/calendar.ics/"
|
||||
|
||||
Reference in New Issue
Block a user