Fix #678 -- Data shredders for personally identifiable information (#817)

* Add data shredders for PII

* First working shredder

* Add more shredders

* Add new shredders and download confirmation

* tmp

* PayPal, Stripe, banktransfer

* Add icon to logs

* Untested payment log shredders

* Add waiting list shredder

* First tests

* Add tests for shredders

* Improve templats, link to shredder

* Test payment info shredders

* More tests

* Documentation

* Fix enabled flag in payment provider overview

* Fix minor issues
This commit is contained in:
Raphael Michel
2018-05-02 15:59:59 +02:00
committed by GitHub
parent 335838f2b2
commit 7bccd62a4f
41 changed files with 1728 additions and 21 deletions

View File

@@ -369,8 +369,9 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
key=lambda s: s.verbose_name
)
for p in context['providers']:
if not p.is_enabled and p.is_meta and p.settings._enabled:
p.is_enabled = True
p.show_enabled = p.is_enabled
if p.is_meta:
p.show_enabled = p.settings._enabled in (True, 'True')
return context

View File

@@ -347,6 +347,8 @@ class OrderInvoiceRegenerate(OrderView):
else:
if inv.canceled:
messages.error(self.request, _('The invoice has already been canceled.'))
elif inv.shredded:
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:
inv = regenerate_invoice(inv)
self.order.log_action('pretix.event.order.invoice.regenerated', user=self.request.user, data={
@@ -370,6 +372,8 @@ class OrderInvoiceReissue(OrderView):
else:
if inv.canceled:
messages.error(self.request, _('The invoice has already been canceled.'))
elif inv.shredded:
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:
c = generate_cancellation(inv)
if self.order.status not in (Order.STATUS_CANCELED, Order.STATUS_REFUNDED):
@@ -447,6 +451,10 @@ class InvoiceDownload(EventPermissionRequiredMixin, View):
invoice_pdf(self.invoice.pk)
self.invoice = Invoice.objects.get(pk=self.invoice.pk)
if self.invoice.shredded:
messages.error(request, _('The invoice file is no longer stored on the server.'))
return redirect(self.get_order_url())
if not self.invoice.file:
# This happens if we have celery installed and the file will be generated in the background
messages.warning(request, _('The invoice file has not yet been generated, we will generate it for you '
@@ -648,7 +656,10 @@ class OrderModifyInformation(OrderQuestionsViewMixin, OrderView):
_("We had difficulties processing your input. Please review the errors below."))
return self.get(request, *args, **kwargs)
self.invoice_form.save()
self.order.log_action('pretix.event.order.modified', user=request.user)
self.order.log_action('pretix.event.order.modified', {
'invoice_data': self.invoice_form.cleaned_data,
'data': [f.cleaned_data for f in self.forms]
}, user=request.user)
if self.invoice_form.has_changed():
success_message = ('The invoice address has been updated. If you want to generate a new invoice, '
'you need to do this manually.')

View File

@@ -0,0 +1,109 @@
import logging
from collections import OrderedDict
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views import View
from django.views.generic import TemplateView
from pretix.base.models import CachedFile
from pretix.base.services.shredder import export, shred
from pretix.base.shredder import ShredError, shred_constraints
from pretix.base.views.async import AsyncAction
from pretix.control.permissions import EventPermissionRequiredMixin
logger = logging.getLogger(__name__)
class ShredderMixin:
@cached_property
def shredders(self):
return OrderedDict(
sorted(self.request.event.get_data_shredders().items(), key=lambda s: s[1].verbose_name)
)
class StartShredView(EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
template_name = 'pretixcontrol/shredder/index.html'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['shredders'] = self.shredders
ctx['constraints'] = shred_constraints(self.request.event)
return ctx
class ShredDownloadView(EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
template_name = 'pretixcontrol/shredder/download.html'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['shredders'] = self.shredders
ctx['file'] = get_object_or_404(CachedFile, pk=kwargs.get("file"))
return ctx
class ShredExportView(EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
task = export
known_errortypes = ['ShredError']
def get_success_message(self, value):
return None
def get_success_url(self, value):
return reverse('control:event.shredder.download', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'file': str(value)
})
def get_error_url(self):
return reverse('control:event.shredder.start', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug
})
def post(self, request, *args, **kwargs):
constr = shred_constraints(self.request.event)
if constr:
return self.error(ShredError(self.get_error_url()))
return self.do(self.request.event.id, request.POST.getlist("shredder"))
class ShredDoView(EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
task = shred
known_errortypes = ['ShredError']
def get_success_url(self, value):
return reverse('control:event.shredder.start', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
})
def get_success_message(self, value):
return _('The selected data was deleted successfully.')
def get_error_url(self):
return reverse('control:event.shredder.download', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'file': self.request.POST.get("file")
})
def post(self, request, *args, **kwargs):
constr = shred_constraints(self.request.event)
if constr:
return self.error(ShredError(self.get_error_url()))
if not self.request.user.check_password(request.POST.get("password")):
return self.error(ShredError(_("The current password you entered was not correct.")))
return self.do(self.request.event.id, request.POST.get("file"), request.POST.get("confirm_code"))