diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index e1145fe4..61a56ebf 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -834,10 +834,31 @@ Default: `(cn={0})` ##### ldap_load_groups -Load the ldap groups of the authenticated user. These groups can be used later on to define rights. +Load the ldap groups of the authenticated user. These groups can be used later on to define rights. This also gives you access to the group calendars, if they exist. +* The group calendar will be placed under collection_root_folder/GROUPS +* The name of the calendar directory is the base64 encoded group name. +* The group calneder folders will not be created automaticaly. This must be created manualy. [Here](https://github.com/Kozea/Radicale/wiki/LDAP-authentication) you can find a script to create group calneder folders https://github.com/Kozea/Radicale/wiki/LDAP-authentication Default: False +##### ldap_use_ssl + +Use ssl on the ldap connection + +Default: False + +##### ldap_ssl_verify_mode + +The certifikat verification mode. NONE, OPTIONAL or REQUIRED + +Default: REQUIRED + +##### ldap_ssl_ca_file + +The path to the CA file in pem format which is used to certificate the server certificate + +Default: + ##### lc_username Сonvert username to lowercase, must be true for case-insensitive auth diff --git a/config b/config index 0751fa75..5fb11290 100644 --- a/config +++ b/config @@ -72,7 +72,16 @@ #ldap_load_groups = True # The filter to find the DN of the user. This filter must contain a python-style placeholder for the login -#ldap_filter = (&(objectClass=person)(cn={0})) +#ldap_filter = (&(objectClass=person)(uid={0})) + +# Use ssl on the ldap connection +#ldap_use_ssl = False + +# The certifikat verification mode. NONE, OPTIONAL, default is REQUIRED +#ldap_ssl_verify_mode = REQUIRED + +# The path to the CA file in pem format which is used to certificate the server certificate +#ldap_ssl_ca_file = # Htpasswd filename #htpasswd_filename = /etc/radicale/users diff --git a/radicale/auth/ldap.py b/radicale/auth/ldap.py index 23355d37..3c87561e 100644 --- a/radicale/auth/ldap.py +++ b/radicale/auth/ldap.py @@ -15,14 +15,20 @@ # along with Radicale. If not, see . """ Authentication backend that checks credentials with a ldap server. -Following parameters are needed in the configuration +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_load_groups If the groups of the authenticated users need to be loaded +Following parameters controls SSL connections: + ldap_use_ssl If the connection + ldap_ssl_verify_mode The certifikat verification mode. NONE, OPTIONAL, default is REQUIRED + ldap_ssl_ca_file + """ +import ssl from radicale import auth, config from radicale.log import logger @@ -36,6 +42,9 @@ class Auth(auth.BaseAuth): _ldap_filter: str _ldap_load_groups: bool _ldap_version: int = 3 + _ldap_use_ssl: bool = False + _ldap_ssl_verify_mode: int = ssl.CERT_REQUIRED + _ldap_ssl_ca_file: str = "" def __init__(self, configuration: config.Configuration) -> None: super().__init__(configuration) @@ -55,6 +64,15 @@ 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") + if self._ldap_version == 3: + self._ldap_use_ssl = configuration.get("auth", "ldap_use_ssl") + if self._ldap_use_ssl: + self._ldap_ssl_ca_file = configuration.get("auth", "ldap_ssl_ca_file") + tmp = configuration.get("auth", "ldap_ssl_verify_mode") + if tmp == "NONE": + self._ldap_ssl_verify_mode = ssl.CERT_NONE + elif tmp == "OPTIONAL": + self._ldap_ssl_verify_mode = ssl.CERT_OPTIONAL def _login2(self, login: str, password: str) -> str: try: @@ -98,7 +116,16 @@ def _login3(self, login: str, password: str) -> str: """Connect the server""" try: logger.debug(f"_login3 {self._ldap_uri}, {self._ldap_reader_dn}") - server = self.ldap3.Server(self._ldap_uri) + if self._ldap_use_ssl: + tls = self.ldap3.Tls(validate=self._ldap_ssl_verify_mode) + if self._ldap_ssl_ca_file != "": + tls = self.ldap3.Tls( + validate=self._ldap_ssl_verify_mode, + ca_certs_file=self._ldap_ssl_ca_file + ) + server = self.ldap3.Server(self._ldap_uri, use_ssl=True, tls=tls) + else: + server = self.ldap3.Server(self._ldap_uri) conn = self.ldap3.Connection(server, self._ldap_reader_dn, password=self._ldap_secret) except self.ldap3.core.exceptions.LDAPSocketOpenError: raise RuntimeError("Unable to reach ldap server") diff --git a/radicale/config.py b/radicale/config.py index 8c9965fe..5bddaf91 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -215,6 +215,18 @@ def json_str(value: Any) -> dict: "value": "False", "help": "load the ldap groups of the authenticated user", "type": bool}), + ("ldap_use_ssl", { + "value": "False", + "help": "Use ssl on the ldap connection", + "type": bool}), + ("ldap_ssl_verify_mode", { + "value": "REQUIRED", + "help": "The certifikat verification mode. NONE, OPTIONAL, default is REQUIRED", + "type": str}), + ("ldap_ssl_ca_file", { + "value": "", + "help": "The path to the CA file in pem format which is used to certificate the server certificate", + "type": str}), ("strip_domain", { "value": "False", "help": "strip domain from username",