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 |
307 self._available_pools.put_nowait(pool) |
307 self._available_pools.put_nowait(pool) |
308 |
308 |
309 def pinfo(self): |
309 def pinfo(self): |
310 # XXX: session.pool is accessed from a local storage, would be interesting |
310 # XXX: session.pool is accessed from a local storage, would be interesting |
311 # to see if there is a pool set in any thread specific data) |
311 # to see if there is a pool set in any thread specific data) |
312 import threading |
|
313 return '%s: %s (%s)' % (self._available_pools.qsize(), |
312 return '%s: %s (%s)' % (self._available_pools.qsize(), |
314 ','.join(session.user.login for session in self._sessions.values() |
313 ','.join(session.user.login for session in self._sessions.values() |
315 if session.pool), |
314 if session.pool), |
316 threading.currentThread()) |
315 threading.currentThread()) |
317 def shutdown(self): |
316 def shutdown(self): |
354 self.info('sql cache usage: %s/%s (%s%%)', hits+ misses, nocache, |
353 self.info('sql cache usage: %s/%s (%s%%)', hits+ misses, nocache, |
355 ((hits + misses) * 100) / (hits + misses + nocache)) |
354 ((hits + misses) * 100) / (hits + misses + nocache)) |
356 except ZeroDivisionError: |
355 except ZeroDivisionError: |
357 pass |
356 pass |
358 |
357 |
|
358 def _login_from_email(self, login): |
|
359 session = self.internal_session() |
|
360 try: |
|
361 rset = session.execute('Any L WHERE U login L, U primary_email M, ' |
|
362 'M address %(login)s', {'login': login}, |
|
363 build_descr=False) |
|
364 if rset.rowcount == 1: |
|
365 login = rset[0][0] |
|
366 finally: |
|
367 session.close() |
|
368 return login |
|
369 |
|
370 def authenticate_user(self, session, login, **kwargs): |
|
371 """validate login / password, raise AuthenticationError on failure |
|
372 return associated CWUser instance on success |
|
373 """ |
|
374 if self.vreg.config['allow-email-login'] and '@' in login: |
|
375 login = self._login_from_email(login) |
|
376 for source in self.sources: |
|
377 if source.support_entity('CWUser'): |
|
378 try: |
|
379 eid = source.authenticate(session, login, **kwargs) |
|
380 break |
|
381 except AuthenticationError: |
|
382 continue |
|
383 else: |
|
384 raise AuthenticationError('authentication failed with all sources') |
|
385 cwuser = self._build_user(session, eid) |
|
386 if self.config.consider_user_state and \ |
|
387 not cwuser.cw_adapt_to('IWorkflowable').state in cwuser.AUTHENTICABLE_STATES: |
|
388 raise AuthenticationError('user is not in authenticable state') |
|
389 return cwuser |
|
390 |
|
391 def _build_user(self, session, eid): |
|
392 """return a CWUser entity for user with the given eid""" |
|
393 cls = self.vreg['etypes'].etype_class('CWUser') |
|
394 rql = cls.fetch_rql(session.user, ['X eid %(x)s']) |
|
395 rset = session.execute(rql, {'x': eid}) |
|
396 assert len(rset) == 1, rset |
|
397 cwuser = rset.get_entity(0, 0) |
|
398 # pylint: disable-msg=W0104 |
|
399 # prefetch / cache cwuser's groups and properties. This is especially |
|
400 # useful for internal sessions to avoid security insertions |
|
401 cwuser.groups |
|
402 cwuser.properties |
|
403 return cwuser |
|
404 |
|
405 # public (dbapi) interface ################################################ |
|
406 |
359 def stats(self): # XXX restrict to managers session? |
407 def stats(self): # XXX restrict to managers session? |
360 import threading |
|
361 results = {} |
408 results = {} |
362 querier = self.querier |
409 querier = self.querier |
363 source = self.system_source |
410 source = self.system_source |
364 for size, maxsize, hits, misses, title in ( |
411 for size, maxsize, hits, misses, title in ( |
365 (len(querier._rql_cache), self.config['rql-cache-size'], |
412 (len(querier._rql_cache), self.config['rql-cache-size'], |
375 results['nb_open_sessions'] = len(self._sessions) |
422 results['nb_open_sessions'] = len(self._sessions) |
376 results['nb_active_threads'] = threading.activeCount() |
423 results['nb_active_threads'] = threading.activeCount() |
377 results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks) |
424 results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks) |
378 results['available_pools'] = self._available_pools.qsize() |
425 results['available_pools'] = self._available_pools.qsize() |
379 return results |
426 return results |
380 |
|
381 def _login_from_email(self, login): |
|
382 session = self.internal_session() |
|
383 try: |
|
384 rset = session.execute('Any L WHERE U login L, U primary_email M, ' |
|
385 'M address %(login)s', {'login': login}, |
|
386 build_descr=False) |
|
387 if rset.rowcount == 1: |
|
388 login = rset[0][0] |
|
389 finally: |
|
390 session.close() |
|
391 return login |
|
392 |
|
393 def authenticate_user(self, session, login, **kwargs): |
|
394 """validate login / password, raise AuthenticationError on failure |
|
395 return associated CWUser instance on success |
|
396 """ |
|
397 if self.vreg.config['allow-email-login'] and '@' in login: |
|
398 login = self._login_from_email(login) |
|
399 for source in self.sources: |
|
400 if source.support_entity('CWUser'): |
|
401 try: |
|
402 eid = source.authenticate(session, login, **kwargs) |
|
403 break |
|
404 except AuthenticationError: |
|
405 continue |
|
406 else: |
|
407 raise AuthenticationError('authentication failed with all sources') |
|
408 cwuser = self._build_user(session, eid) |
|
409 if self.config.consider_user_state and \ |
|
410 not cwuser.cw_adapt_to('IWorkflowable').state in cwuser.AUTHENTICABLE_STATES: |
|
411 raise AuthenticationError('user is not in authenticable state') |
|
412 return cwuser |
|
413 |
|
414 def _build_user(self, session, eid): |
|
415 """return a CWUser entity for user with the given eid""" |
|
416 cls = self.vreg['etypes'].etype_class('CWUser') |
|
417 rql = cls.fetch_rql(session.user, ['X eid %(x)s']) |
|
418 rset = session.execute(rql, {'x': eid}) |
|
419 assert len(rset) == 1, rset |
|
420 cwuser = rset.get_entity(0, 0) |
|
421 # pylint: disable-msg=W0104 |
|
422 # prefetch / cache cwuser's groups and properties. This is especially |
|
423 # useful for internal sessions to avoid security insertions |
|
424 cwuser.groups |
|
425 cwuser.properties |
|
426 return cwuser |
|
427 |
|
428 # public (dbapi) interface ################################################ |
|
429 |
427 |
430 def get_schema(self): |
428 def get_schema(self): |
431 """return the instance schema. This is a public method, not |
429 """return the instance schema. This is a public method, not |
432 requiring a session id |
430 requiring a session id |
433 """ |
431 """ |