web/application.py
branchtls-sprint
changeset 1426 379261551578
parent 1132 96752791c2b6
child 1977 606923dff11b
equal deleted inserted replaced
1425:3ad7cfca481e 1426:379261551578
    24 SESSION_MANAGER = None
    24 SESSION_MANAGER = None
    25 
    25 
    26 class AbstractSessionManager(Component):
    26 class AbstractSessionManager(Component):
    27     """manage session data associated to a session identifier"""
    27     """manage session data associated to a session identifier"""
    28     id = 'sessionmanager'
    28     id = 'sessionmanager'
    29     
    29 
    30     def __init__(self):
    30     def __init__(self):
    31         self.session_time = self.vreg.config['http-session-time'] or None
    31         self.session_time = self.vreg.config['http-session-time'] or None
    32         assert self.session_time is None or self.session_time > 0
    32         assert self.session_time is None or self.session_time > 0
    33         self.cleanup_session_time = self.vreg.config['cleanup-session-time'] or 43200
    33         self.cleanup_session_time = self.vreg.config['cleanup-session-time'] or 43200
    34         assert self.cleanup_session_time > 0
    34         assert self.cleanup_session_time > 0
    37         if self.session_time:
    37         if self.session_time:
    38             assert self.cleanup_session_time < self.session_time
    38             assert self.cleanup_session_time < self.session_time
    39             assert self.cleanup_anon_session_time < self.session_time
    39             assert self.cleanup_anon_session_time < self.session_time
    40         self.authmanager = self.vreg.select_component('authmanager')
    40         self.authmanager = self.vreg.select_component('authmanager')
    41         assert self.authmanager, 'no authentication manager found'
    41         assert self.authmanager, 'no authentication manager found'
    42         
    42 
    43     def clean_sessions(self):
    43     def clean_sessions(self):
    44         """cleanup sessions which has not been unused since a given amount of
    44         """cleanup sessions which has not been unused since a given amount of
    45         time. Return the number of sessions which have been closed.
    45         time. Return the number of sessions which have been closed.
    46         """
    46         """
    47         self.debug('cleaning http sessions')
    47         self.debug('cleaning http sessions')
    55                     closed += 1
    55                     closed += 1
    56             elif no_use_time >= self.cleanup_session_time:
    56             elif no_use_time >= self.cleanup_session_time:
    57                 self.close_session(session)
    57                 self.close_session(session)
    58                 closed += 1
    58                 closed += 1
    59         return closed, total - closed
    59         return closed, total - closed
    60     
    60 
    61     def has_expired(self, session):
    61     def has_expired(self, session):
    62         """return True if the web session associated to the session is expired
    62         """return True if the web session associated to the session is expired
    63         """
    63         """
    64         return not (self.session_time is None or
    64         return not (self.session_time is None or
    65                     time() < session.last_usage_time + self.session_time)
    65                     time() < session.last_usage_time + self.session_time)
    66                 
    66 
    67     def current_sessions(self):
    67     def current_sessions(self):
    68         """return currently open sessions"""
    68         """return currently open sessions"""
    69         raise NotImplementedError()
    69         raise NotImplementedError()
    70             
    70 
    71     def get_session(self, req, sessionid):
    71     def get_session(self, req, sessionid):
    72         """return existing session for the given session identifier"""
    72         """return existing session for the given session identifier"""
    73         raise NotImplementedError()
    73         raise NotImplementedError()
    74 
    74 
    75     def open_session(self, req):
    75     def open_session(self, req):
    76         """open and return a new session for the given request
    76         """open and return a new session for the given request
    77         
    77 
    78         :raise ExplicitLogin: if authentication is required
    78         :raise ExplicitLogin: if authentication is required
    79         """
    79         """
    80         raise NotImplementedError()
    80         raise NotImplementedError()
    81     
    81 
    82     def close_session(self, session):
    82     def close_session(self, session):
    83         """close session on logout or on invalid session detected (expired out,
    83         """close session on logout or on invalid session detected (expired out,
    84         corrupted...)
    84         corrupted...)
    85         """
    85         """
    86         raise NotImplementedError()
    86         raise NotImplementedError()
    90     """authenticate user associated to a request and check session validity"""
    90     """authenticate user associated to a request and check session validity"""
    91     id = 'authmanager'
    91     id = 'authmanager'
    92 
    92 
    93     def authenticate(self, req):
    93     def authenticate(self, req):
    94         """authenticate user and return corresponding user object
    94         """authenticate user and return corresponding user object
    95         
    95 
    96         :raise ExplicitLogin: if authentication is required (no authentication
    96         :raise ExplicitLogin: if authentication is required (no authentication
    97         info found or wrong user/password)
    97         info found or wrong user/password)
    98         """
    98         """
    99         raise NotImplementedError()
    99         raise NotImplementedError()
   100 
   100 
   101     
   101 
   102 class CookieSessionHandler(object):
   102 class CookieSessionHandler(object):
   103     """a session handler using a cookie to store the session identifier
   103     """a session handler using a cookie to store the session identifier
   104 
   104 
   105     :cvar SESSION_VAR:
   105     :cvar SESSION_VAR:
   106       string giving the name of the variable used to store the session
   106       string giving the name of the variable used to store the session
   107       identifier
   107       identifier
   108     """
   108     """
   109     SESSION_VAR = '__session'
   109     SESSION_VAR = '__session'
   110     
   110 
   111     def __init__(self, appli):
   111     def __init__(self, appli):
   112         self.session_manager = appli.vreg.select_component('sessionmanager')
   112         self.session_manager = appli.vreg.select_component('sessionmanager')
   113         assert self.session_manager, 'no session manager found'
   113         assert self.session_manager, 'no session manager found'
   114         global SESSION_MANAGER
   114         global SESSION_MANAGER
   115         SESSION_MANAGER = self.session_manager
   115         SESSION_MANAGER = self.session_manager
   119     def clean_sessions(self):
   119     def clean_sessions(self):
   120         """cleanup sessions which has not been unused since a given amount of
   120         """cleanup sessions which has not been unused since a given amount of
   121         time
   121         time
   122         """
   122         """
   123         self.session_manager.clean_sessions()
   123         self.session_manager.clean_sessions()
   124         
   124 
   125     def set_session(self, req):
   125     def set_session(self, req):
   126         """associate a session to the request
   126         """associate a session to the request
   127 
   127 
   128         Session id is searched from :
   128         Session id is searched from :
   129         - # form variable
   129         - # form variable
   130         - cookie
   130         - cookie
   131 
   131 
   132         if no session id is found, open a new session for the connected user
   132         if no session id is found, open a new session for the connected user
   133         or request authentification as needed
   133         or request authentification as needed
   134 
   134 
   135         :raise Redirect: if authentication has occured and succeed        
   135         :raise Redirect: if authentication has occured and succeed
   136         """
   136         """
   137         assert req.cnx is None # at this point no cnx should be set on the request
   137         assert req.cnx is None # at this point no cnx should be set on the request
   138         cookie = req.get_cookie()
   138         cookie = req.get_cookie()
   139         try:
   139         try:
   140             sessionid = str(cookie[self.SESSION_VAR].value)
   140             sessionid = str(cookie[self.SESSION_VAR].value)
   152         # remember last usage time for web session tracking
   152         # remember last usage time for web session tracking
   153         session.last_usage_time = time()
   153         session.last_usage_time = time()
   154 
   154 
   155     def get_session(self, req, sessionid):
   155     def get_session(self, req, sessionid):
   156         return self.session_manager.get_session(req, sessionid)
   156         return self.session_manager.get_session(req, sessionid)
   157     
   157 
   158     def open_session(self, req):
   158     def open_session(self, req):
   159         session = self.session_manager.open_session(req)
   159         session = self.session_manager.open_session(req)
   160         cookie = req.get_cookie()
   160         cookie = req.get_cookie()
   161         cookie[self.SESSION_VAR] = session.sessionid
   161         cookie[self.SESSION_VAR] = session.sessionid
   162         req.set_cookie(cookie, self.SESSION_VAR, maxage=None)
   162         req.set_cookie(cookie, self.SESSION_VAR, maxage=None)
   175             # ldap user are not writeable for instance
   175             # ldap user are not writeable for instance
   176             req.cnx.rollback()
   176             req.cnx.rollback()
   177         except:
   177         except:
   178             req.cnx.rollback()
   178             req.cnx.rollback()
   179             raise
   179             raise
   180         
   180 
   181     def _postlogin(self, req):
   181     def _postlogin(self, req):
   182         """postlogin: the user has been authenticated, redirect to the original
   182         """postlogin: the user has been authenticated, redirect to the original
   183         page (index by default) with a welcome message
   183         page (index by default) with a welcome message
   184         """
   184         """
   185         # Update last connection date
   185         # Update last connection date
   195             args['rql'] = req.form['rql']
   195             args['rql'] = req.form['rql']
   196         path = req.relative_path(False)
   196         path = req.relative_path(False)
   197         if path == 'login':
   197         if path == 'login':
   198             path = 'view'
   198             path = 'view'
   199         raise Redirect(req.build_url(path, **args))
   199         raise Redirect(req.build_url(path, **args))
   200     
   200 
   201     def logout(self, req):
   201     def logout(self, req):
   202         """logout from the application by cleaning the session and raising
   202         """logout from the application by cleaning the session and raising
   203         `AuthenticationError`
   203         `AuthenticationError`
   204         """
   204         """
   205         self.session_manager.close_session(req.cnx)
   205         self.session_manager.close_session(req.cnx)
   209 
   209 
   210 class CubicWebPublisher(object):
   210 class CubicWebPublisher(object):
   211     """Central registry for the web application. This is one of the central
   211     """Central registry for the web application. This is one of the central
   212     object in the web application, coupling dynamically loaded objects with
   212     object in the web application, coupling dynamically loaded objects with
   213     the application's schema and the application's configuration objects.
   213     the application's schema and the application's configuration objects.
   214     
   214 
   215     It specializes the VRegistry by adding some convenience methods to
   215     It specializes the VRegistry by adding some convenience methods to
   216     access to stored objects. Currently we have the following registries
   216     access to stored objects. Currently we have the following registries
   217     of objects known by the web application (library may use some others
   217     of objects known by the web application (library may use some others
   218     additional registries):
   218     additional registries):
   219     * controllers, which are directly plugged into the application
   219     * controllers, which are directly plugged into the application
   221     * views
   221     * views
   222     * templates
   222     * templates
   223     * components
   223     * components
   224     * actions
   224     * actions
   225     """
   225     """
   226     
   226 
   227     def __init__(self, config, debug=None,
   227     def __init__(self, config, debug=None,
   228                  session_handler_fact=CookieSessionHandler,
   228                  session_handler_fact=CookieSessionHandler,
   229                  vreg=None):
   229                  vreg=None):
   230         super(CubicWebPublisher, self).__init__()
   230         super(CubicWebPublisher, self).__init__()
   231         # connect to the repository and get application's schema
   231         # connect to the repository and get application's schema
   241         # set the correct publish method
   241         # set the correct publish method
   242         if config['query-log-file']:
   242         if config['query-log-file']:
   243             from threading import Lock
   243             from threading import Lock
   244             self._query_log = open(config['query-log-file'], 'a')
   244             self._query_log = open(config['query-log-file'], 'a')
   245             self.publish = self.log_publish
   245             self.publish = self.log_publish
   246             self._logfile_lock = Lock()            
   246             self._logfile_lock = Lock()
   247         else:
   247         else:
   248             self._query_log = None
   248             self._query_log = None
   249             self.publish = self.main_publish
   249             self.publish = self.main_publish
   250         # instantiate session and url resolving helpers
   250         # instantiate session and url resolving helpers
   251         self.session_handler = session_handler_fact(self)
   251         self.session_handler = session_handler_fact(self)
   252         self.url_resolver = vreg.select_component('urlpublisher')
   252         self.url_resolver = vreg.select_component('urlpublisher')
   253     
   253 
   254     def connect(self, req):
   254     def connect(self, req):
   255         """return a connection for a logged user object according to existing
   255         """return a connection for a logged user object according to existing
   256         sessions (i.e. a new connection may be created or an already existing
   256         sessions (i.e. a new connection may be created or an already existing
   257         one may be reused
   257         one may be reused
   258         """
   258         """
   264         try:
   264         try:
   265             return vreg.select(vreg.registry_objects('controllers', oid),
   265             return vreg.select(vreg.registry_objects('controllers', oid),
   266                                req=req, appli=self)
   266                                req=req, appli=self)
   267         except NoSelectableObject:
   267         except NoSelectableObject:
   268             raise Unauthorized(req._('not authorized'))
   268             raise Unauthorized(req._('not authorized'))
   269             
   269 
   270     # publish methods #########################################################
   270     # publish methods #########################################################
   271         
   271 
   272     def log_publish(self, path, req):
   272     def log_publish(self, path, req):
   273         """wrapper around _publish to log all queries executed for a given
   273         """wrapper around _publish to log all queries executed for a given
   274         accessed path
   274         accessed path
   275         """
   275         """
   276         try:
   276         try:
   291             finally:
   291             finally:
   292                 self._logfile_lock.release()
   292                 self._logfile_lock.release()
   293 
   293 
   294     def main_publish(self, path, req):
   294     def main_publish(self, path, req):
   295         """method called by the main publisher to process <path>
   295         """method called by the main publisher to process <path>
   296         
   296 
   297         should return a string containing the resulting page or raise a
   297         should return a string containing the resulting page or raise a
   298         `NotFound` exception
   298         `NotFound` exception
   299 
   299 
   300         :type path: str
   300         :type path: str
   301         :param path: the path part of the url to publish
   301         :param path: the path part of the url to publish
   302         
   302 
   303         :type req: `web.Request`
   303         :type req: `web.Request`
   304         :param req: the request object
   304         :param req: the request object
   305 
   305 
   306         :rtype: str
   306         :rtype: str
   307         :return: the result of the pusblished url
   307         :return: the result of the pusblished url
   368                         'eidmap': req.data.get('eidmap', {})
   368                         'eidmap': req.data.get('eidmap', {})
   369                         }
   369                         }
   370             req.set_session_data(req.form['__errorurl'], forminfo)
   370             req.set_session_data(req.form['__errorurl'], forminfo)
   371             raise Redirect(req.form['__errorurl'])
   371             raise Redirect(req.form['__errorurl'])
   372         self.error_handler(req, ex, tb=False)
   372         self.error_handler(req, ex, tb=False)
   373         
   373 
   374     def error_handler(self, req, ex, tb=False):
   374     def error_handler(self, req, ex, tb=False):
   375         excinfo = sys.exc_info()
   375         excinfo = sys.exc_info()
   376         self.exception(repr(ex))
   376         self.exception(repr(ex))
   377         req.set_header('Cache-Control', 'no-cache')
   377         req.set_header('Cache-Control', 'no-cache')
   378         req.remove_header('Etag')
   378         req.remove_header('Etag')
   387             template = self.main_template_id(req)
   387             template = self.main_template_id(req)
   388             content = self.vreg.main_template(req, template, view=errview)
   388             content = self.vreg.main_template(req, template, view=errview)
   389         except:
   389         except:
   390             content = self.vreg.main_template(req, 'error-template')
   390             content = self.vreg.main_template(req, 'error-template')
   391         raise StatusResponse(500, content)
   391         raise StatusResponse(500, content)
   392     
   392 
   393     def need_login_content(self, req):
   393     def need_login_content(self, req):
   394         return self.vreg.main_template(req, 'login')
   394         return self.vreg.main_template(req, 'login')
   395     
   395 
   396     def loggedout_content(self, req):
   396     def loggedout_content(self, req):
   397         return self.vreg.main_template(req, 'loggedout')
   397         return self.vreg.main_template(req, 'loggedout')
   398     
   398 
   399     def notfound_content(self, req):
   399     def notfound_content(self, req):
   400         req.form['vid'] = '404'
   400         req.form['vid'] = '404'
   401         view = self.vreg.select_view('404', req, None)
   401         view = self.vreg.select_view('404', req, None)
   402         template = self.main_template_id(req)
   402         template = self.main_template_id(req)
   403         return self.vreg.main_template(req, template, view=view)
   403         return self.vreg.main_template(req, template, view=view)
   405     def main_template_id(self, req):
   405     def main_template_id(self, req):
   406         template = req.property_value('ui.main-template')
   406         template = req.property_value('ui.main-template')
   407         if template not in self.vreg.registry('views') :
   407         if template not in self.vreg.registry('views') :
   408             template = 'main-template'
   408             template = 'main-template'
   409         return template
   409         return template
   410         
   410 
   411 
   411 
   412 set_log_methods(CubicWebPublisher, LOGGER)
   412 set_log_methods(CubicWebPublisher, LOGGER)
   413 set_log_methods(CookieSessionHandler, LOGGER)
   413 set_log_methods(CookieSessionHandler, LOGGER)