diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index bbfac0a2..f5d8aad5 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1187,6 +1187,16 @@ Strip domain from username 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 (user@example.com becomes user%40example.com). This setting will force decoding the username. + +Default: `False` + + #### rights ##### type diff --git a/config b/config index bbeb19d1..8f536958 100644 --- a/config +++ b/config @@ -184,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 --git a/radicale/auth/__init__.py b/radicale/auth/__init__.py index 43ce953b..2de8c4e9 100644 --- a/radicale/auth/__init__.py +++ b/radicale/auth/__init__.py @@ -34,6 +34,7 @@ import os 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 @@ def load(configuration: "config.Configuration") -> "BaseAuth": class BaseAuth: _ldap_groups: Set[str] = set([]) + _urldecode_username: bool _lc_username: bool _uc_username: bool _strip_domain: bool @@ -119,9 +121,11 @@ class BaseAuth: 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 @@ class BaseAuth: 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 --git a/radicale/config.py b/radicale/config.py index cb2285c3..c4f5fbe7 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -342,6 +342,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ ("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", {