From 8595bc2ff762e4c5ecbb4103a73d9fa1e428bb2b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 21 Dec 2015 13:10:45 +0100 Subject: [PATCH] [IMP] csrf: better warning and documentation After initial implementation CSRF protection had been left poorly documented tripping up users and developers (#9538, #10139). Add a warning in the logs for developers, and a more extensive explanation of the whole thing in the @route docstring (and the official documentation). Fixes #10158 --- openerp/http.py | 86 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index af151a0e325..2d68fd216ff 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -420,8 +420,7 @@ class WebRequest(object): return consteq(hm, hm_expected) def route(route=None, **kw): - """ - Decorator marking the decorated method as being a handler for + """Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass of ``Controller``. :param route: string or array. The route part that will determine which @@ -446,16 +445,55 @@ def route(route=None, **kw): :param cors: The Access-Control-Allow-Origin cors directive value. :param bool csrf: Whether CSRF protection should be enabled for the route. - Defaults to ``True``. + Defaults to ``True``. See :ref:`CSRF Protection + ` for more. - CSRF protection only applies to *UNSAFE* methods as - defined by :rfc:`7231`: GET, HEAD, TRACE and OPTIONS are - safe, all other methods are unsafe. + .. _csrf: + + .. admonition:: CSRF Protection + :class: alert-warning + + .. versionadded:: 9.0 + + Odoo implements token-based `CSRF protection + `_. + + CSRF protection is enabled by default and applies to *UNSAFE* + HTTP methods as defined by :rfc:`7231` (all methods other than + ``GET``, ``HEAD``, ``TRACE`` and ``OPTIONS``). + + CSRF protection is implemented by checking requests using + unsafe methods for a value called ``csrf_token`` as part of + the request's form data. That value is removed from the form + as part of the validation and does not have to be taken in + account by your own form processing. + + When adding a new controller for an unsafe method (mostly POST + for e.g. forms): + + * if the form is generated in Python, a csrf token is + available via :meth:`request.csrf_token() + `_, CSRF protection + must be disabled on the endpoint. If possible, you may want + to implement other methods of request validation (to ensure + it is not called by an unrelated third-party). - CSRF protection requires a csrf token to be sent as part - of the request's form data, it can be obtained via - :meth:`request.csrf_token() - ` """ routing = kw.copy() assert 'type' not in routing or routing['type'] in ("http", "json") @@ -769,6 +807,34 @@ class HttpRequest(WebRequest): and request.endpoint.routing.get('csrf', True): # csrf checked by default token = self.params.pop('csrf_token', None) if not self.validate_csrf(token): + if token is not None: + _logger.warn("CSRF validation failed on path '%s'", + request.httprequest.path) + else: + _logger.warn("""No CSRF validation token provided for path '%s' + +Odoo URLs are CSRF-protected by default (when accessed with unsafe +HTTP methods). See +https://www.odoo.com/documentation/9.0/reference/http.html#csrf for +more details. + +* if this endpoint is accessed through Odoo via py-QWeb form, embed a CSRF + token in the form, Tokens are available via `request.csrf_token()` + can be provided through a hidden input and must be POST-ed named + `csrf_token` e.g. in your form add: + + + +* if the form is generated or posted in javascript, the token value is + available as `csrf_token` on `web.core` and as the `csrf_token` + value in the default js-qweb execution context + +* if the form is accessed by an external third party (e.g. REST API + endpoint, payment gateway callback) you will need to disable CSRF + protection (and implement your own protection if necessary) by + passing the `csrf=False` parameter to the `route` decorator. + """, request.httprequest.path) + raise werkzeug.exceptions.BadRequest('Invalid CSRF Token') r = self._call_function(**self.params)