mirror of
https://github.com/pretix/pretix.git
synced 2026-08-04 09:47:50 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47638fdf95 | ||
|
|
d44083ee33 | ||
|
|
92090b896a | ||
|
|
df2e82e707 | ||
|
|
899ff4821e |
@@ -266,6 +266,22 @@ def _merge_csp(a, b):
|
||||
|
||||
|
||||
class SecurityMiddleware(MiddlewareMixin):
|
||||
SAFE_TYPES = (
|
||||
# CSP policies are only used for:
|
||||
# - HTML and SVG in top-level contexts
|
||||
# - SVG or JS Workers delivered in embedded contexts
|
||||
# See: https://www.w3.org/TR/CSP2/#which-policy-applies
|
||||
# Therefore, we can save bandwidth on not including our (sometimes huge) policy
|
||||
# on API responses or CSS. We do however include it with other types as a precaution
|
||||
# (whitelist instead of blacklist) and we also do not whitelist JavaScript in
|
||||
# we ever add service workers to not break the protection of this feature:
|
||||
# https://www.w3.org/TR/CSP2/#sandboxing-and-workers
|
||||
'application/json',
|
||||
'text/css',
|
||||
# We used to skip CSP for PDF since it was necessary for inline previews in Safari,
|
||||
# but at the moment it does not seem to be an issue to just send it.
|
||||
)
|
||||
|
||||
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
|
||||
@@ -277,6 +293,11 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
# https://github.com/pretix/pretix/issues/765
|
||||
resp['P3P'] = 'CP=\"ALL DSP COR CUR ADM TAI OUR IND COM NAV INT\"'
|
||||
|
||||
if "Content-Type" in resp and resp["Content-Type"].split(";")[0] in self.SAFE_TYPES:
|
||||
if 'Content-Security-Policy' in resp:
|
||||
del resp['Content-Security-Policy']
|
||||
return resp
|
||||
|
||||
if not getattr(resp, '_csp_ignore', False):
|
||||
resp['Content-Security-Policy'] = _render_csp(self._build_csp(request, resp))
|
||||
elif 'Content-Security-Policy' in resp:
|
||||
|
||||
@@ -560,11 +560,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/plain" id="schema-url">{% static "schema/pdf-layout.schema.json" %}</script>
|
||||
<script type="text/javascript" src="{% static "pdfjs/pdf.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "ajv/ajv2020.bundle.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "fabric/fabric.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/editor.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "schema/pdf-layout.validate.js" %}"></script>
|
||||
<img src="{% static 'pretixpresale/pdf/powered_by_pretix_dark.png' %}" id="poweredby-dark" class="sr-only">
|
||||
<img src="{% static 'pretixpresale/pdf/powered_by_pretix_white.png' %}" id="poweredby-white" class="sr-only">
|
||||
{% for family, styles in fonts.items %}
|
||||
|
||||
@@ -952,7 +952,12 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
context=context,
|
||||
)
|
||||
r = HttpResponse(v, content_type='text/html')
|
||||
r._csp_ignore = True
|
||||
r['Content-Security-Policy'] = (
|
||||
# Plugin-provided email templates will contain inline styles or remote images
|
||||
# but emails should not contain JS
|
||||
"style-src 'unsafe-inline'; "
|
||||
"img-src https: data:"
|
||||
)
|
||||
return r
|
||||
else:
|
||||
raise Http404(_('Unknown email renderer.'))
|
||||
|
||||
@@ -70,7 +70,6 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
if 'placeholders' in request.GET:
|
||||
return self.get_placeholders_help(request)
|
||||
resp = super().get(request, *args, **kwargs)
|
||||
resp._csp_ignore = True
|
||||
return resp
|
||||
|
||||
def get_placeholders_help(self, request):
|
||||
|
||||
@@ -162,7 +162,6 @@ class XHRView(View):
|
||||
|
||||
paypal_order = prov._create_paypal_order(request, None, cart_total)
|
||||
r = JsonResponse(paypal_order.dict() if paypal_order else {})
|
||||
r._csp_ignore = True
|
||||
return r
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ def theme_css(request, **kwargs):
|
||||
obj = getattr(request, "event", request.organizer)
|
||||
css = get_theme_vars_css(obj, widget=False)
|
||||
resp = HttpResponse(css, content_type="text/css")
|
||||
resp._csp_ignore = True
|
||||
resp["Access-Control-Allow-Origin"] = "*"
|
||||
if "version" in request.GET:
|
||||
resp["Expires"] = http_date(time.time() + 3600 * 24 * 30)
|
||||
|
||||
@@ -164,7 +164,6 @@ def widget_css(request, version, **kwargs):
|
||||
css = f"/* v{version} */\n" + theme_css + widget_css
|
||||
|
||||
resp = FileResponse(css, content_type='text/css')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
@@ -246,7 +245,6 @@ def widget_js(request, version, lang, **kwargs):
|
||||
cached_js = cache.get(cache_prefix)
|
||||
if cached_js and not settings.DEBUG:
|
||||
resp = HttpResponse(cached_js, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
@@ -278,7 +276,6 @@ def widget_js(request, version, lang, **kwargs):
|
||||
gs.settings.set(checksum_key, checksum)
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
@@ -414,7 +411,6 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
self.post_process(data)
|
||||
resp = JsonResponse(data)
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
resp._csp_ignore = True
|
||||
return resp
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
-7
File diff suppressed because one or more lines are too long
@@ -1,4 +1,7 @@
|
||||
/*globals $, gettext, fabric, PDFJS*/
|
||||
|
||||
window.module = {} // Hack to load ajv-compiled schema without actual module loading
|
||||
|
||||
fabric.Poweredby = fabric.util.createClass(fabric.Image, {
|
||||
type: 'poweredby',
|
||||
|
||||
@@ -219,7 +222,6 @@ var editor = {
|
||||
uploaded_file_id: null,
|
||||
_window_loaded: false,
|
||||
_fabric_loaded: false,
|
||||
schema: null,
|
||||
|
||||
_px2mm: function (v) {
|
||||
return v / editor.pdf_scale / 72 * editor.pdf_page.userUnit * 25.4;
|
||||
@@ -1320,14 +1322,11 @@ var editor = {
|
||||
|
||||
_source_save: function () {
|
||||
try {
|
||||
var Ajv = window.ajv2020
|
||||
var ajv = new Ajv()
|
||||
var validate = ajv.compile(editor.schema)
|
||||
var data = JSON.parse($("#source-textarea").val())
|
||||
var valid = validate(data)
|
||||
var valid = validate30(data)
|
||||
|
||||
if (!valid) {
|
||||
console.log(validate.errors)
|
||||
console.log(validate30.errors)
|
||||
alert("Invalid input syntax. If you're familiar with this, check out the developer console for a full " +
|
||||
"error log. Otherwise, please contact support.")
|
||||
} else {
|
||||
@@ -1463,10 +1462,6 @@ var editor = {
|
||||
editor._update_save_button();
|
||||
});
|
||||
$("#pdf-info-width, #pdf-info-height").bind('change input', editor._paper_size_warning);
|
||||
|
||||
$.getJSON($("#schema-url").text(), function (data) {
|
||||
editor.schema = data;
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$comment": "If the schema is changed, regenerate pdf-layout.validate.js with $ ajv compile -s ./pretix/static/schema/pdf-layout.schema.json --spec=draft2020 -o ./pretix/static/schema/pdf-layout.validate.js",
|
||||
"title": "Ticket Layout",
|
||||
"description": "Dynamic elements for a PDF layout",
|
||||
"type": "array",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -258,7 +258,7 @@ def test_list_list(token_client, organizer, event, clist, item, subevent, django
|
||||
res["id"] = clist.pk
|
||||
res["limit_products"] = [item.pk]
|
||||
|
||||
with django_assert_num_queries(11):
|
||||
with django_assert_num_queries(9):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/'.format(organizer.slug, event.slug))
|
||||
assert resp.status_code == 200
|
||||
assert [res] == resp.data['results']
|
||||
@@ -437,7 +437,7 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a
|
||||
p3["addon_to"] = p1["id"]
|
||||
|
||||
# All items
|
||||
with django_assert_num_queries(24):
|
||||
with django_assert_num_queries(22):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ordering=positionid'.format(
|
||||
organizer.slug, event.slug, clist_all.pk
|
||||
))
|
||||
|
||||
@@ -1865,7 +1865,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
|
||||
assert resp.status_code == 200
|
||||
event.refresh_from_db()
|
||||
|
||||
with assert_num_queries(12):
|
||||
with assert_num_queries(10):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
|
||||
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=true'
|
||||
.format(organizer.slug, event.slug))
|
||||
@@ -1875,7 +1875,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
|
||||
with scope(organizer=organizer):
|
||||
v0 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-0'))
|
||||
|
||||
with assert_num_queries(14):
|
||||
with assert_num_queries(12):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
|
||||
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=false'
|
||||
.format(organizer.slug, event.slug))
|
||||
@@ -1883,7 +1883,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
|
||||
assert len(resp.data['results']) == 1
|
||||
assert resp.data['results'][0]['voucher']['id'] == v0.pk
|
||||
|
||||
with assert_num_queries(12):
|
||||
with assert_num_queries(10):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
|
||||
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=true'
|
||||
.format(organizer.slug, event.slug))
|
||||
@@ -1894,7 +1894,7 @@ def test_event_expand_seat_filter_and_querycount(token_client, organizer, event,
|
||||
v1 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-1'))
|
||||
v2 = event.vouchers.create(item=item, seat=event.seats.get(seat_guid='0-2'))
|
||||
|
||||
with assert_num_queries(16):
|
||||
with assert_num_queries(14):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/seats/'
|
||||
'?expand=orderposition&expand=cartposition&expand=voucher&is_available=false'
|
||||
.format(organizer.slug, event.slug))
|
||||
|
||||
@@ -425,7 +425,7 @@ def test_item_list(token_client, organizer, event, team, item):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_item_list_queries(token_client, organizer, event, team, item, item3):
|
||||
with assert_num_queries(18):
|
||||
with assert_num_queries(16):
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
@@ -1166,10 +1166,10 @@ def test_orderposition_list(
|
||||
'type': 'entry'
|
||||
}]
|
||||
if '/events/' in endpoint:
|
||||
with django_assert_num_queries(18):
|
||||
with django_assert_num_queries(16):
|
||||
resp = token_client.get(endpoint + '?has_checkin=true')
|
||||
else:
|
||||
with django_assert_num_queries(17):
|
||||
with django_assert_num_queries(15):
|
||||
resp = token_client.get(endpoint + '?has_checkin=true')
|
||||
assert [res] == resp.data['results']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user