bwcompat.py 9.34 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# copyright 2014-2016 UNLISH S.A.S. (Montpellier, FRANCE), all rights reserved.
#
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.

"""Backward compatibility layer for CubicWeb to run as a Pyramid application."""

23
import sys
24
import inspect
25
import logging
26

27
from pyramid import security
28
from pyramid import tweens
29
from pyramid.httpexceptions import HTTPSeeOther
30
from pyramid import httpexceptions
31
from pyramid.settings import asbool
32
33
34

import cubicweb
import cubicweb.web
35
36

from cubicweb.web.application import CubicWebPublisher
37
from cubicweb.debug import emit_to_debug_channel
38
from cubicweb.web import LogOut, PublishException
39

Yann Voté's avatar
Yann Voté committed
40
from cubicweb.pyramid.core import cw_to_pyramid
41
42


43
44
45
log = logging.getLogger(__name__)


46
47
class PyramidSessionHandler(object):
    """A CW Session handler that rely on the pyramid API to fetch the needed
Christophe de Vienne's avatar
Christophe de Vienne committed
48
49
50
51
52
    informations.

    It implements the :class:`cubicweb.web.application.CookieSessionHandler`
    API.
    """
53
54
55
56
57
58
59
60
61
62
63
64

    def __init__(self, appli):
        self.appli = appli

    def get_session(self, req):
        return req._request.cw_session

    def logout(self, req, goto_url):
        raise LogOut(url=goto_url)


class CubicWebPyramidHandler(object):
Christophe de Vienne's avatar
Christophe de Vienne committed
65
66
67
68
69
    """ A Pyramid request handler that rely on a cubicweb instance to do the
    whole job

    :param appli: A CubicWeb 'Application' object.
    """
70
71
72
73
74
    def __init__(self, appli):
        self.appli = appli

    def __call__(self, request):
        """
75
76
        Handler that mimics what CubicWebPublisher.main_handle_request and
        CubicWebPublisher.core_handle do
77
78
        """

79
80
81
        req = request.cw_request
        vreg = request.registry['cubicweb.registry']

82
        try:
83
            content = None
84
85
            try:
                with cw_to_pyramid(request):
Christophe de Vienne's avatar
pep8    
Christophe de Vienne committed
86
87
                    ctrlid, rset = self.appli.url_resolver.process(req,
                                                                   req.path)
88
89
90
91

                    try:
                        controller = vreg['controllers'].select(
                            ctrlid, req, appli=self.appli)
92
93
94
95
                        log.info("REQUEST [%s] '%s' selected controller %s at %s:%s",
                                 ctrlid, req.path, controller,
                                 inspect.getsourcefile(controller.__class__),
                                 inspect.getsourcelines(controller.__class__)[1])
96
97
98
99
100
101
102
                        emit_to_debug_channel("controller", {
                            "kind": ctrlid,
                            "request": req,
                            "path": req.path,
                            "controller": controller,
                            "config": self.appli.repo.config,
                        })
103
                    except cubicweb.NoSelectableObject:
104
                        log.warn("WARNING '%s' unauthorized request", req.path)
105
106
107
108
109
110
                        raise httpexceptions.HTTPUnauthorized(
                            req._('not authorized'))

                    req.update_search_state()
                    content = controller.publish(rset=rset)

Christophe de Vienne's avatar
pep8    
Christophe de Vienne committed
111
112
                    # XXX this auto-commit should be handled by the cw_request
                    # cleanup or the pyramid transaction manager.
113
114
115
116
117
118
119
120
121
122
123
                    # It is kept here to have the ValidationError handling bw
                    # compatible
                    if req.cnx:
                        txuuid = req.cnx.commit()
                        # commited = True
                        if txuuid is not None:
                            req.data['last_undoable_transaction'] = txuuid
            except cubicweb.web.ValidationError as ex:
                # XXX The validation_error_handler implementation is light, we
                # should redo it better in cw_to_pyramid, so it can be properly
                # handled when raised from a cubicweb view.
124
125
126
                # BUT the real handling of validation errors should be done
                # earlier in the controllers, not here. In the end, the
                # ValidationError should never by handled here.
127
                content = self.appli.validation_error_handler(req, ex)
128
            except cubicweb.web.RemoteCallFailed:
129
130
131
132
                # XXX The default pyramid error handler (or one that we provide
                # for this exception) should be enough
                # content = self.appli.ajax_error_handler(req, ex)
                raise
133

134
135
            if content is not None:
                request.response.body = content
136

137
138
139
140
141
        except LogOut as ex:
            # The actual 'logging out' logic should be in separated function
            # that is accessible by the pyramid views
            headers = security.forget(request)
            raise HTTPSeeOther(ex.url, headers=headers)
142
143
144
145
146
        except cubicweb.AuthenticationError:
            # Will occur upon access to req.cnx which is a
            # cubicweb.dbapi._NeedAuthAccessMock.
            if not content:
                content = vreg['views'].main_template(req, 'login')
147
                request.response.status_code = 403
148
                request.response.body = content
Sylvain Thénault's avatar
Sylvain Thénault committed
149
150
151
152
153
        except cubicweb.web.NotFound as ex:
            view = vreg['views'].select('404', req)
            content = vreg['views'].main_template(req, view=view)
            request.response.status_code = ex.status
            request.response.body = content
154
155
156
157
158
159
160
        finally:
            # XXX CubicWebPyramidRequest.headers_out should
            # access directly the pyramid response headers.
            request.response.headers.clear()
            for k, v in req.headers_out.getAllRawHeaders():
                for item in v:
                    request.response.headers.add(k, item)
161
162
163

        return request.response

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    def error_handler(self, exc, request):
        req = request.cw_request
        if isinstance(exc, httpexceptions.HTTPException):
            request.response = exc
        elif isinstance(exc, PublishException) and exc.status is not None:
            request.response = httpexceptions.exception_response(exc.status)
        else:
            request.response = httpexceptions.HTTPInternalServerError()
        request.response.cache_control = 'no-cache'
        vreg = request.registry['cubicweb.registry']
        excinfo = sys.exc_info()
        req.reset_message()
        if req.ajax_request:
            content = self.appli.ajax_error_handler(req, exc)
        else:
            try:
                req.data['ex'] = exc
181
                req.data['excinfo'] = excinfo
182
183
184
185
186
                errview = vreg['views'].select('error', req)
                template = self.appli.main_template_id(req)
                content = vreg['views'].main_template(req, template, view=errview)
            except Exception:
                content = vreg['views'].main_template(req, 'error-template')
187
        log.exception(exc)
188
189
190
        request.response.body = content
        return request.response

191

192
class TweenHandler(object):
Christophe de Vienne's avatar
Christophe de Vienne committed
193
194
195
196
197
198
    """ A Pyramid tween handler that submit unhandled requests to a Cubicweb
    handler.

    The CubicWeb handler to use is expected to be in the pyramid registry, at
    key ``'cubicweb.handler'``.
    """
199
200
201
202
203
204
205
206
207
208
209
210
    def __init__(self, handler, registry):
        self.handler = handler
        self.cwhandler = registry['cubicweb.handler']

    def __call__(self, request):
        try:
            response = self.handler(request)
        except httpexceptions.HTTPNotFound:
            response = self.cwhandler(request)
        return response


211
def includeme(config):
Christophe de Vienne's avatar
Christophe de Vienne committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
    """ Set up a tween app that will handle the request if the main application
    raises a HTTPNotFound exception.

    This is to keep legacy compatibility for cubes that makes use of the
    cubicweb urlresolvers.

    It provides, for now, support for cubicweb controllers, but this feature
    will be reimplemented separatly in a less compatible way.

    It is automatically included by the configuration system, but can be
    disabled in the :ref:`pyramid_settings`:

    .. code-block:: ini

        cubicweb.bwcompat = no
    """
228
229
230
231
232
    cwconfig = config.registry['cubicweb.config']
    repository = config.registry['cubicweb.repository']
    cwappli = CubicWebPublisher(
        repository, cwconfig,
        session_handler_fact=PyramidSessionHandler)
233
    cwhandler = CubicWebPyramidHandler(cwappli)
234
235

    config.registry['cubicweb.appli'] = cwappli
236
    config.registry['cubicweb.handler'] = cwhandler
237

238
    config.add_tween(
Yann Voté's avatar
Yann Voté committed
239
        'cubicweb.pyramid.bwcompat.TweenHandler', under=tweens.EXCVIEW)
240
241
242
243
244
    if asbool(config.registry.settings.get(
            'cubicweb.bwcompat.errorhandler', True)):
        config.add_view(cwhandler.error_handler, context=Exception)
        # XXX why do i need this?
        config.add_view(cwhandler.error_handler, context=httpexceptions.HTTPForbidden)