Compare commits

..

2 Commits

Author SHA1 Message Date
Julien Dupouy
5ca21d4f46 Rename to radicale3_auth_ldap 2021-04-07 01:09:06 -03:00
Julien Dupouy
0e64ecc229 Update to support Radicale 3.x 2021-04-07 00:52:54 -03:00
8 changed files with 66 additions and 35 deletions

View File

@ -1,12 +1,12 @@
# 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:
``` ```
[auth] [auth]
type = radicale_auth_ldap type = radicale3_auth_ldap
# LDAP server URL, with protocol and port # LDAP server URL, with protocol and port
ldap_url = ldap://ldap:389 ldap_url = ldap://ldap:389

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 radicale3_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

@ -3,10 +3,10 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="radicale-auth-ldap", name="radicale3-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=["radicale3_auth_ldap"])

View File

@ -2,7 +2,7 @@
import unittest import unittest
import radicale_auth_ldap import radicale3_auth_ldap
class References(unittest.TestCase): class References(unittest.TestCase):

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

@ -3,23 +3,21 @@
import logging import logging
import unittest import unittest
import radicale_auth_ldap import radicale3_auth_ldap
from test.configuration import TEST_CONFIGURATION, VALID_USER, VALID_PASS from test.configuration import TEST_CONFIGURATION, VALID_USER, VALID_PASS
from test.util import ConfigMock 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 = radicale3_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