Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package Radicale for openSUSE:Factory checked in at 2025-12-09 12:48:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/Radicale (Old) and /work/SRC/openSUSE:Factory/.Radicale.new.1939 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "Radicale" Tue Dec 9 12:48:20 2025 rev:27 rq:1321525 version:3.5.9 Changes: -------- --- /work/SRC/openSUSE:Factory/Radicale/Radicale.changes 2025-11-17 12:24:05.551576792 +0100 +++ /work/SRC/openSUSE:Factory/.Radicale.new.1939/Radicale.changes 2025-12-09 12:55:55.698003482 +0100 @@ -1,0 +2,8 @@ +Mon Dec 1 08:40:59 UTC 2025 - Ákos Szőts <[email protected]> + +- Update to 3.5.9 + * Extend: [auth] add support for type http_remote_user + * Extend: logging of invalid sync-token with user, path, remote host and useragent + * Fix: typo related to collection delete hook (can cause OOM error) + +------------------------------------------------------------------- Old: ---- v3.5.8.tar.gz New: ---- v3.5.9.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ Radicale.spec ++++++ --- /var/tmp/diff_new_pack.cPaMZu/_old 2025-12-09 12:55:57.978099664 +0100 +++ /var/tmp/diff_new_pack.cPaMZu/_new 2025-12-09 12:55:57.978099664 +0100 @@ -27,7 +27,7 @@ %define pk_min_ver 1.1.0 %define pt_min_ver 7 Name: Radicale -Version: 3.5.8 +Version: 3.5.9 Release: 0 Summary: A CalDAV calendar and CardDav contact server License: GPL-3.0-or-later ++++++ v3.5.8.tar.gz -> v3.5.9.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/.github/workflows/test.yml new/Radicale-3.5.9/.github/workflows/test.yml --- old/Radicale-3.5.8/.github/workflows/test.yml 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/.github/workflows/test.yml 2025-11-29 15:33:06.000000000 +0100 @@ -6,7 +6,7 @@ strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.9', 'pypy-3.10', 'pypy-3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.9', 'pypy-3.10', 'pypy-3.11'] exclude: - os: windows-latest python-version: 'pypy-3.9' @@ -72,7 +72,7 @@ - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.13.0' + python-version: '3.13' - name: Install tox run: pip install tox - name: Lint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/CHANGELOG.md new/Radicale-3.5.9/CHANGELOG.md --- old/Radicale-3.5.8/CHANGELOG.md 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/CHANGELOG.md 2025-11-29 15:33:06.000000000 +0100 @@ -1,7 +1,12 @@ # Changelog +## 3.5.9 +* Extend: [auth] add support for type http_remote_user +* Extend: logging of invalid sync-token with user, path, remote host and useragent +* Fix: typo related to collection delete hook + ## 3.5.8 -* Extend [auth]: re-factor & overhaul LDAP authentication, especially for Python's ldap module +* Extend: [auth] re-factor & overhaul LDAP authentication, especially for Python's ldap module * Fix: out-of-range timestamp on 32-bit systems * Feature: extend logging with response size in bytes and flag served as plain or gzip * Feature: [storage] strict_preconditions: new config option to enforce strict preconditions check on PUT in case item already exists [RFC6352#9.2] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/DOCUMENTATION.md new/Radicale-3.5.9/DOCUMENTATION.md --- old/Radicale-3.5.8/DOCUMENTATION.md 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/DOCUMENTATION.md 2025-11-29 15:33:06.000000000 +0100 @@ -272,7 +272,7 @@ Recommendation: check support by [Linux Distribution Packages](#linux-distribution-packages) instead of manual setup / initial configuration. -Create the **radicale** user and group for the Radicale service by running (as `root`: +Create the **radicale** user and group for the Radicale service by running (as `root`): ```bash useradd --system --user-group --home-dir / --shell /sbin/nologin radicale ``` @@ -902,8 +902,15 @@ Requires validation, otherwise clients can supply the header themselves, which then is unconditionally trusted. +* `http_remote_user` _(>= 3.5.9)_ + Takes the username from the Remote-User HTTP header `HTTP_REMOTE_USER` and disables + Radicale's internal HTTP authentication. This can be used to provide the + username from a reverse proxy which authenticated the client upfront. + Requires validation, otherwise clients can supply the header themselves, + which then is unconditionally trusted. + * `http_x_remote_user` - Takes the username from the `X-Remote-User` HTTP header and disables + Takes the username from the X-Remote-User HTTP header `HTTP_X_REMOTE_USER` and disables Radicale's internal HTTP authentication. This can be used to provide the username from a reverse proxy which authenticated the client upfront. Requires validation, otherwise clients can supply the header themselves, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/config new/Radicale-3.5.9/config --- old/Radicale-3.5.8/config 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/config 2025-11-29 15:33:06.000000000 +0100 @@ -63,7 +63,7 @@ [auth] # Authentication method -# Value: none | htpasswd | remote_user | http_x_remote_user | dovecot | ldap | oauth2 | pam | denyall +# Value: none | htpasswd | remote_user | http_remote_user | http_x_remote_user | dovecot | ldap | oauth2 | pam | denyall #type = denyall # Cache logins for until expiration time diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/contrib/caddy/radicale.caddyfile new/Radicale-3.5.9/contrib/caddy/radicale.caddyfile --- old/Radicale-3.5.8/contrib/caddy/radicale.caddyfile 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/contrib/caddy/radicale.caddyfile 2025-11-29 15:33:06.000000000 +0100 @@ -16,11 +16,17 @@ not path /.web/* } + # disable this in case authentication is handled by Radicale basic_auth @not-webui { USER HASH } reverse_proxy localhost:5232 { + # disable this in case authentication is handled by Radicale header_up X-Remote-User {http.auth.user.id} + # replace "HOST" with configured hostname of URL (FQDN) in client + header_up Host HOST + # replace "PORT" with configured port of URL in client + header_up X-Forwarded-Port PORT } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/pyproject.toml new/Radicale-3.5.9/pyproject.toml --- old/Radicale-3.5.8/pyproject.toml 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/pyproject.toml 2025-11-29 15:33:06.000000000 +0100 @@ -3,7 +3,7 @@ # When the version is updated, a new section in the CHANGELOG.md file must be # added too. readme = "README.md" -version = "3.5.8" +version = "3.5.9" authors = [{name = "Guillaume Ayoub", email = "[email protected]"}, {name = "Unrud", email = "[email protected]"}, {name = "Peter Bieringer", email = "[email protected]"}] license = {text = "GNU GPL v3"} description = "CalDAV and CardDAV Server" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/__init__.py new/Radicale-3.5.9/radicale/app/__init__.py --- old/Radicale-3.5.8/radicale/app/__init__.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/__init__.py 2025-11-29 15:33:06.000000000 +0100 @@ -275,7 +275,7 @@ logger.debug("Called by reverse proxy, remove base prefix %r from path: %r => %r", base_prefix, path, path_new) path = path_new else: - if self._auth_type in ['remote_user', 'http_x_remote_user'] and self._web_type == 'internal': + if self._auth_type in ['remote_user', 'http_remote_user', 'http_x_remote_user'] and self._web_type == 'internal': logger.warning("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching (may cause authentication issues using internal WebUI)", base_prefix, path) else: logger.debug("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching", base_prefix, path) @@ -371,7 +371,7 @@ 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") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/delete.py new/Radicale-3.5.9/radicale/app/delete.py --- old/Radicale-3.5.8/radicale/app/delete.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/delete.py 2025-11-29 15:33:06.000000000 +0100 @@ -55,7 +55,7 @@ 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"): @@ -87,7 +87,7 @@ path=access.path, content=i.uid, uid=i.uid, - old_content=item.serialize(), # type: ignore + old_content=i.serialize(), # type: ignore new_content=None ) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/get.py new/Radicale-3.5.9/radicale/app/get.py --- old/Radicale-3.5.8/radicale/app/get.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/get.py 2025-11-29 15:33:06.000000000 +0100 @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> +# Copyright © 2017-2023 Unrud <[email protected]> +# Copyright © 2025-2025 Peter Bieringer <[email protected]> # # 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 @@ 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): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/head.py new/Radicale-3.5.9/radicale/app/head.py --- old/Radicale-3.5.8/radicale/app/head.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/head.py 2025-11-29 15:33:06.000000000 +0100 @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> +# Copyright © 2017-2022 Unrud <[email protected]> +# Copyright © 2025-2025 Peter Bieringer <[email protected]> # # 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 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/mkcalendar.py new/Radicale-3.5.9/radicale/app/mkcalendar.py --- old/Radicale-3.5.8/radicale/app/mkcalendar.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/mkcalendar.py 2025-11-29 15:33:06.000000000 +0100 @@ -33,7 +33,7 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/mkcol.py new/Radicale-3.5.9/radicale/app/mkcol.py --- old/Radicale-3.5.8/radicale/app/mkcol.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/mkcol.py 2025-11-29 15:33:06.000000000 +0100 @@ -33,7 +33,7 @@ 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"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/move.py new/Radicale-3.5.9/radicale/app/move.py --- old/Radicale-3.5.8/radicale/app/move.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/move.py 2025-11-29 15:33:06.000000000 +0100 @@ -48,7 +48,7 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/options.py new/Radicale-3.5.9/radicale/app/options.py --- old/Radicale-3.5.8/radicale/app/options.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/options.py 2025-11-29 15:33:06.000000000 +0100 @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> +# Copyright © 2017-2021 Unrud <[email protected]> +# Copyright © 2025-2025 Peter Bieringer <[email protected]> # # 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 @@ 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( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/post.py new/Radicale-3.5.9/radicale/app/post.py --- old/Radicale-3.5.8/radicale/app/post.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/post.py 2025-11-29 15:33:06.000000000 +0100 @@ -2,8 +2,9 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> -# Copyright © 2020 Tom Hacohen <[email protected]> +# Copyright © 2017-2021 Unrud <[email protected]> +# Copyright © 2020-2020 Tom Hacohen <[email protected]> +# Copyright © 2025-2025 Peter Bieringer <[email protected]> # # 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 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/propfind.py new/Radicale-3.5.9/radicale/app/propfind.py --- old/Radicale-3.5.8/radicale/app/propfind.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/propfind.py 2025-11-29 15:33:06.000000000 +0100 @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> +# Copyright © 2017-2021 Unrud <[email protected]> +# Copyright © 2025-2025 Peter Bieringer <[email protected]> # # 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 @@ 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"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/proppatch.py new/Radicale-3.5.9/radicale/app/proppatch.py --- old/Radicale-3.5.8/radicale/app/proppatch.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/proppatch.py 2025-11-29 15:33:06.000000000 +0100 @@ -73,7 +73,7 @@ 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"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/put.py new/Radicale-3.5.9/radicale/app/put.py --- old/Radicale-3.5.8/radicale/app/put.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/put.py 2025-11-29 15:33:06.000000000 +0100 @@ -142,7 +142,7 @@ 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"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/app/report.py new/Radicale-3.5.9/radicale/app/report.py --- old/Radicale-3.5.8/radicale/app/report.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/app/report.py 2025-11-29 15:33:06.000000000 +0100 @@ -149,7 +149,7 @@ 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 @@ 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 @@ 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 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/auth/__init__.py new/Radicale-3.5.9/radicale/auth/__init__.py --- old/Radicale-3.5.8/radicale/auth/__init__.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/auth/__init__.py 2025-11-29 15:33:06.000000000 +0100 @@ -23,7 +23,7 @@ Authentication is based on usernames and passwords. If something more advanced is needed an external WSGI server or reverse proxy can be used -(see ``remote_user`` or ``http_x_remote_user`` backend). +(see ``remote_user``, ``http_remote_user`` or ``http_x_remote_user`` backend). Take a look at the class ``BaseAuth`` if you want to implement your own. @@ -40,6 +40,7 @@ from radicale.log import logger INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user", + "http_remote_user", "denyall", "htpasswd", "ldap", @@ -59,6 +60,7 @@ INSECURE_IF_NO_LOOPBACK_TYPES: Sequence[str] = ( "remote_user", + "http_remote_user", "http_x_remote_user", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/auth/http_remote_user.py new/Radicale-3.5.9/radicale/auth/http_remote_user.py --- old/Radicale-3.5.8/radicale/auth/http_remote_user.py 1970-01-01 01:00:00.000000000 +0100 +++ new/Radicale-3.5.9/radicale/auth/http_remote_user.py 2025-11-29 15:33:06.000000000 +0100 @@ -0,0 +1,36 @@ +# This file is part of Radicale - CalDAV and CardDAV server +# Copyright © 2025-2025 Peter Bieringer <[email protected]> +# +# 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Radicale. If not, see <http://www.gnu.org/licenses/>. + +""" +Authentication backend that takes the username from the +``HTTP_REMOTE_USER`` header. + +It's intended for use with a reverse proxy. Be aware as this will be insecure +if the reverse proxy is not configured properly. + +""" + +from typing import Tuple, Union + +from radicale import types +from radicale.auth import none + + +class Auth(none.Auth): + + def get_external_login(self, environ: types.WSGIEnviron) -> Union[ + Tuple[()], Tuple[str, str]]: + return environ.get("HTTP_REMOTE_USER", ""), "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/tests/__init__.py new/Radicale-3.5.9/radicale/tests/__init__.py --- old/Radicale-3.5.8/radicale/tests/__init__.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/tests/__init__.py 2025-11-29 15:33:06.000000000 +0100 @@ -1,6 +1,7 @@ # This file is part of Radicale - CalDAV and CardDAV server # Copyright © 2012-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud <[email protected]> +# Copyright © 2017-2023 Unrud <[email protected]> +# Copyright © 2024-2025 Peter Bieringer <[email protected]> # # 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 @@ 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 @@ 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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/tests/test_auth.py new/Radicale-3.5.9/radicale/tests/test_auth.py --- old/Radicale-3.5.8/radicale/tests/test_auth.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/tests/test_auth.py 2025-11-29 15:33:06.000000000 +0100 @@ -263,6 +263,23 @@ href_element = prop.find(xmlutils.make_clark("D:href")) assert href_element is not None and href_element.text == "/test/" + def test_http_remote_user(self) -> None: + self.configure({"auth": {"type": "http_remote_user"}}) + _, responses = self.propfind("/", """\ +<?xml version="1.0" encoding="utf-8"?> +<propfind xmlns="DAV:"> + <prop> + <current-user-principal /> + </prop> +</propfind>""", HTTP_REMOTE_USER="test") + assert responses is not None + response = responses["/"] + assert not isinstance(response, int) + status, prop = response["D:current-user-principal"] + assert status == 200 + href_element = prop.find(xmlutils.make_clark("D:href")) + assert href_element is not None and href_element.text == "/test/" + def test_http_x_remote_user(self) -> None: self.configure({"auth": {"type": "http_x_remote_user"}}) _, responses = self.propfind("/", """\ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/radicale/tests/test_base.py new/Radicale-3.5.9/radicale/tests/test_base.py --- old/Radicale-3.5.8/radicale/tests/test_base.py 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/radicale/tests/test_base.py 2025-11-29 15:33:06.000000000 +0100 @@ -1687,7 +1687,7 @@ </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 @@ <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 @@ 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/" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.8/setup.py.legacy new/Radicale-3.5.9/setup.py.legacy --- old/Radicale-3.5.8/setup.py.legacy 2025-11-06 06:29:21.000000000 +0100 +++ new/Radicale-3.5.9/setup.py.legacy 2025-11-29 15:33:06.000000000 +0100 @@ -20,7 +20,7 @@ # When the version is updated, a new section in the CHANGELOG.md file must be # added too. -VERSION = "3.5.8" +VERSION = "3.5.9" with open("README.md", encoding="utf-8") as f: long_description = f.read()
