mirror of
https://github.com/Kozea/Radicale.git
synced 2025-09-15 20:36:55 +00:00
Merge aced3522dc
into aead3a2a52
This commit is contained in:
commit
ce1e70aaa5
3 changed files with 113 additions and 45 deletions
15
config
15
config
|
@ -93,15 +93,24 @@
|
||||||
# Path of the file containing password of the reader DN
|
# Path of the file containing password of the reader DN
|
||||||
#ldap_secret_file = /run/secrets/ldap_password
|
#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 filter to find the DN of the user. This filter must contain a python-style placeholder for the login
|
# 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}))
|
#ldap_filter = (&(objectClass=person)(uid={0}))
|
||||||
|
|
||||||
# the attribute holding the value to be used as username after authentication
|
# the attribute holding the value to be used as username after authentication
|
||||||
#ldap_user_attribute = cn
|
#ldap_user_attribute = cn
|
||||||
|
|
||||||
|
# The attribute in user entry to read the group memberships from.
|
||||||
|
#ldap_groups_attribute =
|
||||||
|
|
||||||
|
# The attribute in group entries to read the group members from.
|
||||||
|
#ldap_group_members_attribute =
|
||||||
|
|
||||||
|
# The base dn to find the groups. Necessary only if ldap_group_members_attribute is defined and different from ldap_base.
|
||||||
|
#ldap_groups_base =
|
||||||
|
|
||||||
|
# Additional filter to find the groups when ldap_group_members_attribute is defined. The following filter will be built (&{ldap_groups_filter}({ldap_group_members_attribute}={user_dn})
|
||||||
|
#ldap_groups_filter =
|
||||||
|
|
||||||
# Use ssl on the ldap connection
|
# Use ssl on the ldap connection
|
||||||
# Soon to be deprecated, use ldap_security instead
|
# Soon to be deprecated, use ldap_security instead
|
||||||
#ldap_use_ssl = False
|
#ldap_use_ssl = False
|
||||||
|
|
|
@ -17,18 +17,19 @@
|
||||||
"""
|
"""
|
||||||
Authentication backend that checks credentials with a LDAP server.
|
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_uri LDAP URL to the server like ldap://localhost
|
||||||
ldap_base The baseDN of the LDAP server
|
ldap_base 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_reader_dn DN of a LDAP user with read access to get the user accounts
|
||||||
ldap_secret The password of the ldap_reader_dn
|
ldap_secret Password of the ldap_reader_dn
|
||||||
ldap_secret_file The path of the file containing the password of the ldap_reader_dn
|
ldap_secret_file 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_filter Search filter to find the user to authenticate by the username
|
||||||
ldap_user_attribute The attribute to be used as username after authentication
|
ldap_user_attribute Attribute to be used as username after authentication
|
||||||
ldap_groups_attribute The attribute containing group memberships in the LDAP user entry
|
ldap_groups_attribute Attribute containing group memberships in the LDAP user entry
|
||||||
|
ldap_group_base BaseDN of the LDAP server searching for groups.
|
||||||
Following parameters controls SSL connections:
|
Following parameters controls SSL connections:
|
||||||
ldap_use_ssl If ssl encryption should be used (to be deprecated)
|
ldap_use_ssl If ssl encryption should be used (to be deprecated)
|
||||||
ldap_security The encryption mode to be used: *none*|tls|starttls
|
ldap_security Encryption mode to be used: *none*|tls|starttls
|
||||||
ldap_ssl_verify_mode The certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED
|
ldap_ssl_verify_mode Certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED
|
||||||
ldap_ssl_ca_file
|
ldap_ssl_ca_file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -47,6 +48,9 @@ class Auth(auth.BaseAuth):
|
||||||
_ldap_attributes: list[str] = []
|
_ldap_attributes: list[str] = []
|
||||||
_ldap_user_attr: str
|
_ldap_user_attr: str
|
||||||
_ldap_groups_attr: str
|
_ldap_groups_attr: str
|
||||||
|
_ldap_group_members_attr: str
|
||||||
|
_ldap_group_base: str
|
||||||
|
_ldap_group_filter: str
|
||||||
_ldap_module_version: int = 3
|
_ldap_module_version: int = 3
|
||||||
_ldap_use_ssl: bool = False
|
_ldap_use_ssl: bool = False
|
||||||
_ldap_security: str = "none"
|
_ldap_security: str = "none"
|
||||||
|
@ -78,6 +82,11 @@ class Auth(auth.BaseAuth):
|
||||||
self._ldap_filter = configuration.get("auth", "ldap_filter")
|
self._ldap_filter = configuration.get("auth", "ldap_filter")
|
||||||
self._ldap_user_attr = configuration.get("auth", "ldap_user_attribute")
|
self._ldap_user_attr = configuration.get("auth", "ldap_user_attribute")
|
||||||
self._ldap_groups_attr = configuration.get("auth", "ldap_groups_attribute")
|
self._ldap_groups_attr = configuration.get("auth", "ldap_groups_attribute")
|
||||||
|
self._ldap_group_members_attr = configuration.get("auth", "ldap_group_member_attribute")
|
||||||
|
self._ldap_group_base = configuration.get("auth", "ldap_group_base")
|
||||||
|
if self._ldap_group_base == "":
|
||||||
|
self._ldap_group_base = self._ldap_base
|
||||||
|
self._ldap_group_filter = configuration.get("auth", "ldap_group_filter")
|
||||||
ldap_secret_file_path = configuration.get("auth", "ldap_secret_file")
|
ldap_secret_file_path = configuration.get("auth", "ldap_secret_file")
|
||||||
if ldap_secret_file_path:
|
if ldap_secret_file_path:
|
||||||
with open(ldap_secret_file_path, 'r') as file:
|
with open(ldap_secret_file_path, 'r') as file:
|
||||||
|
@ -102,6 +111,7 @@ class Auth(auth.BaseAuth):
|
||||||
logger.info("auth.ldap_base : %r" % self._ldap_base)
|
logger.info("auth.ldap_base : %r" % self._ldap_base)
|
||||||
logger.info("auth.ldap_reader_dn : %r" % self._ldap_reader_dn)
|
logger.info("auth.ldap_reader_dn : %r" % self._ldap_reader_dn)
|
||||||
logger.info("auth.ldap_filter : %r" % self._ldap_filter)
|
logger.info("auth.ldap_filter : %r" % self._ldap_filter)
|
||||||
|
logger.info("auth.ldap_group_base : %r" % self._ldap_group_base)
|
||||||
if self._ldap_user_attr:
|
if self._ldap_user_attr:
|
||||||
logger.info("auth.ldap_user_attribute : %r" % self._ldap_user_attr)
|
logger.info("auth.ldap_user_attribute : %r" % self._ldap_user_attr)
|
||||||
else:
|
else:
|
||||||
|
@ -110,6 +120,14 @@ class Auth(auth.BaseAuth):
|
||||||
logger.info("auth.ldap_groups_attribute: %r" % self._ldap_groups_attr)
|
logger.info("auth.ldap_groups_attribute: %r" % self._ldap_groups_attr)
|
||||||
else:
|
else:
|
||||||
logger.info("auth.ldap_groups_attribute: (not provided)")
|
logger.info("auth.ldap_groups_attribute: (not provided)")
|
||||||
|
if self._ldap_group_members_attr:
|
||||||
|
logger.info("auth.ldap_group_members_attr: %r" % self._ldap_group_members_attr)
|
||||||
|
else:
|
||||||
|
logger.info("auth.ldap_group_members_attr: (not provided)")
|
||||||
|
if self._ldap_group_filter:
|
||||||
|
logger.info("auth.ldap_group_filter: %r" % self._ldap_group_filter)
|
||||||
|
else:
|
||||||
|
logger.info("auth.ldap_group_filter: (not provided)")
|
||||||
if ldap_secret_file_path:
|
if ldap_secret_file_path:
|
||||||
logger.info("auth.ldap_secret_file_path: %r" % ldap_secret_file_path)
|
logger.info("auth.ldap_secret_file_path: %r" % ldap_secret_file_path)
|
||||||
if self._ldap_secret:
|
if self._ldap_secret:
|
||||||
|
@ -137,6 +155,7 @@ class Auth(auth.BaseAuth):
|
||||||
logger.info("ldap_attributes : %r" % self._ldap_attributes)
|
logger.info("ldap_attributes : %r" % self._ldap_attributes)
|
||||||
|
|
||||||
def _login2(self, login: str, password: str) -> str:
|
def _login2(self, login: str, password: str) -> str:
|
||||||
|
gdns: list[str] = []
|
||||||
try:
|
try:
|
||||||
"""Bind as reader dn"""
|
"""Bind as reader dn"""
|
||||||
logger.debug(f"_login2 {self._ldap_uri}, {self._ldap_reader_dn}")
|
logger.debug(f"_login2 {self._ldap_uri}, {self._ldap_reader_dn}")
|
||||||
|
@ -160,6 +179,19 @@ class Auth(auth.BaseAuth):
|
||||||
user_entry = res[0]
|
user_entry = res[0]
|
||||||
user_dn = user_entry[0]
|
user_dn = user_entry[0]
|
||||||
logger.debug(f"_login2 found LDAP user DN {user_dn}")
|
logger.debug(f"_login2 found LDAP user DN {user_dn}")
|
||||||
|
if self._ldap_group_members_attr:
|
||||||
|
"""Collect groups from the member or uniqueMember attributes"""
|
||||||
|
res = conn.search_s(
|
||||||
|
self._ldap_group_base,
|
||||||
|
self.ldap.SCOPE_SUBTREE,
|
||||||
|
filterstr="(&{0}({1}={2}))".format(
|
||||||
|
self._ldap_group_filter,
|
||||||
|
self._ldap_group_members_attr,
|
||||||
|
self.ldap.filter.escape_filter_chars(user_dn)),
|
||||||
|
attrlist=self._ldap_attributes
|
||||||
|
)
|
||||||
|
for group in res:
|
||||||
|
gdns.append(group[0])
|
||||||
"""Close LDAP connection"""
|
"""Close LDAP connection"""
|
||||||
conn.unbind()
|
conn.unbind()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -173,16 +205,16 @@ class Auth(auth.BaseAuth):
|
||||||
conn.simple_bind_s(user_dn, password)
|
conn.simple_bind_s(user_dn, password)
|
||||||
tmp: list[str] = []
|
tmp: list[str] = []
|
||||||
if self._ldap_groups_attr:
|
if self._ldap_groups_attr:
|
||||||
tmp = []
|
gdns = user_entry[1][self._ldap_groups_attr]
|
||||||
for g in user_entry[1][self._ldap_groups_attr]:
|
for g in gdns:
|
||||||
"""Get group g's RDN's attribute value"""
|
"""Get group g's RDN's attribute value"""
|
||||||
try:
|
try:
|
||||||
rdns = self.ldap.dn.explode_dn(g, notypes=True)
|
rdns = self.ldap.dn.explode_dn(g, notypes=True)
|
||||||
tmp.append(rdns[0])
|
tmp.append(rdns[0])
|
||||||
except Exception:
|
except Exception:
|
||||||
tmp.append(g.decode('utf8'))
|
tmp.append(g.decode('utf8'))
|
||||||
self._ldap_groups = set(tmp)
|
self._ldap_groups = set(tmp)
|
||||||
logger.debug("_login2 LDAP groups of user: %s", ",".join(self._ldap_groups))
|
logger.debug("_login2 LDAP groups of user: %s", ",".join(self._ldap_groups))
|
||||||
if self._ldap_user_attr:
|
if self._ldap_user_attr:
|
||||||
if user_entry[1][self._ldap_user_attr]:
|
if user_entry[1][self._ldap_user_attr]:
|
||||||
tmplogin = user_entry[1][self._ldap_user_attr][0]
|
tmplogin = user_entry[1][self._ldap_user_attr][0]
|
||||||
|
@ -195,6 +227,7 @@ class Auth(auth.BaseAuth):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _login3(self, login: str, password: str) -> str:
|
def _login3(self, login: str, password: str) -> str:
|
||||||
|
gdns: list[str] = []
|
||||||
"""Connect the server"""
|
"""Connect the server"""
|
||||||
try:
|
try:
|
||||||
logger.debug(f"_login3 {self._ldap_uri}, {self._ldap_reader_dn}")
|
logger.debug(f"_login3 {self._ldap_uri}, {self._ldap_reader_dn}")
|
||||||
|
@ -249,8 +282,21 @@ class Auth(auth.BaseAuth):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
user_entry = conn.response[0]
|
user_entry = conn.response[0]
|
||||||
conn.unbind()
|
|
||||||
user_dn = user_entry['dn']
|
user_dn = user_entry['dn']
|
||||||
|
if self._ldap_group_members_attr:
|
||||||
|
"""Collect groups from the member or uniqueMember attributes"""
|
||||||
|
conn.search(
|
||||||
|
search_base=self._ldap_group_base,
|
||||||
|
search_filter="(&{0}({1}={2}))".format(
|
||||||
|
self._ldap_group_filter,
|
||||||
|
self._ldap_group_members_attr,
|
||||||
|
self.ldap3.utils.conv.escape_filter_chars(user_dn)),
|
||||||
|
search_scope=self.ldap3.SUBTREE,
|
||||||
|
attributes="dn"
|
||||||
|
)
|
||||||
|
for group in conn.response:
|
||||||
|
gdns.append(group['dn'])
|
||||||
|
conn.unbind()
|
||||||
logger.debug(f"_login3 found LDAP user DN {user_dn}")
|
logger.debug(f"_login3 found LDAP user DN {user_dn}")
|
||||||
try:
|
try:
|
||||||
"""Try to bind as the user itself"""
|
"""Try to bind as the user itself"""
|
||||||
|
@ -264,18 +310,19 @@ class Auth(auth.BaseAuth):
|
||||||
if not conn.bind(read_server_info=False):
|
if not conn.bind(read_server_info=False):
|
||||||
logger.debug(f"_login3 user '{login}' cannot be found")
|
logger.debug(f"_login3 user '{login}' cannot be found")
|
||||||
return ""
|
return ""
|
||||||
tmp: list[str] = []
|
"""Let's collect the groups of the user."""
|
||||||
if self._ldap_groups_attr:
|
if self._ldap_groups_attr:
|
||||||
tmp = []
|
gdns = user_entry['attributes'][self._ldap_groups_attr]
|
||||||
for g in user_entry['attributes'][self._ldap_groups_attr]:
|
tmp: list[str] = []
|
||||||
"""Get group g's RDN's attribute value"""
|
for g in gdns:
|
||||||
try:
|
"""Get group g's RDN's attribute value"""
|
||||||
rdns = self.ldap3.utils.dn.parse_dn(g)
|
try:
|
||||||
tmp.append(rdns[0][1])
|
rdns = self.ldap3.utils.dn.parse_dn(g)
|
||||||
except Exception:
|
tmp.append(rdns[0][1])
|
||||||
tmp.append(g)
|
except Exception:
|
||||||
self._ldap_groups = set(tmp)
|
tmp.append(g)
|
||||||
logger.debug("_login3 LDAP groups of user: %s", ",".join(self._ldap_groups))
|
self._ldap_groups = set(tmp)
|
||||||
|
logger.debug("_login3 LDAP groups of user: %s", ",".join(self._ldap_groups))
|
||||||
if self._ldap_user_attr:
|
if self._ldap_user_attr:
|
||||||
if user_entry['attributes'][self._ldap_user_attr]:
|
if user_entry['attributes'][self._ldap_user_attr]:
|
||||||
if isinstance(user_entry['attributes'][self._ldap_user_attr], list):
|
if isinstance(user_entry['attributes'][self._ldap_user_attr], list):
|
||||||
|
|
|
@ -271,31 +271,43 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_base", {
|
("ldap_base", {
|
||||||
"value": "",
|
"value": "",
|
||||||
"help": "LDAP base DN of the ldap server",
|
"help": "Base DN of the ldap server where the user can be find.",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_reader_dn", {
|
("ldap_reader_dn", {
|
||||||
"value": "",
|
"value": "",
|
||||||
"help": "the DN of a ldap user with read access to get the user accounts",
|
"help": "DN of a ldap user with read access to get the user accounts",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_secret", {
|
("ldap_secret", {
|
||||||
"value": "",
|
"value": "",
|
||||||
"help": "the password of the ldap_reader_dn",
|
"help": "Password of the ldap_reader_dn",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_secret_file", {
|
("ldap_secret_file", {
|
||||||
"value": "",
|
"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}),
|
"type": str}),
|
||||||
("ldap_filter", {
|
("ldap_filter", {
|
||||||
"value": "(cn={0})",
|
"value": "(cn={0})",
|
||||||
"help": "the search filter to find the user DN to authenticate by the username",
|
"help": "Search filter to find the user DN to authenticate by the username",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_user_attribute", {
|
("ldap_user_attribute", {
|
||||||
"value": "",
|
"value": "",
|
||||||
"help": "the attribute to be used as username after authentication",
|
"help": "Attribute to be used as username after authentication",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_groups_attribute", {
|
("ldap_groups_attribute", {
|
||||||
"value": "",
|
"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_member_attribute", {
|
||||||
|
"value": "",
|
||||||
|
"help": "Attribute in the group entries to read the group members from.",
|
||||||
|
"type": str}),
|
||||||
|
("ldap_group_base", {
|
||||||
|
"value": "",
|
||||||
|
"help": "Base DN to find the groups. Necessary only if ldap_group_member_attribute is defined and different from ldap_base.",
|
||||||
|
"type": str}),
|
||||||
|
("ldap_group_filter", {
|
||||||
|
"value": "",
|
||||||
|
"help": "Additional filter to find the groups when ldap_group_member_attribute is defined. Following filter will be built (&{ldap_group_filter}({ldap_group_member_attribute}={user_dn})",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_use_ssl", {
|
("ldap_use_ssl", {
|
||||||
"value": "False",
|
"value": "False",
|
||||||
|
@ -303,15 +315,15 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
|
||||||
"type": bool}),
|
"type": bool}),
|
||||||
("ldap_security", {
|
("ldap_security", {
|
||||||
"value": "none",
|
"value": "none",
|
||||||
"help": "the encryption mode to be used: *none*|tls|starttls",
|
"help": "Encryption mode to be used: *none*|tls|starttls",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_ssl_verify_mode", {
|
("ldap_ssl_verify_mode", {
|
||||||
"value": "REQUIRED",
|
"value": "REQUIRED",
|
||||||
"help": "The certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED",
|
"help": "Certificate verification mode. Works for tls and starttls. NONE, OPTIONAL, default is REQUIRED",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("ldap_ssl_ca_file", {
|
("ldap_ssl_ca_file", {
|
||||||
"value": "",
|
"value": "",
|
||||||
"help": "The path to the CA file in pem format which is used to certificate the server certificate",
|
"help": "Path to the CA file in pem format which is used to certificate the server certificate",
|
||||||
"type": str}),
|
"type": str}),
|
||||||
("imap_host", {
|
("imap_host", {
|
||||||
"value": "localhost",
|
"value": "localhost",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue