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