cubicweb/pyramid/bwcompat.py
changeset 11631 faf279e33298
parent 11619 be13b3ea71de
child 11811 f09efeead7f9
equal deleted inserted replaced
11478:1817f8946c22 11631:faf279e33298
       
     1 import sys
       
     2 import logging
       
     3 
       
     4 from pyramid import security
       
     5 from pyramid import tweens
       
     6 from pyramid.httpexceptions import HTTPSeeOther
       
     7 from pyramid import httpexceptions
       
     8 from pyramid.settings import asbool
       
     9 
       
    10 import cubicweb
       
    11 import cubicweb.web
       
    12 
       
    13 from cubicweb.web.application import CubicWebPublisher
       
    14 
       
    15 from cubicweb.web import LogOut, PublishException
       
    16 
       
    17 from cubicweb.pyramid.core import cw_to_pyramid
       
    18 
       
    19 
       
    20 log = logging.getLogger(__name__)
       
    21 
       
    22 
       
    23 class PyramidSessionHandler(object):
       
    24     """A CW Session handler that rely on the pyramid API to fetch the needed
       
    25     informations.
       
    26 
       
    27     It implements the :class:`cubicweb.web.application.CookieSessionHandler`
       
    28     API.
       
    29     """
       
    30 
       
    31     def __init__(self, appli):
       
    32         self.appli = appli
       
    33 
       
    34     def get_session(self, req):
       
    35         return req._request.cw_session
       
    36 
       
    37     def logout(self, req, goto_url):
       
    38         raise LogOut(url=goto_url)
       
    39 
       
    40 
       
    41 class CubicWebPyramidHandler(object):
       
    42     """ A Pyramid request handler that rely on a cubicweb instance to do the
       
    43     whole job
       
    44 
       
    45     :param appli: A CubicWeb 'Application' object.
       
    46     """
       
    47     def __init__(self, appli):
       
    48         self.appli = appli
       
    49 
       
    50     def __call__(self, request):
       
    51         """
       
    52         Handler that mimics what CubicWebPublisher.main_handle_request and
       
    53         CubicWebPublisher.core_handle do
       
    54         """
       
    55 
       
    56         # XXX The main handler of CW forbid anonymous https connections
       
    57         # I guess we can drop this "feature" but in doubt I leave this comment
       
    58         # so we don't forget about it. (cdevienne)
       
    59 
       
    60         req = request.cw_request
       
    61         vreg = request.registry['cubicweb.registry']
       
    62 
       
    63         try:
       
    64             content = None
       
    65             try:
       
    66                 with cw_to_pyramid(request):
       
    67                     ctrlid, rset = self.appli.url_resolver.process(req,
       
    68                                                                    req.path)
       
    69 
       
    70                     try:
       
    71                         controller = vreg['controllers'].select(
       
    72                             ctrlid, req, appli=self.appli)
       
    73                     except cubicweb.NoSelectableObject:
       
    74                         raise httpexceptions.HTTPUnauthorized(
       
    75                             req._('not authorized'))
       
    76 
       
    77                     req.update_search_state()
       
    78                     content = controller.publish(rset=rset)
       
    79 
       
    80                     # XXX this auto-commit should be handled by the cw_request
       
    81                     # cleanup or the pyramid transaction manager.
       
    82                     # It is kept here to have the ValidationError handling bw
       
    83                     # compatible
       
    84                     if req.cnx:
       
    85                         txuuid = req.cnx.commit()
       
    86                         # commited = True
       
    87                         if txuuid is not None:
       
    88                             req.data['last_undoable_transaction'] = txuuid
       
    89             except cubicweb.web.ValidationError as ex:
       
    90                 # XXX The validation_error_handler implementation is light, we
       
    91                 # should redo it better in cw_to_pyramid, so it can be properly
       
    92                 # handled when raised from a cubicweb view.
       
    93                 # BUT the real handling of validation errors should be done
       
    94                 # earlier in the controllers, not here. In the end, the
       
    95                 # ValidationError should never by handled here.
       
    96                 content = self.appli.validation_error_handler(req, ex)
       
    97             except cubicweb.web.RemoteCallFailed as ex:
       
    98                 # XXX The default pyramid error handler (or one that we provide
       
    99                 # for this exception) should be enough
       
   100                 # content = self.appli.ajax_error_handler(req, ex)
       
   101                 raise
       
   102 
       
   103             if content is not None:
       
   104                 request.response.body = content
       
   105 
       
   106 
       
   107         except LogOut as ex:
       
   108             # The actual 'logging out' logic should be in separated function
       
   109             # that is accessible by the pyramid views
       
   110             headers = security.forget(request)
       
   111             raise HTTPSeeOther(ex.url, headers=headers)
       
   112         except cubicweb.AuthenticationError:
       
   113             # Will occur upon access to req.cnx which is a
       
   114             # cubicweb.dbapi._NeedAuthAccessMock.
       
   115             if not content:
       
   116                 content = vreg['views'].main_template(req, 'login')
       
   117                 request.response.status_code = 403
       
   118                 request.response.body = content
       
   119         finally:
       
   120             # XXX CubicWebPyramidRequest.headers_out should
       
   121             # access directly the pyramid response headers.
       
   122             request.response.headers.clear()
       
   123             for k, v in req.headers_out.getAllRawHeaders():
       
   124                 for item in v:
       
   125                     request.response.headers.add(k, item)
       
   126 
       
   127         return request.response
       
   128 
       
   129     def error_handler(self, exc, request):
       
   130         req = request.cw_request
       
   131         if isinstance(exc, httpexceptions.HTTPException):
       
   132             request.response = exc
       
   133         elif isinstance(exc, PublishException) and exc.status is not None:
       
   134             request.response = httpexceptions.exception_response(exc.status)
       
   135         else:
       
   136             request.response = httpexceptions.HTTPInternalServerError()
       
   137         request.response.cache_control = 'no-cache'
       
   138         vreg = request.registry['cubicweb.registry']
       
   139         excinfo = sys.exc_info()
       
   140         req.reset_message()
       
   141         if req.ajax_request:
       
   142             content = self.appli.ajax_error_handler(req, exc)
       
   143         else:
       
   144             try:
       
   145                 req.data['ex'] = exc
       
   146                 req.data['excinfo'] = excinfo
       
   147                 errview = vreg['views'].select('error', req)
       
   148                 template = self.appli.main_template_id(req)
       
   149                 content = vreg['views'].main_template(req, template, view=errview)
       
   150             except Exception:
       
   151                 content = vreg['views'].main_template(req, 'error-template')
       
   152         log.exception(exc)
       
   153         request.response.body = content
       
   154         return request.response
       
   155 
       
   156 
       
   157 class TweenHandler(object):
       
   158     """ A Pyramid tween handler that submit unhandled requests to a Cubicweb
       
   159     handler.
       
   160 
       
   161     The CubicWeb handler to use is expected to be in the pyramid registry, at
       
   162     key ``'cubicweb.handler'``.
       
   163     """
       
   164     def __init__(self, handler, registry):
       
   165         self.handler = handler
       
   166         self.cwhandler = registry['cubicweb.handler']
       
   167 
       
   168     def __call__(self, request):
       
   169         if request.path.startswith('/https/'):
       
   170             request.environ['PATH_INFO'] = request.environ['PATH_INFO'][6:]
       
   171             assert not request.path.startswith('/https/')
       
   172             request.scheme = 'https'
       
   173         try:
       
   174             response = self.handler(request)
       
   175         except httpexceptions.HTTPNotFound:
       
   176             response = self.cwhandler(request)
       
   177         return response
       
   178 
       
   179 
       
   180 def includeme(config):
       
   181     """ Set up a tween app that will handle the request if the main application
       
   182     raises a HTTPNotFound exception.
       
   183 
       
   184     This is to keep legacy compatibility for cubes that makes use of the
       
   185     cubicweb urlresolvers.
       
   186 
       
   187     It provides, for now, support for cubicweb controllers, but this feature
       
   188     will be reimplemented separatly in a less compatible way.
       
   189 
       
   190     It is automatically included by the configuration system, but can be
       
   191     disabled in the :ref:`pyramid_settings`:
       
   192 
       
   193     .. code-block:: ini
       
   194 
       
   195         cubicweb.bwcompat = no
       
   196     """
       
   197     cwconfig = config.registry['cubicweb.config']
       
   198     repository = config.registry['cubicweb.repository']
       
   199     cwappli = CubicWebPublisher(
       
   200         repository, cwconfig,
       
   201         session_handler_fact=PyramidSessionHandler)
       
   202     cwhandler = CubicWebPyramidHandler(cwappli)
       
   203 
       
   204     config.registry['cubicweb.appli'] = cwappli
       
   205     config.registry['cubicweb.handler'] = cwhandler
       
   206 
       
   207     config.add_tween(
       
   208         'cubicweb.pyramid.bwcompat.TweenHandler', under=tweens.EXCVIEW)
       
   209     if asbool(config.registry.settings.get(
       
   210             'cubicweb.bwcompat.errorhandler', True)):
       
   211         config.add_view(cwhandler.error_handler, context=Exception)
       
   212         # XXX why do i need this?
       
   213         config.add_view(cwhandler.error_handler, context=httpexceptions.HTTPForbidden)