From a9ebe0c7f9965bb0ec330aba38d5977e821661ce Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Mon, 27 Jun 2022 15:26:58 +0200
Subject: [PATCH 1/2] ldap: provide a 'raises' keyword-argument flag on
 connection retrieval (#65491)

---
 src/authentic2/backends/ldap_backend.py | 65 ++++++++++++++++---------
 1 file changed, 43 insertions(+), 22 deletions(-)

diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index 63aa1af17..6e725a233 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -1783,7 +1783,7 @@ class LDAPBackend:
         return new_results
 
     @classmethod
-    def get_connections(cls, block, credentials=()):
+    def get_connections(cls, block, credentials=(), raises=False):
         '''Try each replicas, and yield successfull connections'''
         if not block['url']:
             raise ImproperlyConfigured("block['url'] must contain at least one url")
@@ -1810,42 +1810,49 @@ class LDAPBackend:
             conn.set_option(ldap.OPT_REFERRALS, 1 if block['referrals'] else 0)
             # allow TLS options to be applied
             conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+            errmsg = None
             try:
                 if not url.startswith('ldaps://') and block['use_tls']:
                     try:
                         conn.start_tls_s()
                     except ldap.CONNECT_ERROR:
-                        log.error(
-                            'connection to %r failed when activating TLS, did you forget to declare the TLS'
-                            ' certificate in /etc/ldap/ldap.conf ?',
-                            url,
-                        )
-                        continue
+                        error = (
+                            'connection to %r failed when activating TLS, did you forget to '
+                            'declare the TLS certificate in /etc/ldap/ldap.conf ?'
+                        ) % url
+                        log.error(errmsg)
             except ldap.TIMEOUT:
-                log.error('connection to %r timed out', url)
-                continue
+                errmsg = 'connection to %r timed out' % url
+                log.error(errmsg)
             except ldap.CONNECT_ERROR:
-                log.error(
-                    'connection to %r failed when activating TLS, did you forget to declare the TLS'
-                    ' certificate in /etc/ldap/ldap.conf ?',
-                    url,
-                )
-                continue
+                errmsg = (
+                    'connection to %r failed when activating TLS, did you forget to declare '
+                    'the TLS certificate in /etc/ldap/ldap.conf ?'
+                ) % url
+                log.error(errmsg)
             except ldap.SERVER_DOWN:
+                errmsg = 'ldap %r is down' % url
                 if block['replicas']:
-                    log.warning('ldap %r is down', url)
+                    log.warning(errmsg)
                 else:
-                    log.error('ldap %r is down', url)
-                continue
+                    log.error(errmsg)
+            if errmsg:
+                if raises:
+                    raise ldap.LDAPError(errmsg)
+                else:
+                    continue
             user_credentials = block['connect_with_user_credentials'] and credentials
             success, error = cls.bind(block, conn, credentials=user_credentials)
             if success:
                 yield conn
             else:
+                errmsg = 'admin bind failed on %s: %s' % (url, error)
                 if block['replicas']:
-                    log.warning('admin bind failed on %s: %s', url, error)
+                    log.warning(errmsg)
                 else:
-                    log.error('admin bind failed on %s: %s', url, error)
+                    log.error(errmsg)
+                if raises:
+                    raise ldap.LDAPError(errmsg)
 
     @classmethod
     def bind(cls, block, conn, credentials=()):
@@ -1901,10 +1908,24 @@ class LDAPBackend:
             return False, 'ldap is down'
 
     @classmethod
-    def get_connection(cls, block, credentials=()):
+    def get_connection(cls, block, credentials=(), raises=False):
         '''Try to get at least one connection'''
-        for conn in cls.get_connections(block, credentials=credentials):
+        errors = []
+        conn = None
+        while True:
+            try:
+                conn = next(cls.get_connections(block, credentials=credentials, raises=raises))
+            except ldap.LDAPError as e:
+                errors.append(e)
+            except StopIteration:
+                break
+            else:
+                if conn:
+                    break
+        if conn:
             return conn
+        elif raises:
+            raise ldap.LDAPError(' – '.join(errors))
         return None
 
     @classmethod
-- 
2.36.1

