feat: ldap connection is lazilly opened

This commit is contained in:
Éloi Rivard 2023-12-25 14:21:09 +01:00
parent d0dbaa588c
commit 5c11ebf0d3
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
3 changed files with 52 additions and 85 deletions

View file

@ -44,14 +44,12 @@ class BaseBackend:
This method will be called before each http request, This method will be called before each http request,
it should open the connection to the backend. it should open the connection to the backend.
""" """
raise NotImplementedError()
def teardown(self): def teardown(self):
""" """
This method will be called after each http request, This method will be called after each http request,
it should close the connections to the backend. it should close the connections to the backend.
""" """
raise NotImplementedError()
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):

View file

@ -10,7 +10,6 @@ from canaille.app.configuration import ConfigurationException
from canaille.app.i18n import gettext as _ from canaille.app.i18n import gettext as _
from canaille.backends import BaseBackend from canaille.backends import BaseBackend
from flask import current_app from flask import current_app
from flask import request
from .utils import listify from .utils import listify
@ -51,7 +50,7 @@ def install_schema(config, schema_path):
class Backend(BaseBackend): class Backend(BaseBackend):
def __init__(self, config): def __init__(self, config):
super().__init__(config) super().__init__(config)
self.connection = None self._connection = None
setup_ldap_models(config) setup_ldap_models(config)
@classmethod @classmethod
@ -74,23 +73,18 @@ class Backend(BaseBackend):
os.path.dirname(__file__) + "/schemas/oauth2-openldap.ldif", os.path.dirname(__file__) + "/schemas/oauth2-openldap.ldif",
) )
def setup(self): @property
if self.connection: def connection(self):
return if self._connection:
return self._connection
try: # pragma: no cover
if request.endpoint == "static":
return
except RuntimeError: # pragma: no cover
pass
try: try:
self.connection = ldap.initialize(self.config["BACKENDS"]["LDAP"]["URI"]) self._connection = ldap.initialize(self.config["BACKENDS"]["LDAP"]["URI"])
self.connection.set_option( self._connection.set_option(
ldap.OPT_NETWORK_TIMEOUT, ldap.OPT_NETWORK_TIMEOUT,
self.config["BACKENDS"]["LDAP"].get("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_DN"],
self.config["BACKENDS"]["LDAP"]["BIND_PW"], self.config["BACKENDS"]["LDAP"]["BIND_PW"],
) )
@ -109,82 +103,62 @@ class Backend(BaseBackend):
logging.error(message) logging.error(message)
raise ConfigurationException(message) from exc raise ConfigurationException(message) from exc
def teardown(self): return self._connection
try: # pragma: no cover
if request.endpoint == "static":
return
except RuntimeError: # pragma: no cover
pass
if self.connection: # pragma: no branch def teardown(self):
self.connection.unbind_s() if self._connection: # pragma: no branch
self.connection = None self._connection.unbind_s()
self._connection = None
@classmethod @classmethod
def validate(cls, config): def validate(cls, config):
from canaille.app import models from canaille.app import models
backend = cls(config) with cls(config).session():
try: try:
backend.setup() user = models.User(
models.User.ldap_object_classes() 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: except ldap.INSUFFICIENT_ACCESS as exc:
raise ConfigurationException( raise ConfigurationException(
f'Could not connect to the LDAP server \'{config["BACKENDS"]["LDAP"]["URI"]}\'' f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create '
) from exc f'users at \'{config["BACKENDS"]["LDAP"]["USER_BASE"]}\''
) from exc
except ldap.INVALID_CREDENTIALS as exc: try:
raise ConfigurationException( models.Group.ldap_object_classes()
f'LDAP authentication failed with user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\''
) from exc
try: user = models.User(
user = models.User( cn=f"canaille_{uuid.uuid4()}",
formatted_name=f"canaille_{uuid.uuid4()}", family_name=f"canaille_{uuid.uuid4()}",
family_name=f"canaille_{uuid.uuid4()}", user_name=f"canaille_{uuid.uuid4()}",
user_name=f"canaille_{uuid.uuid4()}", emails=f"canaille_{uuid.uuid4()}@mydomain.tld",
emails=f"canaille_{uuid.uuid4()}@mydomain.tld", password="correct horse battery staple",
password="correct horse battery staple", )
) user.save()
user.save()
user.delete()
except ldap.INSUFFICIENT_ACCESS as exc: group = models.Group(
raise ConfigurationException( display_name=f"canaille_{uuid.uuid4()}",
f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create ' members=[user],
f'users at \'{config["BACKENDS"]["LDAP"]["USER_BASE"]}\'' )
) from exc group.save()
group.delete()
try: except ldap.INSUFFICIENT_ACCESS as exc:
models.Group.ldap_object_classes() 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( finally:
cn=f"canaille_{uuid.uuid4()}", user.delete()
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()
@classmethod @classmethod
def login_placeholder(cls): def login_placeholder(cls):

View file

@ -10,11 +10,6 @@ def test_required_methods(testclient):
BaseBackend.validate({}) BaseBackend.validate({})
backend = BaseBackend(testclient.app.config) backend = BaseBackend(testclient.app.config)
with pytest.raises(NotImplementedError):
backend.setup()
with pytest.raises(NotImplementedError):
backend.teardown()
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
backend.has_account_lockability() backend.has_account_lockability()