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-05-30 14:33:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/Radicale (Old) and /work/SRC/openSUSE:Factory/.Radicale.new.25440 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "Radicale" Fri May 30 14:33:03 2025 rev:21 rq:1280765 version:3.5.3 Changes: -------- --- /work/SRC/openSUSE:Factory/Radicale/Radicale.changes 2025-05-08 18:25:51.574680225 +0200 +++ /work/SRC/openSUSE:Factory/.Radicale.new.25440/Radicale.changes 2025-05-30 17:19:48.329459133 +0200 @@ -1,0 +2,8 @@ +Wed May 21 06:40:59 UTC 2025 - Ákos Szőts <szots...@gmail.com> + +- Update to 3.5.3 + * Add: [auth] htpasswd: support for Argon2 hashes + * Add: [auth] urldecode_username: optional decode provided username (e.g. encoded email address) + * Improve: catch error on calendar collection upload and display problematic item content on debug level + +------------------------------------------------------------------- Old: ---- v3.5.2.tar.gz New: ---- v3.5.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ Radicale.spec ++++++ --- /var/tmp/diff_new_pack.5VHnKc/_old 2025-05-30 17:19:48.777477675 +0200 +++ /var/tmp/diff_new_pack.5VHnKc/_new 2025-05-30 17:19:48.777477675 +0200 @@ -26,7 +26,7 @@ %define vo_min_ver 0.9.6 %define pk_min_ver 1.1.0 Name: Radicale -Version: 3.5.2 +Version: 3.5.3 Release: 0 Summary: A CalDAV calendar and CardDav contact server License: GPL-3.0-or-later ++++++ v3.5.2.tar.gz -> v3.5.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/.github/workflows/docker-publish.yml new/Radicale-3.5.3/.github/workflows/docker-publish.yml --- old/Radicale-3.5.2/.github/workflows/docker-publish.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/Radicale-3.5.3/.github/workflows/docker-publish.yml 2025-05-11 17:15:10.000000000 +0200 @@ -0,0 +1,49 @@ +name: Build and publish Docker image + +on: + release: + types: [published] + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker build + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: latest=true + tags: | + type=semver,pattern={{version}} + type=schedule,prefix=nightly-,pattern={{date 'YYYYMMDD'}} + type=raw,enable=${{ github.event_name == 'workflow_dispatch' }},value=workflow_dispatch-{{branch}}-{{sha}} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/CHANGELOG.md new/Radicale-3.5.3/CHANGELOG.md --- old/Radicale-3.5.2/CHANGELOG.md 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/CHANGELOG.md 2025-05-11 17:15:10.000000000 +0200 @@ -1,5 +1,10 @@ # Changelog +## 3.5.3 +* Add: [auth] htpasswd: support for Argon2 hashes +* Improve: catch error on calendar collection upload and display problematic item content on debug level +* Add: [auth] urldecode_username: optional decode provided username (e.g. encoded email address) + ## 3.5.2 * Adjust: [auth] ldap: use ldap_user_attr either first element of list or directly * Fix: use value of property for time range filter @@ -8,7 +13,6 @@ * Extend: log PYTHONPATH on startup if found in environment ## 3.5.1 - * Fix: auth/htpasswd related to detection and use of bcrypt * Add: option [auth] ldap_ignore_attribute_create_modify_timestamp for support of Authentik LDAP server * Extend: [storage] hook supports now placeholder for "cwd" and "path" (and catches unsupported placeholders) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/DOCUMENTATION.md new/Radicale-3.5.3/DOCUMENTATION.md --- old/Radicale-3.5.2/DOCUMENTATION.md 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/DOCUMENTATION.md 2025-05-11 17:15:10.000000000 +0200 @@ -99,11 +99,6 @@ python3 -m radicale --storage-filesystem-folder=/var/lib/radicale/collections --auth-type none ``` -##### common - -Victory! Open <http://localhost:5232> in your browser! -You can log in with any username and password (no authentication is required as long as not proper configured - INSECURE). - #### Windows The first step is to install Python. Go to @@ -119,9 +114,14 @@ python -m radicale --storage-filesystem-folder=~/radicale/collections --auth-type none ``` +##### Common + Victory! Open <http://localhost:5232> in your browser! You can log in with any username and password (no authentication is required as long as not proper configured - INSECURE). +Just note that default configuration for security reason binds the server to `localhost` (IPv4: `127.0.0.1`, IPv6: `::1`). +See [Addresses](#addresses) and [Configuration/Server](#server) for more. + ### Basic Configuration Installation instructions can be found in the @@ -943,6 +943,10 @@ `sha512` _(>= 3.1.9)_ : This uses an iterated SHA-512 digest of the password with a salt. +`argon2` _(>= 3.5.3)_ +: This uses an iterated ARGON2 digest of the password with a salt. + The installation of **argon2-cffi** is required for this. + `autodetect` _(>= 3.1.9)_ : This selects autodetection of method per entry. @@ -1183,6 +1187,16 @@ Default: `False` +##### urldecode_username + +_(>= 3.5.3)_ + +URL Decode the username. When the username is an email, some clients send the username URL-encoded (notably iOS devices) +breaking the authentication process (u...@example.com becomes user%40example.com). This setting will force decoding the username. + +Default: `False` + + #### rights ##### type diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/README.md new/Radicale-3.5.3/README.md --- old/Radicale-3.5.2/README.md 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/README.md 2025-05-11 17:15:10.000000000 +0200 @@ -25,4 +25,4 @@ * [Radicale Discussions](https://github.com/Kozea/Radicale/discussions) Before reporting an issue, please check -* [Radicale Wiki / Reporting Issues](https://github.com/Kozea/Radicale/wiki/Reporting-Issues) +* [Radicale Wiki / Reporting Issues](https://github.com/Kozea/Radicale/wiki/01-‐-Reporting-Issues) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/config new/Radicale-3.5.3/config --- old/Radicale-3.5.2/config 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/config 2025-05-11 17:15:10.000000000 +0200 @@ -148,8 +148,9 @@ #htpasswd_filename = /etc/radicale/users # Htpasswd encryption method -# Value: plain | bcrypt | md5 | sha256 | sha512 | autodetect +# Value: plain | bcrypt | md5 | sha256 | sha512 | argon2 | autodetect # bcrypt requires the installation of 'bcrypt' module. +# argon2 requires the installation of 'argon2-cffi' module. #htpasswd_encryption = autodetect # Enable caching of htpasswd file based on size and mtime_ns @@ -183,6 +184,8 @@ # Permit overwrite of a collection (global) #permit_overwrite_collection = True +# URL Decode the given username (when URL-encoded by the client - useful for iOS devices when using email address) +# urldecode_username = False [storage] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/pyproject.toml new/Radicale-3.5.3/pyproject.toml --- old/Radicale-3.5.2/pyproject.toml 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/pyproject.toml 2025-05-11 17:15:10.000000000 +0200 @@ -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.2" +version = "3.5.3" authors = [{name = "Guillaume Ayoub", email = "guillaume.ay...@kozea.fr"}, {name = "Unrud", email = "un...@outlook.com"}, {name = "Peter Bieringer", email = "p...@bieringer.de"}] license = {text = "GNU GPL v3"} description = "CalDAV and CardDAV Server" @@ -38,8 +38,9 @@ [project.optional-dependencies] -test = ["pytest>=7", "waitress", "bcrypt"] +test = ["pytest>=7", "waitress", "bcrypt", "argon2-cffi"] bcrypt = ["bcrypt"] +argon2 = ["argon2-cffi"] ldap = ["ldap3"] [project.scripts] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/app/put.py new/Radicale-3.5.3/radicale/app/put.py --- old/Radicale-3.5.2/radicale/app/put.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/app/put.py 2025-05-11 17:15:10.000000000 +0200 @@ -21,6 +21,7 @@ import errno import itertools +import logging import posixpath import re import socket @@ -89,7 +90,15 @@ vobject_collection.add(vobject.base.ContentLine("PRODID", [], PRODID)) item = radicale_item.Item(collection_path=collection_path, vobject_item=vobject_collection) - item.prepare() + logger.debug("Prepare item with UID '%s'", item.uid) + try: + item.prepare() + except ValueError as e: + if logger.isEnabledFor(logging.DEBUG): + logger.warning("Problem during prepare item with UID '%s' (content below): %s\n%s", item.uid, e, item._text) + else: + logger.warning("Problem during prepare item with UID '%s' (content suppressed in this loglevel): %s", item.uid, e) + raise items.append(item) elif write_whole_collection and tag == "VADDRESSBOOK": for vobject_item in vobject_items: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/auth/__init__.py new/Radicale-3.5.3/radicale/auth/__init__.py --- old/Radicale-3.5.2/radicale/auth/__init__.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/auth/__init__.py 2025-05-11 17:15:10.000000000 +0200 @@ -34,6 +34,7 @@ import threading import time from typing import List, Sequence, Set, Tuple, Union, final +from urllib.parse import unquote from radicale import config, types, utils from radicale.log import logger @@ -93,6 +94,7 @@ class BaseAuth: _ldap_groups: Set[str] = set([]) + _urldecode_username: bool _lc_username: bool _uc_username: bool _strip_domain: bool @@ -119,9 +121,11 @@ self._lc_username = configuration.get("auth", "lc_username") self._uc_username = configuration.get("auth", "uc_username") self._strip_domain = configuration.get("auth", "strip_domain") + self._urldecode_username = configuration.get("auth", "urldecode_username") logger.info("auth.strip_domain: %s", self._strip_domain) logger.info("auth.lc_username: %s", self._lc_username) logger.info("auth.uc_username: %s", self._uc_username) + logger.info("auth.urldecode_username: %s", self._urldecode_username) if self._lc_username is True and self._uc_username is True: raise RuntimeError("auth.lc_username and auth.uc_username cannot be enabled together") self._auth_delay = configuration.get("auth", "delay") @@ -219,6 +223,8 @@ login = login.lower() if self._uc_username: login = login.upper() + if self._urldecode_username: + login = unquote(login) if self._strip_domain: login = login.split('@')[0] if self._cache_logins is True: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/auth/htpasswd.py new/Radicale-3.5.3/radicale/auth/htpasswd.py --- old/Radicale-3.5.2/radicale/auth/htpasswd.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/auth/htpasswd.py 2025-05-11 17:15:10.000000000 +0200 @@ -46,6 +46,9 @@ When bcrypt is installed: - BCRYPT (htpasswd -B ...) -- Requires htpasswd 2.4.x +When argon2 is installed: + - ARGON2 (python -c 'from passlib.hash import argon2; print(argon2.using(type="ID").hash("password"))') + """ import functools @@ -72,8 +75,10 @@ _htpasswd_not_ok_time: float _htpasswd_not_ok_reminder_seconds: int _htpasswd_bcrypt_use: int + _htpasswd_argon2_use: int _htpasswd_cache: bool _has_bcrypt: bool + _has_argon2: bool _encryption: str _lock: threading.Lock @@ -89,9 +94,10 @@ logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s'", self._encryption) self._has_bcrypt = False + self._has_argon2 = False self._htpasswd_ok = False self._htpasswd_not_ok_reminder_seconds = 60 # currently hardcoded - (self._htpasswd_ok, self._htpasswd_bcrypt_use, self._htpasswd, self._htpasswd_size, self._htpasswd_mtime_ns) = self._read_htpasswd(True, False) + (self._htpasswd_ok, self._htpasswd_bcrypt_use, self._htpasswd_argon2_use, self._htpasswd, self._htpasswd_size, self._htpasswd_mtime_ns) = self._read_htpasswd(True, False) self._lock = threading.Lock() if self._encryption == "plain": @@ -102,7 +108,8 @@ self._verify = self._sha256 elif self._encryption == "sha512": self._verify = self._sha512 - elif self._encryption == "bcrypt" or self._encryption == "autodetect": + + if self._encryption == "bcrypt" or self._encryption == "autodetect": try: import bcrypt except ImportError as e: @@ -125,7 +132,33 @@ self._verify = self._autodetect if self._htpasswd_bcrypt_use: self._verify_bcrypt = functools.partial(self._bcrypt, bcrypt) - else: + + if self._encryption == "argon2" or self._encryption == "autodetect": + try: + import argon2 + from passlib.hash import argon2 # noqa: F811 + except ImportError as e: + if (self._encryption == "autodetect") and (self._htpasswd_argon2_use == 0): + logger.warning("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' which can require argon2 module, but currently no entries found", self._encryption) + else: + raise RuntimeError( + "The htpasswd encryption method 'argon2' or 'autodetect' requires " + "the argon2 module (entries found: %d)." % self._htpasswd_argon2_use) from e + else: + self._has_argon2 = True + if self._encryption == "autodetect": + if self._htpasswd_argon2_use == 0: + logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and argon2 module found, but currently not required", self._encryption) + else: + logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and argon2 module found (argon2 entries found: %d)", self._encryption, self._htpasswd_argon2_use) + if self._encryption == "argon2": + self._verify = functools.partial(self._argon2, argon2) + else: + self._verify = self._autodetect + if self._htpasswd_argon2_use: + self._verify_argon2 = functools.partial(self._argon2, argon2) + + if not hasattr(self, '_verify'): raise RuntimeError("The htpasswd encryption method %r is not " "supported." % self._encryption) @@ -144,6 +177,9 @@ else: return ("BCRYPT", bcrypt.checkpw(password=password.encode('utf-8'), hashed_password=hash_value.encode())) + def _argon2(self, argon2: Any, hash_value: str, password: str) -> tuple[str, bool]: + return ("ARGON2", argon2.verify(password, hash_value.strip())) + def _md5apr1(self, hash_value: str, password: str) -> tuple[str, bool]: if self._encryption == "autodetect" and len(hash_value) != 37: return self._plain_fallback("MD5-APR1", hash_value, password) @@ -169,6 +205,9 @@ elif re.match(r"^\$2(a|b|x|y)?\$", hash_value): # BCRYPT return self._verify_bcrypt(hash_value, password) + elif re.match(r"^\$argon2(i|d|id)\$", hash_value): + # ARGON2 + return self._verify_argon2(hash_value, password) elif hash_value.startswith("$5$", 0, 3): # SHA-256 return self._sha256(hash_value, password) @@ -178,7 +217,7 @@ else: return self._plain(hash_value, password) - def _read_htpasswd(self, init: bool, suppress: bool) -> Tuple[bool, int, dict, int, int]: + def _read_htpasswd(self, init: bool, suppress: bool) -> Tuple[bool, int, int, dict, int, int]: """Read htpasswd file init == True: stop on error @@ -189,6 +228,7 @@ """ htpasswd_ok = True bcrypt_use = 0 + argon2_use = 0 if (init is True) or (suppress is True): info = "Read" else: @@ -237,6 +277,14 @@ logger.warning("htpasswd file contains bcrypt digest login: '%s' (line: %d / ignored because module is not loaded)", login, line_num) skip = True htpasswd_ok = False + if re.match(r"^\$argon2(i|d|id)\$", digest): + if init is True: + argon2_use += 1 + else: + if self._has_argon2 is False: + logger.warning("htpasswd file contains argon2 digest login: '%s' (line: %d / ignored because module is not loaded)", login, line_num) + skip = True + htpasswd_ok = False if skip is False: htpasswd[login] = digest entries += 1 @@ -259,7 +307,7 @@ self._htpasswd_not_ok_time = 0 else: self._htpasswd_not_ok_time = time.time() - return (htpasswd_ok, bcrypt_use, htpasswd, htpasswd_size, htpasswd_mtime_ns) + return (htpasswd_ok, bcrypt_use, argon2_use, htpasswd, htpasswd_size, htpasswd_mtime_ns) def _login(self, login: str, password: str) -> str: """Validate credentials. @@ -280,7 +328,7 @@ htpasswd_size = os.stat(self._filename).st_size htpasswd_mtime_ns = os.stat(self._filename).st_mtime_ns if (htpasswd_size != self._htpasswd_size) or (htpasswd_mtime_ns != self._htpasswd_mtime_ns): - (self._htpasswd_ok, self._htpasswd_bcrypt_use, self._htpasswd, self._htpasswd_size, self._htpasswd_mtime_ns) = self._read_htpasswd(False, False) + (self._htpasswd_ok, self._htpasswd_bcrypt_use, self._htpasswd_argon2_use, self._htpasswd, self._htpasswd_size, self._htpasswd_mtime_ns) = self._read_htpasswd(False, False) self._htpasswd_not_ok_time = 0 # log reminder of problemantic file every interval @@ -298,7 +346,7 @@ login_ok = True else: # read file on every request - (htpasswd_ok, htpasswd_bcrypt_use, htpasswd, htpasswd_size, htpasswd_mtime_ns) = self._read_htpasswd(False, True) + (htpasswd_ok, htpasswd_bcrypt_use, htpasswd_argon2_use, htpasswd, htpasswd_size, htpasswd_mtime_ns) = self._read_htpasswd(False, True) if htpasswd.get(login): digest = htpasswd[login] login_ok = True @@ -307,7 +355,7 @@ try: (method, password_ok) = self._verify(digest, password) except ValueError as e: - logger.error("Login verification failed for user: '%s' (htpasswd/%s) with errror '%s'", login, self._encryption, e) + logger.error("Login verification failed for user: '%s' (htpasswd/%s) with error '%s'", login, self._encryption, e) return "" if password_ok: logger.debug("Login verification successful for user: '%s' (htpasswd/%s/%s)", login, self._encryption, method) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/config.py new/Radicale-3.5.3/radicale/config.py --- old/Radicale-3.5.2/radicale/config.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/config.py 2025-05-11 17:15:10.000000000 +0200 @@ -342,6 +342,10 @@ ("lc_username", { "value": "False", "help": "convert username to lowercase, must be true for case-insensitive auth providers", + "type": bool}), + ("urldecode_username", { + "value": "False", + "help": "url-decode the username, set to True when clients send url-encoded email address as username", "type": bool})])), ("rights", OrderedDict([ ("type", { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/storage/multifilesystem/__init__.py new/Radicale-3.5.3/radicale/storage/multifilesystem/__init__.py --- old/Radicale-3.5.2/radicale/storage/multifilesystem/__init__.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/storage/multifilesystem/__init__.py 2025-05-11 17:15:10.000000000 +0200 @@ -148,11 +148,11 @@ super().__init__(configuration) logger.info("Storage location: %r", self._filesystem_folder) if not os.path.exists(self._filesystem_folder): - logger.warning("Storage location: %r not existing, create now", self._filesystem_folder) + logger.warning("Storage location: %r does not exist, creating now", self._filesystem_folder) self._makedirs_synced(self._filesystem_folder) logger.info("Storage location subfolder: %r", self._get_collection_root_folder()) if not os.path.exists(self._get_collection_root_folder()): - logger.warning("Storage location subfolder: %r not existing, create now", self._get_collection_root_folder()) + logger.warning("Storage location subfolder: %r does not exist, creating now", self._get_collection_root_folder()) self._makedirs_synced(self._get_collection_root_folder()) logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item) logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history) @@ -176,7 +176,7 @@ if self._use_cache_subfolder_for_item is True or self._use_cache_subfolder_for_history is True or self._use_cache_subfolder_for_synctoken is True: logger.info("Storage cache subfolder: %r", self._get_collection_cache_folder()) if not os.path.exists(self._get_collection_cache_folder()): - logger.warning("Storage cache subfolder: %r not existing, create now", self._get_collection_cache_folder()) + logger.warning("Storage cache subfolder: %r does not exist, creating now", self._get_collection_cache_folder()) self._makedirs_synced(self._get_collection_cache_folder()) if sys.platform != "win32": if not self._folder_umask: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/tests/test_auth.py new/Radicale-3.5.3/radicale/tests/test_auth.py --- old/Radicale-3.5.2/radicale/tests/test_auth.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/tests/test_auth.py 2025-05-11 17:15:10.000000000 +0200 @@ -49,6 +49,15 @@ else: has_bcrypt = 1 + # test for available argon2 module + try: + import argon2 + from passlib.hash import argon2 # noqa: F811 + except ImportError: + has_argon2 = 0 + else: + has_argon2 = 1 + def _test_htpasswd(self, htpasswd_encryption: str, htpasswd_content: str, test_matrix: Union[str, Iterable[Tuple[str, str, bool]]] = "ascii") -> None: @@ -147,6 +156,18 @@ def test_htpasswd_bcrypt_unicode(self) -> None: self._test_htpasswd("bcrypt", "😀:$2y$10$Oyz5aHV4MD9eQJbk6GPemOs4T6edK6U9Sqlzr.W1mMVCS8wJUftnW", "unicode") + @pytest.mark.skipif(has_argon2 == 0, reason="No argon2 module installed") + def test_htpasswd_argon2_i(self) -> None: + self._test_htpasswd("argon2", "tmp:$argon2i$v=19$m=65536,t=3,p=4$NgZg7F1rzRkDoNSaMwag9A$qmsvMKEn5zOXHm8e3O5fKzzcRo0UESwaDr/cETe5YPI") + + @pytest.mark.skipif(has_argon2 == 0, reason="No argon2 module installed") + def test_htpasswd_argon2_d(self) -> None: + self._test_htpasswd("argon2", "tmp:$argon2d$v=19$m=65536,t=3,p=4$ufe+txYiJKR0zlkLwVirVQ$MjGqRyVLes38hA6CEOkloMcTYCuLjxCKgIjtfYZ3iSM") + + @pytest.mark.skipif(has_argon2 == 0, reason="No argon2 module installed") + def test_htpasswd_argon2_id(self) -> None: + self._test_htpasswd("argon2", "tmp:$argon2id$v=19$m=65536,t=3,p=4$t7bWuneOkdIa45xTqjXGmA$ORnRJyz9kHogJs6bDgZrTBPlzi4+p023PSEABb3xX1g") + def test_htpasswd_multi(self) -> None: self._test_htpasswd("plain", "ign:ign\ntmp:bepo") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/radicale/utils.py new/Radicale-3.5.3/radicale/utils.py --- old/Radicale-3.5.2/radicale/utils.py 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/radicale/utils.py 2025-05-11 17:15:10.000000000 +0200 @@ -28,8 +28,8 @@ _T_co = TypeVar("_T_co", covariant=True) RADICALE_MODULES: Sequence[str] = ("radicale", "vobject", "passlib", "defusedxml", - "dateutil", "bcrypt", + "argon2-cffi", "pika", "ldap", "ldap3", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.2/setup.py.legacy new/Radicale-3.5.3/setup.py.legacy --- old/Radicale-3.5.2/setup.py.legacy 2025-04-23 20:38:22.000000000 +0200 +++ new/Radicale-3.5.3/setup.py.legacy 2025-05-11 17:15:10.000000000 +0200 @@ -20,7 +20,7 @@ # When the version is updated, a new section in the CHANGELOG.md file must be # added too. -VERSION = "3.5.2" +VERSION = "3.5.3" with open("README.md", encoding="utf-8") as f: long_description = f.read() @@ -41,8 +41,9 @@ "requests", ] bcrypt_requires = ["bcrypt"] +argon2_requires = ["argon2-cffi"] ldap_requires = ["ldap3"] -test_requires = ["pytest>=7", "waitress", *bcrypt_requires] +test_requires = ["pytest>=7", "waitress", *bcrypt_requires, *argon2_requires] setup( name="Radicale", @@ -60,7 +61,7 @@ package_data={"radicale": [*web_files, "py.typed"]}, entry_points={"console_scripts": ["radicale = radicale.__main__:run"]}, install_requires=install_requires, - extras_require={"test": test_requires, "bcrypt": bcrypt_requires, "ldap": ldap_requires}, + extras_require={"test": test_requires, "bcrypt": bcrypt_requires, "argon2": argon2_requires, "ldap": ldap_requires}, keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], python_requires=">=3.9.0", classifiers=[