Mercurial > libervia-web
comparison src/server/pages.py @ 984:f0fc28b3bd1e
server: moved LiberviaPage code in its own module
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 17 Nov 2017 12:10:56 +0100 |
parents | src/server/server.py@bcacf970f970 |
children | 64826e69f365 |
comparison
equal
deleted
inserted
replaced
983:8c9fdb58de5f | 984:f0fc28b3bd1e |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
5 # Copyright (C) 2011-2017 Jérôme Poisson <goffi@goffi.org> | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 from twisted.web import server | |
20 from twisted.web import resource as web_resource | |
21 from twisted.web import util as web_util | |
22 from twisted.internet import defer | |
23 from twisted.python import failure | |
24 | |
25 from sat.core.i18n import _ | |
26 from sat.core import exceptions | |
27 from sat.tools.common import uri as common_uri | |
28 from sat.core.log import getLogger | |
29 log = getLogger(__name__) | |
30 from libervia.server.constants import Const as C | |
31 from libervia.server import session_iface | |
32 from libervia.server.utils import quote | |
33 import libervia | |
34 | |
35 import os.path | |
36 import urllib | |
37 | |
38 | |
39 class LiberviaPage(web_resource.Resource): | |
40 isLeaf = True # we handle subpages ourself | |
41 named_pages = {} | |
42 uri_callbacks = {} | |
43 pages_redirects = {} | |
44 | |
45 def __init__(self, host, root_dir, url, name=None, redirect=None, access=None, parse_url=None, | |
46 prepare_render=None, render=None, template=None, on_data_post=None): | |
47 """initiate LiberviaPages | |
48 | |
49 LiberviaPages are the main resources of Libervia, using easy to set python files | |
50 The arguments are the variables found in page_meta.py | |
51 @param host(Libervia): the running instance of Libervia | |
52 @param root_dir(unicode): aboslute file path of the page | |
53 @param url(unicode): relative URL to the page | |
54 this URL may not be valid, as pages may require path arguments | |
55 @param name(unicode, None): if not None, a unique name to identify the page | |
56 can then be used for e.g. redirection | |
57 "/" is not allowed in names (as it can be used to construct URL paths) | |
58 @param redirect(unicode, None): if not None, this page will be redirected. A redirected | |
59 parameter is used as in self.pageRedirect. parse_url will not be skipped | |
60 using this redirect parameter is called "full redirection" | |
61 using self.pageRedirect is called "partial redirection" (because some rendering method | |
62 can still be used, e.g. parse_url) | |
63 @param access(unicode, None): permission needed to access the page | |
64 None means public access. | |
65 Pages inherit from parent pages: e.g. if a "settings" page is restricted to admins, | |
66 and if "settings/blog" is public, it still can only be accessed by admins. | |
67 see C.PAGES_ACCESS_* for details | |
68 @param parse_url(callable, None): if set it will be called to handle the URL path | |
69 after this method, the page will be rendered if noting is left in path (request.postpath) | |
70 else a the request will be transmitted to a subpage | |
71 @param prepare_render(callable, None): if set, will be used to prepare the rendering | |
72 that often means gathering data using the bridge | |
73 @param render(callable, None): if not template is set, this method will be called and | |
74 what it returns will be rendered. | |
75 This method is mutually exclusive with template and must return a unicode string. | |
76 @param template(unicode, None): path to the template to render. | |
77 This method is mutually exclusive with render | |
78 @param on_data_post(callable, None): method to call when data is posted | |
79 None if not post is handled | |
80 on_data_post can return a string with following value: | |
81 - C.POST_NO_CONFIRM: confirm flag will not be set | |
82 """ | |
83 | |
84 web_resource.Resource.__init__(self) | |
85 self.host = host | |
86 self.root_dir = root_dir | |
87 self.url = url | |
88 self.name = name | |
89 if name is not None: | |
90 if name in self.named_pages: | |
91 raise exceptions.ConflictError(_(u'a Libervia page named "{}" already exists'.format(name))) | |
92 if u'/' in name: | |
93 raise ValueError(_(u'"/" is not allowed in page names')) | |
94 if not name: | |
95 raise ValueError(_(u"a page name can't be empty")) | |
96 self.named_pages[name] = self | |
97 if access is None: | |
98 access = C.PAGES_ACCESS_PUBLIC | |
99 if access not in (C.PAGES_ACCESS_PUBLIC, C.PAGES_ACCESS_PROFILE, C.PAGES_ACCESS_NONE): | |
100 raise NotImplementedError(_(u"{} access is not implemented yet").format(access)) | |
101 self.access = access | |
102 if redirect is not None: | |
103 # only page access and name make sense in case of full redirection | |
104 # so we check that rendering methods/values are not set | |
105 if not all(lambda x: x is not None | |
106 for x in (parse_url, prepare_render, render, template)): | |
107 raise ValueError(_(u"you can't use full page redirection with other rendering method," | |
108 u"check self.pageRedirect if you need to use them")) | |
109 self.redirect = redirect | |
110 else: | |
111 self.redirect = None | |
112 self.parse_url = parse_url | |
113 self.prepare_render = prepare_render | |
114 self.template = template | |
115 self.render_method = render | |
116 self.on_data_post = on_data_post | |
117 if access == C.PAGES_ACCESS_NONE: | |
118 # none pages just return a 404, no further check is needed | |
119 return | |
120 if template is None: | |
121 if not callable(render): | |
122 log.error(_(u"render must be implemented and callable if template is not set")) | |
123 else: | |
124 if render is not None: | |
125 log.error(_(u"render can't be used at the same time as template")) | |
126 if parse_url is not None and not callable(parse_url): | |
127 log.error(_(u"parse_url must be a callable")) | |
128 | |
129 @classmethod | |
130 def importPages(cls, host, parent=None, path=None): | |
131 """Recursively import Libervia pages""" | |
132 if path is None: | |
133 path = [] | |
134 if parent is None: | |
135 root_dir = os.path.join(os.path.dirname(libervia.__file__), C.PAGES_DIR) | |
136 parent = host | |
137 else: | |
138 root_dir = parent.root_dir | |
139 for d in os.listdir(root_dir): | |
140 dir_path = os.path.join(root_dir, d) | |
141 if not os.path.isdir(dir_path): | |
142 continue | |
143 meta_path = os.path.join(dir_path, C.PAGES_META_FILE) | |
144 if os.path.isfile(meta_path): | |
145 page_data = {} | |
146 new_path = path + [d] | |
147 # we don't want to force the presence of __init__.py | |
148 # so we use execfile instead of import. | |
149 # TODO: when moved to Python 3, __init__.py is not mandatory anymore | |
150 # so we can switch to import | |
151 execfile(meta_path, page_data) | |
152 resource = LiberviaPage( | |
153 host, | |
154 dir_path, | |
155 u'/' + u'/'.join(new_path), | |
156 name=page_data.get('name'), | |
157 redirect=page_data.get('redirect'), | |
158 access=page_data.get('access'), | |
159 parse_url=page_data.get('parse_url'), | |
160 prepare_render=page_data.get('prepare_render'), | |
161 render=page_data.get('render'), | |
162 template=page_data.get('template'), | |
163 on_data_post=page_data.get('on_data_post')) | |
164 parent.putChild(d, resource) | |
165 log.info(u"Added /{path} page".format(path=u'[...]/'.join(new_path))) | |
166 if 'uri_handlers' in page_data: | |
167 if not isinstance(page_data, dict): | |
168 log.error(_(u'uri_handlers must be a dict')) | |
169 else: | |
170 for uri_tuple, cb_name in page_data['uri_handlers'].iteritems(): | |
171 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring): | |
172 log.error(_(u"invalid uri_tuple")) | |
173 continue | |
174 log.info(_(u'setting {}/{} URIs handler').format(*uri_tuple)) | |
175 try: | |
176 cb = page_data[cb_name] | |
177 except KeyError: | |
178 log.error(_(u'missing {name} method to handle {1}/{2}').format( | |
179 name = cb_name, *uri_tuple)) | |
180 continue | |
181 else: | |
182 cls.registerURI(uri_tuple, cb, new_path) | |
183 | |
184 LiberviaPage.importPages(host, resource, new_path) | |
185 | |
186 @classmethod | |
187 def registerURI(cls, uri_tuple, get_uri_cb, pre_path): | |
188 """register a URI handler | |
189 | |
190 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler | |
191 type/subtype as returned by tools/common/parseXMPPUri | |
192 @param get_uri_cb(callable): method which take uri_data dict as only argument | |
193 and return path with correct arguments relative to page itself | |
194 @param pre_path(list[unicode]): prefix path to reference the handler page | |
195 """ | |
196 if uri_tuple in cls.uri_callbacks: | |
197 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler").format(*uri_tuple)) | |
198 cls.uri_callbacks[uri_tuple] = {u'callback': get_uri_cb, | |
199 u'pre_path': pre_path} | |
200 | |
201 def getPagePathFromURI(self, uri): | |
202 """Retrieve page URL from xmpp: URI | |
203 | |
204 @param uri(unicode): URI with a xmpp: scheme | |
205 @return (unicode,None): absolute path (starting from root "/") to page handling the URI | |
206 None is returned if not page has been registered for this URI | |
207 """ | |
208 uri_data = common_uri.parseXMPPUri(uri) | |
209 try: | |
210 callback_data = self.uri_callbacks[uri_data['type'], uri_data.get('sub_type')] | |
211 except KeyError: | |
212 return | |
213 else: | |
214 url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data)) | |
215 return url | |
216 | |
217 @classmethod | |
218 def getPageByName(cls, name): | |
219 """retrieve page instance from its name | |
220 | |
221 @param name(unicode): name of the page | |
222 @return (LiberviaPage): page instance | |
223 @raise KeyError: the page doesn't exist | |
224 """ | |
225 return cls.named_pages[name] | |
226 | |
227 def getPageRedirectURL(self, request, page_name=u'login', url=None): | |
228 """generate URL for a page with redirect_url parameter set | |
229 | |
230 mainly used for login page with redirection to current page | |
231 @param request(server.Request): current HTTP request | |
232 @param page_name(unicode): name of the page to go | |
233 @param url(None, unicode): url to redirect to | |
234 None to use request path (i.e. current page) | |
235 @return (unicode): URL to use | |
236 """ | |
237 return u'{root_url}?redirect_url={redirect_url}'.format( | |
238 root_url = self.getPageByName(page_name).url, | |
239 redirect_url=urllib.quote_plus(request.uri) if url is None else url.encode('utf-8')) | |
240 | |
241 def getURL(self, *args): | |
242 """retrieve URL of the page set arguments | |
243 | |
244 *args(list[unicode]): argument to add to the URL as path elements | |
245 """ | |
246 url_args = [quote(a) for a in args] | |
247 | |
248 if self.name is not None and self.name in self.pages_redirects: | |
249 # we check for redirection | |
250 redirect_data = self.pages_redirects[self.name] | |
251 args_hash = tuple(args) | |
252 for limit in xrange(len(args)+1): | |
253 current_hash = args_hash[:limit] | |
254 if current_hash in redirect_data: | |
255 url_base = redirect_data[current_hash] | |
256 remaining = args[limit:] | |
257 remaining_url = '/'.join(remaining) | |
258 return os.path.join('/', url_base, remaining_url) | |
259 | |
260 return os.path.join(self.url, *url_args) | |
261 | |
262 def getSubPageURL(self, request, page_name, *args): | |
263 """retrieve a page in direct children and build its URL according to request | |
264 | |
265 request's current path is used as base (at current parsing point, | |
266 i.e. it's more prepath than path). | |
267 Requested page is checked in children and an absolute URL is then built | |
268 by the resulting combination. | |
269 This method is useful to construct absolute URLs for children instead of | |
270 using relative path, which may not work in subpages, and are linked to the | |
271 names of directories (i.e. relative URL will break if subdirectory is renamed | |
272 while getSubPageURL won't as long as page_name is consistent). | |
273 Also, request.path is used, keeping real path used by user, | |
274 and potential redirections. | |
275 @param request(server.Request): current HTTP request | |
276 @param page_name(unicode): name of the page to retrieve | |
277 it must be a direct children of current page | |
278 @param *args(list[unicode]): arguments to add as path elements | |
279 @return unicode: absolute URL to the sub page | |
280 """ | |
281 # we get url in the following way (splitting request.path instead of using | |
282 # request.prepath) because request.prepath may have been modified by | |
283 # redirection (if redirection args have been specified), while path reflect | |
284 # the real request | |
285 | |
286 # we ignore empty path elements (i.e. double '/' or '/' at the end) | |
287 path_elts = [p for p in request.path.split('/') if p] | |
288 | |
289 if request.postpath: | |
290 if not request.postpath[-1]: | |
291 # we remove trailing slash | |
292 request.postpath = request.postpath[:-1] | |
293 if request.postpath: | |
294 # getSubPageURL must return subpage from the point where | |
295 # the it is called, so we have to remove remanining | |
296 # path elements | |
297 path_elts = path_elts[:-len(request.postpath)] | |
298 | |
299 current_url = '/' + '/'.join(path_elts).decode('utf-8') | |
300 | |
301 for path, child in self.children.iteritems(): | |
302 try: | |
303 child_name = child.name | |
304 except AttributeError: | |
305 # LiberviaPage have a name, but maybe this is an other Resource | |
306 continue | |
307 if child_name == page_name: | |
308 return os.path.join(u'/', current_url, path, *args) | |
309 raise exceptions.NotFound(_(u'requested sub page has not been found')) | |
310 | |
311 def getChildWithDefault(self, path, request): | |
312 # we handle children ourselves | |
313 raise exceptions.InternalError(u"this method should not be used with LiberviaPage") | |
314 | |
315 def nextPath(self, request): | |
316 """get next URL path segment, and update request accordingly | |
317 | |
318 will move first segment of postpath in prepath | |
319 @param request(server.Request): current HTTP request | |
320 @return (unicode): unquoted segment | |
321 @raise IndexError: there is no segment left | |
322 """ | |
323 pathElement = request.postpath.pop(0) | |
324 request.prepath.append(pathElement) | |
325 return urllib.unquote(pathElement).decode('utf-8') | |
326 | |
327 def HTTPRedirect(self, request, url): | |
328 """redirect to an URL using HTTP redirection | |
329 | |
330 @param request(server.Request): current HTTP request | |
331 @param url(unicode): url to redirect to | |
332 """ | |
333 | |
334 web_util.redirectTo(url.encode('utf-8'), request) | |
335 request.finish() | |
336 raise failure.Failure(exceptions.CancelError(u'HTTP redirection is used')) | |
337 | |
338 def redirectOrContinue(self, request, redirect_arg=u'redirect_url'): | |
339 """helper method to redirect a page to an url given as arg | |
340 | |
341 if the arg is not present, the page will continue normal workflow | |
342 @param request(server.Request): current HTTP request | |
343 @param redirect_arg(unicode): argument to use to get redirection URL | |
344 @interrupt: redirect the page to requested URL | |
345 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used | |
346 """ | |
347 try: | |
348 url = self.getPostedData(request, 'redirect_url') | |
349 except KeyError: | |
350 pass | |
351 else: | |
352 # a redirection is requested | |
353 if not url or url[0] != u'/': | |
354 # we only want local urls | |
355 self.pageError(request, C.HTTP_BAD_REQUEST) | |
356 else: | |
357 self.HTTPRedirect(request, url) | |
358 | |
359 def pageRedirect(self, page_path, request, skip_parse_url=True): | |
360 """redirect a page to a named page | |
361 | |
362 the workflow will continue with the workflow of the named page, | |
363 skipping named page's parse_url method if it exist. | |
364 If you want to do a HTTP redirection, use HTTPRedirect | |
365 @param page_path(unicode): path to page (elements are separated by "/"): | |
366 if path starts with a "/": | |
367 path is a full path starting from root | |
368 else: | |
369 - first element is name as registered in name variable | |
370 - following element are subpages path | |
371 e.g.: "blog" redirect to page named "blog" | |
372 "blog/atom.xml" redirect to atom.xml subpage of "blog" | |
373 "/common/blog/atom.xml" redirect to the page at the fiven full path | |
374 @param request(server.Request): current HTTP request | |
375 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped | |
376 @raise KeyError: there is no known page with this name | |
377 """ | |
378 # FIXME: render non LiberviaPage resources | |
379 path = page_path.rstrip(u'/').split(u'/') | |
380 if not path[0]: | |
381 redirect_page = self.host.root | |
382 else: | |
383 redirect_page = self.named_pages[path[0]] | |
384 | |
385 for subpage in path[1:]: | |
386 if redirect_page is self.host.root: | |
387 redirect_page = redirect_page.children[subpage] | |
388 else: | |
389 redirect_page = redirect_page.original.children[subpage] | |
390 | |
391 redirect_page.renderPage(request, skip_parse_url=True) | |
392 raise failure.Failure(exceptions.CancelError(u'page redirection is used')) | |
393 | |
394 def pageError(self, request, code=C.HTTP_NOT_FOUND): | |
395 """generate an error page and terminate the request | |
396 | |
397 @param request(server.Request): HTTP request | |
398 @param core(int): error code to use | |
399 """ | |
400 template = u'error/' + unicode(code) + '.html' | |
401 | |
402 request.setResponseCode(code) | |
403 | |
404 rendered = self.host.renderer.render( | |
405 template, | |
406 root_path = '/templates/', | |
407 error_code = code, | |
408 **request.template_data) | |
409 | |
410 self.writeData(rendered, request) | |
411 raise failure.Failure(exceptions.CancelError(u'error page is used')) | |
412 | |
413 def writeData(self, data, request): | |
414 """write data to transport and finish the request""" | |
415 if data is None: | |
416 self.pageError(request) | |
417 request.write(data.encode('utf-8')) | |
418 request.finish() | |
419 | |
420 def _subpagesHandler(self, dummy, request): | |
421 """render subpage if suitable | |
422 | |
423 this method checks if there is still an unmanaged part of the path | |
424 and check if it corresponds to a subpage. If so, it render the subpage | |
425 else it render a NoResource. | |
426 If there is no unmanaged part of the segment, current page workflow is pursued | |
427 """ | |
428 if request.postpath: | |
429 subpage = self.nextPath(request) | |
430 try: | |
431 child = self.children[subpage] | |
432 except KeyError: | |
433 self.pageError(request) | |
434 else: | |
435 child.render(request) | |
436 raise failure.Failure(exceptions.CancelError(u'subpage page is used')) | |
437 | |
438 def _prepare_render(self, dummy, request): | |
439 return defer.maybeDeferred(self.prepare_render, self, request) | |
440 | |
441 def _render_method(self, dummy, request): | |
442 return defer.maybeDeferred(self.render_method, self, request) | |
443 | |
444 def _render_template(self, dummy, request): | |
445 template_data = request.template_data | |
446 | |
447 # if confirm variable is set in case of successfuly data post | |
448 session_data = self.host.getSessionData(request, session_iface.ISATSession) | |
449 if session_data.popPageFlag(self, C.FLAG_CONFIRM): | |
450 template_data[u'confirm'] = True | |
451 | |
452 return self.host.renderer.render( | |
453 self.template, | |
454 root_path = '/templates/', | |
455 media_path = '/' + C.MEDIA_DIR, | |
456 **template_data) | |
457 | |
458 def _renderEb(self, failure_, request): | |
459 """don't raise error on CancelError""" | |
460 failure_.trap(exceptions.CancelError) | |
461 | |
462 def _internalError(self, failure_, request): | |
463 """called if an error is not catched""" | |
464 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format( | |
465 url = request.URLPath(), | |
466 msg = failure_)) | |
467 self.pageError(request, C.HTTP_INTERNAL_ERROR) | |
468 | |
469 def _on_data_post_redirect(self, ret, request): | |
470 """called when page's on_data_post has been done successfuly | |
471 | |
472 This will do a Post/Redirect/Get pattern. | |
473 this method redirect to the same page or to request.data['post_redirect_page'] | |
474 post_redirect_page can be either a page or a tuple with page as first item, then a list of unicode arguments to append to the url. | |
475 if post_redirect_page is not used, initial request.uri (i.e. the same page as where the data have been posted) will be used for redirection. | |
476 HTTP status code "See Other" (303) is used as it is the recommanded code in this case. | |
477 @param ret(None, unicode, iterable): on_data_post return value | |
478 see LiberviaPage.__init__ on_data_post docstring | |
479 """ | |
480 if ret is None: | |
481 ret = () | |
482 elif isinstance(ret, basestring): | |
483 ret = (ret,) | |
484 else: | |
485 ret = tuple(ret) | |
486 raise NotImplementedError(_(u'iterable in on_data_post return value is not used yet')) | |
487 session_data = self.host.getSessionData(request, session_iface.ISATSession) | |
488 request_data = self.getRData(request) | |
489 if 'post_redirect_page' in request_data: | |
490 redirect_page_data = request_data['post_redirect_page'] | |
491 if isinstance(redirect_page_data, tuple): | |
492 redirect_page = redirect_page_data[0] | |
493 redirect_page_args = redirect_page_data[1:] | |
494 redirect_uri = redirect_page.getURL(*redirect_page_args) | |
495 else: | |
496 redirect_page = redirect_page_data | |
497 redirect_uri = redirect_page.url | |
498 else: | |
499 redirect_page = self | |
500 redirect_uri = request.uri | |
501 | |
502 if not C.POST_NO_CONFIRM in ret: | |
503 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM) | |
504 request.setResponseCode(C.HTTP_SEE_OTHER) | |
505 request.setHeader("location", redirect_uri) | |
506 request.finish() | |
507 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) | |
508 | |
509 def _on_data_post(self, dummy, request): | |
510 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token | |
511 try: | |
512 given_csrf = self.getPostedData(request, u'csrf_token') | |
513 except KeyError: | |
514 given_csrf = None | |
515 if given_csrf is None or given_csrf != csrf_token: | |
516 log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( | |
517 url=request.uri, | |
518 ip=request.getClientIP())) | |
519 self.pageError(request, C.HTTP_UNAUTHORIZED) | |
520 d = defer.maybeDeferred(self.on_data_post, self, request) | |
521 d.addCallback(self._on_data_post_redirect, request) | |
522 return d | |
523 | |
524 def getPostedData(self, request, keys, multiple=False): | |
525 """get data from a POST request and decode it | |
526 | |
527 @param request(server.Request): request linked to the session | |
528 @param keys(unicode, iterable[unicode]): name of the value(s) to get | |
529 unicode to get one value | |
530 iterable to get more than one | |
531 @param multiple(bool): True if multiple values are possible/expected | |
532 if False, the first value is returned | |
533 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) | |
534 @raise KeyError: one specific key has been requested, and it is missing | |
535 """ | |
536 if isinstance(keys, basestring): | |
537 keys = [keys] | |
538 get_first = True | |
539 else: | |
540 get_first = False | |
541 | |
542 ret = [] | |
543 for key in keys: | |
544 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[])) | |
545 if multiple: | |
546 ret.append(gen) | |
547 else: | |
548 try: | |
549 ret.append(next(gen)) | |
550 except StopIteration: | |
551 raise KeyError(key) | |
552 | |
553 return ret[0] if get_first else ret | |
554 | |
555 def getAllPostedData(self, request, except_=()): | |
556 """get all posted data | |
557 | |
558 @param request(server.Request): request linked to the session | |
559 @param except_(iterable[unicode]): key of values to ignore | |
560 csrf_token will always be ignored | |
561 @return (dict[unicode, list[unicode]]): post values | |
562 """ | |
563 except_ = tuple(except_) + (u'csrf_token',) | |
564 ret = {} | |
565 for key, values in request.args.iteritems(): | |
566 key = urllib.unquote(key).decode('utf-8') | |
567 if key in except_: | |
568 continue | |
569 ret[key] = [urllib.unquote(v).decode('utf-8') for v in values] | |
570 return ret | |
571 | |
572 def getProfile(self, request): | |
573 """helper method to easily get current profile | |
574 | |
575 @return (unicode, None): current profile | |
576 None if no profile session is started | |
577 """ | |
578 sat_session = self.host.getSessionData(request, session_iface.ISATSession) | |
579 return sat_session.profile | |
580 | |
581 def getRData(self, request): | |
582 """helper method to get request data dict | |
583 | |
584 this dictionnary if for the request only, it is not saved in session | |
585 It is mainly used to pass data between pages/methods called during request workflow | |
586 @return (dict): request data | |
587 """ | |
588 try: | |
589 return request.data | |
590 except AttributeError: | |
591 request.data = {} | |
592 return request.data | |
593 | |
594 def _checkAccess(self, data, request): | |
595 """Check access according to self.access | |
596 | |
597 if access is not granted, show a HTTP_UNAUTHORIZED pageError and stop request, | |
598 else return data (so it can be inserted in deferred chain | |
599 """ | |
600 if self.access == C.PAGES_ACCESS_PUBLIC: | |
601 pass | |
602 elif self.access == C.PAGES_ACCESS_PROFILE: | |
603 profile = self.getProfile(request) | |
604 if not profile: | |
605 # no session started | |
606 if not self.host.options["allow_registration"]: | |
607 # registration not allowed, access is not granted | |
608 self.pageError(request, C.HTTP_UNAUTHORIZED) | |
609 else: | |
610 # registration allowed, we redirect to login page | |
611 login_url = self.getPageRedirectURL(request) | |
612 self.HTTPRedirect(request, login_url) | |
613 | |
614 return data | |
615 | |
616 def renderPage(self, request, skip_parse_url=False): | |
617 """Main method to handle the workflow of a LiberviaPage""" | |
618 # template_data are the variables passed to template | |
619 if not hasattr(request, 'template_data'): | |
620 session_data = self.host.getSessionData(request, session_iface.ISATSession) | |
621 csrf_token = session_data.csrf_token | |
622 request.template_data = {u'csrf_token': csrf_token} | |
623 | |
624 # XXX: here is the code which need to be executed once | |
625 # at the beginning of the request hanling | |
626 if request.postpath and not request.postpath[-1]: | |
627 # we don't differenciate URLs finishing with '/' or not | |
628 del request.postpath[-1] | |
629 | |
630 d = defer.Deferred() | |
631 d.addCallback(self._checkAccess, request) | |
632 | |
633 if self.redirect is not None: | |
634 self.pageRedirect(self.redirect, request, skip_parse_url=False) | |
635 | |
636 if self.parse_url is not None and not skip_parse_url: | |
637 d.addCallback(self.parse_url, request) | |
638 | |
639 d.addCallback(self._subpagesHandler, request) | |
640 | |
641 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST): | |
642 # only HTTP GET and POST are handled so far | |
643 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST)) | |
644 | |
645 if request.method == C.HTTP_METHOD_POST: | |
646 if self.on_data_post is None: | |
647 # if we don't have on_data_post, the page was not expecting POST | |
648 # so we return an error | |
649 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST)) | |
650 else: | |
651 d.addCallback(self._on_data_post, request) | |
652 # by default, POST follow normal behaviour after on_data_post is called | |
653 # this can be changed by a redirection or other method call in on_data_post | |
654 | |
655 if self.prepare_render: | |
656 d.addCallback(self._prepare_render, request) | |
657 | |
658 if self.template: | |
659 d.addCallback(self._render_template, request) | |
660 elif self.render_method: | |
661 d.addCallback(self._render_method, request) | |
662 | |
663 d.addCallback(self.writeData, request) | |
664 d.addErrback(self._renderEb, request) | |
665 d.addErrback(self._internalError, request) | |
666 d.callback(self) | |
667 return server.NOT_DONE_YET | |
668 | |
669 def render_GET(self, request): | |
670 return self.renderPage(request) | |
671 | |
672 def render_POST(self, request): | |
673 return self.renderPage(request) |