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