23 |
23 |
24 * brings these classes all together to provide a single access |
24 * brings these classes all together to provide a single access |
25 point to a cubicweb instance. |
25 point to a cubicweb instance. |
26 * handles session management |
26 * handles session management |
27 * provides method for pyro registration, to call if pyro is enabled |
27 * provides method for pyro registration, to call if pyro is enabled |
28 |
|
29 |
|
30 """ |
28 """ |
|
29 |
31 from __future__ import with_statement |
30 from __future__ import with_statement |
32 |
31 |
33 __docformat__ = "restructuredtext en" |
32 __docformat__ = "restructuredtext en" |
34 |
33 |
35 import sys |
34 import sys |
|
35 import threading |
36 import Queue |
36 import Queue |
37 from os.path import join |
37 from os.path import join |
38 from datetime import datetime |
38 from datetime import datetime |
39 from time import time, localtime, strftime |
39 from time import time, localtime, strftime |
40 |
40 |
313 self._available_pools.put_nowait(pool) |
313 self._available_pools.put_nowait(pool) |
314 |
314 |
315 def pinfo(self): |
315 def pinfo(self): |
316 # XXX: session.pool is accessed from a local storage, would be interesting |
316 # XXX: session.pool is accessed from a local storage, would be interesting |
317 # to see if there is a pool set in any thread specific data) |
317 # to see if there is a pool set in any thread specific data) |
318 import threading |
|
319 return '%s: %s (%s)' % (self._available_pools.qsize(), |
318 return '%s: %s (%s)' % (self._available_pools.qsize(), |
320 ','.join(session.user.login for session in self._sessions.values() |
319 ','.join(session.user.login for session in self._sessions.values() |
321 if session.pool), |
320 if session.pool), |
322 threading.currentThread()) |
321 threading.currentThread()) |
323 def shutdown(self): |
322 def shutdown(self): |
360 self.info('sql cache usage: %s/%s (%s%%)', hits+ misses, nocache, |
359 self.info('sql cache usage: %s/%s (%s%%)', hits+ misses, nocache, |
361 ((hits + misses) * 100) / (hits + misses + nocache)) |
360 ((hits + misses) * 100) / (hits + misses + nocache)) |
362 except ZeroDivisionError: |
361 except ZeroDivisionError: |
363 pass |
362 pass |
364 |
363 |
|
364 def _login_from_email(self, login): |
|
365 session = self.internal_session() |
|
366 try: |
|
367 rset = session.execute('Any L WHERE U login L, U primary_email M, ' |
|
368 'M address %(login)s', {'login': login}, |
|
369 build_descr=False) |
|
370 if rset.rowcount == 1: |
|
371 login = rset[0][0] |
|
372 finally: |
|
373 session.close() |
|
374 return login |
|
375 |
|
376 def authenticate_user(self, session, login, **kwargs): |
|
377 """validate login / password, raise AuthenticationError on failure |
|
378 return associated CWUser instance on success |
|
379 """ |
|
380 if self.vreg.config['allow-email-login'] and '@' in login: |
|
381 login = self._login_from_email(login) |
|
382 for source in self.sources: |
|
383 if source.support_entity('CWUser'): |
|
384 try: |
|
385 eid = source.authenticate(session, login, **kwargs) |
|
386 break |
|
387 except AuthenticationError: |
|
388 continue |
|
389 else: |
|
390 raise AuthenticationError('authentication failed with all sources') |
|
391 cwuser = self._build_user(session, eid) |
|
392 if self.config.consider_user_state and \ |
|
393 not cwuser.state in cwuser.AUTHENTICABLE_STATES: |
|
394 raise AuthenticationError('user is not in authenticable state') |
|
395 return cwuser |
|
396 |
|
397 def _build_user(self, session, eid): |
|
398 """return a CWUser entity for user with the given eid""" |
|
399 cls = self.vreg['etypes'].etype_class('CWUser') |
|
400 rql = cls.fetch_rql(session.user, ['X eid %(x)s']) |
|
401 rset = session.execute(rql, {'x': eid}) |
|
402 assert len(rset) == 1, rset |
|
403 cwuser = rset.get_entity(0, 0) |
|
404 # pylint: disable-msg=W0104 |
|
405 # prefetch / cache cwuser's groups and properties. This is especially |
|
406 # useful for internal sessions to avoid security insertions |
|
407 cwuser.groups |
|
408 cwuser.properties |
|
409 return cwuser |
|
410 |
|
411 # public (dbapi) interface ################################################ |
|
412 |
365 def stats(self): # XXX restrict to managers session? |
413 def stats(self): # XXX restrict to managers session? |
366 import threading |
|
367 results = {} |
414 results = {} |
368 querier = self.querier |
415 querier = self.querier |
369 source = self.system_source |
416 source = self.system_source |
370 for size, maxsize, hits, misses, title in ( |
417 for size, maxsize, hits, misses, title in ( |
371 (len(querier._rql_cache), self.config['rql-cache-size'], |
418 (len(querier._rql_cache), self.config['rql-cache-size'], |
381 results['nb_open_sessions'] = len(self._sessions) |
428 results['nb_open_sessions'] = len(self._sessions) |
382 results['nb_active_threads'] = threading.activeCount() |
429 results['nb_active_threads'] = threading.activeCount() |
383 results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks) |
430 results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks) |
384 results['available_pools'] = self._available_pools.qsize() |
431 results['available_pools'] = self._available_pools.qsize() |
385 return results |
432 return results |
386 |
|
387 def _login_from_email(self, login): |
|
388 session = self.internal_session() |
|
389 try: |
|
390 rset = session.execute('Any L WHERE U login L, U primary_email M, ' |
|
391 'M address %(login)s', {'login': login}, |
|
392 build_descr=False) |
|
393 if rset.rowcount == 1: |
|
394 login = rset[0][0] |
|
395 finally: |
|
396 session.close() |
|
397 return login |
|
398 |
|
399 def authenticate_user(self, session, login, **kwargs): |
|
400 """validate login / password, raise AuthenticationError on failure |
|
401 return associated CWUser instance on success |
|
402 """ |
|
403 if self.vreg.config['allow-email-login'] and '@' in login: |
|
404 login = self._login_from_email(login) |
|
405 for source in self.sources: |
|
406 if source.support_entity('CWUser'): |
|
407 try: |
|
408 eid = source.authenticate(session, login, **kwargs) |
|
409 break |
|
410 except AuthenticationError: |
|
411 continue |
|
412 else: |
|
413 raise AuthenticationError('authentication failed with all sources') |
|
414 cwuser = self._build_user(session, eid) |
|
415 if self.config.consider_user_state and \ |
|
416 not cwuser.state in cwuser.AUTHENTICABLE_STATES: |
|
417 raise AuthenticationError('user is not in authenticable state') |
|
418 return cwuser |
|
419 |
|
420 def _build_user(self, session, eid): |
|
421 """return a CWUser entity for user with the given eid""" |
|
422 cls = self.vreg['etypes'].etype_class('CWUser') |
|
423 rql = cls.fetch_rql(session.user, ['X eid %(x)s']) |
|
424 rset = session.execute(rql, {'x': eid}) |
|
425 assert len(rset) == 1, rset |
|
426 cwuser = rset.get_entity(0, 0) |
|
427 # pylint: disable-msg=W0104 |
|
428 # prefetch / cache cwuser's groups and properties. This is especially |
|
429 # useful for internal sessions to avoid security insertions |
|
430 cwuser.groups |
|
431 cwuser.properties |
|
432 return cwuser |
|
433 |
|
434 # public (dbapi) interface ################################################ |
|
435 |
433 |
436 def get_schema(self): |
434 def get_schema(self): |
437 """return the instance schema. This is a public method, not |
435 """return the instance schema. This is a public method, not |
438 requiring a session id |
436 requiring a session id |
439 """ |
437 """ |