From 8d7a9162f9cc54b94e2ba1f2b0dd84780a033467 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 13 Dec 2021 14:46:14 +0100 Subject: [PATCH] [IMP] core: set CSP header on some non-HTML resources Core client remains incompatible with CSP, however it can't hurt to CSP the sub-resources. Current scheme is simplistic, however if useful of necessary it could be made more flexible e.g. there could be a map of mimetypes to CSP configuration, that sort of things. --- odoo/addons/base/models/ir_http.py | 2 +- odoo/http.py | 56 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index d1d9b57aa1a..bbdb1cd6cf5 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -426,7 +426,7 @@ class IrHttp(models.AbstractModel): return status, content, filename, mimetype, filehash def _binary_set_headers(self, status, content, filename, mimetype, unique, filehash=None, download=False): - headers = [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')] + headers = [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff'), ('Content-Security-Policy', "default-src 'none'")] # cache etag = bool(request) and request.httprequest.headers.get('If-None-Match') status = status or 200 diff --git a/odoo/http.py b/odoo/http.py index 87d2fe8faaf..3acab5506a7 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -3,6 +3,7 @@ # OpenERP HTTP layer #---------------------------------------------------------- import ast +import cgi import collections import contextlib import copy @@ -1393,6 +1394,7 @@ class Root(object): response = Response(result, mimetype='text/html') else: response = result + self.set_csp(response) save_session = (not request.endpoint) or request.endpoint.routing.get('save_session', True) if not save_session: @@ -1418,6 +1420,23 @@ class Root(object): return response + + def set_csp(self, response): + # ignore HTTP errors + if not isinstance(response, werkzeug.wrappers.BaseResponse): + return + + headers = response.headers + if 'Content-Security-Policy' in headers: + return + + mime, _params = cgi.parse_header(headers.get('Content-Type', '')) + if not mime.startswith('image/'): + return + + headers['Content-Security-Policy'] = "default-src 'none'" + + def dispatch(self, environ, start_response): """ Performs the actual WSGI dispatching for the application. @@ -1675,35 +1694,16 @@ def set_safe_image_headers(headers, content): `Content-Type` set appropriately depending on the given `content` only if it is safe to do, as well as `X-Content-Type-Options: nosniff` so that if the file is of an unsafe type, it is not interpreted as that type if the - `Content-type` header was already set to a different mimetype""" - content_type = guess_mimetype(content) - safe_types = ['image/jpeg', 'image/png', 'image/gif', 'image/x-icon'] - if content_type in safe_types: - headers = set_header_field(headers, 'Content-Type', content_type) - headers = set_header_field(headers, 'X-Content-Type-Options', 'nosniff') - set_header_field(headers, 'Content-Length', len(content)) - return headers - - -def set_header_field(headers, name, value): - """ Return new headers based on `headers` but with `value` set for the - header field `name`. - - :param headers: the existing headers - :type headers: list of tuples (name, value) - - :param name: the header field name - :type name: string - - :param value: the value to set for the `name` header - :type value: string - - :return: the updated headers - :rtype: list of tuples (name, value) + `Content-type` header was already set to a different mimetype """ - dictheaders = dict(headers) - dictheaders[name] = value - return list(dictheaders.items()) + headers = werkzeug.datastructures.Headers(headers) + safe_types = {'image/jpeg', 'image/png', 'image/gif', 'image/x-icon'} + content_type = guess_mimetype(content) + if content_type in safe_types: + headers['Content-Type'] = content_type + headers['X-Content-Type-Options'] = 'nosniff' + headers['Content-Length'] = len(content) + return list(headers) # main wsgi handler