diff --git a/src/pretix/base/middleware.py b/src/pretix/base/middleware.py index a656ae94e..5808d18fb 100644 --- a/src/pretix/base/middleware.py +++ b/src/pretix/base/middleware.py @@ -7,6 +7,7 @@ from django.core.urlresolvers import get_script_prefix from django.http import HttpRequest, HttpResponse from django.utils import timezone, translation from django.utils.cache import patch_vary_headers +from django.utils.crypto import get_random_string from django.utils.deprecation import MiddlewareMixin from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation.trans_real import ( @@ -165,6 +166,9 @@ class SecurityMiddleware(MiddlewareMixin): '/api/v1/docs/', ) + def process_request(self, request): + request.csp_nonce = get_random_string(length=32) + def process_response(self, request, resp): if settings.DEBUG and resp.status_code >= 400: # Don't use CSP on debug error page as it breaks of Django's fancy error @@ -179,9 +183,9 @@ class SecurityMiddleware(MiddlewareMixin): # frame-src is deprecated but kept for compatibility with CSP 1.0 browsers, e.g. Safari 9 'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'], 'child-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'], - 'style-src': ["{static}"], + 'style-src': ["{static}", "'nonce-{nonce}'"], 'connect-src': ["{dynamic}", "https://checkout.stripe.com"], - 'img-src': ["{static}", "data:", "https://*.stripe.com"], + 'img-src': ["{static}", "{media}", "data:", "https://*.stripe.com"], # form-action is not only used to match on form actions, but also on URLs # form-actions redirect to. In the context of e.g. payment providers or # single-sign-on this can be nearly anything so we cannot really restrict @@ -193,6 +197,9 @@ class SecurityMiddleware(MiddlewareMixin): staticdomain = "'self'" dynamicdomain = "'self'" + mediadomain = "'self'" + if settings.MEDIA_URL.startswith('http'): + mediadomain += " " + settings.MEDIA_URL[:settings.MEDIA_URL.find('/', 9)] if settings.STATIC_URL.startswith('http'): staticdomain += " " + settings.STATIC_URL[:settings.STATIC_URL.find('/', 9)] if settings.SITE_URL.startswith('http'): @@ -212,5 +219,6 @@ class SecurityMiddleware(MiddlewareMixin): dynamicdomain += " " + domain if request.path not in self.CSP_EXEMPT: - resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain) + resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain, + media=mediadomain, nonce=request.csp_nonce) return resp diff --git a/src/pretix/presale/style.py b/src/pretix/presale/style.py index cbb110d4f..7d3639b2a 100644 --- a/src/pretix/presale/style.py +++ b/src/pretix/presale/style.py @@ -1,16 +1,19 @@ import hashlib import logging import os +from urllib.parse import urljoin, urlsplit import django_libsass import sass from django.conf import settings from django.core.files.base import ContentFile from django.core.files.storage import default_storage +from django.templatetags.static import static as _static from pretix.base.models import Event from pretix.base.services.async import ProfiledTask from pretix.celery_app import app +from pretix.multidomain.urlreverse import get_domain logger = logging.getLogger('pretix.presale.style') @@ -25,10 +28,25 @@ def regenerate_css(event_id: int): sassrules.append('$brand-primary: {};'.format(event.settings.get('primary_color'))) sassrules.append('@import "main.scss";') + def static(path): + sp = _static(path) + if not settings.MEDIA_URL.startswith("/") and sp.startswith("/"): + domain = get_domain(event.organizer) + if domain: + siteurlsplit = urlsplit(settings.SITE_URL) + if siteurlsplit.port and siteurlsplit.port not in (80, 443): + domain = '%s:%d' % (domain, siteurlsplit.port) + sp = urljoin('%s://%s' % (siteurlsplit.scheme, domain), sp) + else: + sp = urljoin(settings.SITE_URL, sp) + return '"{}"'.format(sp) + + cf = dict(django_libsass.CUSTOM_FUNCTIONS) + cf['static'] = static css = sass.compile( string="\n".join(sassrules), include_paths=[sassdir], output_style='compressed', - custom_functions=django_libsass.CUSTOM_FUNCTIONS + custom_functions=cf ) checksum = hashlib.sha1(css.encode('utf-8')).hexdigest() fname = '{}/{}/presale.{}.css'.format( diff --git a/src/pretix/presale/templates/pretixpresale/base.html b/src/pretix/presale/templates/pretixpresale/base.html index 669ee7aed..23b6b2e57 100644 --- a/src/pretix/presale/templates/pretixpresale/base.html +++ b/src/pretix/presale/templates/pretixpresale/base.html @@ -11,7 +11,7 @@ {% endcompress %} {% if css_file %} - + {% else %} {% compress css %}