From 5c11ebf0d3097c085ccff3bae5b35d3d4d675708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Mon, 25 Dec 2023 14:21:09 +0100 Subject: [PATCH] feat: ldap connection is lazilly opened --- canaille/backends/__init__.py | 2 - canaille/backends/ldap/backend.py | 130 ++++++++++++------------------ tests/backends/test_backends.py | 5 -- 3 files changed, 52 insertions(+), 85 deletions(-) diff --git a/canaille/backends/__init__.py b/canaille/backends/__init__.py index 7c1bb77d..29f5d330 100644 --- a/canaille/backends/__init__.py +++ b/canaille/backends/__init__.py @@ -44,14 +44,12 @@ class BaseBackend: This method will be called before each http request, it should open the connection to the backend. """ - raise NotImplementedError() def teardown(self): """ This method will be called after each http request, it should close the connections to the backend. """ - raise NotImplementedError() @classmethod def validate(cls, config): diff --git a/canaille/backends/ldap/backend.py b/canaille/backends/ldap/backend.py index 7353f8fd..ec649f47 100644 --- a/canaille/backends/ldap/backend.py +++ b/canaille/backends/ldap/backend.py @@ -10,7 +10,6 @@ from canaille.app.configuration import ConfigurationException from canaille.app.i18n import gettext as _ from canaille.backends import BaseBackend from flask import current_app -from flask import request from .utils import listify @@ -51,7 +50,7 @@ def install_schema(config, schema_path): class Backend(BaseBackend): def __init__(self, config): super().__init__(config) - self.connection = None + self._connection = None setup_ldap_models(config) @classmethod @@ -74,23 +73,18 @@ class Backend(BaseBackend): os.path.dirname(__file__) + "/schemas/oauth2-openldap.ldif", ) - def setup(self): - if self.connection: - return - - try: # pragma: no cover - if request.endpoint == "static": - return - except RuntimeError: # pragma: no cover - pass + @property + def connection(self): + if self._connection: + return self._connection try: - self.connection = ldap.initialize(self.config["BACKENDS"]["LDAP"]["URI"]) - self.connection.set_option( + self._connection = ldap.initialize(self.config["BACKENDS"]["LDAP"]["URI"]) + self._connection.set_option( ldap.OPT_NETWORK_TIMEOUT, self.config["BACKENDS"]["LDAP"].get("TIMEOUT"), ) - self.connection.simple_bind_s( + self._connection.simple_bind_s( self.config["BACKENDS"]["LDAP"]["BIND_DN"], self.config["BACKENDS"]["LDAP"]["BIND_PW"], ) @@ -109,82 +103,62 @@ class Backend(BaseBackend): logging.error(message) raise ConfigurationException(message) from exc - def teardown(self): - try: # pragma: no cover - if request.endpoint == "static": - return - except RuntimeError: # pragma: no cover - pass + return self._connection - if self.connection: # pragma: no branch - self.connection.unbind_s() - self.connection = None + def teardown(self): + if self._connection: # pragma: no branch + self._connection.unbind_s() + self._connection = None @classmethod def validate(cls, config): from canaille.app import models - backend = cls(config) - try: - backend.setup() - models.User.ldap_object_classes() + with cls(config).session(): + try: + user = models.User( + formatted_name=f"canaille_{uuid.uuid4()}", + family_name=f"canaille_{uuid.uuid4()}", + user_name=f"canaille_{uuid.uuid4()}", + emails=f"canaille_{uuid.uuid4()}@mydomain.tld", + password="correct horse battery staple", + ) + user.save() + user.delete() - except ldap.SERVER_DOWN as exc: - raise ConfigurationException( - f'Could not connect to the LDAP server \'{config["BACKENDS"]["LDAP"]["URI"]}\'' - ) from exc + except ldap.INSUFFICIENT_ACCESS as exc: + raise ConfigurationException( + f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create ' + f'users at \'{config["BACKENDS"]["LDAP"]["USER_BASE"]}\'' + ) from exc - except ldap.INVALID_CREDENTIALS as exc: - raise ConfigurationException( - f'LDAP authentication failed with user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\'' - ) from exc + try: + models.Group.ldap_object_classes() - try: - user = models.User( - formatted_name=f"canaille_{uuid.uuid4()}", - family_name=f"canaille_{uuid.uuid4()}", - user_name=f"canaille_{uuid.uuid4()}", - emails=f"canaille_{uuid.uuid4()}@mydomain.tld", - password="correct horse battery staple", - ) - user.save() - user.delete() + user = models.User( + cn=f"canaille_{uuid.uuid4()}", + family_name=f"canaille_{uuid.uuid4()}", + user_name=f"canaille_{uuid.uuid4()}", + emails=f"canaille_{uuid.uuid4()}@mydomain.tld", + password="correct horse battery staple", + ) + user.save() - except ldap.INSUFFICIENT_ACCESS as exc: - raise ConfigurationException( - f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create ' - f'users at \'{config["BACKENDS"]["LDAP"]["USER_BASE"]}\'' - ) from exc + group = models.Group( + display_name=f"canaille_{uuid.uuid4()}", + members=[user], + ) + group.save() + group.delete() - try: - models.Group.ldap_object_classes() + except ldap.INSUFFICIENT_ACCESS as exc: + raise ConfigurationException( + f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create ' + f'groups at \'{config["BACKENDS"]["LDAP"]["GROUP_BASE"]}\'' + ) from exc - user = models.User( - cn=f"canaille_{uuid.uuid4()}", - family_name=f"canaille_{uuid.uuid4()}", - user_name=f"canaille_{uuid.uuid4()}", - emails=f"canaille_{uuid.uuid4()}@mydomain.tld", - password="correct horse battery staple", - ) - user.save() - - group = models.Group( - display_name=f"canaille_{uuid.uuid4()}", - members=[user], - ) - group.save() - group.delete() - - except ldap.INSUFFICIENT_ACCESS as exc: - raise ConfigurationException( - f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create ' - f'groups at \'{config["BACKENDS"]["LDAP"]["GROUP_BASE"]}\'' - ) from exc - - finally: - user.delete() - - backend.teardown() + finally: + user.delete() @classmethod def login_placeholder(cls): diff --git a/tests/backends/test_backends.py b/tests/backends/test_backends.py index d263bccd..be5af49f 100644 --- a/tests/backends/test_backends.py +++ b/tests/backends/test_backends.py @@ -10,11 +10,6 @@ def test_required_methods(testclient): BaseBackend.validate({}) backend = BaseBackend(testclient.app.config) - with pytest.raises(NotImplementedError): - backend.setup() - - with pytest.raises(NotImplementedError): - backend.teardown() with pytest.raises(NotImplementedError): backend.has_account_lockability()