[ldapfeed] make code compatible with ldap3>=2 3.26
authorJulien Tayon <julien.tayon@logilab.fr>
Tue, 25 Feb 2020 23:22:58 +0100
branch3.26
changeset 12902 5c35b94debfc
parent 12901 1206b6fa1173
child 12903 4ebfdf607b49
[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@logilab.fr> Closes #16073071
cubicweb/server/sources/ldapfeed.py
requirements/test-server.txt
--- a/cubicweb/server/sources/ldapfeed.py	Tue Feb 25 22:45:42 2020 +0100
+++ b/cubicweb/server/sources/ldapfeed.py	Tue Feb 25 23:22:58 2020 +0100
@@ -34,10 +34,11 @@
 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,
@@ -254,7 +255,7 @@
         # 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()
@@ -270,15 +271,27 @@
 
     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)
+        conn = ldap3.Connection(
+            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:
@@ -289,7 +302,6 @@
         return conn
 
     def _auth_simple(self, conn, user, userpwd):
-        conn.authentication = ldap3.AUTH_SIMPLE
         conn.user = user['dn']
         conn.password = userpwd
         return conn.bind()
@@ -314,7 +326,7 @@
         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,14 +342,14 @@
         """Turn an ldap received item into a proper dict."""
         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')
+            if self.user_attrs.get(key) == 'upassword':  # XXx better password detection
+                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 PY2 and value and isinstance(value[0], str):
                     value = [unicode(val, 'utf-8', 'replace') for val in value]
--- a/requirements/test-server.txt	Tue Feb 25 22:45:42 2020 +0100
+++ b/requirements/test-server.txt	Tue Feb 25 23:22:58 2020 +0100
@@ -1,9 +1,9 @@
 mock
 psycopg2-binary
-ldap3 < 2
 cubicweb-basket
 cubicweb-card
 cubicweb-comment
 cubicweb-file >= 2.2.2
 cubicweb-localperms
 cubicweb-tag
+ldap3<3,>2