diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 2d5e838d..596a5125 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -824,7 +824,13 @@ Default: ##### ldap_secret -The password of the ldap_reader_dn. This parameter must be provided if auth type is ldap. +The password of the ldap_reader_dn. Either this parameter or `ldap_secret_file` must be provided if auth type is ldap. + +Default: + +##### ldap_secret_file + +Path of the file containing the password of the ldap_reader_dn. Either this parameter or `ldap_secret` must be provided if auth type is ldap. Default: @@ -869,7 +875,7 @@ Default: ##### lc_username -Сonvert username to lowercase, must be true for case-insensitive auth +Сonvert username to lowercase, must be true for case-insensitive auth providers like ldap, kerberos Default: `False` diff --git a/Dockerfile b/Dockerfile index 90259508..f6ac22f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM python:3-alpine AS builder # Version of Radicale (e.g. v3) ARG VERSION=master -# Optional dependencies (e.g. bcrypt) +# Optional dependencies (e.g. bcrypt or ldap) ARG DEPENDENCIES=bcrypt RUN apk add --no-cache --virtual gcc libffi-dev musl-dev \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 65e00fcd..2f6f9fc0 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,6 @@ FROM python:3-alpine AS builder -# Optional dependencies (e.g. bcrypt) +# Optional dependencies (e.g. bcrypt or ldap) ARG DEPENDENCIES=bcrypt COPY . /app diff --git a/config b/config index 20e459c8..cb50ab72 100644 --- a/config +++ b/config @@ -68,6 +68,9 @@ # Password of the reader DN #ldap_secret = ldapreader-secret +# Path of the file containing password of the reader DN +#ldap_secret_file = /run/secrets/ldap_password + # If the ldap groups of the user need to be loaded #ldap_load_groups = True diff --git a/pyproject.toml b/pyproject.toml index c2df6f66..ff95d0a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dependencies = [ [project.optional-dependencies] test = ["pytest>=7", "waitress", "bcrypt"] bcrypt = ["bcrypt"] +ldap = ["ldap3"] [project.scripts] radicale = "radicale.__main__:run" diff --git a/radicale/auth/ldap.py b/radicale/auth/ldap.py index 56261105..a8e94794 100644 --- a/radicale/auth/ldap.py +++ b/radicale/auth/ldap.py @@ -16,11 +16,12 @@ """ Authentication backend that checks credentials with a ldap server. Following parameters are needed in the configuration: - ldap_uri The ldap url to the server like ldap://localhost - ldap_base The baseDN of the ldap server - ldap_reader_dn The DN of a ldap user with read access to get the user accounts - ldap_secret The password of the ldap_reader_dn - ldap_filter The search filter to find the user to authenticate by the username + ldap_uri The ldap url to the server like ldap://localhost + ldap_base The baseDN of the ldap server + ldap_reader_dn The DN of a ldap user with read access to get the user accounts + ldap_secret The password of the ldap_reader_dn + ldap_secret_file The path of the file containing the password of the ldap_reader_dn + ldap_filter The search filter to find the user to authenticate by the username ldap_load_groups If the groups of the authenticated users need to be loaded Following parameters controls SSL connections: ldap_use_ssl If the connection @@ -64,6 +65,10 @@ def __init__(self, configuration: config.Configuration) -> None: self._ldap_load_groups = configuration.get("auth", "ldap_load_groups") self._ldap_secret = configuration.get("auth", "ldap_secret") self._ldap_filter = configuration.get("auth", "ldap_filter") + ldap_secret_file_path = configuration.get("auth", "ldap_secret_file") + if ldap_secret_file_path: + with open(ldap_secret_file_path, 'r') as file: + self._ldap_secret = file.read().rstrip('\n') if self._ldap_version == 3: self._ldap_use_ssl = configuration.get("auth", "ldap_use_ssl") if self._ldap_use_ssl: diff --git a/radicale/config.py b/radicale/config.py index 5bb24534..3e91a6fa 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -200,17 +200,21 @@ def json_str(value: Any) -> dict: "help": "URI to the ldap server", "type": str}), ("ldap_base", { - "value": "none", + "value": "", "help": "LDAP base DN of the ldap server", "type": str}), ("ldap_reader_dn", { - "value": "none", + "value": "", "help": "the DN of a ldap user with read access to get the user accounts", "type": str}), ("ldap_secret", { - "value": "none", + "value": "", "help": "the password of the ldap_reader_dn", "type": str}), + ("ldap_secret_file", { + "value": "", + "help": "path of the file containing the password of the ldap_reader_dn", + "type": str}), ("ldap_filter", { "value": "(cn={0})", "help": "the search filter to find the user DN to authenticate by the username", diff --git a/setup.py.legacy b/setup.py.legacy index 4d108ccd..d82d289e 100644 --- a/setup.py.legacy +++ b/setup.py.legacy @@ -40,6 +40,7 @@ install_requires = ["defusedxml", "passlib", "vobject>=0.9.6", "pika>=1.1.0", ] bcrypt_requires = ["bcrypt"] +ldap_requires = ["ldap3"] test_requires = ["pytest>=7", "waitress", *bcrypt_requires] setup( @@ -58,7 +59,7 @@ setup( 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}, + extras_require={"test": test_requires, "bcrypt": bcrypt_requires, "ldap": ldap_requires}, keywords=["calendar", "addressbook", "CalDAV", "CardDAV"], python_requires=">=3.8.0", classifiers=[