comparison src/server/server.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 bcacf970f970
children 64826e69f365
comparison
equal deleted inserted replaced
983:8c9fdb58de5f 984:f0fc28b3bd1e
37 from sat.core.i18n import _, D_ 37 from sat.core.i18n import _, D_
38 from sat.core import exceptions 38 from sat.core import exceptions
39 from sat.tools import utils 39 from sat.tools import utils
40 from sat.tools.common import regex 40 from sat.tools.common import regex
41 from sat.tools.common import template 41 from sat.tools.common import template
42 from sat.tools.common import uri as common_uri
43 42
44 import re 43 import re
45 import glob 44 import glob
46 import os.path 45 import os.path
47 import sys 46 import sys
50 import uuid 49 import uuid
51 import urlparse 50 import urlparse
52 import urllib 51 import urllib
53 from httplib import HTTPS_PORT 52 from httplib import HTTPS_PORT
54 import libervia 53 import libervia
54 from libervia.server.pages import LiberviaPage
55 from libervia.server.utils import quote
55 56
56 try: 57 try:
57 import OpenSSL 58 import OpenSSL
58 from twisted.internet import ssl 59 from twisted.internet import ssl
59 except ImportError: 60 except ImportError:
64 from libervia.server import session_iface 65 from libervia.server import session_iface
65 66
66 67
67 # following value are set from twisted.plugins.libervia_server initialise (see the comment there) 68 # following value are set from twisted.plugins.libervia_server initialise (see the comment there)
68 DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = coerceDataDir = None 69 DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = coerceDataDir = None
69
70
71 def quote(value):
72 """shortcut to quote an unicode value for URL"""
73 return urllib.quote_plus(value.encode('utf-8')).replace('%40','@')
74 70
75 71
76 class LiberviaSession(server.Session): 72 class LiberviaSession(server.Session):
77 sessionTimeout = C.SESSION_TIMEOUT 73 sessionTimeout = C.SESSION_TIMEOUT
78 74
1364 """ 1360 """
1365 profile = session_iface.ISATSession(request.getSession()).profile 1361 profile = session_iface.ISATSession(request.getSession()).profile
1366 return ("setAvatar", filepath, profile) 1362 return ("setAvatar", filepath, profile)
1367 1363
1368 1364
1369 class LiberviaPage(web_resource.Resource):
1370 isLeaf = True # we handle subpages ourself
1371 named_pages = {}
1372 uri_callbacks = {}
1373 pages_redirects = {}
1374
1375 def __init__(self, host, root_dir, url, name=None, redirect=None, access=None, parse_url=None,
1376 prepare_render=None, render=None, template=None, on_data_post=None):
1377 """initiate LiberviaPages
1378
1379 LiberviaPages are the main resources of Libervia, using easy to set python files
1380 The arguments are the variables found in page_meta.py
1381 @param host(Libervia): the running instance of Libervia
1382 @param root_dir(unicode): aboslute file path of the page
1383 @param url(unicode): relative URL to the page
1384 this URL may not be valid, as pages may require path arguments
1385 @param name(unicode, None): if not None, a unique name to identify the page
1386 can then be used for e.g. redirection
1387 "/" is not allowed in names (as it can be used to construct URL paths)
1388 @param redirect(unicode, None): if not None, this page will be redirected. A redirected
1389 parameter is used as in self.pageRedirect. parse_url will not be skipped
1390 using this redirect parameter is called "full redirection"
1391 using self.pageRedirect is called "partial redirection" (because some rendering method
1392 can still be used, e.g. parse_url)
1393 @param access(unicode, None): permission needed to access the page
1394 None means public access.
1395 Pages inherit from parent pages: e.g. if a "settings" page is restricted to admins,
1396 and if "settings/blog" is public, it still can only be accessed by admins.
1397 see C.PAGES_ACCESS_* for details
1398 @param parse_url(callable, None): if set it will be called to handle the URL path
1399 after this method, the page will be rendered if noting is left in path (request.postpath)
1400 else a the request will be transmitted to a subpage
1401 @param prepare_render(callable, None): if set, will be used to prepare the rendering
1402 that often means gathering data using the bridge
1403 @param render(callable, None): if not template is set, this method will be called and
1404 what it returns will be rendered.
1405 This method is mutually exclusive with template and must return a unicode string.
1406 @param template(unicode, None): path to the template to render.
1407 This method is mutually exclusive with render
1408 @param on_data_post(callable, None): method to call when data is posted
1409 None if not post is handled
1410 on_data_post can return a string with following value:
1411 - C.POST_NO_CONFIRM: confirm flag will not be set
1412 """
1413
1414 web_resource.Resource.__init__(self)
1415 self.host = host
1416 self.root_dir = root_dir
1417 self.url = url
1418 self.name = name
1419 if name is not None:
1420 if name in self.named_pages:
1421 raise exceptions.ConflictError(_(u'a Libervia page named "{}" already exists'.format(name)))
1422 if u'/' in name:
1423 raise ValueError(_(u'"/" is not allowed in page names'))
1424 if not name:
1425 raise ValueError(_(u"a page name can't be empty"))
1426 self.named_pages[name] = self
1427 if access is None:
1428 access = C.PAGES_ACCESS_PUBLIC
1429 if access not in (C.PAGES_ACCESS_PUBLIC, C.PAGES_ACCESS_PROFILE, C.PAGES_ACCESS_NONE):
1430 raise NotImplementedError(_(u"{} access is not implemented yet").format(access))
1431 self.access = access
1432 if redirect is not None:
1433 # only page access and name make sense in case of full redirection
1434 # so we check that rendering methods/values are not set
1435 if not all(lambda x: x is not None
1436 for x in (parse_url, prepare_render, render, template)):
1437 raise ValueError(_(u"you can't use full page redirection with other rendering method,"
1438 u"check self.pageRedirect if you need to use them"))
1439 self.redirect = redirect
1440 else:
1441 self.redirect = None
1442 self.parse_url = parse_url
1443 self.prepare_render = prepare_render
1444 self.template = template
1445 self.render_method = render
1446 self.on_data_post = on_data_post
1447 if access == C.PAGES_ACCESS_NONE:
1448 # none pages just return a 404, no further check is needed
1449 return
1450 if template is None:
1451 if not callable(render):
1452 log.error(_(u"render must be implemented and callable if template is not set"))
1453 else:
1454 if render is not None:
1455 log.error(_(u"render can't be used at the same time as template"))
1456 if parse_url is not None and not callable(parse_url):
1457 log.error(_(u"parse_url must be a callable"))
1458
1459 @classmethod
1460 def importPages(cls, host, parent=None, path=None):
1461 """Recursively import Libervia pages"""
1462 if path is None:
1463 path = []
1464 if parent is None:
1465 root_dir = os.path.join(os.path.dirname(libervia.__file__), C.PAGES_DIR)
1466 parent = host
1467 else:
1468 root_dir = parent.root_dir
1469 for d in os.listdir(root_dir):
1470 dir_path = os.path.join(root_dir, d)
1471 if not os.path.isdir(dir_path):
1472 continue
1473 meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
1474 if os.path.isfile(meta_path):
1475 page_data = {}
1476 new_path = path + [d]
1477 # we don't want to force the presence of __init__.py
1478 # so we use execfile instead of import.
1479 # TODO: when moved to Python 3, __init__.py is not mandatory anymore
1480 # so we can switch to import
1481 execfile(meta_path, page_data)
1482 resource = LiberviaPage(
1483 host,
1484 dir_path,
1485 u'/' + u'/'.join(new_path),
1486 name=page_data.get('name'),
1487 redirect=page_data.get('redirect'),
1488 access=page_data.get('access'),
1489 parse_url=page_data.get('parse_url'),
1490 prepare_render=page_data.get('prepare_render'),
1491 render=page_data.get('render'),
1492 template=page_data.get('template'),
1493 on_data_post=page_data.get('on_data_post'))
1494 parent.putChild(d, resource)
1495 log.info(u"Added /{path} page".format(path=u'[...]/'.join(new_path)))
1496 if 'uri_handlers' in page_data:
1497 if not isinstance(page_data, dict):
1498 log.error(_(u'uri_handlers must be a dict'))
1499 else:
1500 for uri_tuple, cb_name in page_data['uri_handlers'].iteritems():
1501 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring):
1502 log.error(_(u"invalid uri_tuple"))
1503 continue
1504 log.info(_(u'setting {}/{} URIs handler').format(*uri_tuple))
1505 try:
1506 cb = page_data[cb_name]
1507 except KeyError:
1508 log.error(_(u'missing {name} method to handle {1}/{2}').format(
1509 name = cb_name, *uri_tuple))
1510 continue
1511 else:
1512 cls.registerURI(uri_tuple, cb, new_path)
1513
1514 LiberviaPage.importPages(host, resource, new_path)
1515
1516 @classmethod
1517 def registerURI(cls, uri_tuple, get_uri_cb, pre_path):
1518 """register a URI handler
1519
1520 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler
1521 type/subtype as returned by tools/common/parseXMPPUri
1522 @param get_uri_cb(callable): method which take uri_data dict as only argument
1523 and return path with correct arguments relative to page itself
1524 @param pre_path(list[unicode]): prefix path to reference the handler page
1525 """
1526 if uri_tuple in cls.uri_callbacks:
1527 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler").format(*uri_tuple))
1528 cls.uri_callbacks[uri_tuple] = {u'callback': get_uri_cb,
1529 u'pre_path': pre_path}
1530
1531 def getPagePathFromURI(self, uri):
1532 """Retrieve page URL from xmpp: URI
1533
1534 @param uri(unicode): URI with a xmpp: scheme
1535 @return (unicode,None): absolute path (starting from root "/") to page handling the URI
1536 None is returned if not page has been registered for this URI
1537 """
1538 uri_data = common_uri.parseXMPPUri(uri)
1539 try:
1540 callback_data = self.uri_callbacks[uri_data['type'], uri_data.get('sub_type')]
1541 except KeyError:
1542 return
1543 else:
1544 url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data))
1545 return url
1546
1547 @classmethod
1548 def getPageByName(cls, name):
1549 """retrieve page instance from its name
1550
1551 @param name(unicode): name of the page
1552 @return (LiberviaPage): page instance
1553 @raise KeyError: the page doesn't exist
1554 """
1555 return cls.named_pages[name]
1556
1557 def getPageRedirectURL(self, request, page_name=u'login', url=None):
1558 """generate URL for a page with redirect_url parameter set
1559
1560 mainly used for login page with redirection to current page
1561 @param request(server.Request): current HTTP request
1562 @param page_name(unicode): name of the page to go
1563 @param url(None, unicode): url to redirect to
1564 None to use request path (i.e. current page)
1565 @return (unicode): URL to use
1566 """
1567 return u'{root_url}?redirect_url={redirect_url}'.format(
1568 root_url = self.getPageByName(page_name).url,
1569 redirect_url=urllib.quote_plus(request.uri) if url is None else url.encode('utf-8'))
1570
1571 def getURL(self, *args):
1572 """retrieve URL of the page set arguments
1573
1574 *args(list[unicode]): argument to add to the URL as path elements
1575 """
1576 url_args = [quote(a) for a in args]
1577
1578 if self.name is not None and self.name in self.pages_redirects:
1579 # we check for redirection
1580 redirect_data = self.pages_redirects[self.name]
1581 args_hash = tuple(args)
1582 for limit in xrange(len(args)+1):
1583 current_hash = args_hash[:limit]
1584 if current_hash in redirect_data:
1585 url_base = redirect_data[current_hash]
1586 remaining = args[limit:]
1587 remaining_url = '/'.join(remaining)
1588 return os.path.join('/', url_base, remaining_url)
1589
1590 return os.path.join(self.url, *url_args)
1591
1592 def getSubPageURL(self, request, page_name, *args):
1593 """retrieve a page in direct children and build its URL according to request
1594
1595 request's current path is used as base (at current parsing point,
1596 i.e. it's more prepath than path).
1597 Requested page is checked in children and an absolute URL is then built
1598 by the resulting combination.
1599 This method is useful to construct absolute URLs for children instead of
1600 using relative path, which may not work in subpages, and are linked to the
1601 names of directories (i.e. relative URL will break if subdirectory is renamed
1602 while getSubPageURL won't as long as page_name is consistent).
1603 Also, request.path is used, keeping real path used by user,
1604 and potential redirections.
1605 @param request(server.Request): current HTTP request
1606 @param page_name(unicode): name of the page to retrieve
1607 it must be a direct children of current page
1608 @param *args(list[unicode]): arguments to add as path elements
1609 @return unicode: absolute URL to the sub page
1610 """
1611 # we get url in the following way (splitting request.path instead of using
1612 # request.prepath) because request.prepath may have been modified by
1613 # redirection (if redirection args have been specified), while path reflect
1614 # the real request
1615
1616 # we ignore empty path elements (i.e. double '/' or '/' at the end)
1617 path_elts = [p for p in request.path.split('/') if p]
1618
1619 if request.postpath:
1620 if not request.postpath[-1]:
1621 # we remove trailing slash
1622 request.postpath = request.postpath[:-1]
1623 if request.postpath:
1624 # getSubPageURL must return subpage from the point where
1625 # the it is called, so we have to remove remanining
1626 # path elements
1627 path_elts = path_elts[:-len(request.postpath)]
1628
1629 current_url = '/' + '/'.join(path_elts).decode('utf-8')
1630
1631 for path, child in self.children.iteritems():
1632 try:
1633 child_name = child.name
1634 except AttributeError:
1635 # LiberviaPage have a name, but maybe this is an other Resource
1636 continue
1637 if child_name == page_name:
1638 return os.path.join(u'/', current_url, path, *args)
1639 raise exceptions.NotFound(_(u'requested sub page has not been found'))
1640
1641 def getChildWithDefault(self, path, request):
1642 # we handle children ourselves
1643 raise exceptions.InternalError(u"this method should not be used with LiberviaPage")
1644
1645 def nextPath(self, request):
1646 """get next URL path segment, and update request accordingly
1647
1648 will move first segment of postpath in prepath
1649 @param request(server.Request): current HTTP request
1650 @return (unicode): unquoted segment
1651 @raise IndexError: there is no segment left
1652 """
1653 pathElement = request.postpath.pop(0)
1654 request.prepath.append(pathElement)
1655 return urllib.unquote(pathElement).decode('utf-8')
1656
1657 def HTTPRedirect(self, request, url):
1658 """redirect to an URL using HTTP redirection
1659
1660 @param request(server.Request): current HTTP request
1661 @param url(unicode): url to redirect to
1662 """
1663
1664 web_util.redirectTo(url.encode('utf-8'), request)
1665 request.finish()
1666 raise failure.Failure(exceptions.CancelError(u'HTTP redirection is used'))
1667
1668 def redirectOrContinue(self, request, redirect_arg=u'redirect_url'):
1669 """helper method to redirect a page to an url given as arg
1670
1671 if the arg is not present, the page will continue normal workflow
1672 @param request(server.Request): current HTTP request
1673 @param redirect_arg(unicode): argument to use to get redirection URL
1674 @interrupt: redirect the page to requested URL
1675 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used
1676 """
1677 try:
1678 url = self.getPostedData(request, 'redirect_url')
1679 except KeyError:
1680 pass
1681 else:
1682 # a redirection is requested
1683 if not url or url[0] != u'/':
1684 # we only want local urls
1685 self.pageError(request, C.HTTP_BAD_REQUEST)
1686 else:
1687 self.HTTPRedirect(request, url)
1688
1689 def pageRedirect(self, page_path, request, skip_parse_url=True):
1690 """redirect a page to a named page
1691
1692 the workflow will continue with the workflow of the named page,
1693 skipping named page's parse_url method if it exist.
1694 If you want to do a HTTP redirection, use HTTPRedirect
1695 @param page_path(unicode): path to page (elements are separated by "/"):
1696 if path starts with a "/":
1697 path is a full path starting from root
1698 else:
1699 - first element is name as registered in name variable
1700 - following element are subpages path
1701 e.g.: "blog" redirect to page named "blog"
1702 "blog/atom.xml" redirect to atom.xml subpage of "blog"
1703 "/common/blog/atom.xml" redirect to the page at the fiven full path
1704 @param request(server.Request): current HTTP request
1705 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped
1706 @raise KeyError: there is no known page with this name
1707 """
1708 # FIXME: render non LiberviaPage resources
1709 path = page_path.rstrip(u'/').split(u'/')
1710 if not path[0]:
1711 redirect_page = self.host.root
1712 else:
1713 redirect_page = self.named_pages[path[0]]
1714
1715 for subpage in path[1:]:
1716 if redirect_page is self.host.root:
1717 redirect_page = redirect_page.children[subpage]
1718 else:
1719 redirect_page = redirect_page.original.children[subpage]
1720
1721 redirect_page.renderPage(request, skip_parse_url=True)
1722 raise failure.Failure(exceptions.CancelError(u'page redirection is used'))
1723
1724 def pageError(self, request, code=C.HTTP_NOT_FOUND):
1725 """generate an error page and terminate the request
1726
1727 @param request(server.Request): HTTP request
1728 @param core(int): error code to use
1729 """
1730 template = u'error/' + unicode(code) + '.html'
1731
1732 request.setResponseCode(code)
1733
1734 rendered = self.host.renderer.render(
1735 template,
1736 root_path = '/templates/',
1737 error_code = code,
1738 **request.template_data)
1739
1740 self.writeData(rendered, request)
1741 raise failure.Failure(exceptions.CancelError(u'error page is used'))
1742
1743 def writeData(self, data, request):
1744 """write data to transport and finish the request"""
1745 if data is None:
1746 self.pageError(request)
1747 request.write(data.encode('utf-8'))
1748 request.finish()
1749
1750 def _subpagesHandler(self, dummy, request):
1751 """render subpage if suitable
1752
1753 this method checks if there is still an unmanaged part of the path
1754 and check if it corresponds to a subpage. If so, it render the subpage
1755 else it render a NoResource.
1756 If there is no unmanaged part of the segment, current page workflow is pursued
1757 """
1758 if request.postpath:
1759 subpage = self.nextPath(request)
1760 try:
1761 child = self.children[subpage]
1762 except KeyError:
1763 self.pageError(request)
1764 else:
1765 child.render(request)
1766 raise failure.Failure(exceptions.CancelError(u'subpage page is used'))
1767
1768 def _prepare_render(self, dummy, request):
1769 return defer.maybeDeferred(self.prepare_render, self, request)
1770
1771 def _render_method(self, dummy, request):
1772 return defer.maybeDeferred(self.render_method, self, request)
1773
1774 def _render_template(self, dummy, request):
1775 template_data = request.template_data
1776
1777 # if confirm variable is set in case of successfuly data post
1778 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1779 if session_data.popPageFlag(self, C.FLAG_CONFIRM):
1780 template_data[u'confirm'] = True
1781
1782 return self.host.renderer.render(
1783 self.template,
1784 root_path = '/templates/',
1785 media_path = '/' + C.MEDIA_DIR,
1786 **template_data)
1787
1788 def _renderEb(self, failure_, request):
1789 """don't raise error on CancelError"""
1790 failure_.trap(exceptions.CancelError)
1791
1792 def _internalError(self, failure_, request):
1793 """called if an error is not catched"""
1794 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format(
1795 url = request.URLPath(),
1796 msg = failure_))
1797 self.pageError(request, C.HTTP_INTERNAL_ERROR)
1798
1799 def _on_data_post_redirect(self, ret, request):
1800 """called when page's on_data_post has been done successfuly
1801
1802 This will do a Post/Redirect/Get pattern.
1803 this method redirect to the same page or to request.data['post_redirect_page']
1804 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.
1805 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.
1806 HTTP status code "See Other" (303) is used as it is the recommanded code in this case.
1807 @param ret(None, unicode, iterable): on_data_post return value
1808 see LiberviaPage.__init__ on_data_post docstring
1809 """
1810 if ret is None:
1811 ret = ()
1812 elif isinstance(ret, basestring):
1813 ret = (ret,)
1814 else:
1815 ret = tuple(ret)
1816 raise NotImplementedError(_(u'iterable in on_data_post return value is not used yet'))
1817 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1818 request_data = self.getRData(request)
1819 if 'post_redirect_page' in request_data:
1820 redirect_page_data = request_data['post_redirect_page']
1821 if isinstance(redirect_page_data, tuple):
1822 redirect_page = redirect_page_data[0]
1823 redirect_page_args = redirect_page_data[1:]
1824 redirect_uri = redirect_page.getURL(*redirect_page_args)
1825 else:
1826 redirect_page = redirect_page_data
1827 redirect_uri = redirect_page.url
1828 else:
1829 redirect_page = self
1830 redirect_uri = request.uri
1831
1832 if not C.POST_NO_CONFIRM in ret:
1833 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM)
1834 request.setResponseCode(C.HTTP_SEE_OTHER)
1835 request.setHeader("location", redirect_uri)
1836 request.finish()
1837 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used'))
1838
1839 def _on_data_post(self, dummy, request):
1840 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token
1841 try:
1842 given_csrf = self.getPostedData(request, u'csrf_token')
1843 except KeyError:
1844 given_csrf = None
1845 if given_csrf is None or given_csrf != csrf_token:
1846 log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format(
1847 url=request.uri,
1848 ip=request.getClientIP()))
1849 self.pageError(request, C.HTTP_UNAUTHORIZED)
1850 d = defer.maybeDeferred(self.on_data_post, self, request)
1851 d.addCallback(self._on_data_post_redirect, request)
1852 return d
1853
1854 def getPostedData(self, request, keys, multiple=False):
1855 """get data from a POST request and decode it
1856
1857 @param request(server.Request): request linked to the session
1858 @param keys(unicode, iterable[unicode]): name of the value(s) to get
1859 unicode to get one value
1860 iterable to get more than one
1861 @param multiple(bool): True if multiple values are possible/expected
1862 if False, the first value is returned
1863 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s)
1864 @raise KeyError: one specific key has been requested, and it is missing
1865 """
1866 if isinstance(keys, basestring):
1867 keys = [keys]
1868 get_first = True
1869 else:
1870 get_first = False
1871
1872 ret = []
1873 for key in keys:
1874 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[]))
1875 if multiple:
1876 ret.append(gen)
1877 else:
1878 try:
1879 ret.append(next(gen))
1880 except StopIteration:
1881 raise KeyError(key)
1882
1883 return ret[0] if get_first else ret
1884
1885 def getAllPostedData(self, request, except_=()):
1886 """get all posted data
1887
1888 @param request(server.Request): request linked to the session
1889 @param except_(iterable[unicode]): key of values to ignore
1890 csrf_token will always be ignored
1891 @return (dict[unicode, list[unicode]]): post values
1892 """
1893 except_ = tuple(except_) + (u'csrf_token',)
1894 ret = {}
1895 for key, values in request.args.iteritems():
1896 key = urllib.unquote(key).decode('utf-8')
1897 if key in except_:
1898 continue
1899 ret[key] = [urllib.unquote(v).decode('utf-8') for v in values]
1900 return ret
1901
1902 def getProfile(self, request):
1903 """helper method to easily get current profile
1904
1905 @return (unicode, None): current profile
1906 None if no profile session is started
1907 """
1908 sat_session = self.host.getSessionData(request, session_iface.ISATSession)
1909 return sat_session.profile
1910
1911 def getRData(self, request):
1912 """helper method to get request data dict
1913
1914 this dictionnary if for the request only, it is not saved in session
1915 It is mainly used to pass data between pages/methods called during request workflow
1916 @return (dict): request data
1917 """
1918 try:
1919 return request.data
1920 except AttributeError:
1921 request.data = {}
1922 return request.data
1923
1924 def _checkAccess(self, data, request):
1925 """Check access according to self.access
1926
1927 if access is not granted, show a HTTP_UNAUTHORIZED pageError and stop request,
1928 else return data (so it can be inserted in deferred chain
1929 """
1930 if self.access == C.PAGES_ACCESS_PUBLIC:
1931 pass
1932 elif self.access == C.PAGES_ACCESS_PROFILE:
1933 profile = self.getProfile(request)
1934 if not profile:
1935 # no session started
1936 if not self.host.options["allow_registration"]:
1937 # registration not allowed, access is not granted
1938 self.pageError(request, C.HTTP_UNAUTHORIZED)
1939 else:
1940 # registration allowed, we redirect to login page
1941 login_url = self.getPageRedirectURL(request)
1942 self.HTTPRedirect(request, login_url)
1943
1944 return data
1945
1946 def renderPage(self, request, skip_parse_url=False):
1947 """Main method to handle the workflow of a LiberviaPage"""
1948 # template_data are the variables passed to template
1949 if not hasattr(request, 'template_data'):
1950 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1951 csrf_token = session_data.csrf_token
1952 request.template_data = {u'csrf_token': csrf_token}
1953
1954 # XXX: here is the code which need to be executed once
1955 # at the beginning of the request hanling
1956 if request.postpath and not request.postpath[-1]:
1957 # we don't differenciate URLs finishing with '/' or not
1958 del request.postpath[-1]
1959
1960 d = defer.Deferred()
1961 d.addCallback(self._checkAccess, request)
1962
1963 if self.redirect is not None:
1964 self.pageRedirect(self.redirect, request, skip_parse_url=False)
1965
1966 if self.parse_url is not None and not skip_parse_url:
1967 d.addCallback(self.parse_url, request)
1968
1969 d.addCallback(self._subpagesHandler, request)
1970
1971 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST):
1972 # only HTTP GET and POST are handled so far
1973 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST))
1974
1975 if request.method == C.HTTP_METHOD_POST:
1976 if self.on_data_post is None:
1977 # if we don't have on_data_post, the page was not expecting POST
1978 # so we return an error
1979 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST))
1980 else:
1981 d.addCallback(self._on_data_post, request)
1982 # by default, POST follow normal behaviour after on_data_post is called
1983 # this can be changed by a redirection or other method call in on_data_post
1984
1985 if self.prepare_render:
1986 d.addCallback(self._prepare_render, request)
1987
1988 if self.template:
1989 d.addCallback(self._render_template, request)
1990 elif self.render_method:
1991 d.addCallback(self._render_method, request)
1992
1993 d.addCallback(self.writeData, request)
1994 d.addErrback(self._renderEb, request)
1995 d.addErrback(self._internalError, request)
1996 d.callback(self)
1997 return server.NOT_DONE_YET
1998
1999 def render_GET(self, request):
2000 return self.renderPage(request)
2001
2002 def render_POST(self, request):
2003 return self.renderPage(request)
2004
2005
2006 class Libervia(service.Service): 1365 class Libervia(service.Service):
2007 1366
2008 def __init__(self, options): 1367 def __init__(self, options):
2009 self.options = options 1368 self.options = options
2010 self.initialised = defer.Deferred() 1369 self.initialised = defer.Deferred()