[ldapfeed] make code compatible with ldap3>=2 3.27
authorJulien Tayon <julien.tayon@logilab.fr>
Tue, 25 Feb 2020 23:22:58 +0100
branch3.27
changeset 12895 5a9d1e64f505
parent 12894 ba528f08ddfa
child 12896 d18bd998852c
[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
@@ -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 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 @@
         # 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 @@
 
     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 @@
         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 @@
         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 @@
         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]
--- 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,2 +1,2 @@
 psycopg2-binary
-ldap3 < 2
+ldap3<3,>2