Mercurial > libervia-web
comparison src/server/server.py @ 956:dabecab10faa
server (pages): impleted CSRF protection:
A basic CSRF protection has been implemented using CSRF token. The token is created on session creation, and checked on data post.
The process should be fully automatic, and a hidden field is added in forms in sat_templates when csrf_token is present in template data (require to import input/form.html with context).
If token is wrong on absent, an unauthorized error page is returned (and client ip is logged).
Also don't use anymore inlineCallbacks in _on_data_post, as StopIteration exception are catched by inlineCallbacks, resulting in bad behaviour. As a further security, getPostedDate raise a KeyError instead of StopIteration is a specific key is looked for and missing.
Added HTTP_SEE_OTHER status code in constants.
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 10 Jul 2017 19:10:31 +0200 |
parents | 4f7cb6335a33 |
children | 67bf14c91d5c |
comparison
equal
deleted
inserted
replaced
955:4f7cb6335a33 | 956:dabecab10faa |
---|---|
1688 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format( | 1688 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format( |
1689 url = request.URLPath(), | 1689 url = request.URLPath(), |
1690 msg = failure_)) | 1690 msg = failure_)) |
1691 self.pageError(request, C.HTTP_INTERNAL_ERROR) | 1691 self.pageError(request, C.HTTP_INTERNAL_ERROR) |
1692 | 1692 |
1693 @defer.inlineCallbacks | 1693 def _on_data_post_redirect(self, ret, request): |
1694 def _on_data_post(self, dummy, request): | 1694 """called when page's on_data_post has been called successfuly |
1695 yield defer.maybeDeferred(self.on_data_post, self, request) | 1695 |
1696 request.setResponseCode(303) | 1696 this method redirect to the same page, using Post/Redirect/Get pattern |
1697 HTTP status code "See Other" (303) is the recommanded code in this case | |
1698 """ | |
1699 request.setResponseCode(C.HTTP_SEE_OTHER) | |
1697 request.setHeader("location", request.uri) | 1700 request.setHeader("location", request.uri) |
1698 request.finish() | 1701 request.finish() |
1699 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) | 1702 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) |
1703 | |
1704 def _on_data_post(self, dummy, request): | |
1705 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token | |
1706 try: | |
1707 given_csrf = self.getPostedData(request, u'csrf_token') | |
1708 except KeyError: | |
1709 given_csrf = None | |
1710 if given_csrf is None or given_csrf != csrf_token: | |
1711 log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( | |
1712 url=request.uri, | |
1713 ip=request.getClientIP())) | |
1714 self.pageError(request, C.HTTP_UNAUTHORIZED) | |
1715 d = defer.maybeDeferred(self.on_data_post, self, request) | |
1716 d.addCallback(self._on_data_post_redirect, request) | |
1717 return d | |
1700 | 1718 |
1701 | 1719 |
1702 def getPostedData(self, request, keys, multiple=False): | 1720 def getPostedData(self, request, keys, multiple=False): |
1703 """get data from a POST request and decode it | 1721 """get data from a POST request and decode it |
1704 | 1722 |
1707 unicode to get one value | 1725 unicode to get one value |
1708 iterable to get more than one | 1726 iterable to get more than one |
1709 @param multiple(bool): True if multiple values are possible/expected | 1727 @param multiple(bool): True if multiple values are possible/expected |
1710 if False, the first value is returned | 1728 if False, the first value is returned |
1711 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) | 1729 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) |
1730 @raise KeyError: one specific key has been requested, and it is missing | |
1712 """ | 1731 """ |
1713 if isinstance(keys, basestring): | 1732 if isinstance(keys, basestring): |
1714 keys = [keys] | 1733 keys = [keys] |
1715 get_first = True | 1734 get_first = True |
1716 else: | 1735 else: |
1720 for key in keys: | 1739 for key in keys: |
1721 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[])) | 1740 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[])) |
1722 if multiple: | 1741 if multiple: |
1723 ret.append(gen) | 1742 ret.append(gen) |
1724 else: | 1743 else: |
1725 ret.append(next(gen)) | 1744 try: |
1745 ret.append(next(gen)) | |
1746 except StopIteration: | |
1747 return KeyError(key) | |
1726 | 1748 |
1727 return ret[0] if get_first else ret | 1749 return ret[0] if get_first else ret |
1728 | 1750 |
1729 def getProfile(self, request): | 1751 def getProfile(self, request): |
1730 """helper method to easily get current profile | 1752 """helper method to easily get current profile |
1766 | 1788 |
1767 def renderPage(self, request, skip_parse_url=False): | 1789 def renderPage(self, request, skip_parse_url=False): |
1768 """Main method to handle the workflow of a LiberviaPage""" | 1790 """Main method to handle the workflow of a LiberviaPage""" |
1769 # template_data are the variables passed to template | 1791 # template_data are the variables passed to template |
1770 if not hasattr(request, 'template_data'): | 1792 if not hasattr(request, 'template_data'): |
1771 request.template_data = {} | 1793 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token |
1794 request.template_data = {'csrf_token': csrf_token} | |
1772 | 1795 |
1773 # XXX: here is the code which need to be executed once | 1796 # XXX: here is the code which need to be executed once |
1774 # at the beginning of the request hanling | 1797 # at the beginning of the request hanling |
1775 if request.postpath and not request.postpath[-1]: | 1798 if request.postpath and not request.postpath[-1]: |
1776 # we don't differenciate URLs finishing with '/' or not | 1799 # we don't differenciate URLs finishing with '/' or not |