diff --git a/README.md b/README.md index 7d4a4be..7f37cad 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # 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 You will need to set a few options inside your radicale config file. Example: diff --git a/radicale_auth_ldap/__init__.py b/radicale_auth_ldap/__init__.py index 55ed3d5..6c0b8ad 100644 --- a/radicale_auth_ldap/__init__.py +++ b/radicale_auth_ldap/__init__.py @@ -30,12 +30,41 @@ import ldap3 import ldap3.core.exceptions from radicale.auth import BaseAuth +from radicale.log import logger 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): - 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.""" SERVER = ldap3.Server(self.configuration.get("auth", "ldap_url")) BASE = self.configuration.get("auth", "ldap_base") @@ -44,7 +73,7 @@ class Auth(BaseAuth): BINDDN = self.configuration.get("auth", "ldap_binddn") PASSWORD = self.configuration.get("auth", "ldap_password") 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: conn = ldap3.Connection(SERVER, BINDDN, PASSWORD) @@ -53,18 +82,18 @@ class Auth(BaseAuth): conn.bind() 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: - self.logger.debug("LDAP error: %s" % err) + logger.debug("LDAP error: %s" % err) 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: filter_string = "(&(%s)%s)" % (distinguished_name, FILTER) else: filter_string = distinguished_name - self.logger.debug("LDAP filter: %s" % filter_string) + logger.debug("LDAP filter: %s" % filter_string) conn.search(search_base=BASE, search_scope=SCOPE, @@ -76,28 +105,28 @@ class Auth(BaseAuth): if users: user_dn = users[0]['dn'] 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: conn = ldap3.Connection(SERVER, user_dn, password) conn.bind() - self.logger.debug(conn.result) + logger.debug(conn.result) if SUPPORT_EXTENDED: whoami = conn.extend.standard.who_am_i() - self.logger.debug("LDAP whoami: %s" % whoami) + logger.debug("LDAP whoami: %s" % whoami) else: - self.logger.debug("LDAP skip extended: call whoami") + logger.debug("LDAP skip extended: call whoami") whoami = conn.result['result'] == 0 if whoami: - self.logger.debug("LDAP bind OK") - return True + logger.debug("LDAP bind OK") + return uid[0] else: - self.logger.debug("LDAP bind failed") - return False + logger.debug("LDAP bind failed") + return "" except ldap3.core.exceptions.LDAPInvalidCredentialsResult: - self.logger.debug("LDAP invalid credentials") + logger.debug("LDAP invalid credentials") except Exception as err: - self.logger.debug("LDAP error %s" % err) - return False + logger.debug("LDAP error %s" % err) + return "" else: - self.logger.debug("LDAP user %s not found" % user) - return False + logger.debug("LDAP user %s not found" % user) + return "" diff --git a/setup.py b/setup.py index eb6e257..aa3c62f 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,9 @@ from setuptools import setup setup( name="radicale-auth-ldap", - version="0.1", - description="LDAP Authentication Plugin for Radicale 2", + version="3.0", + description="LDAP Authentication Plugin for Radicale 3", author="Raoul Thill", license="GNU GPL v3", - install_requires=["radicale >= 2.0", "ldap3 >= 2.3"], + install_requires=["radicale >= 3.0", "ldap3 >= 2.3"], packages=["radicale_auth_ldap"]) diff --git a/test/configuration.py b/test/configuration.py index 31a40ba..75a02ae 100644 --- a/test/configuration.py +++ b/test/configuration.py @@ -8,7 +8,8 @@ TEST_CONFIGURATION = { 'ldap_filter': '(objectClass=person)', 'ldap_binddn': 'cn=xxx,dc=xxx,dc=xx', 'ldap_password': '', - 'ldap_scope': 'LEVEL' + 'ldap_scope': 'LEVEL', + 'ldap_support_extended': True } } diff --git a/test/integration_tests.py b/test/integration_tests.py index f6f8820..d38b32c 100644 --- a/test/integration_tests.py +++ b/test/integration_tests.py @@ -10,16 +10,14 @@ from test.util import ConfigMock class Authentication(unittest.TestCase): configuration = None - logger = None @classmethod def setUpClass(cls): cls.configuration = ConfigMock(TEST_CONFIGURATION) - cls.logger = logging.getLogger(__name__) def test_authentication_works(self): - auth = radicale_auth_ldap.Auth(self.__class__.configuration, self.__class__.logger) - self.assertTrue(auth.is_authenticated(VALID_USER, VALID_PASS)) + auth = radicale_auth_ldap.Auth(self.__class__.configuration) + self.assertTrue(auth.login(VALID_USER, VALID_PASS)) if __name__ == '__main__': diff --git a/test/util.py b/test/util.py index 97566b0..4d2084a 100644 --- a/test/util.py +++ b/test/util.py @@ -11,3 +11,6 @@ class ConfigMock: def get(self, a, b): return self.configuration[a][b] + + def copy(self, c): + return self