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] |