1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-08-01 18:18:31 +00:00

Merge branch 'master' into react0r

This commit is contained in:
ray-react0r 2024-08-15 15:07:49 -06:00 committed by GitHub
commit 3cba4b32a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 134 additions and 59 deletions

View file

@ -61,7 +61,7 @@ def _get_application_instance(config_path: str, wsgi_errors: types.ErrorStream
if not miss and source != "default config":
default_config_active = False
if default_config_active:
logger.warn("%s", "No config file found/readable - only default config is active")
logger.warning("%s", "No config file found/readable - only default config is active")
_application_instance = Application(configuration)
if _application_config_path != config_path:
raise ValueError("RADICALE_CONFIG must not change: %r != %r" %

View file

@ -175,7 +175,7 @@ def run() -> None:
default_config_active = False
if default_config_active:
logger.warn("%s", "No config file found/readable - only default config is active")
logger.warning("%s", "No config file found/readable - only default config is active")
if args_ns.verify_storage:
logger.info("Verifying storage")
@ -183,7 +183,7 @@ def run() -> None:
storage_ = storage.load(configuration)
with storage_.acquire_lock("r"):
if not storage_.verify():
logger.critical("Storage verifcation failed")
logger.critical("Storage verification failed")
sys.exit(1)
except Exception as e:
logger.critical("An exception occurred during storage "

View file

@ -232,7 +232,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
path.rstrip("/").endswith("/.well-known/carddav")):
return response(*httputils.redirect(
base_prefix + "/", client.MOVED_PERMANENTLY))
# Return NOT FOUND for all other paths containing ".well-knwon"
# Return NOT FOUND for all other paths containing ".well-known"
if path.endswith("/.well-known") or "/.well-known/" in path:
return response(*httputils.NOT_FOUND)

View file

@ -322,13 +322,13 @@ def xml_propfind_response(
responses[404 if is404 else 200].append(element)
for status_code, childs in responses.items():
if not childs:
for status_code, children in responses.items():
if not children:
continue
propstat = ET.Element(xmlutils.make_clark("D:propstat"))
response.append(propstat)
prop = ET.Element(xmlutils.make_clark("D:prop"))
prop.extend(childs)
prop.extend(children)
propstat.append(prop)
status = ET.Element(xmlutils.make_clark("D:status"))
status.text = xmlutils.make_response(status_code)

View file

@ -363,7 +363,7 @@ def _make_vobject_expanded_item(
if hasattr(item.vobject_item.vevent, 'rrule'):
rruleset = vevent.getrruleset()
# There is something strage behavour during serialization native datetime, so converting manualy
# There is something strange behaviour during serialization native datetime, so converting manually
vevent.dtstart.value = vevent.dtstart.value.strftime(dt_format)
if dt_end is not None:
vevent.dtend.value = vevent.dtend.value.strftime(dt_format)

View file

@ -52,6 +52,7 @@ def load(configuration: "config.Configuration") -> "BaseAuth":
class BaseAuth:
_lc_username: bool
_strip_domain: bool
def __init__(self, configuration: "config.Configuration") -> None:
"""Initialize BaseAuth.
@ -63,6 +64,7 @@ class BaseAuth:
"""
self.configuration = configuration
self._lc_username = configuration.get("auth", "lc_username")
self._strip_domain = configuration.get("auth", "strip_domain")
def get_external_login(self, environ: types.WSGIEnviron) -> Union[
Tuple[()], Tuple[str, str]]:
@ -91,4 +93,8 @@ class BaseAuth:
raise NotImplementedError
def login(self, login: str, password: str) -> str:
return self._login(login, password).lower() if self._lc_username else self._login(login, password)
if self._lc_username:
login = login.lower()
if self._strip_domain:
login = login.split('@')[0]
return self._login(login, password)

View file

@ -36,7 +36,7 @@ pointed to by the ``htpasswd_filename`` configuration value while assuming
the password encryption method specified via the ``htpasswd_encryption``
configuration value.
The following htpasswd password encrpytion methods are supported by Radicale
The following htpasswd password encryption methods are supported by Radicale
out-of-the-box:
- plain-text (created by htpasswd -p ...) -- INSECURE
- MD5-APR1 (htpasswd -m ...) -- htpasswd's default method, INSECURE

View file

@ -191,6 +191,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
"value": "1",
"help": "incorrect authentication delay",
"type": positive_float}),
("strip_domain", {
"value": "False",
"help": "strip domain from username",
"type": bool}),
("lc_username", {
"value": "False",
"help": "convert username to lowercase, must be true for case-insensitive auth providers",

View file

@ -3,14 +3,23 @@ from enum import Enum
from typing import Sequence
from radicale import pathutils, utils
from radicale.log import logger
INTERNAL_TYPES: Sequence[str] = ("none", "rabbitmq")
def load(configuration):
"""Load the storage module chosen in configuration."""
return utils.load_plugin(
INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration)
try:
return utils.load_plugin(
INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration)
except Exception as e:
logger.warn(e)
logger.warn("Hook \"%s\" failed to load, falling back to \"none\"." % configuration.get("hook", "type"))
configuration = configuration.copy()
configuration.update({"hook": {"type": "none"}}, "hook", privileged=True)
return utils.load_plugin(
INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration)
class BaseHook:

View file

@ -49,6 +49,12 @@ def read_components(s: str) -> List[vobject.base.Component]:
s = re.sub(r"^(PHOTO(?:;[^:\r\n]*)?;ENCODING=b(?:;[^:\r\n]*)?:)"
r"data:[^;,\r\n]*;base64,", r"\1", s,
flags=re.MULTILINE | re.IGNORECASE)
# Workaround for bug with malformed ICS files containing control codes
# Filter out all control codes except those we expect to find:
# * 0x09 Horizontal Tab
# * 0x0A Line Feed
# * 0x0D Carriage Return
s = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', s)
return list(vobject.readComponents(s, allowQP=True))
@ -298,7 +304,7 @@ def find_time_range(vobject_item: vobject.base.Component, tag: str
Returns a tuple (``start``, ``end``) where ``start`` and ``end`` are
POSIX timestamps.
This is intened to be used for matching against simplified prefilters.
This is intended to be used for matching against simplified prefilters.
"""
if not tag:

View file

@ -241,7 +241,7 @@ def visit_time_ranges(vobject_item: vobject.base.Component, child_name: str,
"""
# HACK: According to rfc5545-3.8.4.4 an recurrance that is resheduled
# HACK: According to rfc5545-3.8.4.4 a recurrence that is rescheduled
# with Recurrence ID affects the recurrence itself and all following
# recurrences too. This is not respected and client don't seem to bother
# either.

View file

@ -22,7 +22,7 @@ config (section "rights", key "file").
The login is matched against the "user" key, and the collection path
is matched against the "collection" key. In the "collection" regex you can use
`{user}` and get groups from the "user" regex with `{0}`, `{1}`, etc.
In consequence of the parameter subsitution you have to write `{{` and `}}`
In consequence of the parameter substitution you have to write `{{` and `}}`
if you want to use regular curly braces in the "user" and "collection" regexes.
For example, for the "user" key, ".+" means "authenticated user" and ".*"

View file

@ -291,7 +291,7 @@ def serve(configuration: config.Configuration,
try:
getaddrinfo = socket.getaddrinfo(address_port[0], address_port[1], 0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
except OSError as e:
logger.warn("cannot retrieve IPv4 or IPv6 address of '%s': %s" % (format_address(address_port), e))
logger.warning("cannot retrieve IPv4 or IPv6 address of '%s': %s" % (format_address(address_port), e))
continue
logger.debug("getaddrinfo of '%s': %s" % (format_address(address_port), getaddrinfo))
for (address_family, socket_kind, socket_proto, socket_flags, socket_address) in getaddrinfo:
@ -299,7 +299,7 @@ def serve(configuration: config.Configuration,
try:
server = server_class(configuration, address_family, (socket_address[0], socket_address[1]), RequestHandler)
except OSError as e:
logger.warn("cannot create server socket on '%s': %s" % (format_address(socket_address), e))
logger.warning("cannot create server socket on '%s': %s" % (format_address(socket_address), e))
continue
servers[server.socket] = server
server.set_app(application)

View file

@ -84,7 +84,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock,
cache_content = self._load_item_cache(href, cache_hash)
if cache_content is None:
with self._acquire_cache_lock("item"):
# Lock the item cache to prevent multpile processes from
# Lock the item cache to prevent multiple processes from
# generating the same data in parallel.
# This improves the performance for multiple requests.
if self._storage._lock.locked == "r":
@ -127,7 +127,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock,
def get_multi(self, hrefs: Iterable[str]
) -> Iterator[Tuple[str, Optional[radicale_item.Item]]]:
# It's faster to check for file name collissions here, because
# It's faster to check for file name collisions here, because
# we only need to call os.listdir once.
files = None
for href in hrefs:
@ -146,7 +146,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock,
def get_all(self) -> Iterator[radicale_item.Item]:
for href in self._list():
# We don't need to check for collissions, because the file names
# We don't need to check for collisions, because the file names
# are from os.listdir.
item = self._get(href, verify_href=False)
if item is not None:

View file

@ -112,7 +112,7 @@ class BaseTest:
for response in xml.findall(xmlutils.make_clark("D:response")):
href = response.find(xmlutils.make_clark("D:href"))
assert href.text not in path_responses
prop_respones: Dict[str, Tuple[int, ET.Element]] = {}
prop_responses: Dict[str, Tuple[int, ET.Element]] = {}
for propstat in response.findall(
xmlutils.make_clark("D:propstat")):
status = propstat.find(xmlutils.make_clark("D:status"))
@ -121,16 +121,16 @@ class BaseTest:
for element in propstat.findall(
"./%s/*" % xmlutils.make_clark("D:prop")):
human_tag = xmlutils.make_human_tag(element.tag)
assert human_tag not in prop_respones
prop_respones[human_tag] = (status_code, element)
assert human_tag not in prop_responses
prop_responses[human_tag] = (status_code, element)
status = response.find(xmlutils.make_clark("D:status"))
if status is not None:
assert not prop_respones
assert not prop_responses
assert status.text.startswith("HTTP/1.1 ")
status_code = int(status.text.split(" ")[1])
path_responses[href.text] = status_code
else:
path_responses[href.text] = prop_respones
path_responses[href.text] = prop_responses
return path_responses
@staticmethod

View file

@ -115,6 +115,16 @@ class TestBaseAuthRequests(BaseTest):
def test_htpasswd_comment(self) -> None:
self._test_htpasswd("plain", "#comment\n #comment\n \ntmp:bepo\n\n")
def test_htpasswd_lc_username(self) -> None:
self.configure({"auth": {"lc_username": "True"}})
self._test_htpasswd("plain", "tmp:bepo", (
("tmp", "bepo", True), ("TMP", "bepo", True), ("tmp1", "bepo", False)))
def test_htpasswd_strip_domain(self) -> None:
self.configure({"auth": {"strip_domain": "True"}})
self._test_htpasswd("plain", "tmp:bepo", (
("tmp", "bepo", True), ("tmp@domain.example", "bepo", True), ("tmp1", "bepo", False)))
def test_remote_user(self) -> None:
self.configure({"auth": {"type": "remote_user"}})
_, responses = self.propfind("/", """\

View file

@ -360,7 +360,7 @@ permissions: RrWw""")
self.get(path1, check=404)
self.get(path2)
def test_move_between_colections(self) -> None:
def test_move_between_collections(self) -> None:
"""Move a item."""
self.mkcalendar("/calendar1.ics/")
self.mkcalendar("/calendar2.ics/")
@ -373,7 +373,7 @@ permissions: RrWw""")
self.get(path1, check=404)
self.get(path2)
def test_move_between_colections_duplicate_uid(self) -> None:
def test_move_between_collections_duplicate_uid(self) -> None:
"""Move a item to a collection which already contains the UID."""
self.mkcalendar("/calendar1.ics/")
self.mkcalendar("/calendar2.ics/")
@ -389,7 +389,7 @@ permissions: RrWw""")
assert xml.tag == xmlutils.make_clark("D:error")
assert xml.find(xmlutils.make_clark("C:no-uid-conflict")) is not None
def test_move_between_colections_overwrite(self) -> None:
def test_move_between_collections_overwrite(self) -> None:
"""Move a item to a collection which already contains the item."""
self.mkcalendar("/calendar1.ics/")
self.mkcalendar("/calendar2.ics/")
@ -403,8 +403,8 @@ permissions: RrWw""")
self.request("MOVE", path1, check=204, HTTP_OVERWRITE="T",
HTTP_DESTINATION="http://127.0.0.1/"+path2)
def test_move_between_colections_overwrite_uid_conflict(self) -> None:
"""Move a item to a collection which already contains the item with
def test_move_between_collections_overwrite_uid_conflict(self) -> None:
"""Move an item to a collection which already contains the item with
a different UID."""
self.mkcalendar("/calendar1.ics/")
self.mkcalendar("/calendar2.ics/")