cubicweb/server/sources/ldapfeed.py
branch3.26
changeset 12902 5c35b94debfc
parent 12901 1206b6fa1173
child 12904 2ad148f22c2f
equal deleted inserted replaced
12901:1206b6fa1173 12902:5c35b94debfc
    32 from cubicweb.server.sources import datafeed
    32 from cubicweb.server.sources import datafeed
    33 
    33 
    34 from cubicweb import _
    34 from cubicweb import _
    35 
    35 
    36 # search scopes
    36 # search scopes
    37 LDAP_SCOPES = {'BASE': ldap3.SEARCH_SCOPE_BASE_OBJECT,
    37 LDAP_SCOPES = {
    38                'ONELEVEL': ldap3.SEARCH_SCOPE_SINGLE_LEVEL,
    38     'BASE': ldap3.BASE,
    39                'SUBTREE': ldap3.SEARCH_SCOPE_WHOLE_SUBTREE}
    39     'ONELEVEL': ldap3.LEVEL,
    40 
    40     'SUBTREE': ldap3.SUBTREE,
       
    41 }
    41 
    42 
    42 # map ldap protocol to their standard port
    43 # map ldap protocol to their standard port
    43 PROTO_PORT = {'ldap': 389,
    44 PROTO_PORT = {'ldap': 389,
    44               'ldaps': 636,
    45               'ldaps': 636,
    45               'ldapi': None,
    46               'ldapi': None,
   252             # no such user
   253             # no such user
   253             raise AuthenticationError()
   254             raise AuthenticationError()
   254         # check password by establishing a (unused) connection
   255         # check password by establishing a (unused) connection
   255         try:
   256         try:
   256             self._connect(user, password)
   257             self._connect(user, password)
   257         except ldap3.LDAPException as ex:
   258         except ldap3.core.exceptions.LDAPException as ex:
   258             # Something went wrong, most likely bad credentials
   259             # Something went wrong, most likely bad credentials
   259             self.info('while trying to authenticate %s: %s', user, ex)
   260             self.info('while trying to authenticate %s: %s', user, ex)
   260             raise AuthenticationError()
   261             raise AuthenticationError()
   261         except Exception:
   262         except Exception:
   262             self.error('while trying to authenticate %s', user, exc_info=True)
   263             self.error('while trying to authenticate %s', user, exc_info=True)
   268             raise AuthenticationError()
   269             raise AuthenticationError()
   269         return rset[0][0]
   270         return rset[0][0]
   270 
   271 
   271     def _connect(self, user=None, userpwd=None):
   272     def _connect(self, user=None, userpwd=None):
   272         protocol, host, port = self.connection_info()
   273         protocol, host, port = self.connection_info()
       
   274         kwargs = {}
       
   275         if user:
       
   276             kwargs['user'] = user['dn']
       
   277         elif self.cnx_dn:
       
   278             kwargs['user'] = self.cnx_dn
       
   279             if self.cnx_pwd:
       
   280                 kwargs['password'] = self.cnx_pwd
   273         self.info('connecting %s://%s:%s as %s', protocol, host, port,
   281         self.info('connecting %s://%s:%s as %s', protocol, host, port,
   274                   user and user['dn'] or 'anonymous')
   282                   kwargs.get('user', 'anonymous'))
   275         server = ldap3.Server(host, port=int(port))
   283         server = ldap3.Server(host, port=int(port))
   276         conn = ldap3.Connection(server, user=user and user['dn'], client_strategy=ldap3.STRATEGY_SYNC_RESTARTABLE, auto_referrals=False)
   284         conn = ldap3.Connection(
       
   285             server, client_strategy=ldap3.RESTARTABLE, auto_referrals=False,
       
   286             raise_exceptions=True,
       
   287             **kwargs)
       
   288 
   277         # Now bind with the credentials given. Let exceptions propagate out.
   289         # Now bind with the credentials given. Let exceptions propagate out.
   278         if user is None:
   290         if user is None:
   279             # XXX always use simple bind for data connection
   291             # anonymous bind
   280             if not self.cnx_dn:
   292             if not self.cnx_dn:
   281                 conn.bind()
   293                 if not conn.bind():
       
   294                     raise AuthenticationError(conn.result["message"])
   282             else:
   295             else:
   283                 self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd)
   296                 self._authenticate(conn, {'dn': self.cnx_dn}, self.cnx_pwd)
   284         else:
   297         else:
   285             # user specified, we want to check user/password, no need to return
   298             # user specified, we want to check user/password, no need to return
   286             # the connection which will be thrown out
   299             # the connection which will be thrown out
   287             if not self._authenticate(conn, user, userpwd):
   300             if not self._authenticate(conn, user, userpwd):
   288                 raise AuthenticationError()
   301                 raise AuthenticationError()
   289         return conn
   302         return conn
   290 
   303 
   291     def _auth_simple(self, conn, user, userpwd):
   304     def _auth_simple(self, conn, user, userpwd):
   292         conn.authentication = ldap3.AUTH_SIMPLE
       
   293         conn.user = user['dn']
   305         conn.user = user['dn']
   294         conn.password = userpwd
   306         conn.password = userpwd
   295         return conn.bind()
   307         return conn.bind()
   296 
   308 
   297     def _auth_digest_md5(self, conn, user, userpwd):
   309     def _auth_digest_md5(self, conn, user, userpwd):
   312         self.debug('ldap search %s %s %s %s %s', self.uri, base, scope,
   324         self.debug('ldap search %s %s %s %s %s', self.uri, base, scope,
   313                    searchstr, list(attrs))
   325                    searchstr, list(attrs))
   314         if self._conn is None:
   326         if self._conn is None:
   315             self._conn = self._connect()
   327             self._conn = self._connect()
   316         ldapcnx = self._conn
   328         ldapcnx = self._conn
   317         if not ldapcnx.search(base, searchstr, search_scope=scope, attributes=attrs):
   329         if not ldapcnx.search(base, searchstr, search_scope=scope, attributes=set(attrs) - {'dn'}):
   318             return []
   330             return []
   319         result = []
   331         result = []
   320         for rec in ldapcnx.response:
   332         for rec in ldapcnx.response:
   321             if rec['type'] != 'searchResEntry':
   333             if rec['type'] != 'searchResEntry':
   322                 continue
   334                 continue
   328 
   340 
   329     def _process_ldap_item(self, dn, iterator):
   341     def _process_ldap_item(self, dn, iterator):
   330         """Turn an ldap received item into a proper dict."""
   342         """Turn an ldap received item into a proper dict."""
   331         itemdict = {'dn': dn}
   343         itemdict = {'dn': dn}
   332         for key, value in iterator:
   344         for key, value in iterator:
   333             if self.user_attrs.get(key) == 'upassword': # XXx better password detection
   345             if self.user_attrs.get(key) == 'upassword':  # XXx better password detection
   334                 value = value[0].encode('utf-8')
   346                 value = value[0]
   335                 # we only support ldap_salted_sha1 for ldap sources, see: server/utils.py
   347                 # we only support ldap_salted_sha1 for ldap sources, see: server/utils.py
   336                 if not value.startswith(b'{SSHA}'):
   348                 if not value.startswith(b'{SSHA}'):
   337                     value = utils.crypt_password(value)
   349                     value = utils.crypt_password(value)
   338                 itemdict[key] = Binary(value)
   350                 itemdict[key] = Binary(value)
   339             elif self.user_attrs.get(key) == 'modification_date':
   351             elif self.user_attrs.get(key) == 'modification_date':
   340                 itemdict[key] = datetime.strptime(value[0], '%Y%m%d%H%M%SZ')
   352                 itemdict[key] = value
   341             else:
   353             else:
   342                 if PY2 and value and isinstance(value[0], str):
   354                 if PY2 and value and isinstance(value[0], str):
   343                     value = [unicode(val, 'utf-8', 'replace') for val in value]
   355                     value = [unicode(val, 'utf-8', 'replace') for val in value]
   344                 if len(value) == 1:
   356                 if len(value) == 1:
   345                     itemdict[key] = value = value[0]
   357                     itemdict[key] = value = value[0]