Use new permissions, remove inconsistencies

This commit is contained in:
Raphael Michel
2026-01-12 18:30:39 +01:00
parent e60d10217a
commit e53e70b174
61 changed files with 1214 additions and 780 deletions

View File

@@ -707,7 +707,10 @@ class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class EventSettingsSerializer(SettingsSerializer):
default_write_permission = 'event.settings.general:write'
default_fields = [
# These are readable for all users with access to the events, therefore secrets made in the settings store
# should not be included!
'imprint_url',
'checkout_email_helptext',
'presale_has_ended_text',

View File

@@ -24,7 +24,7 @@ from decimal import Decimal
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import OrderPositionSerializer
@@ -66,6 +66,9 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
super().__init__(*args, **kwargs)
if 'linked_giftcard' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_giftcards"]:
raise PermissionDenied("No permission to access gift card details.")
self.fields['linked_giftcard'] = NestedGiftCardSerializer(read_only=True, context=self.context)
if 'linked_giftcard.owner_ticket' in self.context['request'].query_params.getlist('expand'):
self.fields['linked_giftcard'].fields['owner_ticket'] = NestedOrderPositionSerializer(read_only=True, context=self.context)
@@ -77,6 +80,8 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
)
if 'linked_orderposition' in self.context['request'].query_params.getlist('expand'):
# No additional permission check performed, documented limitation of the permission system
# Would get to complex/unusable otherwise since the permission depends on the event
self.fields['linked_orderposition'] = NestedOrderPositionSerializer(read_only=True)
else:
self.fields['linked_orderposition'] = serializers.PrimaryKeyRelatedField(
@@ -86,6 +91,9 @@ class ReusableMediaSerializer(I18nAwareModelSerializer):
)
if 'customer' in self.context['request'].query_params.getlist('expand'):
if not self.context["can_read_customers"]:
raise PermissionDenied("No permission to access customer details.")
self.fields['customer'] = CustomerSerializer(read_only=True)
else:
self.fields['customer'] = serializers.SlugRelatedField(

View File

@@ -422,7 +422,7 @@ class DeviceSerializer(serializers.ModelSerializer):
created = serializers.DateTimeField(read_only=True)
revoked = serializers.BooleanField(read_only=True)
initialized = serializers.DateTimeField(read_only=True)
initialization_token = serializers.DateTimeField(read_only=True)
initialization_token = serializers.CharField(read_only=True)
security_profile = serializers.ChoiceField(choices=[], required=False, default="full")
class Meta:
@@ -436,6 +436,8 @@ class DeviceSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['security_profile'].choices = [(k, v.verbose_name) for k, v in get_all_security_profiles().items()]
if not self.context['can_see_tokens']:
del self.fields['initialization_token']
class TeamInviteSerializer(serializers.ModelSerializer):
@@ -519,7 +521,10 @@ class TeamMemberSerializer(serializers.ModelSerializer):
class OrganizerSettingsSerializer(SettingsSerializer):
default_write_permission = 'organizer.settings.general:write'
default_fields = [
# These are readable for all users with access to the events, therefore secrets made in the settings store
# should not be included!
'customer_accounts',
'customer_accounts_native',
'customer_accounts_link_by_email',

View File

@@ -37,6 +37,8 @@ logger = logging.getLogger(__name__)
class SettingsSerializer(serializers.Serializer):
default_fields = []
readonly_fields = []
default_write_permission = 'organizer.settings.general:write'
write_permission_required = {}
def __init__(self, *args, **kwargs):
self.changed_data = []
@@ -58,9 +60,17 @@ class SettingsSerializer(serializers.Serializer):
f._label = str(form_kwargs.get('label', fname))
f._help_text = str(form_kwargs.get('help_text'))
f.parent = self
self.write_permission_required[fname] = DEFAULTS[fname].get('write_permission', self.default_write_permission)
self.fields[fname] = f
def validate(self, attrs):
for k in attrs.keys():
p = self.write_permission_required.get(k, self.default_write_permission)
if p not in self.context["permissions"]:
raise ValidationError({k: f"Setting this field requires permission {p}"})
return {k: v for k, v in attrs.items() if k not in self.readonly_fields}
def update(self, instance: HierarkeyProxy, validated_data):

View File

@@ -432,7 +432,7 @@ with scopes_disabled():
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer
queryset = SubEvent.objects.none()
write_permission = 'event.settings.general:write'
write_permission = 'event.subevents:write'
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
ordering = ('date_from',)
ordering_fields = ('id', 'date_from', 'last_modified')
@@ -552,7 +552,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = TaxRuleSerializer
queryset = TaxRule.objects.none()
write_permission = 'event.settings.general:write'
write_permission = 'event.settings.tax:write'
def get_queryset(self):
return self.request.event.tax_rules.all()
@@ -647,14 +647,13 @@ class EventSettingsView(views.APIView):
def get(self, request, *args, **kwargs):
if isinstance(request.auth, Device):
s = DeviceEventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request
})
elif 'event.settings.general:write' in request.eventpermset:
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request
'request': request, 'permissions': request.eventpermset
})
else:
raise PermissionDenied()
s = EventSettingsSerializer(instance=request.event.settings, event=request.event, context={
'request': request, 'permissions': request.eventpermset,
})
if 'explain' in request.GET:
return Response({
fname: {
@@ -668,7 +667,7 @@ class EventSettingsView(views.APIView):
def patch(self, request, *wargs, **kwargs):
s = EventSettingsSerializer(instance=request.event.settings, data=request.data, partial=True,
event=request.event, context={'request': request})
event=request.event, context={'request': request, 'permissions': request.eventpermset})
s.is_valid(raise_exception=True)
with transaction.atomic():
s.save()
@@ -680,7 +679,7 @@ class EventSettingsView(views.APIView):
)
s = EventSettingsSerializer(
instance=request.event.settings, event=request.event, context={
'request': request
'request': request, 'permissions': request.eventpermset
})
return Response(s.data)

View File

@@ -95,6 +95,8 @@ class ReusableMediaViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
ctx['can_read_giftcards'] = 'organizer.giftcards:read' in self.request.orgapermset
ctx['can_read_customers'] = 'organizer.customers:read' in self.request.orgapermset
return ctx
@transaction.atomic()

View File

@@ -221,7 +221,7 @@ with scopes_disabled():
class GiftCardViewSet(viewsets.ModelViewSet):
serializer_class = GiftCardSerializer
queryset = GiftCard.objects.none()
permission = 'organizer.giftcards:write'
permission = 'organizer.giftcards:read'
write_permission = 'organizer.giftcards:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = GiftCardFilter
@@ -344,7 +344,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
class GiftCardTransactionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = GiftCardTransactionSerializer
queryset = GiftCardTransaction.objects.none()
permission = 'organizer.giftcards:write'
permission = 'organizer.giftcards:read'
write_permission = 'organizer.giftcards:write'
@cached_property
@@ -532,8 +532,8 @@ class DeviceViewSet(mixins.CreateModelMixin,
GenericViewSet):
serializer_class = DeviceSerializer
queryset = Device.objects.none()
permission = 'organizer.settings.general:write'
write_permission = 'organizer.settings.general:write'
permission = 'organizer.devices:read'
write_permission = 'organizer.devices:write'
lookup_field = 'device_id'
def get_queryset(self):
@@ -542,6 +542,9 @@ class DeviceViewSet(mixins.CreateModelMixin,
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
ctx['can_see_tokens'] = (
self.request.user if self.request.user and self.request.user.is_authenticated else self.request.auth
).has_organizer_permission(self.request.organizer, 'organizer.devices:write', request=self.request)
return ctx
@transaction.atomic()
@@ -572,7 +575,7 @@ class OrganizerSettingsView(views.APIView):
def get(self, request, *args, **kwargs):
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
'request': request
'request': request, 'permissions': request.orgapermset
})
if 'explain' in request.GET:
return Response({
@@ -589,7 +592,7 @@ class OrganizerSettingsView(views.APIView):
s = OrganizerSettingsSerializer(
instance=request.organizer.settings, data=request.data, partial=True,
organizer=request.organizer, context={
'request': request
'request': request, 'permissions': request.orgapermset
}
)
s.is_valid(raise_exception=True)
@@ -601,7 +604,7 @@ class OrganizerSettingsView(views.APIView):
}
)
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
'request': request
'request': request, 'permissions': request.orgapermset
})
return Response(s.data)
@@ -618,7 +621,8 @@ with scopes_disabled():
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.none()
permission = 'organizer.customers:write'
permission = 'organizer.customers:read'
write_permission = 'organizer.customers:write'
lookup_field = 'identifier'
filter_backends = (DjangoFilterBackend,)
filterset_class = CustomerFilter
@@ -735,7 +739,8 @@ with scopes_disabled():
class MembershipViewSet(viewsets.ModelViewSet):
serializer_class = MembershipSerializer
queryset = Membership.objects.none()
permission = 'organizer.customers:write'
permission = 'organizer.customers:read'
write_permission = 'organizer.customers:write'
filter_backends = (DjangoFilterBackend,)
filterset_class = MembershipFilter