mirror of
https://github.com/Kozea/Radicale.git
synced 2025-06-26 16:45:52 +00:00
LDAP auth: indroduce config option 'ldap_user_attribute'
This option gives us - flexible authentication options where the name used for logging on does not have to be the account name e.g. use ldap_filter = (&(obhjectclass=inetOrgperson)(|(cn={0]})(mail={0}))) to allow loginng on using the cn or the mail address - automatically consistent / canonicalized username values (i.e. exactly the way the LDAP server returns them)
This commit is contained in:
parent
0253682c00
commit
99f5ec389d
4 changed files with 46 additions and 9 deletions
|
@ -901,6 +901,12 @@ The search filter to find the user DN to authenticate by the username. User '{0}
|
|||
|
||||
Default: `(cn={0})`
|
||||
|
||||
#### ldap_user_attribute
|
||||
|
||||
The LDAP attribute whose value shall be used as the user name after successful authentication
|
||||
|
||||
Default: not set, i.e. the login name given is used directly.
|
||||
|
||||
##### ldap_load_groups
|
||||
|
||||
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.
|
||||
|
|
3
config
3
config
|
@ -83,6 +83,9 @@
|
|||
# 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}))
|
||||
|
||||
# the attribute holding the value to be used as username after authentication
|
||||
#ldap_user_attribute = cn
|
||||
|
||||
# Use ssl on the ldap connection
|
||||
#ldap_use_ssl = False
|
||||
|
||||
|
|
|
@ -17,13 +17,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_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
|
||||
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_user_attribute The attribute to be used as username after authentication
|
||||
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 certificate verification mode. NONE, OPTIONAL, default is REQUIRED
|
||||
|
@ -42,6 +43,7 @@ class Auth(auth.BaseAuth):
|
|||
_ldap_reader_dn: str
|
||||
_ldap_secret: str
|
||||
_ldap_filter: str
|
||||
_ldap_user_attr: str
|
||||
_ldap_load_groups: bool
|
||||
_ldap_module_version: int = 3
|
||||
_ldap_use_ssl: bool = False
|
||||
|
@ -66,6 +68,7 @@ class Auth(auth.BaseAuth):
|
|||
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")
|
||||
self._ldap_user_attr = configuration.get("auth", "ldap_user_attribute")
|
||||
ldap_secret_file_path = configuration.get("auth", "ldap_secret_file")
|
||||
if ldap_secret_file_path:
|
||||
with open(ldap_secret_file_path, 'r') as file:
|
||||
|
@ -84,6 +87,10 @@ class Auth(auth.BaseAuth):
|
|||
logger.info("auth.ldap_reader_dn : %r" % self._ldap_reader_dn)
|
||||
logger.info("auth.ldap_load_groups : %s" % self._ldap_load_groups)
|
||||
logger.info("auth.ldap_filter : %r" % self._ldap_filter)
|
||||
if self._ldap_user_attr:
|
||||
logger.info("auth.ldap_user_attribute : %r" % self._ldap_user_attr)
|
||||
else:
|
||||
logger.info("auth.ldap_user_attribute : (not provided)")
|
||||
if ldap_secret_file_path:
|
||||
logger.info("auth.ldap_secret_file_path: %r" % ldap_secret_file_path)
|
||||
if self._ldap_secret:
|
||||
|
@ -114,11 +121,15 @@ class Auth(auth.BaseAuth):
|
|||
"""Search for the dn of user to authenticate"""
|
||||
escaped_login = self.ldap.filter.escape_filter_chars(login)
|
||||
logger.debug(f"_login2 login escaped for LDAP filters: {escaped_login}")
|
||||
attrs = ['memberof']
|
||||
if self._ldap_user_attr:
|
||||
attrs = ['memberOf', self._ldap_user_attr]
|
||||
logger.debug(f"_login2 attrs: {attrs}")
|
||||
res = conn.search_s(
|
||||
self._ldap_base,
|
||||
self.ldap.SCOPE_SUBTREE,
|
||||
filterstr=self._ldap_filter.format(escaped_login),
|
||||
attrlist=['memberOf']
|
||||
attrlist=attrs
|
||||
)
|
||||
if len(res) != 1:
|
||||
"""User could not be found unambiguously"""
|
||||
|
@ -147,6 +158,11 @@ class Auth(auth.BaseAuth):
|
|||
tmp.append(g.partition('=')[2])
|
||||
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]
|
||||
login = tmplogin.decode('utf-8')
|
||||
logger.debug(f"_login2 user set to: '{login}'")
|
||||
conn.unbind()
|
||||
logger.debug(f"_login2 {login} successfully authenticated")
|
||||
return login
|
||||
|
@ -182,11 +198,15 @@ class Auth(auth.BaseAuth):
|
|||
"""Search the user dn"""
|
||||
escaped_login = self.ldap3.utils.conv.escape_filter_chars(login)
|
||||
logger.debug(f"_login3 login escaped for LDAP filters: {escaped_login}")
|
||||
attrs = ['memberof']
|
||||
if self._ldap_user_attr:
|
||||
attrs = ['memberOf', self._ldap_user_attr]
|
||||
logger.debug(f"_login3 attrs: {attrs}")
|
||||
conn.search(
|
||||
search_base=self._ldap_base,
|
||||
search_filter=self._ldap_filter.format(escaped_login),
|
||||
search_scope=self.ldap3.SUBTREE,
|
||||
attributes=['memberOf']
|
||||
attributes=attrs
|
||||
)
|
||||
if len(conn.entries) != 1:
|
||||
"""User could not be found unambiguously"""
|
||||
|
@ -212,6 +232,10 @@ class Auth(auth.BaseAuth):
|
|||
tmp.append(g.partition('=')[2])
|
||||
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]:
|
||||
login = user_entry['attributes'][self._ldap_user_attr][0]
|
||||
logger.debug(f"_login3 user set to: '{login}'")
|
||||
conn.unbind()
|
||||
logger.debug(f"_login3 {login} successfully authenticated")
|
||||
return login
|
||||
|
|
|
@ -227,6 +227,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
|
|||
"value": "(cn={0})",
|
||||
"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",
|
||||
"type": str}),
|
||||
("ldap_load_groups", {
|
||||
"value": "False",
|
||||
"help": "load the ldap groups of the authenticated user",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue