1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00
This commit is contained in:
Dipl Ing. Péter Varkoly 2025-06-23 06:49:58 +02:00 committed by GitHub
commit d216a9a2f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 70 additions and 39 deletions

10
config
View file

@ -93,8 +93,14 @@
# Path of the file containing password of the reader DN
#ldap_secret_file = /run/secrets/ldap_password
# the attribute to read the group memberships from in the user's LDAP entry (default: not set)
#ldap_groups_attribute = memberOf
# The attribute in user entry to read the group memberships.
#ldap_groups_attribute =
# The attribute in group entries to read the group memberships.
#ldap_group_members_attribute =
# The base dn to find the groups. Necessary only if ldap_group_members_attribute is defined and other then ldap_base.
#ldap_groups_base =
# The filter to find the DN of the user. This filter must contain a python-style placeholder for the login
#ldap_filter = (&(objectClass=person)(uid={0}))

View file

@ -27,7 +27,6 @@ from http import client
from typing import Dict, Optional, cast
import defusedxml.ElementTree as DefusedET
import radicale.item as radicale_item
from radicale import httputils, storage, types, xmlutils
from radicale.app.base import Access, ApplicationBase

View file

@ -60,7 +60,6 @@ import time
from typing import Any, Tuple
from passlib.hash import apr_md5_crypt, sha256_crypt, sha512_crypt
from radicale import auth, config, logger

View file

@ -18,13 +18,14 @@
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_base The baseDN of the LDAP server searching for users.
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_user_attribute The attribute to be used as username after authentication
ldap_groups_attribute The attribute containing group memberships in the LDAP user entry
ldap_groups_base The baseDN of the LDAP server searching for groups.
Following parameters controls SSL connections:
ldap_use_ssl If ssl encryption should be used (to be deprecated)
ldap_security The encryption mode to be used: *none*|tls|starttls
@ -78,6 +79,10 @@ class Auth(auth.BaseAuth):
self._ldap_filter = configuration.get("auth", "ldap_filter")
self._ldap_user_attr = configuration.get("auth", "ldap_user_attribute")
self._ldap_groups_attr = configuration.get("auth", "ldap_groups_attribute")
self._ldap_group_members_attr = configuration.get("auth", "ldap_group_members_attribute")
self._ldap_groups_base = configuration.get("auth", "ldap_groups_base")
if self._ldap_groups_base == "":
self._ldap_groups_base = self._ldap_base
ldap_secret_file_path = configuration.get("auth", "ldap_secret_file")
if ldap_secret_file_path:
with open(ldap_secret_file_path, 'r') as file:
@ -172,17 +177,25 @@ class Auth(auth.BaseAuth):
conn.set_option(self.ldap.OPT_REFERRALS, 0)
conn.simple_bind_s(user_dn, password)
tmp: list[str] = []
gdns: list[str] = []
if self._ldap_groups_attr:
tmp = []
for g in user_entry[1][self._ldap_groups_attr]:
"""Get group g's RDN's attribute value"""
try:
rdns = self.ldap.dn.explode_dn(g, notypes=True)
tmp.append(rdns[0])
except Exception:
tmp.append(g.decode('utf8'))
self._ldap_groups = set(tmp)
logger.debug("_login2 LDAP groups of user: %s", ",".join(self._ldap_groups))
gdns = user_entry[1][self._ldap_groups_attr]
elif self._ldap_group_members_attr:
res = conn.search_s(
self._ldap_groups_base,
self.ldap.SCOPE_SUBTREE,
filterstr="({0}={1})".format(self._ldap_group_members_attr,user_dn),
attrlist=self._ldap_attributes
)
for g in gdns:
"""Get group g's RDN's attribute value"""
try:
rdns = self.ldap.dn.explode_dn(g, notypes=True)
tmp.append(rdns[0])
except Exception:
tmp.append(g.decode('utf8'))
self._ldap_groups = set(tmp)
logger.debug("_login2 LDAP groups of user: %s", ",".join(self._ldap_groups))
if self._ldap_user_attr:
if user_entry[1][self._ldap_user_attr]:
tmplogin = user_entry[1][self._ldap_user_attr][0]
@ -261,17 +274,28 @@ class Auth(auth.BaseAuth):
logger.debug(f"_login3 user '{login}' cannot be found")
return ""
tmp: list[str] = []
gdns: list[str] = []
"""Let's collect the groups of the user."""
if self._ldap_groups_attr:
tmp = []
for g in user_entry['attributes'][self._ldap_groups_attr]:
"""Get group g's RDN's attribute value"""
try:
rdns = self.ldap3.utils.dn.parse_dn(g)
tmp.append(rdns[0][1])
except Exception:
tmp.append(g)
self._ldap_groups = set(tmp)
logger.debug("_login3 LDAP groups of user: %s", ",".join(self._ldap_groups))
gdns = user_entry['attributes'][self._ldap_groups_attr]
elif self._ldap_group_members_attr:
conn.search(
search_base=self._ldap_groups_base,
search_filter="({0}={1})".format(self._ldap_group_members_attr,user_dn),
search_scope=self.ldap3.SUBTREE,
attributes="dn"
)
for group in conn.response:
gdns.append(group['dn'])
for g in gdns:
"""Get group g's RDN's attribute value"""
try:
rdns = self.ldap3.utils.dn.parse_dn(g)
tmp.append(rdns[0][1])
except Exception:
tmp.append(g)
self._ldap_groups = set(tmp)
logger.debug("_login3 LDAP groups of user: %s", ",".join(self._ldap_groups))
if self._ldap_user_attr:
if user_entry['attributes'][self._ldap_user_attr]:
if isinstance(user_entry['attributes'][self._ldap_user_attr], list):

View file

@ -269,31 +269,39 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
"type": str}),
("ldap_base", {
"value": "",
"help": "LDAP base DN of the ldap server",
"help": "The base DN of the ldap server where the user can be find.",
"type": str}),
("ldap_reader_dn", {
"value": "",
"help": "the DN of a ldap user with read access to get the user accounts",
"help": "The DN of a ldap user with read access to get the user accounts",
"type": str}),
("ldap_secret", {
"value": "",
"help": "the password of the ldap_reader_dn",
"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",
"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",
"help": "The search filter to find the user DN to authenticate by the username",
"type": str}),
("ldap_user_attribute", {
"value": "",
"help": "the attribute to be used as username after authentication",
"help": "The attribute to be used as username after authentication",
"type": str}),
("ldap_groups_attribute", {
"value": "",
"help": "attribute to read the group memberships from",
"help": "Attribute in the user entry to read the group memberships from.",
"type": str}),
("ldap_group_members_attribute", {
"value": "",
"help": "Attribute in the group entries to read the group memberships from.",
"type": str}),
("ldap_groups_base_dn", {
"value": "",
"help": "The base dn to find the groups. Necessary only if ldap_group_members_attribute is defined and other then ldap_base.",
"type": str}),
("ldap_use_ssl", {
"value": "False",

View file

@ -1,6 +1,5 @@
import pika
from pika.exceptions import ChannelWrongStateError, StreamLostError
from radicale import hook
from radicale.hook import HookNotificationItem
from radicale.log import logger

View file

@ -31,9 +31,9 @@ from io import BytesIO
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import quote
import defusedxml.ElementTree as DefusedET
import vobject
import defusedxml.ElementTree as DefusedET
import radicale
from radicale import app, config, types, xmlutils

View file

@ -29,7 +29,6 @@ import sys
from typing import Iterable, Tuple, Union
import pytest
from radicale import xmlutils
from radicale.tests import BaseTest

View file

@ -26,9 +26,9 @@ import os
import posixpath
from typing import Any, Callable, ClassVar, Iterable, List, Optional, Tuple
import defusedxml.ElementTree as DefusedET
import vobject
import defusedxml.ElementTree as DefusedET
from radicale import storage, xmlutils
from radicale.tests import RESPONSES, BaseTest
from radicale.tests.helpers import get_file_content

View file

@ -21,7 +21,6 @@ from configparser import RawConfigParser
from typing import List, Tuple
import pytest
from radicale import config, types
from radicale.tests.helpers import configuration_to_dict

View file

@ -34,7 +34,6 @@ from urllib import request
from urllib.error import HTTPError, URLError
import pytest
from radicale import config, server
from radicale.tests import BaseTest
from radicale.tests.helpers import configuration_to_dict, get_file_path

View file

@ -26,7 +26,6 @@ import shutil
from typing import ClassVar, cast
import pytest
import radicale.tests.custom.storage_simple_sync
from radicale.tests import BaseTest
from radicale.tests.helpers import get_file_content