Mercurial > libervia-web
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() |