Mercurial > libervia-web
diff 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 |
line wrap: on
line diff
--- a/src/server/server.py Sun Jul 09 22:28:40 2017 +0200 +++ b/src/server/server.py Mon Jul 10 19:10:31 2017 +0200 @@ -1690,14 +1690,32 @@ msg = failure_)) self.pageError(request, C.HTTP_INTERNAL_ERROR) - @defer.inlineCallbacks - def _on_data_post(self, dummy, request): - yield defer.maybeDeferred(self.on_data_post, self, request) - request.setResponseCode(303) + def _on_data_post_redirect(self, ret, request): + """called when page's on_data_post has been called successfuly + + this method redirect to the same page, using Post/Redirect/Get pattern + HTTP status code "See Other" (303) is the recommanded code in this case + """ + request.setResponseCode(C.HTTP_SEE_OTHER) request.setHeader("location", request.uri) request.finish() raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) + def _on_data_post(self, dummy, request): + csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token + try: + given_csrf = self.getPostedData(request, u'csrf_token') + except KeyError: + given_csrf = None + if given_csrf is None or given_csrf != csrf_token: + log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( + url=request.uri, + ip=request.getClientIP())) + self.pageError(request, C.HTTP_UNAUTHORIZED) + d = defer.maybeDeferred(self.on_data_post, self, request) + d.addCallback(self._on_data_post_redirect, request) + return d + def getPostedData(self, request, keys, multiple=False): """get data from a POST request and decode it @@ -1709,6 +1727,7 @@ @param multiple(bool): True if multiple values are possible/expected if False, the first value is returned @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) + @raise KeyError: one specific key has been requested, and it is missing """ if isinstance(keys, basestring): keys = [keys] @@ -1722,7 +1741,10 @@ if multiple: ret.append(gen) else: - ret.append(next(gen)) + try: + ret.append(next(gen)) + except StopIteration: + return KeyError(key) return ret[0] if get_first else ret @@ -1768,7 +1790,8 @@ """Main method to handle the workflow of a LiberviaPage""" # template_data are the variables passed to template if not hasattr(request, 'template_data'): - request.template_data = {} + csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token + request.template_data = {'csrf_token': csrf_token} # XXX: here is the code which need to be executed once # at the beginning of the request hanling