mirror of
https://github.com/pretix/pretix.git
synced 2026-05-10 16:04:02 +00:00
Easier PCI DSS compliance for payment pages (#4273)
* Assign names to compressed scripts * Make PCI-relevant pages detectable * Make payment summary markup more consistant to easy work in tracking plugin * Add docs note
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
{% load compress %}
|
{% load compress %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% compress js %}
|
{% compress js file paypal %}
|
||||||
<script type="text/javascript" src="{% static "pretixplugins/paypal2/pretix-paypal.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixplugins/paypal2/pretix-paypal.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ class XHRView(View):
|
|||||||
class PayView(PaypalOrderView, TemplateView):
|
class PayView(PaypalOrderView, TemplateView):
|
||||||
template_name = ''
|
template_name = ''
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.request.pci_dss_payment_page = True
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if self.payment.state != OrderPayment.PAYMENT_STATE_CREATED:
|
if self.payment.state != OrderPayment.PAYMENT_STATE_CREATED:
|
||||||
return self._redirect_to_order()
|
return self._redirect_to_order()
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
{% load compress %}
|
{% load compress %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% compress js %}
|
{% compress js file stripe %}
|
||||||
<script type="text/javascript" src="{% static "pretixplugins/stripe/pretix-stripe.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixplugins/stripe/pretix-stripe.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% compress css %}
|
{% compress css file stripe %}
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "pretixplugins/stripe/pretix-stripe.css" %}">
|
<link type="text/css" rel="stylesheet" href="{% static "pretixplugins/stripe/pretix-stripe.css" %}">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% if testmode %}
|
{% if testmode %}
|
||||||
|
|||||||
@@ -1263,6 +1263,7 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.request.pci_dss_payment_page = True
|
||||||
|
|
||||||
if "remove_payment" in request.POST:
|
if "remove_payment" in request.POST:
|
||||||
self._remove_payment(request.POST["remove_payment"])
|
self._remove_payment(request.POST["remove_payment"])
|
||||||
@@ -1427,6 +1428,10 @@ class PaymentStep(CartMixin, TemplateFlowStep):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
self.request.pci_dss_payment_page = True
|
||||||
|
return super().get(request)
|
||||||
|
|
||||||
|
|
||||||
class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||||
priority = 1001
|
priority = 1001
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ of every page in the frontend. You will get the request as the keyword argument
|
|||||||
``request`` and are expected to return plain HTML.
|
``request`` and are expected to return plain HTML.
|
||||||
|
|
||||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
|
|
||||||
|
**Note:** If PCI DSS compliance is important to you and you keep an inventory according to
|
||||||
|
rule 6.4.3 of PCI DSS, all plugins that are not required to load on a payment page should
|
||||||
|
not return additional JavaScripts if ``getattr(request, 'pci_dss_payment_page', False)``
|
||||||
|
is ``True``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
seatingframe_html_head = EventPluginSignal()
|
seatingframe_html_head = EventPluginSignal()
|
||||||
@@ -112,6 +117,11 @@ of every page in the frontend. You will get the request as the keyword argument
|
|||||||
``request`` and are expected to return plain HTML.
|
``request`` and are expected to return plain HTML.
|
||||||
|
|
||||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
|
|
||||||
|
**Note:** If PCI DSS compliance is important to you and you keep an inventory according to
|
||||||
|
rule 6.4.3 of PCI DSS, all plugins that are not required to load on a payment page should
|
||||||
|
not return additional JavaScripts if ``getattr(request, 'pci_dss_payment_page', False)``
|
||||||
|
is ``True``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
footer_link = EventPluginSignal()
|
footer_link = EventPluginSignal()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<html{% if rtl %} dir="rtl" class="rtl"{% endif %} lang="{{ html_locale }}">
|
<html{% if rtl %} dir="rtl" class="rtl"{% endif %} lang="{{ html_locale }}">
|
||||||
<head>
|
<head>
|
||||||
<title>{% block thetitle %}{% endblock %}</title>
|
<title>{% block thetitle %}{% endblock %}</title>
|
||||||
{% compress css %}
|
{% compress css file presale %}
|
||||||
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixpresale/scss/main.scss" %}"/>
|
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixpresale/scss/main.scss" %}"/>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% if css_theme %}
|
{% if css_theme %}
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
<script src="{% statici18n request.LANGUAGE_CODE %}"></script>
|
<script src="{% statici18n request.LANGUAGE_CODE %}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.session.iframe_session %}
|
{% if request.session.iframe_session %}
|
||||||
{% compress js %}
|
{% compress js file iframeresizer %}
|
||||||
<script type="text/javascript" src="{% static "iframeresizer/iframeResizer.contentWindow.js" %}"></script>
|
<script type="text/javascript" src="{% static "iframeresizer/iframeResizer.contentWindow.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -50,19 +50,15 @@
|
|||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
{% for payment, rendered_block in payments %}
|
{% for payment, rendered_block in payments %}
|
||||||
<li class="list-group-item payment">
|
<li class="list-group-item payment">
|
||||||
{% if payments|length > 1 %}
|
<div class="row">
|
||||||
<div class="row">
|
<div class="{% if payments|length > 1 %}col-sm-10 {% endif %}col-xs-12">
|
||||||
<div class="col-sm-10 col-xs-12">
|
<h4 {% if payments|length == 1 %}class="sr-only"{% endif %}>{{ payment.provider_name }}</h4>
|
||||||
<h4>{{ payment.provider_name }}</h4>
|
{{ rendered_block }}
|
||||||
{{ rendered_block }}
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2 col-xs-12 text-right">
|
|
||||||
<h4>{{ payment.payment_amount|money:request.event.currency }}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
<div class="col-sm-2 col-xs-12 text-right {% if payments|length == 1 %}sr-only{% endif %}">
|
||||||
{{ rendered_block }}
|
<h4>{{ payment.payment_amount|money:request.event.currency }}</h4>
|
||||||
{% endif %}
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
|
|
||||||
{% compress js %}
|
{% compress js file walletdetection %}
|
||||||
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
|
<script type="text/javascript" src="{% static "pretixpresale/js/walletdetection.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
{% compress js %}
|
{% compress js file presale %}
|
||||||
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
|
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "moment/moment-with-locales.js" %}"></script>
|
<script type="text/javascript" src="{% static "moment/moment-with-locales.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "moment/moment-timezone-with-data-1970-2030.js" %}"></script>
|
<script type="text/javascript" src="{% static "moment/moment-timezone-with-data-1970-2030.js" %}"></script>
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ class OrderPaymentStart(EventViewMixin, OrderDetailMixin, TemplateView):
|
|||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.request.pci_dss_payment_page = True
|
||||||
if not self.order:
|
if not self.order:
|
||||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||||
if (self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED)
|
if (self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED)
|
||||||
@@ -553,6 +554,7 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
|
|||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.request.pci_dss_payment_page = True
|
||||||
if not self.order:
|
if not self.order:
|
||||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||||
if self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) or self.order._can_be_paid() is not True:
|
if self.order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) or self.order._can_be_paid() is not True:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class BaseCheckoutTestCase:
|
|||||||
self.event = Event.objects.create(
|
self.event = Event.objects.create(
|
||||||
organizer=self.orga, name='30C3', slug='30c3',
|
organizer=self.orga, name='30C3', slug='30c3',
|
||||||
date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc),
|
date_from=datetime.datetime(now().year + 1, 12, 26, tzinfo=datetime.timezone.utc),
|
||||||
plugins='pretix.plugins.stripe,pretix.plugins.banktransfer',
|
plugins='pretix.plugins.stripe,pretix.plugins.banktransfer,tests.testdummy',
|
||||||
live=True
|
live=True
|
||||||
)
|
)
|
||||||
self.tr19 = self.event.tax_rules.create(rate=19)
|
self.tr19 = self.event.tax_rules.create(rate=19)
|
||||||
@@ -151,6 +151,28 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
|||||||
self._set_session('invoice_address', ia.pk)
|
self._set_session('invoice_address', ia.pk)
|
||||||
return ia
|
return ia
|
||||||
|
|
||||||
|
def test_pci_page(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
|
||||||
|
r = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug))
|
||||||
|
assert b'TRACKING SCRIPT' in r.content
|
||||||
|
|
||||||
|
payment_r = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||||
|
'is_business': 'business',
|
||||||
|
'company': 'Foo',
|
||||||
|
'name': 'Bar',
|
||||||
|
'street': 'Baz',
|
||||||
|
'zipcode': '1234',
|
||||||
|
'city': 'Here',
|
||||||
|
'country': 'AT',
|
||||||
|
'email': 'admin@localhost'
|
||||||
|
}, follow=True)
|
||||||
|
assert b'TRACKING SCRIPT' not in payment_r.content
|
||||||
|
|
||||||
def test_empty_cart(self):
|
def test_empty_cart(self):
|
||||||
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
response = self.client.get('/%s/%s/checkout/start' % (self.orga.slug, self.event.slug), follow=True)
|
||||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from pretix.base.signals import (
|
|||||||
register_payment_providers, register_sales_channel_types,
|
register_payment_providers, register_sales_channel_types,
|
||||||
register_ticket_outputs,
|
register_ticket_outputs,
|
||||||
)
|
)
|
||||||
|
from pretix.presale.signals import html_head
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_ticket_outputs, dispatch_uid="output_dummy")
|
@receiver(register_ticket_outputs, dispatch_uid="output_dummy")
|
||||||
@@ -61,3 +62,11 @@ class FoobarSalesChannel(SalesChannelType):
|
|||||||
@receiver(register_sales_channel_types, dispatch_uid="sc_dummy")
|
@receiver(register_sales_channel_types, dispatch_uid="sc_dummy")
|
||||||
def register_sc(sender, **kwargs):
|
def register_sc(sender, **kwargs):
|
||||||
return [FoobarSalesChannel, FoobazSalesChannel]
|
return [FoobarSalesChannel, FoobazSalesChannel]
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(html_head, dispatch_uid="dummy_html_head")
|
||||||
|
def html_head_presale(sender, request=None, **kwargs):
|
||||||
|
if getattr(request, 'pci_dss_payment_page', False):
|
||||||
|
# No tracking scripts on PCI DSS relevant payment pages
|
||||||
|
return ""
|
||||||
|
return "<script>alert('BAD TRACKING SCRIPT')</script>"
|
||||||
|
|||||||
Reference in New Issue
Block a user