330 self._query_log.write('\n'.join(result).encode(req.encoding)) |
335 self._query_log.write('\n'.join(result).encode(req.encoding)) |
331 self._query_log.flush() |
336 self._query_log.flush() |
332 except Exception: |
337 except Exception: |
333 self.exception('error while logging queries') |
338 self.exception('error while logging queries') |
334 |
339 |
335 def main_publish(self, path, req): |
340 |
|
341 |
|
342 def main_handle_request(self, req, path): |
|
343 if not isinstance(req, CubicWebRequestBase): |
|
344 warn('[3.15] Application entry poin arguments are now (req, path) ' |
|
345 'not (path, req)', DeprecationWarning, 2) |
|
346 req, path = path, req |
|
347 if req.authmode == 'http': |
|
348 # activate realm-based auth |
|
349 realm = self.vreg.config['realm'] |
|
350 req.set_header('WWW-Authenticate', [('Basic', {'realm' : realm })], raw=False) |
|
351 try: |
|
352 self.connect(req) |
|
353 # DENY https acces for anonymous_user |
|
354 if (req.https |
|
355 and req.session.anonymous_session |
|
356 and self.vreg.config['https-deny-anonymous']): |
|
357 # don't allow anonymous on https connection |
|
358 raise AuthenticationError() |
|
359 content = '' |
|
360 # nested try to allow LogOut to delegate logic to AuthenticationError |
|
361 # handler |
|
362 try: |
|
363 ### Try to generate the actual request content |
|
364 content = self.core_handle(req, path) |
|
365 # Handle user log-out |
|
366 except LogOut, ex: |
|
367 # When authentification is handled by cookie the code that |
|
368 # raised LogOut must has invalidated the cookie. We can just |
|
369 # reload the original url without authentification |
|
370 if self.vreg.config['auth-mode'] == 'cookie' and ex.url: |
|
371 req.headers_out.setHeader('location', str(ex.url)) |
|
372 if ex.status is not None: |
|
373 req.status_out = httplib.SEE_OTHER |
|
374 # When the authentification is handled by http we must |
|
375 # explicitly ask for authentification to flush current http |
|
376 # authentification information |
|
377 else: |
|
378 # Render "logged out" content. |
|
379 # assignement to ``content`` prevent standard |
|
380 # AuthenticationError code to overwrite it. |
|
381 content = self.loggedout_content(req) |
|
382 # let the explicitly reset http credential |
|
383 raise AuthenticationError() |
|
384 # Wrong, absent or Reseted credential |
|
385 except AuthenticationError: |
|
386 # If there is an https url configured and |
|
387 # the request do not used https, redirect to login form |
|
388 https_url = self.vreg.config['https-url'] |
|
389 if https_url and req.base_url() != https_url: |
|
390 req.status_out = httplib.SEE_OTHER |
|
391 req.headers_out.setHeader('location', https_url + 'login') |
|
392 else: |
|
393 # We assume here that in http auth mode the user *May* provide |
|
394 # Authentification Credential if asked kindly. |
|
395 if self.vreg.config['auth-mode'] == 'http': |
|
396 req.status_out = httplib.UNAUTHORIZED |
|
397 # In the other case (coky auth) we assume that there is no way |
|
398 # for the user to provide them... |
|
399 # XXX But WHY ? |
|
400 else: |
|
401 req.status_out = httplib.FORBIDDEN |
|
402 # If previous error handling already generated a custom content |
|
403 # do not overwrite it. This is used by LogOut Except |
|
404 # XXX ensure we don't actually serve content |
|
405 if not content: |
|
406 content = self.need_login_content(req) |
|
407 return content |
|
408 |
|
409 |
|
410 |
|
411 def core_handle(self, req, path): |
336 """method called by the main publisher to process <path> |
412 """method called by the main publisher to process <path> |
337 |
413 |
338 should return a string containing the resulting page or raise a |
414 should return a string containing the resulting page or raise a |
339 `NotFound` exception |
415 `NotFound` exception |
340 |
416 |
353 # remove user callbacks on a new request (except for json controllers |
429 # remove user callbacks on a new request (except for json controllers |
354 # to avoid callbacks being unregistered before they could be called) |
430 # to avoid callbacks being unregistered before they could be called) |
355 tstart = clock() |
431 tstart = clock() |
356 commited = False |
432 commited = False |
357 try: |
433 try: |
|
434 ### standard processing of the request |
358 try: |
435 try: |
359 ctrlid, rset = self.url_resolver.process(req, path) |
436 ctrlid, rset = self.url_resolver.process(req, path) |
360 try: |
437 try: |
361 controller = self.vreg['controllers'].select(ctrlid, req, |
438 controller = self.vreg['controllers'].select(ctrlid, req, |
362 appli=self) |
439 appli=self) |
363 except NoSelectableObject: |
440 except NoSelectableObject: |
364 raise Unauthorized(req._('not authorized')) |
441 raise Unauthorized(req._('not authorized')) |
365 req.update_search_state() |
442 req.update_search_state() |
366 result = controller.publish(rset=rset) |
443 result = controller.publish(rset=rset) |
367 if req.cnx: |
444 except StatusResponse, ex: |
368 # no req.cnx if anonymous aren't allowed and we are |
445 warn('StatusResponse is deprecated use req.status_out', |
369 # displaying some anonymous enabled view such as the cookie |
446 DeprecationWarning) |
370 # authentication form |
447 result = ex.content |
371 txuuid = req.cnx.commit() |
448 req.status_out = ex.status |
372 if txuuid is not None: |
449 except Redirect, ex: |
373 req.data['last_undoable_transaction'] = txuuid |
450 # handle redirect |
374 commited = True |
451 # - comply to ex status |
375 except (StatusResponse, DirectResponse): |
452 # - set header field |
376 if req.cnx: |
453 # |
377 req.cnx.commit() |
454 # Redirect Maybe be is raised by edit controller when |
378 raise |
455 # everything went fine, so try to commit |
379 except (AuthenticationError, LogOut): |
456 self.debug('redirecting to %s', str(ex.location)) |
380 raise |
457 req.headers_out.setHeader('location', str(ex.location)) |
381 except Redirect: |
458 assert 300<= ex.status < 400 |
382 # redirect is raised by edit controller when everything went fine, |
459 req.status_out = ex.status |
383 # so try to commit |
460 result = '' |
384 try: |
461 if req.cnx: |
385 if req.cnx: |
462 txuuid = req.cnx.commit() |
386 txuuid = req.cnx.commit() |
463 commited = True |
387 if txuuid is not None: |
464 if txuuid is not None: |
388 req.data['last_undoable_transaction'] = txuuid |
465 req.data['last_undoable_transaction'] = txuuid |
389 except ValidationError, ex: |
466 ### error case |
390 self.validation_error_handler(req, ex) |
467 except NotFound, ex: |
391 except Unauthorized, ex: |
468 result = self.notfound_content(req) |
392 req.data['errmsg'] = req._('You\'re not authorized to access this page. ' |
469 req.status_out = ex.status |
393 'If you think you should, please contact the site administrator.') |
470 except ValidationError, ex: |
394 self.error_handler(req, ex, tb=False) |
471 req.status_out = httplib.CONFLICT |
395 except Exception, ex: |
472 result = self.validation_error_handler(req, ex) |
396 self.error_handler(req, ex, tb=True) |
473 except RemoteCallFailed, ex: |
397 else: |
474 result = self.ajax_error_handler(req, ex) |
398 self.add_undo_link_to_msg(req) |
475 except Unauthorized, ex: |
399 # delete validation errors which may have been previously set |
476 req.data['errmsg'] = req._('You\'re not authorized to access this page. ' |
400 if '__errorurl' in req.form: |
477 'If you think you should, please contact the site administrator.') |
401 req.session.data.pop(req.form['__errorurl'], None) |
478 req.status_out = httplib.UNAUTHORIZED |
402 raise |
479 result = self.error_handler(req, ex, tb=False) |
403 except RemoteCallFailed, ex: |
480 except (BadRQLQuery, RequestError), ex: |
404 req.set_header('content-type', 'application/json') |
481 result = self.error_handler(req, ex, tb=False) |
405 raise StatusResponse(500, ex.dumps()) |
482 ### pass through exception |
406 except NotFound: |
483 except DirectResponse: |
407 raise StatusResponse(404, self.notfound_content(req)) |
484 if req.cnx: |
408 except ValidationError, ex: |
485 req.cnx.commit() |
409 self.validation_error_handler(req, ex) |
486 raise |
410 except Unauthorized, ex: |
487 except (AuthenticationError, LogOut): |
411 self.error_handler(req, ex, tb=False, code=403) |
488 # the rollback is handled in the finally |
412 except (BadRQLQuery, RequestError), ex: |
489 raise |
413 self.error_handler(req, ex, tb=False) |
490 ### Last defence line |
414 except BaseException, ex: |
491 except BaseException, ex: |
415 self.error_handler(req, ex, tb=True) |
492 result = self.error_handler(req, ex, tb=True) |
416 except: |
|
417 self.critical('Catch all triggered!!!') |
|
418 self.exception('this is what happened') |
|
419 result = 'oops' |
|
420 finally: |
493 finally: |
421 if req.cnx and not commited: |
494 if req.cnx and not commited: |
422 try: |
495 try: |
423 req.cnx.rollback() |
496 req.cnx.rollback() |
424 except Exception: |
497 except Exception: |
425 pass # ignore rollback error at this point |
498 pass # ignore rollback error at this point |
|
499 # request may be referenced by "onetime callback", so clear its entity |
|
500 # cache to avoid memory usage |
|
501 req.drop_entity_cache() |
426 self.add_undo_link_to_msg(req) |
502 self.add_undo_link_to_msg(req) |
427 self.info('query %s executed in %s sec', req.relative_path(), clock() - tstart) |
503 self.info('query %s executed in %s sec', req.relative_path(), clock() - tstart) |
428 return result |
504 return result |
429 |
505 |
430 def add_undo_link_to_msg(self, req): |
506 ### Error handler |
431 txuuid = req.data.get('last_undoable_transaction') |
|
432 if txuuid is not None: |
|
433 msg = u'<span class="undo">[<a href="%s">%s</a>]</span>' %( |
|
434 req.build_url('undo', txuuid=txuuid), req._('undo')) |
|
435 req.append_to_redirect_message(msg) |
|
436 |
|
437 def validation_error_handler(self, req, ex): |
507 def validation_error_handler(self, req, ex): |
438 ex.errors = dict((k, v) for k, v in ex.errors.items()) |
508 ex.errors = dict((k, v) for k, v in ex.errors.items()) |
439 if '__errorurl' in req.form: |
509 if '__errorurl' in req.form: |
440 forminfo = {'error': ex, |
510 forminfo = {'error': ex, |
441 'values': req.form, |
511 'values': req.form, |
444 req.session.data[req.form['__errorurl']] = forminfo |
514 req.session.data[req.form['__errorurl']] = forminfo |
445 # XXX form session key / __error_url should be differentiated: |
515 # XXX form session key / __error_url should be differentiated: |
446 # session key is 'url + #<form dom id', though we usually don't want |
516 # session key is 'url + #<form dom id', though we usually don't want |
447 # the browser to move to the form since it hides the global |
517 # the browser to move to the form since it hides the global |
448 # messages. |
518 # messages. |
449 raise Redirect(req.form['__errorurl'].rsplit('#', 1)[0]) |
519 location = req.form['__errorurl'].rsplit('#', 1)[0] |
450 self.error_handler(req, ex, tb=False) |
520 req.headers_out.setHeader('location', str(location)) |
451 |
521 req.status_out = httplib.SEE_OTHER |
452 def error_handler(self, req, ex, tb=False, code=500): |
522 return '' |
|
523 return self.error_handler(req, ex, tb=False) |
|
524 |
|
525 def error_handler(self, req, ex, tb=False): |
453 excinfo = sys.exc_info() |
526 excinfo = sys.exc_info() |
454 self.exception(repr(ex)) |
527 self.exception(repr(ex)) |
455 req.set_header('Cache-Control', 'no-cache') |
528 req.set_header('Cache-Control', 'no-cache') |
456 req.remove_header('Etag') |
529 req.remove_header('Etag') |
457 req.reset_message() |
530 req.reset_message() |
458 req.reset_headers() |
531 req.reset_headers() |
459 if req.ajax_request: |
532 if req.ajax_request: |
460 raise RemoteCallFailed(unicode(ex)) |
533 return ajax_error_handler(req, ex) |
461 try: |
534 try: |
462 req.data['ex'] = ex |
535 req.data['ex'] = ex |
463 if tb: |
536 if tb: |
464 req.data['excinfo'] = excinfo |
537 req.data['excinfo'] = excinfo |
465 req.form['vid'] = 'error' |
538 req.form['vid'] = 'error' |
466 errview = self.vreg['views'].select('error', req) |
539 errview = self.vreg['views'].select('error', req) |
467 template = self.main_template_id(req) |
540 template = self.main_template_id(req) |
468 content = self.vreg['views'].main_template(req, template, view=errview) |
541 content = self.vreg['views'].main_template(req, template, view=errview) |
469 except Exception: |
542 except Exception: |
470 content = self.vreg['views'].main_template(req, 'error-template') |
543 content = self.vreg['views'].main_template(req, 'error-template') |
471 raise StatusResponse(code, content) |
544 if getattr(ex, 'status', None) is not None: |
|
545 req.status_out = ex.status |
|
546 return content |
|
547 |
|
548 def add_undo_link_to_msg(self, req): |
|
549 txuuid = req.data.get('last_undoable_transaction') |
|
550 if txuuid is not None: |
|
551 msg = u'<span class="undo">[<a href="%s">%s</a>]</span>' %( |
|
552 req.build_url('undo', txuuid=txuuid), req._('undo')) |
|
553 req.append_to_redirect_message(msg) |
|
554 |
|
555 |
|
556 |
|
557 def ajax_error_handler(self, req, ex): |
|
558 req.set_header('content-type', 'application/json') |
|
559 status = ex.status |
|
560 if status is None: |
|
561 status = httplib.INTERNAL_SERVER_ERROR |
|
562 json_dumper = getattr(ex, 'dumps', lambda : unicode(ex)) |
|
563 req.status_out = status |
|
564 return json_dumper() |
|
565 |
|
566 # special case handling |
472 |
567 |
473 def need_login_content(self, req): |
568 def need_login_content(self, req): |
474 return self.vreg['views'].main_template(req, 'login') |
569 return self.vreg['views'].main_template(req, 'login') |
475 |
570 |
476 def loggedout_content(self, req): |
571 def loggedout_content(self, req): |