Update to support Radicale 3.x

This commit is contained in:
Julien Dupouy 2021-04-07 00:52:54 -03:00
parent a396d8da41
commit 0e64ecc229
6 changed files with 59 additions and 28 deletions

View File

@ -1,5 +1,5 @@
# What is this? # What is this?
This is an authentication plugin for Radicale 2. It adds an LDAP authentication backend which can be used for authenticating users against an LDAP server. This is an authentication plugin for Radicale 3. It adds an LDAP authentication backend which can be used for authenticating users against an LDAP server.
# How to configure # How to configure
You will need to set a few options inside your radicale config file. Example: You will need to set a few options inside your radicale config file. Example:

View File

@ -30,12 +30,41 @@ import ldap3
import ldap3.core.exceptions import ldap3.core.exceptions
from radicale.auth import BaseAuth from radicale.auth import BaseAuth
from radicale.log import logger
import radicale_auth_ldap.ldap3imports import radicale_auth_ldap.ldap3imports
PLUGIN_CONFIG_SCHEMA = {"auth": {
"ldap_url": {
"value": "ldap://localhost:389",
"type": str},
"ldap_base": {
"value": "ou=Users",
"type": str},
"ldap_attribute": {
"value": "uid",
"type": str},
"ldap_filter": {
"value": "(objectClass=*)",
"type": str},
"ldap_binddn": {
"value": None,
"type": str},
"ldap_password": {
"value": None,
"type": str},
"ldap_scope": {
"value": "SUBTREE",
"type": str},
"ldap_support_extended": {
"value": True,
"type": bool}}}
class Auth(BaseAuth): class Auth(BaseAuth):
def is_authenticated(self, user, password): def __init__(self, configuration):
super().__init__(configuration.copy(PLUGIN_CONFIG_SCHEMA))
def login(self, user, password):
"""Check if ``user``/``password`` couple is valid.""" """Check if ``user``/``password`` couple is valid."""
SERVER = ldap3.Server(self.configuration.get("auth", "ldap_url")) SERVER = ldap3.Server(self.configuration.get("auth", "ldap_url"))
BASE = self.configuration.get("auth", "ldap_base") BASE = self.configuration.get("auth", "ldap_base")
@ -44,7 +73,7 @@ class Auth(BaseAuth):
BINDDN = self.configuration.get("auth", "ldap_binddn") BINDDN = self.configuration.get("auth", "ldap_binddn")
PASSWORD = self.configuration.get("auth", "ldap_password") PASSWORD = self.configuration.get("auth", "ldap_password")
SCOPE = self.configuration.get("auth", "ldap_scope") SCOPE = self.configuration.get("auth", "ldap_scope")
SUPPORT_EXTENDED = self.configuration.getboolean("auth", "ldap_support_extended", fallback=True) SUPPORT_EXTENDED = self.configuration.get("auth", "ldap_support_extended")
if BINDDN and PASSWORD: if BINDDN and PASSWORD:
conn = ldap3.Connection(SERVER, BINDDN, PASSWORD) conn = ldap3.Connection(SERVER, BINDDN, PASSWORD)
@ -53,18 +82,18 @@ class Auth(BaseAuth):
conn.bind() conn.bind()
try: try:
self.logger.debug("LDAP whoami: %s" % conn.extend.standard.who_am_i()) logger.debug("LDAP whoami: %s" % conn.extend.standard.who_am_i())
except Exception as err: except Exception as err:
self.logger.debug("LDAP error: %s" % err) logger.debug("LDAP error: %s" % err)
distinguished_name = "%s=%s" % (ATTRIBUTE, ldap3imports.escape_attribute_value(user)) distinguished_name = "%s=%s" % (ATTRIBUTE, ldap3imports.escape_attribute_value(user))
self.logger.debug("LDAP bind for %s in base %s" % (distinguished_name, BASE)) logger.debug("LDAP bind for %s in base %s" % (distinguished_name, BASE))
if FILTER: if FILTER:
filter_string = "(&(%s)%s)" % (distinguished_name, FILTER) filter_string = "(&(%s)%s)" % (distinguished_name, FILTER)
else: else:
filter_string = distinguished_name filter_string = distinguished_name
self.logger.debug("LDAP filter: %s" % filter_string) logger.debug("LDAP filter: %s" % filter_string)
conn.search(search_base=BASE, conn.search(search_base=BASE,
search_scope=SCOPE, search_scope=SCOPE,
@ -76,28 +105,28 @@ class Auth(BaseAuth):
if users: if users:
user_dn = users[0]['dn'] user_dn = users[0]['dn']
uid = users[0]['attributes'][ATTRIBUTE] uid = users[0]['attributes'][ATTRIBUTE]
self.logger.debug("LDAP user %s (%s) found" % (uid, user_dn)) logger.debug("LDAP user %s (%s) found" % (uid, user_dn))
try: try:
conn = ldap3.Connection(SERVER, user_dn, password) conn = ldap3.Connection(SERVER, user_dn, password)
conn.bind() conn.bind()
self.logger.debug(conn.result) logger.debug(conn.result)
if SUPPORT_EXTENDED: if SUPPORT_EXTENDED:
whoami = conn.extend.standard.who_am_i() whoami = conn.extend.standard.who_am_i()
self.logger.debug("LDAP whoami: %s" % whoami) logger.debug("LDAP whoami: %s" % whoami)
else: else:
self.logger.debug("LDAP skip extended: call whoami") logger.debug("LDAP skip extended: call whoami")
whoami = conn.result['result'] == 0 whoami = conn.result['result'] == 0
if whoami: if whoami:
self.logger.debug("LDAP bind OK") logger.debug("LDAP bind OK")
return True return uid[0]
else: else:
self.logger.debug("LDAP bind failed") logger.debug("LDAP bind failed")
return False return ""
except ldap3.core.exceptions.LDAPInvalidCredentialsResult: except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
self.logger.debug("LDAP invalid credentials") logger.debug("LDAP invalid credentials")
except Exception as err: except Exception as err:
self.logger.debug("LDAP error %s" % err) logger.debug("LDAP error %s" % err)
return False return ""
else: else:
self.logger.debug("LDAP user %s not found" % user) logger.debug("LDAP user %s not found" % user)
return False return ""

View File

@ -4,9 +4,9 @@ from setuptools import setup
setup( setup(
name="radicale-auth-ldap", name="radicale-auth-ldap",
version="0.1", version="3.0",
description="LDAP Authentication Plugin for Radicale 2", description="LDAP Authentication Plugin for Radicale 3",
author="Raoul Thill", author="Raoul Thill",
license="GNU GPL v3", license="GNU GPL v3",
install_requires=["radicale >= 2.0", "ldap3 >= 2.3"], install_requires=["radicale >= 3.0", "ldap3 >= 2.3"],
packages=["radicale_auth_ldap"]) packages=["radicale_auth_ldap"])

View File

@ -8,7 +8,8 @@ TEST_CONFIGURATION = {
'ldap_filter': '(objectClass=person)', 'ldap_filter': '(objectClass=person)',
'ldap_binddn': 'cn=xxx,dc=xxx,dc=xx', 'ldap_binddn': 'cn=xxx,dc=xxx,dc=xx',
'ldap_password': '', 'ldap_password': '',
'ldap_scope': 'LEVEL' 'ldap_scope': 'LEVEL',
'ldap_support_extended': True
} }
} }

View File

@ -10,16 +10,14 @@ from test.util import ConfigMock
class Authentication(unittest.TestCase): class Authentication(unittest.TestCase):
configuration = None configuration = None
logger = None
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.configuration = ConfigMock(TEST_CONFIGURATION) cls.configuration = ConfigMock(TEST_CONFIGURATION)
cls.logger = logging.getLogger(__name__)
def test_authentication_works(self): def test_authentication_works(self):
auth = radicale_auth_ldap.Auth(self.__class__.configuration, self.__class__.logger) auth = radicale_auth_ldap.Auth(self.__class__.configuration)
self.assertTrue(auth.is_authenticated(VALID_USER, VALID_PASS)) self.assertTrue(auth.login(VALID_USER, VALID_PASS))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -11,3 +11,6 @@ class ConfigMock:
def get(self, a, b): def get(self, a, b):
return self.configuration[a][b] return self.configuration[a][b]
def copy(self, c):
return self