bwcompat.py 9.46 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
                        emit_to_debug_channel("vreg", {
                            "vreg": vreg,
                        })
99
100
101
102
103
104
105
                        emit_to_debug_channel("controller", {
                            "kind": ctrlid,
                            "request": req,
                            "path": req.path,
                            "controller": controller,
                            "config": self.appli.repo.config,
                        })
106
                    except cubicweb.NoSelectableObject:
107
                        log.warn("WARNING '%s' unauthorized request", req.path)
108
109
110
111
112
113
                        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
114
115
                    # XXX this auto-commit should be handled by the cw_request
                    # cleanup or the pyramid transaction manager.
116
117
118
119
120
121
122
123
124
125
126
                    # 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.
127
128
129
                # 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.
130
                content = self.appli.validation_error_handler(req, ex)
131
            except cubicweb.web.RemoteCallFailed:
132
133
134
135
                # 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
136

137
138
            if content is not None:
                request.response.body = content
139

140
141
142
143
144
        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)
145
146
147
148
149
        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')
150
                request.response.status_code = 403
151
                request.response.body = content
Sylvain Thénault's avatar
Sylvain Thénault committed
152
153
154
155
156
        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
157
158
159
160
161
162
163
        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)
164
165
166

        return request.response

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
    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
184
                req.data['excinfo'] = excinfo
185
186
187
188
189
                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')
190
        log.exception(exc)
191
192
193
        request.response.body = content
        return request.response

194

195
class TweenHandler(object):
Christophe de Vienne's avatar
Christophe de Vienne committed
196
197
198
199
200
201
    """ 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'``.
    """
202
203
204
205
206
207
208
209
210
211
212
213
    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


214
def includeme(config):
Christophe de Vienne's avatar
Christophe de Vienne committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    """ 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
    """
231
232
233
234
235
    cwconfig = config.registry['cubicweb.config']
    repository = config.registry['cubicweb.repository']
    cwappli = CubicWebPublisher(
        repository, cwconfig,
        session_handler_fact=PyramidSessionHandler)
236
    cwhandler = CubicWebPyramidHandler(cwappli)
237
238

    config.registry['cubicweb.appli'] = cwappli
239
    config.registry['cubicweb.handler'] = cwhandler
240

241
    config.add_tween(
Yann Voté's avatar
Yann Voté committed
242
        'cubicweb.pyramid.bwcompat.TweenHandler', under=tweens.EXCVIEW)
243
244
245
246
247
    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)