mirror of
https://github.com/pretix/pretix.git
synced 2026-06-10 01:15:05 +00:00
Bank transfer: More restrictive approach to permissions (Z#23235727)
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
from django.db.models import Exists, OuterRef, Q
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
@@ -76,10 +77,18 @@ def control_nav_import(sender, request=None, **kwargs):
|
|||||||
@receiver(nav_organizer, dispatch_uid="payment_banktransfer_organav")
|
@receiver(nav_organizer, dispatch_uid="payment_banktransfer_organav")
|
||||||
def control_nav_orga_import(sender, request=None, **kwargs):
|
def control_nav_orga_import(sender, request=None, **kwargs):
|
||||||
url = resolve(request.path_info)
|
url = resolve(request.path_info)
|
||||||
has_any_event_perm = request.user.get_events_with_permission(
|
|
||||||
"event.orders:write", request=request
|
events_without_permission = request.organizer.events.filter(
|
||||||
).filter(organizer=request.organizer).exists()
|
~Exists(
|
||||||
if not has_any_event_perm:
|
request.user.teams.with_event_permission(
|
||||||
|
"event.orders:read"
|
||||||
|
).filter(
|
||||||
|
Q(all_events=True) | Q(limit_events=OuterRef("pk")),
|
||||||
|
organizer_id=OuterRef("organizer_id"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
if events_without_permission:
|
||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
Therefore, you won't be able to mark any order as paid here.
|
Therefore, you won't be able to mark any order as paid here.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% elif can_write %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% trans "Upload a new file" %}</h3>
|
<h3 class="panel-title">{% trans "Upload a new file" %}</h3>
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
{% if not filter_form.is_valid %}
|
{% if not filter_form.is_valid and can_write %}
|
||||||
<form action="" method="post" class="helper-display-inline pull-right flip">
|
<form action="" method="post" class="helper-display-inline pull-right flip">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="btn btn-danger" type="submit" name="discard" value="all">
|
<button class="btn btn-danger" type="submit" name="discard" value="all">
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if num_new > 0 %}
|
{% if num_new > 0 and can_write %}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="btn btn-primary">
|
<button class="btn btn-primary">
|
||||||
|
|||||||
@@ -20,24 +20,24 @@
|
|||||||
{% for trans in list %}
|
{% for trans in list %}
|
||||||
<tr data-id="{{ trans.id }}">
|
<tr data-id="{{ trans.id }}">
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
{% if trans.order and trans.state == 'invalid' %}
|
{% if trans.order and trans.state == 'invalid' and can_write %}
|
||||||
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="accept"
|
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="accept"
|
||||||
data-toggle="tooltip" title="{% trans "Accept anyway" %}" data-placement="right">
|
data-toggle="tooltip" title="{% trans "Accept anyway" %}" data-placement="right">
|
||||||
<span class="fa fa-check"></span>
|
<span class="fa fa-check"></span>
|
||||||
</button>
|
</button>
|
||||||
{% elif trans.state == 'nomatch' %}
|
{% elif trans.state == 'nomatch' and can_write %}
|
||||||
<input type="text" class="form-control" placeholder="{% trans "Order code" %}">
|
<input type="text" class="form-control" placeholder="{% trans "Order code" %}">
|
||||||
<button class="btn btn-default" type="button" name="action_{{ trans.id }}"
|
<button class="btn btn-default" type="button" name="action_{{ trans.id }}"
|
||||||
value="assign" data-toggle="tooltip" title="{% trans "Assign to order" %}"
|
value="assign" data-toggle="tooltip" title="{% trans "Assign to order" %}"
|
||||||
data-placement="right">
|
data-placement="right">
|
||||||
<span class="fa fa-check"></span>
|
<span class="fa fa-check"></span>
|
||||||
</button>
|
</button>
|
||||||
{% elif trans.state == 'error' %}
|
{% elif trans.state == 'error' and can_write %}
|
||||||
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="retry"
|
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="retry"
|
||||||
data-toggle="tooltip" title="{% trans "Retry" %}" data-placement="right">
|
data-toggle="tooltip" title="{% trans "Retry" %}" data-placement="right">
|
||||||
<span class="fa fa-refresh"></span>
|
<span class="fa fa-refresh"></span>
|
||||||
</button>
|
</button>
|
||||||
{% elif trans.state == 'already' %}
|
{% elif trans.state == 'already' and can_write %}
|
||||||
<input type="text" class="form-control" placeholder="{% trans "Order code" %}">
|
<input type="text" class="form-control" placeholder="{% trans "Order code" %}">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button class="btn btn-default" type="button" name="action_{{ trans.id }}"
|
<button class="btn btn-default" type="button" name="action_{{ trans.id }}"
|
||||||
@@ -76,13 +76,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{{ trans.reference }}
|
{{ trans.reference }}
|
||||||
<div class="comment-box" data-plain="{{ trans.comment }}">
|
{% if can_write %}
|
||||||
<strong>{% trans "Comment:" %}</strong>
|
<div class="comment-box" data-plain="{{ trans.comment }}">
|
||||||
<span class="comment">{{ trans.comment|rich_text }}</span>
|
<strong>{% trans "Comment:" %}</strong>
|
||||||
<a href="#" class="comment-modify btn btn-default btn-xs">
|
<span class="comment">{{ trans.comment|rich_text }}</span>
|
||||||
<span class="fa fa-edit"></span>
|
<a href="#" class="comment-modify btn btn-default btn-xs">
|
||||||
</a>
|
<span class="fa fa-edit"></span>
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if trans.currency %}
|
{% if trans.currency %}
|
||||||
@@ -119,10 +121,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="discard">
|
<td class="discard">
|
||||||
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="discard"
|
{% if can_write %}
|
||||||
data-toggle="tooltip" title="{% trans "Discard" %}">
|
<button type="button" class="btn btn-default" name="action_{{ trans.id }}" value="discard"
|
||||||
<span class="fa fa-trash"></span>
|
data-toggle="tooltip" title="{% trans "Discard" %}">
|
||||||
</button>
|
<span class="fa fa-trash"></span>
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ from django import forms
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, Q, QuerySet
|
from django.db.models import Count, Exists, OuterRef, Q, QuerySet
|
||||||
from django.http import FileResponse, JsonResponse
|
from django.http import FileResponse, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -586,6 +586,7 @@ class ImportView(ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
ctx['job_running'] = self.job_running
|
ctx['job_running'] = self.job_running
|
||||||
|
ctx['can_write'] = self.can_write
|
||||||
ctx['no_more_payments'] = False
|
ctx['no_more_payments'] = False
|
||||||
ctx['filter_form'] = BankTransactionFilterForm(self.request.GET or None)
|
ctx['filter_form'] = BankTransactionFilterForm(self.request.GET or None)
|
||||||
|
|
||||||
@@ -623,45 +624,94 @@ class ImportView(ListView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class OrganizerBanktransferView:
|
class EventPermissionOnAllEventsRequiredMixin:
|
||||||
|
@cached_property
|
||||||
|
def can_write(self):
|
||||||
|
perm_name = self.event_permission
|
||||||
|
if hasattr(self, 'write_event_permission'):
|
||||||
|
perm_name = self.write_event_permission
|
||||||
|
|
||||||
|
events_without_permission = self.request.organizer.events.filter(
|
||||||
|
~Exists(
|
||||||
|
self.request.user.teams.with_event_permission(
|
||||||
|
perm_name
|
||||||
|
).filter(
|
||||||
|
Q(all_events=True) | Q(limit_events=OuterRef("pk")),
|
||||||
|
organizer_id=OuterRef("organizer_id"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
return not events_without_permission
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
has_any_event_perm = request.user.get_events_with_permission(
|
perm_name = self.event_permission
|
||||||
"event.orders:write", request=request
|
if request.method not in ("GET", "HEAD") and hasattr(self, 'write_event_permission'):
|
||||||
).filter(organizer=request.organizer).exists()
|
perm_name = self.write_event_permission
|
||||||
if not has_any_event_perm:
|
|
||||||
|
events_without_permission = self.request.organizer.events.filter(
|
||||||
|
~Exists(
|
||||||
|
self.request.user.teams.with_event_permission(
|
||||||
|
perm_name
|
||||||
|
).filter(
|
||||||
|
Q(all_events=True) | Q(limit_events=OuterRef("pk")),
|
||||||
|
organizer_id=OuterRef("organizer_id"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).exists()
|
||||||
|
if events_without_permission:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class EventImportView(EventPermissionRequiredMixin, ImportView):
|
class PostEventPermissionRequiredMixin(EventPermissionRequiredMixin):
|
||||||
|
@cached_property
|
||||||
|
def can_write(self):
|
||||||
|
return self.request.user.has_event_permission(
|
||||||
|
self.request.organizer, self.request.event, self.write_permission, request=self.request
|
||||||
|
)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
if request.method not in ("GET", "HEAD"):
|
||||||
|
if not self.can_write:
|
||||||
|
raise PermissionDenied()
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EventImportView(PostEventPermissionRequiredMixin, ImportView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:write'
|
||||||
|
write_permission = 'event.orders:write'
|
||||||
|
|
||||||
|
|
||||||
class OrganizerImportView(OrganizerBanktransferView, OrganizerDetailViewMixin,
|
class OrganizerImportView(EventPermissionOnAllEventsRequiredMixin, OrganizerDetailViewMixin,
|
||||||
ImportView):
|
ImportView):
|
||||||
pass
|
event_permission = 'event.orders:read'
|
||||||
|
write_event_permission = 'event.orders:write'
|
||||||
|
|
||||||
|
|
||||||
class EventJobDetailView(EventPermissionRequiredMixin, JobDetailView):
|
class EventJobDetailView(EventPermissionRequiredMixin, JobDetailView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:read'
|
||||||
|
|
||||||
|
|
||||||
class OrganizerJobDetailView(OrganizerBanktransferView, OrganizerDetailViewMixin,
|
class OrganizerJobDetailView(EventPermissionOnAllEventsRequiredMixin, OrganizerDetailViewMixin,
|
||||||
JobDetailView):
|
JobDetailView):
|
||||||
pass
|
event_permission = 'event.orders:read'
|
||||||
|
|
||||||
|
|
||||||
class EventActionView(EventPermissionRequiredMixin, ActionView):
|
class EventActionView(PostEventPermissionRequiredMixin, ActionView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:read'
|
||||||
|
write_permission = 'event.orders:write'
|
||||||
|
|
||||||
|
|
||||||
class OrganizerActionView(OrganizerBanktransferView, OrganizerDetailViewMixin,
|
class OrganizerActionView(EventPermissionOnAllEventsRequiredMixin, OrganizerDetailViewMixin,
|
||||||
ActionView):
|
ActionView):
|
||||||
|
event_permission = "event.orders:read"
|
||||||
|
write_event_permission = "event.orders:write"
|
||||||
|
|
||||||
def order_qs(self):
|
def order_qs(self):
|
||||||
|
# The filters here are basically pointless with EventPermissionOnAllEventsRequiredMixin
|
||||||
|
# but let's keep them for safety with future refactorings
|
||||||
all = self.request.user.teams.filter(
|
all = self.request.user.teams.filter(
|
||||||
TeamQuerySet.event_permission_q("event.orders:read"),
|
TeamQuerySet.event_permission_q("event.orders:read"),
|
||||||
TeamQuerySet.event_permission_q("event.orders:write"),
|
|
||||||
all_events=True,
|
all_events=True,
|
||||||
organizer=self.request.organizer,
|
organizer=self.request.organizer,
|
||||||
).exists()
|
).exists()
|
||||||
@@ -671,7 +721,6 @@ class OrganizerActionView(OrganizerBanktransferView, OrganizerDetailViewMixin,
|
|||||||
return Order.objects.filter(
|
return Order.objects.filter(
|
||||||
event_id__in=self.request.user.teams.filter(
|
event_id__in=self.request.user.teams.filter(
|
||||||
TeamQuerySet.event_permission_q("event.orders:read"),
|
TeamQuerySet.event_permission_q("event.orders:read"),
|
||||||
TeamQuerySet.event_permission_q("event.orders:write"),
|
|
||||||
organizer=self.request.organizer,
|
organizer=self.request.organizer,
|
||||||
).values_list('limit_events__id', flat=True)
|
).values_list('limit_events__id', flat=True)
|
||||||
)
|
)
|
||||||
@@ -712,6 +761,7 @@ class RefundExportListView(ListView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
ctx['num_new'] = self.get_unexported().count()
|
ctx['num_new'] = self.get_unexported().count()
|
||||||
|
ctx['can_write'] = self.can_write
|
||||||
ctx['basetpl'] = "pretixcontrol/event/base.html"
|
ctx['basetpl'] = "pretixcontrol/event/base.html"
|
||||||
if not hasattr(self.request, 'event'):
|
if not hasattr(self.request, 'event'):
|
||||||
ctx['basetpl'] = "pretixcontrol/organizers/base.html"
|
ctx['basetpl'] = "pretixcontrol/organizers/base.html"
|
||||||
@@ -764,8 +814,9 @@ class RefundExportListView(ListView):
|
|||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class EventRefundExportListView(EventPermissionRequiredMixin, RefundExportListView):
|
class EventRefundExportListView(PostEventPermissionRequiredMixin, RefundExportListView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:read'
|
||||||
|
write_permission = 'event.orders:write'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('plugins:banktransfer:refunds.list', kwargs={
|
return reverse('plugins:banktransfer:refunds.list', kwargs={
|
||||||
@@ -787,7 +838,9 @@ class EventRefundExportListView(EventPermissionRequiredMixin, RefundExportListVi
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrganizerRefundExportListView(OrganizerBanktransferView, RefundExportListView):
|
class OrganizerRefundExportListView(EventPermissionOnAllEventsRequiredMixin, RefundExportListView):
|
||||||
|
event_permission = 'event.orders:read'
|
||||||
|
write_event_permission = 'event.orders:write'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('plugins:banktransfer:refunds.list', kwargs={
|
return reverse('plugins:banktransfer:refunds.list', kwargs={
|
||||||
@@ -820,7 +873,7 @@ class DownloadRefundExportView(DetailView):
|
|||||||
|
|
||||||
|
|
||||||
class EventDownloadRefundExportView(EventPermissionRequiredMixin, DownloadRefundExportView):
|
class EventDownloadRefundExportView(EventPermissionRequiredMixin, DownloadRefundExportView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:read'
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
return get_object_or_404(
|
return get_object_or_404(
|
||||||
@@ -830,7 +883,8 @@ class EventDownloadRefundExportView(EventPermissionRequiredMixin, DownloadRefund
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrganizerDownloadRefundExportView(OrganizerBanktransferView, OrganizerDetailViewMixin, DownloadRefundExportView):
|
class OrganizerDownloadRefundExportView(EventPermissionOnAllEventsRequiredMixin, OrganizerDetailViewMixin, DownloadRefundExportView):
|
||||||
|
event_permission = 'event.orders:read'
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
return get_object_or_404(
|
return get_object_or_404(
|
||||||
@@ -877,7 +931,7 @@ class SepaXMLExportView(SingleObjectMixin, FormView):
|
|||||||
|
|
||||||
|
|
||||||
class EventSepaXMLExportView(EventPermissionRequiredMixin, SepaXMLExportView):
|
class EventSepaXMLExportView(EventPermissionRequiredMixin, SepaXMLExportView):
|
||||||
permission = 'event.orders:write'
|
permission = 'event.orders:read'
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
return get_object_or_404(
|
return get_object_or_404(
|
||||||
@@ -892,7 +946,8 @@ class EventSepaXMLExportView(EventPermissionRequiredMixin, SepaXMLExportView):
|
|||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
class OrganizerSepaXMLExportView(OrganizerBanktransferView, OrganizerDetailViewMixin, SepaXMLExportView):
|
class OrganizerSepaXMLExportView(EventPermissionOnAllEventsRequiredMixin, OrganizerDetailViewMixin, SepaXMLExportView):
|
||||||
|
permission = 'event.orders:read'
|
||||||
|
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
return get_object_or_404(
|
return get_object_or_404(
|
||||||
|
|||||||
Reference in New Issue
Block a user