mirror of
https://github.com/Kozea/Radicale.git
synced 2025-06-26 16:45:52 +00:00
Initial version of ldap authentication backend.
This commit is contained in:
parent
47e42a46c1
commit
2dc0fd29dc
4 changed files with 156 additions and 2 deletions
|
@ -667,6 +667,9 @@ Available backends:
|
|||
authentication. This can be used to provide the username from a reverse
|
||||
proxy.
|
||||
|
||||
`ldap`
|
||||
: Use a LDAP or AD server to authenticate users.
|
||||
|
||||
Default: `none`
|
||||
|
||||
##### htpasswd_filename
|
||||
|
@ -713,6 +716,42 @@ Message displayed in the client when a password is needed.
|
|||
|
||||
Default: `Radicale - Password Required`
|
||||
|
||||
##### ldap_uri
|
||||
|
||||
The URI to the ldap server
|
||||
|
||||
Default: `ldap://localhost`
|
||||
|
||||
##### ldap_base
|
||||
|
||||
LDAP base DN of the ldap server. This parameter must be provided if auth type is ldap.
|
||||
|
||||
Default:
|
||||
|
||||
##### ldap_reader_dn
|
||||
|
||||
The DN of a ldap user with read access to get the user accounts. This parameter must be provided if auth type is ldap.
|
||||
|
||||
Default:
|
||||
|
||||
##### ldap_secret
|
||||
|
||||
The password of the ldap_reader_dn. This parameter must be provided if auth type is ldap.
|
||||
|
||||
Default:
|
||||
|
||||
##### ldap_filter
|
||||
|
||||
The search filter to find the user DN to authenticate by the username. User '{0}' as placeholder for the user name.
|
||||
|
||||
Default: `(cn={0})`
|
||||
|
||||
##### ldap_load_groups
|
||||
|
||||
Load the ldap groups of the authenticated user. These groups can be used later on to define rights.
|
||||
|
||||
Default: False
|
||||
|
||||
#### rights
|
||||
|
||||
##### type
|
||||
|
|
|
@ -33,7 +33,7 @@ from typing import Sequence, Tuple, Union
|
|||
from radicale import config, types, utils
|
||||
|
||||
INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
|
||||
"htpasswd")
|
||||
"htpasswd", "ldap")
|
||||
|
||||
|
||||
def load(configuration: "config.Configuration") -> "BaseAuth":
|
||||
|
|
90
radicale/auth/ldap.py
Normal file
90
radicale/auth/ldap.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# This file is part of Radicale - CalDAV and CardDAV server
|
||||
# Copyright 2022 Peter Varkoly
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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_load_groups If the groups of the authenticated users need to be loaded
|
||||
"""
|
||||
|
||||
import ldap
|
||||
from radicale import auth, config
|
||||
from radicale.log import logger
|
||||
|
||||
class Auth(auth.BaseAuth):
|
||||
_ldap_uri: str
|
||||
_ldap_base: str
|
||||
_ldap_reader_dn: str
|
||||
_ldap_secret: str
|
||||
_ldap_filter: str
|
||||
_ldap_load_groups: bool
|
||||
_ldap_groups = []
|
||||
|
||||
def __init__(self, configuration: config.Configuration) -> None:
|
||||
super().__init__(configuration)
|
||||
self._ldap_uri = configuration.get("auth", "ldap_uri")
|
||||
self._ldap_base = configuration.get("auth", "ldap_base")
|
||||
self._ldap_reader_dn = configuration.get("auth", "ldap_reader_dn")
|
||||
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")
|
||||
|
||||
def login(self, login: str, password: str) -> str:
|
||||
"""Validate credentials.
|
||||
In first step we make a connection to the ldap server with the ldap_reader_dn credential.
|
||||
In next step the DN of the user to authenticate will be searched.
|
||||
In the last step the authentication of the user will be proceeded.
|
||||
|
||||
"""
|
||||
try:
|
||||
"""Bind as reader dn"""
|
||||
conn = ldap.initialize(self._ldap_uri)
|
||||
conn.protocol_version = 3
|
||||
conn.set_option(ldap.OPT_REFERRALS, 0)
|
||||
conn.simple_bind_s(self._ldap_reader_dn, self._ldap_secret)
|
||||
"""Search for the dn of user to authenticate"""
|
||||
res = conn.search_s(self._ldap_base, ldap.SCOPE_SUBTREE, filterstr=self._ldap_filter.format(login), attrlist=['memberOf'])
|
||||
if len(res) == 0:
|
||||
"""User could not be find"""
|
||||
return ""
|
||||
user_dn = res[0][0]
|
||||
logger.debug("LDAP Auth user: %s",user_dn)
|
||||
"""Close ldap connection"""
|
||||
conn.unbind()
|
||||
except Exception:
|
||||
raise RuntimeError("Invalide ldap configuration")
|
||||
|
||||
try:
|
||||
"""Bind as user to authenticate"""
|
||||
conn = ldap.initialize(self._ldap_uri)
|
||||
conn.protocol_version = 3
|
||||
conn.set_option(ldap.OPT_REFERRALS, 0)
|
||||
conn.simple_bind_s(user_dn,password)
|
||||
if self._ldap_load_groups:
|
||||
self._ldap_groups = []
|
||||
for t in res[0][1]['memberOf']:
|
||||
self._ldap_groups.append(t.decode('utf-8').split(',')[0][3:])
|
||||
logger.debug("LDAP Auth groups of user: %s",",".join(self._ldap_groups))
|
||||
conn.unbind()
|
||||
return login
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
return ""
|
||||
|
||||
|
|
@ -177,7 +177,32 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
|
|||
("delay", {
|
||||
"value": "1",
|
||||
"help": "incorrect authentication delay",
|
||||
"type": positive_float})])),
|
||||
"type": positive_float}),
|
||||
("ldap_uri", {
|
||||
"value": "ldap://localhost",
|
||||
"help": "URI to the ldap server",
|
||||
"type": str}),
|
||||
("ldap_base", {
|
||||
"value": "none",
|
||||
"help": "LDAP base DN of the ldap server",
|
||||
"type": str}),
|
||||
("ldap_reader_dn", {
|
||||
"value": "none",
|
||||
"help": "the DN of a ldap user with read access to get the user accounts",
|
||||
"type": str}),
|
||||
("ldap_secret", {
|
||||
"value": "none",
|
||||
"help": "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",
|
||||
"type": str}),
|
||||
("ldap_load_groups", {
|
||||
"value": "False",
|
||||
"help": "load the ldap groups of the authenticated user",
|
||||
"type": bool}),
|
||||
])),
|
||||
("rights", OrderedDict([
|
||||
("type", {
|
||||
"value": "owner_only",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue