Pluggable permissions (#5728)

* Data model draft

* Refactor query and assignment usages of old permissions

* Backend UI

* API serializer

* Big string replace

* Docs, tests and fixes for teams api

* Update docs for device auth

* Eliminate old names

* Make tests pass

* Use new permissions, remove inconsistencies

* Add test for translations

* Show plugin permissions

* Add permission for seating plans

* Fix plugin activation

* Fix failing test

* Refactor to permission groups

* Update doc/api/resources/devices.rst

Co-authored-by: luelista <weller@rami.io>

* Update doc/api/resources/events.rst

Co-authored-by: luelista <weller@rami.io>

* Update src/pretix/api/serializers/organizer.py

Co-authored-by: luelista <weller@rami.io>

* Fix typo

* Fix python version compat

* Replacement after rebase

* Add proper permission handling for exports

* Docs for exporters

* Runtime linting of permission names

* Fix typos

* Show export page even without orders permission

* More legacy compat

* Do not strongly validate before plugins are loaded

* Rebase migration

* Add permission for outgoing mails

* Review notes

* Update doc/api/resources/teams.rst

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

* Clean up logic around exporters

* Review and failures

* Fix migration leading to forbidden combination

* Handle permissions on event copying

* Remove print-statements

* Make test clearer

* Review feedback

* Add AnyPermissionOf

* migration safety

---------

Co-authored-by: luelista <weller@rami.io>
Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
This commit is contained in:
Raphael Michel
2026-03-17 14:43:56 +01:00
committed by GitHub
parent eddde2b6c0
commit df0b580dd6
203 changed files with 5374 additions and 2331 deletions

View File

@@ -213,6 +213,28 @@ class SuperuserPermissionSet:
return True
class EventPermissionSet(set):
def __contains__(self, item):
from pretix.base.permissions import assert_valid_event_permission
if super().__contains__(item):
return True
assert_valid_event_permission(item, allow_tuple=False)
return False
class OrganizerPermissionSet(set):
def __contains__(self, item):
from pretix.base.permissions import assert_valid_organizer_permission
if super().__contains__(item):
return True
assert_valid_organizer_permission(item, allow_tuple=False)
return False
class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
"""
This is the user model used by pretix for authentication.
@@ -473,7 +495,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: set
"""
teams = self._get_teams_for_event(organizer, event)
sets = [t.permission_set() for t in teams]
sets = [t.event_permission_set() for t in teams]
if sets:
return set.union(*sets)
else:
@@ -487,7 +509,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: set
"""
teams = self._get_teams_for_organizer(organizer)
sets = [t.permission_set() for t in teams]
sets = [t.organizer_permission_set() for t in teams]
if sets:
return set.union(*sets)
else:
@@ -502,7 +524,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: The current request (optional)
:param session_key: The current session key (optional)
:return: bool
@@ -514,8 +536,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
if teams:
self._teamcache['e{}'.format(event.pk)] = teams
if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return any([any(team.has_event_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_event_permission(perm_name) for team in teams]):
return True
return False
@@ -525,7 +547,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer.events:create``
:param request: The current request (optional). Required to detect staff sessions properly.
:return: bool
"""
@@ -534,8 +556,8 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
teams = self._get_teams_for_organizer(organizer)
if teams:
if isinstance(perm_name, (tuple, list)):
return any([any(team.has_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_permission(perm_name) for team in teams]):
return any([any(team.has_organizer_permission(p) for team in teams) for p in perm_name])
if not perm_name or any([team.has_organizer_permission(perm_name) for team in teams]):
return True
return False
@@ -566,14 +588,15 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Events
"""
from .event import Event
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key):
return Event.objects.all()
if isinstance(permission, (tuple, list)):
q = reduce(operator.or_, [Q(**{p: True}) for p in permission])
q = reduce(operator.or_, [TeamQuerySet.event_permission_q(p) for p in permission])
else:
q = Q(**{permission: True})
q = TeamQuerySet.event_permission_q(permission)
return Event.objects.filter(
Q(organizer_id__in=self.teams.filter(q, all_events=True).values_list('organizer', flat=True))
@@ -606,14 +629,13 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
:return: Iterable of Organizers
"""
from .event import Organizer
from .organizer import TeamQuerySet
if request and self.has_active_staff_session(request.session.session_key):
return Organizer.objects.all()
kwargs = {permission: True}
return Organizer.objects.filter(
id__in=self.teams.filter(**kwargs).values_list('organizer', flat=True)
id__in=self.teams.filter(TeamQuerySet.organizer_permission_q(permission)).values_list('organizer', flat=True)
)
def has_active_staff_session(self, session_key=None):

View File

@@ -29,6 +29,9 @@ from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
from pretix.base.models import LoggedModel
from pretix.base.permissions import (
AnyPermissionOf, assert_valid_event_permission,
)
@scopes_disabled()
@@ -189,13 +192,19 @@ class Device(LoggedModel):
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
super().save(*args, **kwargs)
def permission_set(self) -> set:
def _event_permission_set(self) -> set:
return {
'can_view_orders',
'can_change_orders',
'can_view_vouchers',
'can_manage_gift_cards',
'can_manage_reusable_media',
'event.orders:read',
'event.orders:write',
'event.vouchers:read',
}
def _organizer_permission_set(self) -> set:
return {
'organizer.giftcards:read',
'organizer.giftcards:write',
'organizer.reusablemedia:read',
'organizer.reusablemedia:write',
}
def get_event_permission_set(self, organizer, event) -> set:
@@ -209,7 +218,7 @@ class Device(LoggedModel):
has_event_access = (self.all_events and organizer == self.organizer) or (
event in self.limit_events.all()
)
return self.permission_set() if has_event_access else set()
return self._event_permission_set() if has_event_access else set()
def get_organizer_permission_set(self, organizer) -> set:
"""
@@ -218,7 +227,7 @@ class Device(LoggedModel):
:param organizer: The organizer of the event
:return: set of permissions
"""
return self.permission_set() if self.organizer == organizer else set()
return self._organizer_permission_set() if self.organizer == organizer else set()
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
"""
@@ -227,7 +236,7 @@ class Device(LoggedModel):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
@@ -235,8 +244,8 @@ class Device(LoggedModel):
event in self.limit_events.all()
)
if isinstance(perm_name, (tuple, list)):
return has_event_access and any(p in self.permission_set() for p in perm_name)
return has_event_access and (not perm_name or perm_name in self.permission_set())
return has_event_access and any(p in self._event_permission_set() for p in perm_name)
return has_event_access and (not perm_name or perm_name in self._event_permission_set())
def has_organizer_permission(self, organizer, perm_name=None, request=None):
"""
@@ -244,13 +253,13 @@ class Device(LoggedModel):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer.events:create``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
if isinstance(perm_name, (tuple, list)):
return organizer == self.organizer and any(p in self.permission_set() for p in perm_name)
return organizer == self.organizer and (not perm_name or perm_name in self.permission_set())
return organizer == self.organizer and any(p in self._organizer_permission_set() for p in perm_name)
return organizer == self.organizer and (not perm_name or perm_name in self._organizer_permission_set())
def get_events_with_any_permission(self):
"""
@@ -270,9 +279,10 @@ class Device(LoggedModel):
:param request: Ignored, for compatibility with User model
:return: Iterable of Events
"""
assert_valid_event_permission(permission)
if (
isinstance(permission, (list, tuple)) and any(p in self.permission_set() for p in permission)
) or (isinstance(permission, str) and permission in self.permission_set()):
isinstance(permission, (AnyPermissionOf, list, tuple)) and any(p in self._event_permission_set() for p in permission)
) or (isinstance(permission, str) and permission in self._event_permission_set()):
return self.get_events_with_any_permission()
else:
return self.organizer.events.none()

View File

@@ -843,6 +843,33 @@ class Event(EventMixin, LoggedModel):
time(hour=23, minute=59, second=59)
), tz)
def allow_copy_data(self, new_organizer, auth) -> bool:
"""
Returns whether it is allowed to copy the event to the target organizer. Auth can be TeamAPIToken or User.
"""
from ..permissions import get_all_event_permissions
from .auth import User
if self.organizer == new_organizer:
# Copying in the same organizer is always okay with any read access, we just need to ensure it does not
# grant more permissions than I had before, but that is handled by the view logic
return auth.has_event_permission(self.organizer, self, None)
if isinstance(auth, User):
# Cross-organizer copying requires almost full permission of source to prevent settings extraction
required_permissions = get_all_event_permissions() - {
# We do not require these, as this data is not copied
"event.orders:read", "event.orders:write", "event.vouchers:read", "event.vouchers:write",
"event.subevents:write",
}
given_permission = auth.get_event_permission_set(self.organizer, self)
return all(p in given_permission for p in required_permissions if ":" in p)
else:
# Tokens or devices can never copy between organizers, as they are organizer-bound. Kept for future
# compatibility and easier calling
return False
def copy_data_from(self, other, skip_meta_data=False):
from ..signals import event_copy_data
from . import (
@@ -1386,14 +1413,13 @@ class Event(EventMixin, LoggedModel):
from .auth import User
if permission:
kwargs = {permission: True}
qs = Team.objects.with_event_permission(permission)
else:
kwargs = {}
qs = Team.objects.all()
team_with_perm = Team.objects.filter(
team_with_perm = qs.filter(
members__pk=OuterRef('pk'),
organizer=self.organizer,
**kwargs
).filter(
Q(all_events=True) | Q(limit_events__pk=self.pk)
)

View File

@@ -31,9 +31,10 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import operator
import string
from datetime import date, datetime, time
from functools import reduce
import pytz_deprecation_shim
from django.conf import settings
@@ -53,6 +54,10 @@ from i18nfield.strings import LazyI18nString
from pretix.base.models.base import LoggedModel
from pretix.base.validators import OrganizerSlugBanlistValidator
from ...helpers.permission_migration import (
OLD_TO_NEW_EVENT_COMPAT, OLD_TO_NEW_ORGANIZER_COMPAT,
LegacyPermissionProperty,
)
from ..settings import settings_hierarkey
from .auth import User
@@ -309,6 +314,38 @@ def generate_api_token():
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
class TeamQuerySet(models.QuerySet):
@classmethod
def event_permission_q(cls, perm_name):
from ..permissions import assert_valid_event_permission
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_EVENT_COMPAT: # legacy
return reduce(operator.and_, [cls.event_permission_q(p) for p in OLD_TO_NEW_EVENT_COMPAT[perm_name]])
assert_valid_event_permission(perm_name, allow_legacy=False)
return (
Q(all_event_permissions=True) |
Q(**{f'limit_event_permissions__{perm_name}': True})
)
@classmethod
def organizer_permission_q(cls, perm_name):
from ..permissions import assert_valid_organizer_permission
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_ORGANIZER_COMPAT: # legacy
return reduce(operator.and_, [cls.organizer_permission_q(p) for p in OLD_TO_NEW_ORGANIZER_COMPAT[perm_name]])
assert_valid_organizer_permission(perm_name, allow_legacy=False)
return (
Q(all_organizer_permissions=True) |
Q(**{f'limit_organizer_permissions__{perm_name}': True})
)
def with_event_permission(self, perm_name):
return self.filter(self.event_permission_q(perm_name))
def with_organizer_permission(self, perm_name):
return self.filter(self.organizer_permission_q(perm_name))
class Team(LoggedModel):
"""
A team is a collection of people given certain access rights to one or more events of an organizer.
@@ -321,36 +358,10 @@ class Team(LoggedModel):
:param all_events: Whether this team has access to all events of this organizer
:type all_events: bool
:param limit_events: A set of events this team has access to. Irrelevant if ``all_events`` is ``True``.
:param can_create_events: Whether or not the members can create new events with this organizer account.
:type can_create_events: bool
:param can_change_teams: If ``True``, the members can change the teams of this organizer account.
:type can_change_teams: bool
:param can_manage_customers: If ``True``, the members can view and change organizer-level customer accounts.
:type can_manage_customers: bool
:param can_manage_reusable_media: If ``True``, the members can view and change organizer-level reusable media.
:type can_manage_reusable_media: bool
:param can_change_organizer_settings: If ``True``, the members can change the settings of this organizer account.
:type can_change_organizer_settings: bool
:param can_change_event_settings: If ``True``, the members can change the settings of the associated events.
:type can_change_event_settings: bool
:param can_change_items: If ``True``, the members can change and add items and related objects for the associated events.
:type can_change_items: bool
:param can_view_orders: If ``True``, the members can inspect details of all orders of the associated events.
:type can_view_orders: bool
:param can_change_orders: If ``True``, the members can change details of orders of the associated events.
:type can_change_orders: bool
:param can_checkin_orders: If ``True``, the members can perform check-in related actions.
:type can_checkin_orders: bool
:param can_view_vouchers: If ``True``, the members can inspect details of all vouchers of the associated events.
:type can_view_vouchers: bool
:param can_change_vouchers: If ``True``, the members can change and create vouchers for the associated events.
:type can_change_vouchers: bool
"""
organizer = models.ForeignKey(Organizer, related_name="teams", on_delete=models.CASCADE)
name = models.CharField(max_length=190, verbose_name=_("Team name"))
members = models.ManyToManyField(User, related_name="teams", verbose_name=_("Team members"))
all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
require_2fa = models.BooleanField(
default=False, verbose_name=_("Require all members of this team to use two-factor authentication"),
help_text=_("If you turn this on, all members of the team will be required to either set up two-factor "
@@ -358,62 +369,33 @@ class Team(LoggedModel):
"all users.")
)
can_create_events = models.BooleanField(
default=False,
verbose_name=_("Can create events"),
)
can_change_teams = models.BooleanField(
default=False,
verbose_name=_("Can change teams and permissions"),
)
can_change_organizer_settings = models.BooleanField(
default=False,
verbose_name=_("Can change organizer settings"),
help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy '
'reports, so be careful who you add to this team!')
)
can_manage_customers = models.BooleanField(
default=False,
verbose_name=_("Can manage customer accounts")
)
can_manage_reusable_media = models.BooleanField(
default=False,
verbose_name=_("Can manage reusable media")
)
can_manage_gift_cards = models.BooleanField(
default=False,
verbose_name=_("Can manage gift cards")
)
can_change_event_settings = models.BooleanField(
default=False,
verbose_name=_("Can change event settings")
)
can_change_items = models.BooleanField(
default=False,
verbose_name=_("Can change product settings")
)
can_view_orders = models.BooleanField(
default=False,
verbose_name=_("Can view orders")
)
can_change_orders = models.BooleanField(
default=False,
verbose_name=_("Can change orders")
)
can_checkin_orders = models.BooleanField(
default=False,
verbose_name=_("Can perform check-ins"),
help_text=_('This includes searching for attendees, which can be used to obtain personal information about '
'attendees. Users with "can change orders" can also perform check-ins.')
)
can_view_vouchers = models.BooleanField(
default=False,
verbose_name=_("Can view vouchers")
)
can_change_vouchers = models.BooleanField(
default=False,
verbose_name=_("Can change vouchers")
)
# Scope
all_events = models.BooleanField(default=False, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('Event', verbose_name=_("Limit to events"), blank=True)
# Permissions
# We store them as {key: True} instead of [key] because otherwise not all lookups we need are supported on SQLite
all_event_permissions = models.BooleanField(default=False, verbose_name=_("All event permissions"))
limit_event_permissions = models.JSONField(default=dict, verbose_name=_("Event permissions"))
all_organizer_permissions = models.BooleanField(default=False, verbose_name=_("All organizer permissions"))
limit_organizer_permissions = models.JSONField(default=dict, verbose_name=_("Organizer permissions"))
# Legacy lookups for plugin compatibility
can_change_event_settings = LegacyPermissionProperty()
can_change_items = LegacyPermissionProperty()
can_view_orders = LegacyPermissionProperty()
can_change_orders = LegacyPermissionProperty()
can_checkin_orders = LegacyPermissionProperty()
can_view_vouchers = LegacyPermissionProperty()
can_change_vouchers = LegacyPermissionProperty()
can_create_events = LegacyPermissionProperty()
can_change_organizer_settings = LegacyPermissionProperty()
can_change_teams = LegacyPermissionProperty()
can_manage_gift_cards = LegacyPermissionProperty()
can_manage_customers = LegacyPermissionProperty()
can_manage_reusable_media = LegacyPermissionProperty()
objects = TeamQuerySet.as_manager()
def __str__(self) -> str:
return _("%(name)s on %(object)s") % {
@@ -421,21 +403,62 @@ class Team(LoggedModel):
'object': str(self.organizer),
}
def permission_set(self) -> set:
attribs = dir(self)
return {
a for a in attribs if a.startswith('can_') and self.has_permission(a)
}
def event_permission_set(self, include_legacy=True) -> set:
from ..permissions import get_all_event_permission_groups
result = set()
for pg in get_all_event_permission_groups().values():
for action in pg.actions:
if self.all_event_permissions or self.limit_event_permissions.get(f"{pg.name}:{action}"):
result.add(f"{pg.name}:{action}")
if include_legacy:
# Add legacy permissions as well for plugin compatibility
for k, v in OLD_TO_NEW_EVENT_COMPAT.items():
if self.all_event_permissions or all(self.limit_event_permissions.get(kk) for kk in v):
result.add(k)
if "can_change_event_settings" in result:
result.add("can_change_settings")
return result
def organizer_permission_set(self, include_legacy=True) -> set:
from ..permissions import get_all_organizer_permission_groups
result = set()
for pg in get_all_organizer_permission_groups().values():
for action in pg.actions:
if self.all_organizer_permissions or self.limit_organizer_permissions.get(f"{pg.name}:{action}"):
result.add(f"{pg.name}:{action}")
if include_legacy:
# Add legacy permissions as well for plugin compatibility
for k, v in OLD_TO_NEW_ORGANIZER_COMPAT.items():
if self.all_organizer_permissions or all(self.limit_organizer_permissions.get(kk) for kk in v):
result.add(k)
return result
@property
def can_change_settings(self): # Legacy compatiblilty
def can_change_settings(self): # Legacy compatibility
return self.can_change_event_settings
def has_permission(self, perm_name):
try:
def has_event_permission(self, perm_name):
from ..permissions import assert_valid_event_permission
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name)
except AttributeError:
raise ValueError('Invalid required permission: %s' % perm_name)
assert_valid_event_permission(perm_name, allow_legacy=False)
return self.all_event_permissions or self.limit_event_permissions.get(perm_name, False)
def has_organizer_permission(self, perm_name):
from ..permissions import assert_valid_organizer_permission
if perm_name.startswith('can_') and hasattr(self, perm_name): # legacy
return getattr(self, perm_name)
assert_valid_organizer_permission(perm_name, allow_legacy=False)
return self.all_organizer_permissions or self.limit_organizer_permissions.get(perm_name, False)
def permission_for_event(self, event):
if self.all_events:
@@ -447,6 +470,19 @@ class Team(LoggedModel):
def active_tokens(self):
return self.tokens.filter(active=True)
def save(self, **kwargs):
if not isinstance(self.limit_event_permissions, dict):
raise TypeError("Permissions must be a dictionary")
if not isinstance(self.limit_organizer_permissions, dict):
raise TypeError("Permissions must be a dictionary")
for k in self.limit_event_permissions.values():
if k is not True:
raise TypeError("Permissions must only contain True values")
for k in self.limit_organizer_permissions.values():
if k is not True:
raise TypeError("Permissions must only contain True values")
return super().save(**kwargs)
class Meta:
verbose_name = _("Team")
verbose_name_plural = _("Teams")
@@ -503,7 +539,7 @@ class TeamAPIToken(models.Model):
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
event in self.team.limit_events.all()
)
return self.team.permission_set() if has_event_access else set()
return self.team.event_permission_set() if has_event_access else set()
def get_organizer_permission_set(self, organizer) -> set:
"""
@@ -512,7 +548,7 @@ class TeamAPIToken(models.Model):
:param organizer: The organizer of the event
:return: set of permissions
"""
return self.team.permission_set() if self.team.organizer == organizer else set()
return self.team.organizer_permission_set() if self.team.organizer == organizer else set()
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
"""
@@ -521,7 +557,7 @@ class TeamAPIToken(models.Model):
:param organizer: The organizer of the event
:param event: The event to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``event.orders:read``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
@@ -529,8 +565,8 @@ class TeamAPIToken(models.Model):
event in self.team.limit_events.all()
)
if isinstance(perm_name, (tuple, list)):
return has_event_access and any(self.team.has_permission(p) for p in perm_name)
return has_event_access and (not perm_name or self.team.has_permission(perm_name))
return has_event_access and any(self.team.has_event_permission(p) for p in perm_name)
return has_event_access and (not perm_name or self.team.has_event_permission(perm_name))
def has_organizer_permission(self, organizer, perm_name=None, request=None):
"""
@@ -538,13 +574,13 @@ class TeamAPIToken(models.Model):
to the organizer ``organizer``.
:param organizer: The organizer to check
:param perm_name: The permission, e.g. ``can_change_teams``
:param perm_name: The permission, e.g. ``organizer.events:create``
:param request: This parameter is ignored and only defined for compatibility reasons.
:return: bool
"""
if isinstance(perm_name, (tuple, list)):
return organizer == self.team.organizer and any(self.team.has_permission(p) for p in perm_name)
return organizer == self.team.organizer and (not perm_name or self.team.has_permission(perm_name))
return organizer == self.team.organizer and any(self.team.has_organizer_permission(p) for p in perm_name)
return organizer == self.team.organizer and (not perm_name or self.team.has_organizer_permission(perm_name))
def get_events_with_any_permission(self):
"""
@@ -564,9 +600,11 @@ class TeamAPIToken(models.Model):
:param request: Ignored, for compatibility with User model
:return: Iterable of Events
"""
from pretix.base.permissions import AnyPermissionOf
if (
isinstance(permission, (list, tuple)) and any(getattr(self.team, p, False) for p in permission)
) or (isinstance(permission, str) and getattr(self.team, permission, False)):
isinstance(permission, (AnyPermissionOf, list, tuple)) and any(self.team.has_event_permission(p) for p in permission)
) or (isinstance(permission, str) and self.team.has_event_permission(permission)):
return self.get_events_with_any_permission()
else:
return self.team.organizer.events.none()