[PATCH 3.27 V2] [ldapfeed] make code compatible with ldap3>=2

Philippe Pepiot philippe.pepiot at logilab.fr
Tue Mar 3 10:49:34 CET 2020


# HG changeset patch
# User Julien Tayon <julien.tayon at logilab.fr>
# Date 1582669378 -3600
#      Tue Feb 25 23:22:58 2020 +0100
# Branch 3.27
# Node ID 5a9d1e64f505551d27f64500f465a4a48bceeac2
# Parent  ba528f08ddfa57e09328a4c96da19e0c82a8ff85
# Available At https://hg.logilab.org/review/cubicweb
#              hg pull https://hg.logilab.org/review/cubicweb -r 5a9d1e64f505
[ldapfeed] make code compatible with ldap3>=2

* Some constants have been renamed.
* Directly bind when data-cnx-dn/data-cnx-password are provided, some servers,
  including ours require this.
* Use raise_exceptions=True to avoid ignored ldap errors
* raise in case of failed anonymous bind
* do not search for "dn" attribute because this raise an "invalid attribute"
  with new ldap3 versions
* Password is now returned as bytes, so no longer need to encode them before crypt.
* modification_date is now returned as a datetime object

Co-Authored-By: Philippe Pepiot <philippe.pepiot at logilab.fr>

Closes #16073071

diff --git a/cubicweb/server/sources/ldapfeed.py b/cubicweb/server/sources/ldapfeed.py
--- a/cubicweb/server/sources/ldapfeed.py
+++ b/cubicweb/server/sources/ldapfeed.py
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb ldap feed source"""
 
-from datetime import datetime
-
 import ldap3
 
 from logilab.common.configuration import merge_options
@@ -30,10 +28,11 @@ from cubicweb.server.sources import data
 from cubicweb import _
 
 # search scopes
-LDAP_SCOPES = {'BASE': ldap3.SEARCH_SCOPE_BASE_OBJECT,
-               'ONELEVEL': ldap3.SEARCH_SCOPE_SINGLE_LEVEL,
-               'SUBTREE': ldap3.SEARCH_SCOPE_WHOLE_SUBTREE}
-
+LDAP_SCOPES = {
+    'BASE': ldap3.BASE,
+    'ONELEVEL': ldap3.LEVEL,
+    'SUBTREE': ldap3.SUBTREE,
+}
 
 # map ldap protocol to their standard port
 PROTO_PORT = {'ldap': 389,
@@ -250,7 +249,7 @@ You can set multiple groups by separatin
         # check password by establishing a (unused) connection
         try:
             self._connect(user, password)
-        except ldap3.LDAPException as ex:
+        except ldap3.core.exceptions.LDAPException as ex:
             # Something went wrong, most likely bad credentials
             self.info('while trying to authenticate %s: %s', user, ex)
             raise AuthenticationError()
@@ -266,18 +265,27 @@ You can set multiple groups by separatin
 
     def _connect(self, user=None, userpwd=None):
         protocol, host, port = self.connection_info()
+        kwargs = {}
+        if user:
+            kwargs['user'] = user['dn']
+        elif self.cnx_dn:
+            kwargs['user'] = self.cnx_dn
+            if self.cnx_pwd:
+                kwargs['password'] = self.cnx_pwd
         self.info('connecting %s://%s:%s as %s', protocol, host, port,
-                  user and user['dn'] or 'anonymous')
+                  kwargs.get('user', 'anonymous'))
         server = ldap3.Server(host, port=int(port))
         conn = ldap3.Connection(
-            server, user=user and user['dn'],
-            client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE,
-            auto_referrals=False)
+            server, client_strategy=ldap3.RESTARTABLE, auto_referrals=False,
+            raise_exceptions=True,
+            **kwargs)
+
         # Now bind with the credentials given. Let exceptions propagate out.
         if user is None:
-            # XXX always use simple bind for data connection
+            # anonymous bind
             if not self.cnx_dn:
-                conn.bind()
+                if not conn.bind():
+                    raise AuthenticationError(conn.result["message"])
             else:
                 self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd)
         else:
@@ -288,7 +296,6 @@ You can set multiple groups by separatin
         return conn
 
     def _auth_simple(self, conn, user, userpwd):
-        conn.authentication = ldap3.AUTH_SIMPLE
         conn.user = user['dn']
         conn.password = userpwd
         return conn.bind()
@@ -313,7 +320,7 @@ You can set multiple groups by separatin
         if self._conn is None:
             self._conn = self._connect()
         ldapcnx = self._conn
-        if not ldapcnx.search(base, searchstr, search_scope=scope, attributes=attrs):
+        if not ldapcnx.search(base, searchstr, search_scope=scope, attributes=set(attrs) - {'dn'}):
             return []
         result = []
         for rec in ldapcnx.response:
@@ -330,13 +337,13 @@ You can set multiple groups by separatin
         itemdict = {'dn': dn}
         for key, value in iterator:
             if self.user_attrs.get(key) == 'upassword':  # XXx better password detection
-                value = value[0].encode('utf-8')
+                value = value[0]
                 # we only support ldap_salted_sha1 for ldap sources, see: server/utils.py
                 if not value.startswith(b'{SSHA}'):
                     value = utils.crypt_password(value)
                 itemdict[key] = Binary(value)
             elif self.user_attrs.get(key) == 'modification_date':
-                itemdict[key] = datetime.strptime(value[0], '%Y%m%d%H%M%SZ')
+                itemdict[key] = value
             else:
                 if len(value) == 1:
                     itemdict[key] = value = value[0]
diff --git a/requirements/test-server.txt b/requirements/test-server.txt
--- a/requirements/test-server.txt
+++ b/requirements/test-server.txt
@@ -1,2 +1,2 @@
 psycopg2-binary
-ldap3 < 2
+ldap3<3,>2




More information about the cubicweb-devel mailing list