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

@@ -106,17 +106,8 @@ def team(organizer):
return Team.objects.create(
organizer=organizer,
name="Test-Team",
can_change_teams=True,
can_manage_gift_cards=True,
can_change_items=True,
can_create_events=True,
can_change_event_settings=True,
can_change_vouchers=True,
can_view_vouchers=True,
can_change_orders=True,
can_manage_customers=True,
can_manage_reusable_media=True,
can_change_organizer_settings=True
all_event_permissions=True,
all_organizer_permissions=True,
)
@@ -140,8 +131,9 @@ def user():
@pytest.fixture
@scopes_disabled()
def user_client(client, team, user):
team.can_view_orders = True
team.can_view_vouchers = True
if not team.all_event_permissions:
team.limit_event_permissions["event.orders:read"] = True
team.limit_event_permissions["event.vouchers:read"] = True
team.all_events = True
team.save()
team.members.add(user)
@@ -152,8 +144,9 @@ def user_client(client, team, user):
@pytest.fixture
@scopes_disabled()
def token_client(client, team):
team.can_view_orders = True
team.can_view_vouchers = True
if not team.all_event_permissions:
team.limit_event_permissions["event.orders:read"] = True
team.limit_event_permissions["event.vouchers:read"] = True
team.all_events = True
team.save()
t = team.tokens.create(name='Foo')

View File

@@ -1382,9 +1382,8 @@ def test_checkin_pdf_data_requires_permission(token_client, event, team, organiz
))
assert resp.data['results'][0].get('pdf_data')
with scopes_disabled():
team.can_view_orders = False
team.can_change_orders = False
team.can_checkin_orders = True
team.limit_event_permissions = {"event.orders:checkin": True}
team.all_event_permissions = False
team.save()
resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?search=z3fsn8jyu&pdf_data=true'.format(
organizer.slug, event.slug, clist_all.pk

View File

@@ -984,9 +984,8 @@ def test_search_multiple_lists(token_client, organizer, clist_all, clist_event2,
@pytest.mark.django_db
def test_without_permission(token_client, event, team, organizer, clist_all, order):
with scopes_disabled():
team.can_view_orders = False
team.can_change_orders = False
team.can_checkin_orders = False
team.limit_event_permissions = {}
team.all_event_permissions = False
team.save()
resp = token_client.get(
'/api/v1/organizers/{}/checkinrpc/search/?list={}&search=dummy.test&ordering=attendee_name'.format(organizer.slug, clist_all.pk))
@@ -1043,9 +1042,8 @@ def test_checkin_only_permission(token_client, event, team, organizer, clist_all
assert resp.data['position'].get('pdf_data')
with scopes_disabled():
team.can_view_orders = False
team.can_change_orders = False
team.can_checkin_orders = True
team.limit_event_permissions = {"event.orders:checkin": True}
team.all_event_permissions = False
team.save()
# With limited permissions, I can not search with a 2-character query

View File

@@ -243,7 +243,8 @@ def test_event_create(team, token_client, organizer, event, meta_prop):
{"key": "Workshop", "label": {"en": "Workshop"}},
]
meta_prop.save()
team.can_change_organizer_settings = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.save()
organizer.meta_properties.create(
name="protected", protected=True
@@ -576,21 +577,15 @@ def test_event_create_with_clone_unknown_source(user, user_client, organizer, ev
@pytest.mark.django_db
def test_event_create_with_clone_across_organizers(user, user_client, organizer, event, taxrule):
def test_event_create_with_clone_across_organizers(user, user_client, organizer, event, taxrule, team):
team.all_event_permissions = True
team.save()
with scopes_disabled():
target_org = Organizer.objects.create(name='Dummy', slug='dummy2')
team = target_org.teams.create(
name="Test-Team",
can_change_teams=True,
can_manage_gift_cards=True,
can_change_items=True,
can_create_events=True,
can_change_event_settings=True,
can_change_vouchers=True,
can_view_vouchers=True,
can_change_orders=True,
can_manage_customers=True,
can_change_organizer_settings=True
all_event_permissions=True,
all_organizer_permissions=True,
)
team.members.add(user)
@@ -629,6 +624,51 @@ def test_event_create_with_clone_across_organizers(user, user_client, organizer,
assert cloned_event.tax_rules.exists()
@pytest.mark.django_db
def test_event_create_with_clone_across_organizers_lack_of_permission_on_source(user, user_client, team, organizer, event, taxrule):
team.all_event_permissions = False
team.limit_event_permissions = {
"event.settings.general:write": True,
}
team.save()
with scopes_disabled():
target_org = Organizer.objects.create(name='Dummy', slug='dummy2')
team = target_org.teams.create(
name="Test-Team",
all_event_permissions=True,
all_organizer_permissions=True,
)
team.members.add(user)
resp = user_client.post(
'/api/v1/organizers/{}/events/?clone_from={}/{}'.format(target_org.slug, organizer.slug, event.slug),
{
"name": {
"de": "Demo Konference 2020 Test",
"en": "Demo Conference 2020 Test"
},
"live": False,
"testmode": True,
"currency": "EUR",
"date_from": "2018-12-27T10:00:00Z",
"date_to": "2018-12-28T10:00:00Z",
"date_admission": None,
"is_public": False,
"presale_start": None,
"presale_end": None,
"location": None,
"slug": "2030",
"plugins": [
"pretix.plugins.ticketoutputpdf"
],
"timezone": "Europe/Vienna"
},
format='json'
)
assert resp.status_code == 403
assert resp.data["detail"] == "Not sufficient permission on source event to copy"
@pytest.mark.django_db
def test_event_put_with_clone(token_client, organizer, event, meta_prop):
resp = token_client.put(
@@ -1394,7 +1434,14 @@ def test_get_event_settings(token_client, organizer, event):
@pytest.mark.django_db
def test_patch_event_settings(token_client, organizer, event):
def test_patch_event_settings(token_client, organizer, event, team):
team.all_event_permissions = False
team.limit_event_permissions = {
"event.settings.general:write": True,
"event.settings.tax:write": True,
}
team.save()
organizer.settings.imprint_url = 'https://example.org'
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
@@ -1510,6 +1557,29 @@ def test_patch_event_settings(token_client, organizer, event):
event.settings.flush()
assert set(event.settings.locales) == set(locales)
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
{
'display_net_prices': True,
},
format='json'
)
assert resp.status_code == 200
event.settings.flush()
assert event.settings.display_net_prices
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/settings/'.format(organizer.slug, event.slug),
{
'invoice_address_asked': False,
},
format='json'
)
assert resp.status_code == 400
assert resp.data == {
'invoice_address_asked': ['Setting this field requires permission event.settings.invoicing:write']
}
@pytest.mark.django_db
def test_patch_event_settings_validation(token_client, organizer, event):

View File

@@ -39,8 +39,11 @@ from datetime import time
import pytest
from django.utils.timezone import now
from rest_framework.test import APIClient
from pretix.base.models import CachedFile, User
from pretix.base.models import (
CachedFile, Event, ScheduledEventExport, ScheduledOrganizerExport, User,
)
SAMPLE_EXPORTER_CONFIG = {
"identifier": "orderlist",
@@ -111,6 +114,10 @@ def test_org_list(token_client, organizer, event):
"name": "events",
"required": False
})
c['input_parameters'].insert(0, {
"name": "all_events",
"required": False
})
c['input_parameters'].remove({
"name": "items",
"required": False
@@ -144,13 +151,6 @@ def test_org_validate_events(token_client, organizer, team, event):
}, format='json')
assert resp.status_code == 202
resp = token_client.post('/api/v1/organizers/{}/exporters/orderlist/run/'.format(organizer.slug), data={
'_format': 'xlsx',
'events': []
}, format='json')
assert resp.status_code == 400
assert resp.data == {"events": ["This list may not be empty."]}
resp = token_client.post('/api/v1/organizers/{}/exporters/orderlist/run/'.format(organizer.slug), data={
'_format': 'xlsx',
'events': ["nonexisting"]
@@ -280,7 +280,8 @@ def test_org_level_export(token_client, organizer, team, event):
}, format='json')
assert resp.status_code == 202
team.can_manage_gift_cards = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.save()
resp = token_client.post('/api/v1/organizers/{}/exporters/giftcardlist/run/'.format(organizer.slug), data={
@@ -339,10 +340,12 @@ def test_event_scheduled_export_list_token(token_client, organizer, event, user,
assert resp.status_code == 200
assert [res] == resp.data['results']
team.can_change_event_settings = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.all_event_permissions = False
team.save()
# Token can no longer sees it an gets error message
# Token can no longer sees it and gets error message
resp = token_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert resp.status_code == 403
@@ -361,7 +364,9 @@ def test_event_scheduled_export_list_user(user_client, organizer, event, user, t
resp = user_client.get('/api/v1/organizers/{}/events/{}/scheduled_exports/'.format(organizer.slug, event.slug))
assert [res] == resp.data['results']
team.can_change_event_settings = False
team.all_organizer_permissions = False
team.limit_event_permissions = {"event.orders:read": True}
team.all_event_permissions = False
team.save()
# Owner still can
@@ -498,7 +503,8 @@ def test_org_scheduled_export_list_token(token_client, organizer, user, team, or
assert resp.status_code == 200
assert [res] == resp.data['results']
team.can_change_organizer_settings = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.save()
# Token can no longer sees it an gets error message
@@ -521,7 +527,8 @@ def test_org_scheduled_export_list_user(user_client, organizer, user, team, org_
resp = user_client.get('/api/v1/organizers/{}/scheduled_exports/'.format(organizer.slug))
assert [res] == resp.data['results']
team.can_change_organizer_settings = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.save()
# Owner still can
@@ -817,3 +824,254 @@ def test_org_scheduled_export_validate_rrule(user_client, organizer, user):
)
assert resp.status_code == 400
assert resp.data == {"schedule_rrule": ["BYEASTER not supported"]}
def _get_and_patch_org_export(client, scheduled, can_see=True, can_edit=None):
if can_edit is None:
can_edit = can_see
response = client.get(
'/api/v1/organizers/{}/scheduled_exports/{}/'.format("dummy", scheduled.pk),
)
if can_see:
assert response.status_code == 200
else:
assert response.status_code > 400
assert can_edit is False # Check against useless test usage
return True # No point in editing, we don't have a body
response = client.patch(
'/api/v1/organizers/{}/scheduled_exports/{}/'.format("dummy", scheduled.pk),
data=response.data,
format='json',
)
if can_edit:
assert response.status_code == 200
else:
assert response.status_code > 400 or (response.status_code == 400 and "export_identifier" in response.data)
return True
@pytest.mark.django_db(transaction=True)
def test_organizer_edit_restrictions(client, event, organizer, user, team):
# This tests the prevention of a possible privilege escalation where user A creates a scheduled export and
# user B has settings permission (= they can see the export configuration), but not enough permission
# to run the export themselves. Without this check, user B could modify the export and add themselves
# as a recipient. Thereby, user B would gain access to data they can't have.
user1_client = APIClient()
user1_client.force_authenticate(user=user)
user2 = User.objects.create_user("dummy2@dummy.dummy", "dummy")
user2_client = APIClient()
user2_client.force_authenticate(user=user2)
team1_client = APIClient()
t = team.tokens.create(name='Foo')
team1_client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
event1 = event
event2 = Event.objects.create(
organizer=organizer, name="Dummy", slug="dummy2",
date_from=now(), plugins="pretix.plugins.banktransfer,pretix.plugins.stripe,tests.testdummy"
)
team1 = team
team1.all_organizer_permissions = False
team1.all_event_permissions = False
team1.all_events = False
team1.limit_organizer_permissions = {"organizer.settings.general:write": True}
team1.limit_event_permissions = {"event.orders:read": True, "event.settings.general:write": True}
team1.save()
team1.limit_events.add(event1)
team1.members.add(user)
t = team.tokens.create(name='Foo')
client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
team2 = organizer.teams.create(
all_organizer_permissions=False, all_event_permissions=False, all_events=False,
limit_event_permissions={"event.orders:read": True},
limit_organizer_permissions={"organizer.giftcards:read": True}
)
team2.limit_events.add(event2)
team2.members.add(user2)
# Scenario 1
# User 2 created an export for all events. User 2 can edit it, because they own it.
# User 1 can see it, because they have permission to see scheduled exports, but can't change it, because they
# don't have access to all events.
s1 = ScheduledOrganizerExport.objects.create(
organizer=organizer,
owner=user2,
export_identifier="dummy_orders",
export_form_data={"all_events": True, "events": []},
mail_subject="Test",
mail_template="Test",
locale="en",
schedule_rrule="DTSTART:20230118T000000\nRRULE:FREQ=DAILY;INTERVAL=1;WKST=MO",
schedule_rrule_time=time(2, 30, 0)
)
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_org_export(user2_client, s1)
assert _get_and_patch_org_export(user1_client, s1, can_see=True, can_edit=False)
assert _get_and_patch_org_export(team1_client, s1, can_see=True, can_edit=False)
# Scenario 2
# User 2 created an export for all events. User 2 can edit it, because they own it.
# User 1 can see it, because they have permission to see scheduled exports, and change it, because they
# have access to all events.
team1.all_events = True
team1.save()
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_org_export(user2_client, s1)
assert _get_and_patch_org_export(user1_client, s1)
assert _get_and_patch_org_export(team1_client, s1)
# Scenario 3
# User 2 created an export for a specific event. User 2 can edit it, because they own it.
# User 1 can see it, because they have permission to see scheduled exports, but can't change it, because they
# don't have access to that event.
team1.all_events = False
team1.save()
s1.export_form_data = {"all_events": False, "events": [event2.pk]}
s1.save()
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_org_export(user2_client, s1)
assert _get_and_patch_org_export(user1_client, s1, can_see=True, can_edit=False)
assert _get_and_patch_org_export(team1_client, s1, can_see=True, can_edit=False)
# Scenario 4
# User 2 created an export for a specific event. User 2 can edit it, because they own it.
# User 1 can see it, because they have permission to see scheduled exports, and change it, because they
# have access to that event.
team1.limit_events.add(event2)
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_org_export(user2_client, s1)
assert _get_and_patch_org_export(user1_client, s1)
assert _get_and_patch_org_export(team1_client, s1)
# Scenario 5
# User 2 created an export that requires a special permission on organizer level
# user 1 can see it, because they have permission to see scheduled exports, but can't change it, because they lack
# that special permission
s2 = ScheduledOrganizerExport.objects.create(
organizer=organizer,
owner=user2,
export_identifier="giftcardlist",
mail_subject="Test",
mail_template="Test",
locale="en",
schedule_rrule="DTSTART:20230118T000000\nRRULE:FREQ=DAILY;INTERVAL=1;WKST=MO",
schedule_rrule_time=time(2, 30, 0)
)
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_org_export(user2_client, s2)
assert _get_and_patch_org_export(user1_client, s2, can_see=True, can_edit=False)
assert _get_and_patch_org_export(team1_client, s2, can_see=True, can_edit=False)
# Scenario 6
# User 2 created an export that requires a special permission on organizer level
# user 1 can see it, because they have permission to see scheduled exports, and change it, because they have
# that special permission
team1.limit_organizer_permissions["organizer.giftcards:read"] = True
team1.save()
user._teamcache = {}
assert _get_and_patch_org_export(user2_client, s2)
assert _get_and_patch_org_export(team1_client, s2)
assert _get_and_patch_org_export(user1_client, s2)
def _get_and_patch_event_export(client, scheduled, can_see=True, can_edit=True):
if can_edit is None:
can_edit = can_see
response = client.get(
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format("dummy", "dummy", scheduled.pk),
)
if can_see:
assert response.status_code == 200
else:
assert response.status_code > 400
assert can_edit is False # Check against useless test usage
return True # No point in editing, we don't have a body
response = client.patch(
'/api/v1/organizers/{}/events/{}/scheduled_exports/{}/'.format("dummy", "dummy", scheduled.pk),
data=response.data,
format='json',
)
if can_edit:
assert response.status_code == 200
else:
assert response.status_code > 400 or (response.status_code == 400 and "export_identifier" in response.data)
return True
@pytest.mark.django_db(transaction=True)
def test_event_edit_restrictions(client, event, organizer, user, team):
# This tests the prevention of a possible privilege escalation where user A creates a scheduled export and
# user B has settings permission (= they can see the export configuration), but not enough permission
# to run the export themselves. Without this check, user B could modify the export and add themselves
# as a recipient. Thereby, user B would gain access to data they can't have.
user1_client = APIClient()
user1_client.force_authenticate(user=user)
user2 = User.objects.create_user("dummy2@dummy.dummy", "dummy")
user2_client = APIClient()
user2_client.force_authenticate(user=user2)
team1_client = APIClient()
t = team.tokens.create(name='Foo')
team1_client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
event1 = event
team1 = team
team1.all_organizer_permissions = False
team1.all_event_permissions = False
team1.all_events = False
team1.limit_organizer_permissions = {"organizer.settings.general:write": True}
team1.limit_event_permissions = {"event.orders:read": True, "event.settings.general:write": True}
team1.save()
team1.limit_events.add(event1)
team1.members.add(user)
t = team.tokens.create(name='Foo')
client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
team2 = organizer.teams.create(
all_organizer_permissions=False, all_event_permissions=False, all_events=False,
limit_event_permissions={"event.orders:read": True, "event.vouchers:read": True},
limit_organizer_permissions={"organizer.giftcards:read": True}
)
team2.limit_events.add(event1)
team2.members.add(user2)
# User 2 created an export that requires a special permission on organizer level
# user 1 can see it, because they have permission to see scheduled exports, but can't change it, because they lack
# that special permission
s2 = ScheduledEventExport.objects.create(
event=event,
owner=user2,
export_identifier="dummy_vouchers",
mail_subject="Test",
mail_template="Test",
locale="en",
schedule_rrule="DTSTART:20230118T000000\nRRULE:FREQ=DAILY;INTERVAL=1;WKST=MO",
schedule_rrule_time=time(2, 30, 0)
)
user._teamcache = {}
user2._teamcache = {}
assert _get_and_patch_event_export(user2_client, s2)
assert _get_and_patch_event_export(user1_client, s2, can_see=True, can_edit=False)
assert _get_and_patch_event_export(team1_client, s2, can_see=True, can_edit=False)
# Scenario 6
# User 2 created an export that requires a special permission on organizer level
# user 1 can see it, because they have permission to see scheduled exports, and change it, because they have
# that special permission
team1.limit_event_permissions["event.vouchers:read"] = True
team1.save()
user._teamcache = {}
assert _get_and_patch_event_export(user2_client, s2)
assert _get_and_patch_event_export(team1_client, s2)
assert _get_and_patch_event_export(user1_client, s2)

View File

@@ -340,6 +340,8 @@ def test_invoice_list_multi_filter(token_client, organizer, event, order, order2
@pytest.mark.django_db
def test_organizer_level(token_client, organizer, team, event, event2, invoice, invoice2):
team.all_events = True
team.save()
resp = token_client.get('/api/v1/organizers/{}/invoices/'.format(organizer.slug))
assert resp.status_code == 200
assert len(resp.data['results']) == 2

View File

@@ -53,8 +53,13 @@ def organizer():
@pytest.fixture
def admin_team(organizer):
return Team.objects.create(organizer=organizer, can_change_teams=True, name='Admin team', all_events=True,
can_create_events=True)
return Team.objects.create(
organizer=organizer,
name='Admin team',
all_events=True,
all_event_permissions=True,
all_organizer_permissions=True,
)
@pytest.fixture
@@ -387,7 +392,7 @@ def test_token_from_code(client, admin_user, organizer, application: OAuthApplic
@pytest.mark.django_db
def test_use_token_for_access_one_organizer(client, admin_user, organizer, application: OAuthApplication):
o2 = Organizer.objects.create(name='A', slug='a')
t2 = Team.objects.create(organizer=o2, can_change_teams=True, name='Admin team', all_events=True)
t2 = Team.objects.create(organizer=o2, all_organizer_permissions=True, name='Admin team', all_events=True)
t2.members.add(admin_user)
client.login(email='dummy@dummy.dummy', password='dummy')
@@ -434,7 +439,13 @@ def test_use_token_for_access_one_organizer(client, admin_user, organizer, appli
@pytest.mark.django_db
def test_use_token_for_access_two_organizers(client, admin_user, organizer, application: OAuthApplication):
o2 = Organizer.objects.create(name='A', slug='a')
t2 = Team.objects.create(organizer=o2, can_change_teams=True, name='Admin team', all_events=True)
t2 = Team.objects.create(
organizer=o2,
all_event_permissions=True,
all_organizer_permissions=True,
name='Admin team',
all_events=True
)
t2.members.add(admin_user)
client.login(email='dummy@dummy.dummy', password='dummy')

View File

@@ -186,18 +186,8 @@ def team2(organizer, event2):
team2 = Team.objects.create(
organizer=organizer,
name="Test-Team 2",
can_change_teams=True,
can_manage_gift_cards=True,
can_change_items=True,
can_create_events=True,
can_change_event_settings=True,
can_change_vouchers=True,
can_view_vouchers=True,
can_change_orders=True,
can_manage_customers=True,
can_manage_reusable_media=True,
can_change_organizer_settings=True,
all_event_permissions=True,
all_organizer_permissions=True,
)
team2.limit_events.add(event2)
team2.save()
@@ -209,6 +199,11 @@ def team2(organizer, event2):
def limited_token_client(client, team2):
team2.can_view_orders = True
team2.can_view_vouchers = True
team2.all_event_permissions = True
team2.limit_event_permissions = {
"event.vouchers:read": True,
"event.orders:read": True,
}
team2.save()
t = team2.tokens.create(name='Foo')
client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)

View File

@@ -43,247 +43,255 @@ from pretix.base.models import Organizer
event_urls = [
(None, ''),
(None, 'categories/'),
('can_view_orders', 'invoices/'),
('event.orders:read', 'invoices/'),
(None, 'items/'),
('can_view_orders', 'orders/'),
('can_view_orders', 'orderpositions/'),
('event.orders:read', 'orders/'),
('event.orders:read', 'orderpositions/'),
(None, 'questions/'),
(None, 'quotas/'),
('can_view_vouchers', 'vouchers/'),
('event.vouchers:read', 'vouchers/'),
(None, 'subevents/'),
(None, 'taxrules/'),
('can_view_orders', 'waitinglistentries/'),
('can_view_orders', 'checkinlists/'),
('can_view_orders', 'checkins/'),
('event.orders:read', 'waitinglistentries/'),
('event.orders:read', 'checkinlists/'),
('event.orders:read', 'checkins/'),
(None, 'seats/'),
]
event_permission_sub_urls = [
('get', 'can_change_event_settings', 'settings/', 200),
('patch', 'can_change_event_settings', 'settings/', 200),
('get', 'can_view_orders', 'revokedsecrets/', 200),
('get', 'can_view_orders', 'revokedsecrets/1/', 404),
('get', 'can_view_orders', 'blockedsecrets/', 200),
('get', 'can_view_orders', 'blockedsecrets/1/', 404),
('get', 'can_view_orders', 'transactions/', 200),
('get', 'can_view_orders', 'transactions/1/', 404),
('get', 'can_view_orders', 'orders/', 200),
('get', 'can_view_orders', 'orderpositions/', 200),
('delete', 'can_change_orders', 'orderpositions/1/', 404),
('post', 'can_change_orders', 'orderpositions/1/price_calc/', 404),
('get', 'can_view_vouchers', 'vouchers/', 200),
('get', 'can_view_orders', 'invoices/', 200),
('get', 'can_view_orders', 'invoices/1/', 404),
('post', 'can_change_orders', 'invoices/1/regenerate/', 404),
('post', 'can_change_orders', 'invoices/1/reissue/', 404),
('post', 'can_change_orders', 'invoices/1/retransmit/', 404),
('get', 'can_view_orders', 'waitinglistentries/', 200),
('get', 'can_view_orders', 'waitinglistentries/1/', 404),
('post', 'can_change_orders', 'waitinglistentries/', 400),
('delete', 'can_change_orders', 'waitinglistentries/1/', 404),
('patch', 'can_change_orders', 'waitinglistentries/1/', 404),
('put', 'can_change_orders', 'waitinglistentries/1/', 404),
('post', 'can_change_orders', 'waitinglistentries/1/send_voucher/', 404),
('get', None, 'settings/', 200),
('patch', 'event.settings.general:write', 'settings/', 200),
('get', 'event.orders:read', 'revokedsecrets/', 200),
('get', 'event.orders:read', 'revokedsecrets/1/', 404),
('get', 'event.orders:read', 'blockedsecrets/', 200),
('get', 'event.orders:read', 'blockedsecrets/1/', 404),
('get', 'event.orders:read', 'transactions/', 200),
('get', 'event.orders:read', 'transactions/1/', 404),
('get', 'event.orders:read', 'orders/', 200),
('get', 'event.orders:read', 'orderpositions/', 200),
('delete', 'event.orders:write', 'orderpositions/1/', 404),
('post', 'event.orders:write', 'orderpositions/1/price_calc/', 404),
('get', 'event.vouchers:read', 'vouchers/', 200),
('get', 'event.orders:read', 'invoices/', 200),
('get', 'event.orders:read', 'invoices/1/', 404),
('post', 'event.orders:write', 'invoices/1/regenerate/', 404),
('post', 'event.orders:write', 'invoices/1/reissue/', 404),
('post', 'event.orders:write', 'invoices/1/retransmit/', 404),
('get', 'event.orders:read', 'waitinglistentries/', 200),
('get', 'event.orders:read', 'waitinglistentries/1/', 404),
('post', 'event.orders:write', 'waitinglistentries/', 400),
('delete', 'event.orders:write', 'waitinglistentries/1/', 404),
('patch', 'event.orders:write', 'waitinglistentries/1/', 404),
('put', 'event.orders:write', 'waitinglistentries/1/', 404),
('post', 'event.orders:write', 'waitinglistentries/1/send_voucher/', 404),
('get', None, 'categories/', 200),
('get', None, 'items/', 200),
('get', None, 'questions/', 200),
('get', None, 'quotas/', 200),
('get', None, 'discounts/', 200),
('post', 'can_change_items', 'items/', 400),
('post', 'event.items:write', 'items/', 400),
('get', None, 'items/1/', 404),
('put', 'can_change_items', 'items/1/', 404),
('patch', 'can_change_items', 'items/1/', 404),
('delete', 'can_change_items', 'items/1/', 404),
('post', 'can_change_items', 'categories/', 400),
('put', 'event.items:write', 'items/1/', 404),
('patch', 'event.items:write', 'items/1/', 404),
('delete', 'event.items:write', 'items/1/', 404),
('post', 'event.items:write', 'categories/', 400),
('get', None, 'categories/1/', 404),
('put', 'can_change_items', 'categories/1/', 404),
('patch', 'can_change_items', 'categories/1/', 404),
('delete', 'can_change_items', 'categories/1/', 404),
('post', 'can_change_items', 'discounts/', 400),
('put', 'event.items:write', 'categories/1/', 404),
('patch', 'event.items:write', 'categories/1/', 404),
('delete', 'event.items:write', 'categories/1/', 404),
('post', 'event.items:write', 'discounts/', 400),
('get', None, 'discounts/1/', 404),
('put', 'can_change_items', 'discounts/1/', 404),
('patch', 'can_change_items', 'discounts/1/', 404),
('delete', 'can_change_items', 'discounts/1/', 404),
('post', 'can_change_items', 'items/1/variations/', 404),
('put', 'event.items:write', 'discounts/1/', 404),
('patch', 'event.items:write', 'discounts/1/', 404),
('delete', 'event.items:write', 'discounts/1/', 404),
('post', 'event.items:write', 'items/1/variations/', 404),
('get', None, 'items/1/variations/', 404),
('get', None, 'items/1/variations/1/', 404),
('put', 'can_change_items', 'items/1/variations/1/', 404),
('patch', 'can_change_items', 'items/1/variations/1/', 404),
('delete', 'can_change_items', 'items/1/variations/1/', 404),
('put', 'event.items:write', 'items/1/variations/1/', 404),
('patch', 'event.items:write', 'items/1/variations/1/', 404),
('delete', 'event.items:write', 'items/1/variations/1/', 404),
('get', None, 'items/1/addons/', 404),
('get', None, 'items/1/addons/1/', 404),
('post', 'can_change_items', 'items/1/addons/', 404),
('put', 'can_change_items', 'items/1/addons/1/', 404),
('patch', 'can_change_items', 'items/1/addons/1/', 404),
('delete', 'can_change_items', 'items/1/addons/1/', 404),
('post', 'event.items:write', 'items/1/addons/', 404),
('put', 'event.items:write', 'items/1/addons/1/', 404),
('patch', 'event.items:write', 'items/1/addons/1/', 404),
('delete', 'event.items:write', 'items/1/addons/1/', 404),
('get', None, 'subevents/', 200),
('get', None, 'subevents/1/', 404),
('post', 'event.subevents:write', 'subevents/', 400),
('patch', 'event.subevents:write', 'subevents/1/', 404),
('put', 'event.subevents:write', 'subevents/1/', 404),
('get', None, 'taxrules/', 200),
('get', None, 'taxrules/1/', 404),
('post', 'can_change_event_settings', 'taxrules/', 400),
('put', 'can_change_event_settings', 'taxrules/1/', 404),
('patch', 'can_change_event_settings', 'taxrules/1/', 404),
('delete', 'can_change_event_settings', 'taxrules/1/', 404),
('get', 'can_change_event_settings', 'sendmail_rules/', 200),
('get', 'can_change_event_settings', 'sendmail_rules/1/', 404),
('post', 'can_change_event_settings', 'sendmail_rules/', 400),
('put', 'can_change_event_settings', 'sendmail_rules/1/', 404),
('patch', 'can_change_event_settings', 'sendmail_rules/1/', 404),
('delete', 'can_change_event_settings', 'sendmail_rules/1/', 404),
('get', 'can_view_vouchers', 'vouchers/', 200),
('get', 'can_view_vouchers', 'vouchers/1/', 404),
('post', 'can_change_vouchers', 'vouchers/', 201),
('put', 'can_change_vouchers', 'vouchers/1/', 404),
('patch', 'can_change_vouchers', 'vouchers/1/', 404),
('delete', 'can_change_vouchers', 'vouchers/1/', 404),
('post', 'event.settings.tax:write', 'taxrules/', 400),
('put', 'event.settings.tax:write', 'taxrules/1/', 404),
('patch', 'event.settings.tax:write', 'taxrules/1/', 404),
('delete', 'event.settings.tax:write', 'taxrules/1/', 404),
('get', 'event.settings.general:write', 'sendmail_rules/', 200),
('get', 'event.settings.general:write', 'sendmail_rules/1/', 404),
('post', 'event.settings.general:write', 'sendmail_rules/', 400),
('put', 'event.settings.general:write', 'sendmail_rules/1/', 404),
('patch', 'event.settings.general:write', 'sendmail_rules/1/', 404),
('delete', 'event.settings.general:write', 'sendmail_rules/1/', 404),
('get', 'event.vouchers:read', 'vouchers/', 200),
('get', 'event.vouchers:read', 'vouchers/1/', 404),
('post', 'event.vouchers:write', 'vouchers/', 201),
('put', 'event.vouchers:write', 'vouchers/1/', 404),
('patch', 'event.vouchers:write', 'vouchers/1/', 404),
('delete', 'event.vouchers:write', 'vouchers/1/', 404),
('get', None, 'quotas/', 200),
('get', None, 'quotas/1/', 404),
('post', 'can_change_items', 'quotas/', 400),
('put', 'can_change_items', 'quotas/1/', 404),
('patch', 'can_change_items', 'quotas/1/', 404),
('delete', 'can_change_items', 'quotas/1/', 404),
('post', 'event.items:write', 'quotas/', 400),
('put', 'event.items:write', 'quotas/1/', 404),
('patch', 'event.items:write', 'quotas/1/', 404),
('delete', 'event.items:write', 'quotas/1/', 404),
('get', None, 'questions/', 200),
('get', None, 'questions/1/', 404),
('post', 'can_change_items', 'questions/', 400),
('put', 'can_change_items', 'questions/1/', 404),
('patch', 'can_change_items', 'questions/1/', 404),
('delete', 'can_change_items', 'questions/1/', 404),
('post', 'event.items:write', 'questions/', 400),
('put', 'event.items:write', 'questions/1/', 404),
('patch', 'event.items:write', 'questions/1/', 404),
('delete', 'event.items:write', 'questions/1/', 404),
('get', None, 'questions/1/options/', 404),
('get', None, 'questions/1/options/1/', 404),
('put', 'can_change_items', 'questions/1/options/1/', 404),
('patch', 'can_change_items', 'questions/1/options/1/', 404),
('delete', 'can_change_items', 'questions/1/options/1/', 404),
('post', 'can_change_orders', 'orders/', 400),
('patch', 'can_change_orders', 'orders/ABC12/', 404),
('post', 'can_change_orders', 'orders/ABC12/mark_paid/', 404),
('post', 'can_change_orders', 'orders/ABC12/mark_pending/', 404),
('post', 'can_change_orders', 'orders/ABC12/mark_expired/', 404),
('post', 'can_change_orders', 'orders/ABC12/mark_canceled/', 404),
('post', 'can_change_orders', 'orders/ABC12/approve/', 404),
('post', 'can_change_orders', 'orders/ABC12/deny/', 404),
('post', 'can_change_orders', 'orders/ABC12/extend/', 400),
('post', 'can_change_orders', 'orders/ABC12/create_invoice/', 404),
('post', 'can_change_orders', 'orders/ABC12/resend_link/', 404),
('post', 'can_change_orders', 'orders/ABC12/regenerate_secrets/', 404),
('get', 'can_view_orders', 'orders/ABC12/payments/', 404),
('get', 'can_view_orders', 'orders/ABC12/payments/1/', 404),
('get', 'can_view_orders', 'orders/ABC12/refunds/', 404),
('get', 'can_view_orders', 'orders/ABC12/refunds/1/', 404),
('post', 'can_change_orders', 'orders/ABC12/payments/1/confirm/', 404),
('post', 'can_change_orders', 'orders/ABC12/payments/1/refund/', 404),
('post', 'can_change_orders', 'orders/ABC12/payments/1/cancel/', 404),
('post', 'can_change_orders', 'orders/ABC12/refunds/1/cancel/', 404),
('post', 'can_change_orders', 'orders/ABC12/refunds/1/process/', 404),
('post', 'can_change_orders', 'orders/ABC12/refunds/1/done/', 404),
('get', 'can_view_orders', 'checkinlists/', 200),
('post', 'can_change_orders', 'checkinlists/1/failed_checkins/', 400),
('get', 'can_view_orders', 'checkins/', 200),
('get', 'can_view_orders', 'checkins/1/', 404),
('post', 'can_change_event_settings', 'checkinlists/', 400),
('put', 'can_change_event_settings', 'checkinlists/1/', 404),
('patch', 'can_change_event_settings', 'checkinlists/1/', 404),
('delete', 'can_change_event_settings', 'checkinlists/1/', 404),
('get', 'can_view_orders', 'checkinlists/1/positions/', 404),
('post', 'can_change_orders', 'checkinlists/1/positions/3/redeem/', 404),
('post', 'can_create_events', 'clone/', 400),
('get', 'can_view_orders', 'cartpositions/', 200),
('get', 'can_view_orders', 'cartpositions/1/', 404),
('post', 'can_change_orders', 'cartpositions/', 400),
('delete', 'can_change_orders', 'cartpositions/1/', 404),
('post', 'can_view_orders', 'exporters/invoicedata/run/', 400),
('get', 'can_view_orders', 'exporters/invoicedata/download/bc3f9884-26ee-425b-8636-80613f84b6fa/3cb49ae6-eda3'
'-4605-814e-099e23777b36/', 404),
('put', 'event.items:write', 'questions/1/options/1/', 404),
('patch', 'event.items:write', 'questions/1/options/1/', 404),
('delete', 'event.items:write', 'questions/1/options/1/', 404),
('post', 'event.orders:write', 'orders/', 400),
('patch', 'event.orders:write', 'orders/ABC12/', 404),
('post', 'event.orders:write', 'orders/ABC12/mark_paid/', 404),
('post', 'event.orders:write', 'orders/ABC12/mark_pending/', 404),
('post', 'event.orders:write', 'orders/ABC12/mark_expired/', 404),
('post', 'event.orders:write', 'orders/ABC12/mark_canceled/', 404),
('post', 'event.orders:write', 'orders/ABC12/approve/', 404),
('post', 'event.orders:write', 'orders/ABC12/deny/', 404),
('post', 'event.orders:write', 'orders/ABC12/extend/', 400),
('post', 'event.orders:write', 'orders/ABC12/create_invoice/', 404),
('post', 'event.orders:write', 'orders/ABC12/resend_link/', 404),
('post', 'event.orders:write', 'orders/ABC12/regenerate_secrets/', 404),
('get', 'event.orders:read', 'orders/ABC12/payments/', 404),
('get', 'event.orders:read', 'orders/ABC12/payments/1/', 404),
('get', 'event.orders:read', 'orders/ABC12/refunds/', 404),
('get', 'event.orders:read', 'orders/ABC12/refunds/1/', 404),
('post', 'event.orders:write', 'orders/ABC12/payments/1/confirm/', 404),
('post', 'event.orders:write', 'orders/ABC12/payments/1/refund/', 404),
('post', 'event.orders:write', 'orders/ABC12/payments/1/cancel/', 404),
('post', 'event.orders:write', 'orders/ABC12/refunds/1/cancel/', 404),
('post', 'event.orders:write', 'orders/ABC12/refunds/1/process/', 404),
('post', 'event.orders:write', 'orders/ABC12/refunds/1/done/', 404),
('get', 'event.orders:read', 'checkinlists/', 200),
('post', 'event.orders:write', 'checkinlists/1/failed_checkins/', 400),
('get', 'event.orders:read', 'checkins/', 200),
('get', 'event.orders:read', 'checkins/1/', 404),
('post', 'event.settings.general:write', 'checkinlists/', 400),
('put', 'event.settings.general:write', 'checkinlists/1/', 404),
('patch', 'event.settings.general:write', 'checkinlists/1/', 404),
('delete', 'event.settings.general:write', 'checkinlists/1/', 404),
('get', 'event.orders:read', 'checkinlists/1/positions/', 404),
('post', 'event.orders:write', 'checkinlists/1/positions/3/redeem/', 404),
('post', ('organizer.events:create', 'event.settings.general:write'), 'clone/', 400),
('get', 'event.orders:read', 'cartpositions/', 200),
('get', 'event.orders:read', 'cartpositions/1/', 404),
('post', 'event.orders:write', 'cartpositions/', 400),
('delete', 'event.orders:write', 'cartpositions/1/', 404),
('post', 'event.orders:read', 'exporters/invoicedata/run/', 400),
('get', None, 'item_meta_properties/', 200),
('get', None, 'item_meta_properties/0/', 404),
('post', 'can_change_event_settings', 'item_meta_properties/', 400),
('patch', 'can_change_event_settings', 'item_meta_properties/0/', 404),
('delete', 'can_change_event_settings', 'item_meta_properties/0/', 404),
('post', 'event.settings.general:write', 'item_meta_properties/', 400),
('patch', 'event.settings.general:write', 'item_meta_properties/0/', 404),
('delete', 'event.settings.general:write', 'item_meta_properties/0/', 404),
('get', None, 'seats/', 200),
('get', 'can_view_orders', 'seats/?expand=orderposition', 200),
('get', 'can_view_orders', 'seats/?expand=cartposition', 200),
('get', 'can_view_vouchers', 'seats/?expand=voucher', 200),
('get', 'event.orders:read', 'seats/?expand=orderposition', 200),
('get', 'event.orders:read', 'seats/?expand=cartposition', 200),
('get', 'event.vouchers:read', 'seats/?expand=voucher', 200),
('get', None, 'seats/1/', 404),
('patch', 'can_change_event_settings', 'seats/1/', 404),
('patch', 'event.settings.general:write', 'seats/1/', 404),
]
org_permission_sub_urls = [
('patch', 'can_change_organizer_settings', '', 200),
('patch', 'can_change_organizer_settings', 'settings/', 200),
('get', 'can_change_organizer_settings', 'webhooks/', 200),
('post', 'can_change_organizer_settings', 'webhooks/', 400),
('get', 'can_change_organizer_settings', 'webhooks/1/', 404),
('put', 'can_change_organizer_settings', 'webhooks/1/', 404),
('patch', 'can_change_organizer_settings', 'webhooks/1/', 404),
('delete', 'can_change_organizer_settings', 'webhooks/1/', 404),
('get', 'can_manage_customers', 'customers/', 200),
('post', 'can_manage_customers', 'customers/', 201),
('get', 'can_manage_customers', 'customers/1/', 404),
('patch', 'can_manage_customers', 'customers/1/', 404),
('post', 'can_manage_customers', 'customers/1/anonymize/', 404),
('put', 'can_manage_customers', 'customers/1/', 404),
('delete', 'can_manage_customers', 'customers/1/', 404),
('get', 'can_manage_customers', 'memberships/', 200),
('post', 'can_manage_customers', 'memberships/', 400),
('get', 'can_manage_customers', 'memberships/1/', 404),
('patch', 'can_manage_customers', 'memberships/1/', 404),
('put', 'can_manage_customers', 'memberships/1/', 404),
('delete', 'can_manage_customers', 'memberships/1/', 404),
('get', 'can_change_organizer_settings', 'saleschannels/', 200),
('post', 'can_change_organizer_settings', 'saleschannels/', 400),
('get', 'can_change_organizer_settings', 'saleschannels/web/', 200),
('patch', 'can_change_organizer_settings', 'saleschannels/web/', 200),
('put', 'can_change_organizer_settings', 'saleschannels/api.1/', 404),
('delete', 'can_change_organizer_settings', 'saleschannels/api.1/', 404),
('get', 'can_change_organizer_settings', 'membershiptypes/', 200),
('post', 'can_change_organizer_settings', 'membershiptypes/', 400),
('get', 'can_change_organizer_settings', 'membershiptypes/1/', 404),
('patch', 'can_change_organizer_settings', 'membershiptypes/1/', 404),
('put', 'can_change_organizer_settings', 'membershiptypes/1/', 404),
('delete', 'can_change_organizer_settings', 'membershiptypes/1/', 404),
('get', 'can_manage_gift_cards', 'giftcards/', 200),
('post', 'can_manage_gift_cards', 'giftcards/', 400),
('get', 'can_manage_gift_cards', 'giftcards/1/', 404),
('put', 'can_manage_gift_cards', 'giftcards/1/', 404),
('patch', 'can_manage_gift_cards', 'giftcards/1/', 404),
('get', 'can_manage_gift_cards', 'giftcards/1/transactions/', 404),
('get', 'can_manage_gift_cards', 'giftcards/1/transactions/1/', 404),
('get', 'can_change_organizer_settings', 'devices/', 200),
('post', 'can_change_organizer_settings', 'devices/', 400),
('get', 'can_change_organizer_settings', 'devices/1/', 404),
('put', 'can_change_organizer_settings', 'devices/1/', 404),
('patch', 'can_change_organizer_settings', 'devices/1/', 404),
('get', 'can_change_teams', 'teams/', 200),
('post', 'can_change_teams', 'teams/', 400),
('get', 'can_change_teams', 'teams/{team_id}/', 200),
('put', 'can_change_teams', 'teams/{team_id}/', 400),
('patch', 'can_change_teams', 'teams/{team_id}/', 200),
('get', 'can_change_teams', 'teams/{team_id}/members/', 200),
('delete', 'can_change_teams', 'teams/{team_id}/members/2/', 404),
('get', 'can_change_teams', 'teams/{team_id}/invites/', 200),
('get', 'can_change_teams', 'teams/{team_id}/invites/2/', 404),
('delete', 'can_change_teams', 'teams/{team_id}/invites/2/', 404),
('post', 'can_change_teams', 'teams/{team_id}/invites/', 400),
('get', 'can_change_teams', 'teams/{team_id}/tokens/', 200),
('get', 'can_change_teams', 'teams/{team_id}/tokens/0/', 404),
('delete', 'can_change_teams', 'teams/{team_id}/tokens/0/', 404),
('post', 'can_change_teams', 'teams/{team_id}/tokens/', 400),
('get', 'can_manage_reusable_media', 'reusablemedia/1/', 404),
('patch', 'organizer.settings.general:write', '', 200),
('patch', 'organizer.settings.general:write', 'settings/', 200),
('get', 'organizer.settings.general:write', 'webhooks/', 200),
('post', 'organizer.settings.general:write', 'webhooks/', 400),
('get', 'organizer.settings.general:write', 'webhooks/1/', 404),
('put', 'organizer.settings.general:write', 'webhooks/1/', 404),
('patch', 'organizer.settings.general:write', 'webhooks/1/', 404),
('delete', 'organizer.settings.general:write', 'webhooks/1/', 404),
('get', 'organizer.customers:read', 'customers/', 200),
('post', 'organizer.customers:write', 'customers/', 201),
('get', 'organizer.customers:read', 'customers/1/', 404),
('patch', 'organizer.customers:write', 'customers/1/', 404),
('post', 'organizer.customers:write', 'customers/1/anonymize/', 404),
('put', 'organizer.customers:write', 'customers/1/', 404),
('delete', 'organizer.customers:write', 'customers/1/', 404),
('get', 'organizer.customers:read', 'memberships/', 200),
('post', 'organizer.customers:write', 'memberships/', 400),
('get', 'organizer.customers:read', 'memberships/1/', 404),
('patch', 'organizer.customers:write', 'memberships/1/', 404),
('put', 'organizer.customers:write', 'memberships/1/', 404),
('delete', 'organizer.customers:write', 'memberships/1/', 404),
('get', 'organizer.settings.general:write', 'saleschannels/', 200),
('post', 'organizer.settings.general:write', 'saleschannels/', 400),
('get', 'organizer.settings.general:write', 'saleschannels/web/', 200),
('patch', 'organizer.settings.general:write', 'saleschannels/web/', 200),
('put', 'organizer.settings.general:write', 'saleschannels/api.1/', 404),
('delete', 'organizer.settings.general:write', 'saleschannels/api.1/', 404),
('get', 'organizer.settings.general:write', 'membershiptypes/', 200),
('post', 'organizer.settings.general:write', 'membershiptypes/', 400),
('get', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('patch', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('put', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('delete', 'organizer.settings.general:write', 'membershiptypes/1/', 404),
('get', 'organizer.giftcards:read', 'giftcards/', 200),
('post', 'organizer.giftcards:write', 'giftcards/', 400),
('get', 'organizer.giftcards:read', 'giftcards/1/', 404),
('put', 'organizer.giftcards:write', 'giftcards/1/', 404),
('patch', 'organizer.giftcards:write', 'giftcards/1/', 404),
('get', 'organizer.giftcards:read', 'giftcards/1/transactions/', 404),
('get', 'organizer.giftcards:read', 'giftcards/1/transactions/1/', 404),
('get', 'organizer.devices:read', 'devices/', 200),
('post', 'organizer.devices:write', 'devices/', 400),
('get', 'organizer.devices:read', 'devices/1/', 404),
('put', 'organizer.devices:write', 'devices/1/', 404),
('patch', 'organizer.devices:write', 'devices/1/', 404),
('get', 'organizer.teams:write', 'teams/', 200),
('post', 'organizer.teams:write', 'teams/', 400),
('get', 'organizer.teams:write', 'teams/{team_id}/', 200),
('put', 'organizer.teams:write', 'teams/{team_id}/', 400),
('patch', 'organizer.teams:write', 'teams/{team_id}/', 200),
('get', 'organizer.teams:write', 'teams/{team_id}/members/', 200),
('delete', 'organizer.teams:write', 'teams/{team_id}/members/2/', 404),
('get', 'organizer.teams:write', 'teams/{team_id}/invites/', 200),
('get', 'organizer.teams:write', 'teams/{team_id}/invites/2/', 404),
('delete', 'organizer.teams:write', 'teams/{team_id}/invites/2/', 404),
('post', 'organizer.teams:write', 'teams/{team_id}/invites/', 400),
('get', 'organizer.teams:write', 'teams/{team_id}/tokens/', 200),
('get', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404),
('delete', 'organizer.teams:write', 'teams/{team_id}/tokens/0/', 404),
('post', 'organizer.teams:write', 'teams/{team_id}/tokens/', 400),
('get', 'organizer.reusablemedia:read', 'reusablemedia/', 200),
('get', 'organizer.reusablemedia:read', 'reusablemedia/1/', 404),
('post', 'organizer.reusablemedia:write', 'reusablemedia/', 400),
('patch', 'organizer.reusablemedia:write', 'reusablemedia/1/', 404),
('put', 'organizer.reusablemedia:write', 'reusablemedia/1/', 404),
('post', 'organizer.seatingplans:write', 'seatingplans/', 400),
('patch', 'organizer.seatingplans:write', 'seatingplans/1/', 404),
('put', 'organizer.seatingplans:write', 'seatingplans/1/', 404),
]
event_permission_root_urls = [
('post', 'can_create_events', 400),
('put', 'can_change_event_settings', 400),
('patch', 'can_change_event_settings', 200),
('delete', 'can_change_event_settings', 204),
('post', 'organizer.events:create', 400),
('put', 'event.settings.general:write', 400),
('patch', 'event.settings.general:write', 200),
('delete', 'event.settings.general:write', 204),
]
@pytest.fixture
def token_client(client, team):
team.can_view_orders = True
team.can_view_vouchers = True
team.can_change_items = True
team.limit_event_permissions["event.orders:read"] = True
team.limit_event_permissions["event.vouchers:read"] = True
team.limit_event_permissions["event.items:write"] = True
team.save()
t = team.tokens.create(name='Foo')
client.credentials(HTTP_AUTHORIZATION='Token ' + t.token)
@@ -329,7 +337,7 @@ def test_event_allowed_all_events(token_client, team, organizer, event, url):
@pytest.mark.parametrize("url", event_urls)
def test_event_allowed_all_events_device(device_client, device, organizer, event, url):
resp = device_client.get('/api/v1/organizers/{}/events/{}/{}'.format(organizer.slug, event.slug, url[1]))
if url[0] is None or url[0] in device.permission_set():
if url[0] is None or url[0] in device._event_permission_set():
assert resp.status_code == 200
else:
assert resp.status_code == 403
@@ -352,7 +360,7 @@ def test_event_allowed_limit_events_device(device_client, organizer, device, eve
device.save()
device.limit_events.add(event)
resp = device_client.get('/api/v1/organizers/{}/events/{}/{}'.format(organizer.slug, event.slug, url[1]))
if url[0] is None or url[0] in device.permission_set():
if url[0] is None or url[0] in device._event_permission_set():
assert resp.status_code == 200
else:
assert resp.status_code == 403
@@ -387,8 +395,14 @@ def test_event_not_existing(token_client, organizer, url, event):
@pytest.mark.parametrize("urlset", event_permission_sub_urls)
def test_token_event_subresources_permission_allowed(token_client, team, organizer, event, urlset):
team.all_events = True
if urlset[1]:
setattr(team, urlset[1], True)
if urlset[1] is not None:
for t in ((urlset[1],) if isinstance(urlset[1], str) else urlset[1]):
if "organizer" in urlset[1]:
team.all_organizer_permissions = False
team.limit_organizer_permissions[t] = True
else:
team.all_event_permissions = False
team.limit_event_permissions[t] = True
team.save()
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/{}/{}'.format(
organizer.slug, event.slug, urlset[2]))
@@ -402,7 +416,10 @@ def test_token_event_subresources_permission_not_allowed(token_client, team, org
team.all_events = False
else:
team.all_events = True
setattr(team, urlset[1], False)
team.all_event_permissions = False
team.limit_event_permissions.pop(urlset[1], None)
team.all_organizer_permissions = False
team.limit_organizer_permissions.pop(urlset[1], None)
team.save()
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/{}/{}'.format(
organizer.slug, event.slug, urlset[2]))
@@ -416,7 +433,14 @@ def test_token_event_subresources_permission_not_allowed(token_client, team, org
@pytest.mark.parametrize("urlset", event_permission_root_urls)
def test_token_event_permission_allowed(token_client, team, organizer, event, urlset):
team.all_events = True
setattr(team, urlset[1], True)
if urlset[1] is not None:
for t in ((urlset[1],) if isinstance(urlset[1], str) else urlset[1]):
if "organizer" in urlset[1]:
team.all_organizer_permissions = False
team.limit_organizer_permissions[t] = True
else:
team.all_event_permissions = False
team.limit_event_permissions[t] = True
team.save()
if urlset[0] == 'post':
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/'.format(organizer.slug))
@@ -429,7 +453,9 @@ def test_token_event_permission_allowed(token_client, team, organizer, event, ur
@pytest.mark.parametrize("urlset", event_permission_root_urls)
def test_token_event_permission_not_allowed(token_client, team, organizer, event, urlset):
team.all_events = True
setattr(team, urlset[1], False)
team.all_event_permissions = False
team.limit_event_permissions.pop(urlset[1], None)
team.all_organizer_permissions = False
team.save()
if urlset[0] == 'post':
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/events/'.format(organizer.slug))
@@ -537,11 +563,11 @@ def test_update_session_activity(user_client, team, organizer, event):
@pytest.mark.django_db
@pytest.mark.parametrize("urlset", event_permission_sub_urls)
def test_device_subresource_permission_check(device_client, device, organizer, event, urlset):
if urlset == ('get', 'can_change_event_settings', 'settings/', 200):
if urlset == ('get', 'event.settings.general:write', 'settings/', 200):
return
resp = getattr(device_client, urlset[0])('/api/v1/organizers/{}/events/{}/{}'.format(
organizer.slug, event.slug, urlset[2]))
if urlset[1] is None or urlset[1] in device.permission_set():
if urlset[1] is None or urlset[1] in device._event_permission_set():
assert resp.status_code == urlset[3]
else:
if urlset[3] == 404:
@@ -555,7 +581,8 @@ def test_device_subresource_permission_check(device_client, device, organizer, e
def test_token_org_subresources_permission_allowed(token_client, team, organizer, event, urlset):
team.all_events = True
if urlset[1]:
setattr(team, urlset[1], True)
team.all_organizer_permissions = False
team.limit_organizer_permissions[urlset[1]] = True
team.save()
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/{}'.format(
organizer.slug, urlset[2].format(team_id=team.pk)))
@@ -568,8 +595,8 @@ def test_token_org_subresources_permission_not_allowed(token_client, team, organ
if urlset[1] is None:
team.all_events = False
else:
team.all_events = True
setattr(team, urlset[1], False)
team.all_organizer_permissions = False
team.limit_organizer_permissions.pop(urlset[1], None)
team.save()
resp = getattr(token_client, urlset[0])('/api/v1/organizers/{}/{}'.format(
organizer.slug, urlset[2].format(team_id=team.pk)))

View File

@@ -119,7 +119,39 @@ def test_medium_list(token_client, organizer, event, medium):
@pytest.mark.django_db
def test_medium_detail(token_client, organizer, event, medium, giftcard, customer):
def test_medium_detail_permission_missing(token_client, organizer, event, medium, giftcard, customer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
}
team.save()
resp = token_client.get(
'/api/v1/organizers/{}/reusablemedia/{}/?expand=linked_giftcard'.format(
organizer.slug, medium.pk
)
)
assert resp.status_code == 403
assert "No permission to access gift card details." in str(resp.data)
resp = token_client.get(
'/api/v1/organizers/{}/reusablemedia/{}/?expand=customer'.format(
organizer.slug, medium.pk
)
)
assert resp.status_code == 403
assert "No permission to access customer details." in str(resp.data)
@pytest.mark.django_db
def test_medium_detail(token_client, organizer, event, medium, giftcard, customer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
res = dict(TEST_MEDIUM_RES)
res["id"] = medium.pk
res["created"] = medium.created.isoformat().replace('+00:00', 'Z')
@@ -340,7 +372,16 @@ def test_medium_lookup_not_found(token_client, organizer, organizer2, medium):
@pytest.mark.django_db
def test_medium_lookup_autocreate(token_client, organizer):
def test_medium_lookup_autocreate(token_client, organizer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:read": True,
"organizer.reusablemedia:write": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
# Disabled
resp = token_client.post(
'/api/v1/organizers/{}/reusablemedia/lookup/'.format(organizer.slug),
@@ -386,7 +427,15 @@ def test_medium_lookup_autocreate(token_client, organizer):
@pytest.mark.django_db
def test_medium_autocreate_giftcard(token_client, organizer):
def test_medium_autocreate_giftcard(token_client, organizer, team):
team.all_organizer_permissions = False
team.limit_organizer_permissions = {
"organizer.reusablemedia:write": True,
"organizer.reusablemedia:read": True,
"organizer.customers:read": True,
"organizer.giftcards:read": True,
}
team.save()
organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard = True
organizer.settings.reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency = 'USD'
resp = token_client.post(

View File

@@ -260,7 +260,8 @@ def test_all_subevents_list_filter(token_client, organizer, event, subevent):
def test_subevent_create(team, token_client, organizer, event, subevent, meta_prop, item):
meta_prop.choices = [{"key": "Conference", "label": {"en": "Conference"}}, {"key": "Workshop", "label": {"en": "Workshop"}}]
meta_prop.save()
team.can_change_organizer_settings = False
team.limit_organizer_permissions = {"organizer.events:create": True}
team.all_organizer_permissions = False
team.save()
organizer.meta_properties.create(
name="protected", protected=True

View File

@@ -31,6 +31,7 @@ def second_team(organizer, event):
t = organizer.teams.create(
name='User team',
all_events=False,
limit_event_permissions={"event.orders:read": True},
)
t.limit_events.add(event)
return t
@@ -41,8 +42,10 @@ TEST_TEAM_RES = {
'can_change_teams': True, 'can_change_organizer_settings': True, 'can_manage_gift_cards': True,
'can_manage_customers': True, 'can_manage_reusable_media': True,
'can_change_event_settings': True, 'can_change_items': True, 'can_view_orders': True, 'can_change_orders': True,
'can_view_vouchers': True, 'can_change_vouchers': True, 'can_checkin_orders': False,
'can_view_vouchers': True, 'can_change_vouchers': True, 'can_checkin_orders': True,
'require_2fa': False,
'all_event_permissions': True, 'limit_event_permissions': [],
'all_organizer_permissions': True, 'limit_organizer_permissions': [],
}
SECOND_TEAM_RES = {
@@ -50,9 +53,11 @@ SECOND_TEAM_RES = {
'can_create_events': False,
'can_manage_customers': False, 'can_manage_reusable_media': False,
'can_change_teams': False, 'can_change_organizer_settings': False, 'can_manage_gift_cards': False,
'can_change_event_settings': False, 'can_change_items': False, 'can_view_orders': False, 'can_change_orders': False,
'can_change_event_settings': False, 'can_change_items': False, 'can_view_orders': True, 'can_change_orders': False,
'can_view_vouchers': False, 'can_change_vouchers': False, 'can_checkin_orders': False,
'require_2fa': False,
'all_event_permissions': False, 'limit_event_permissions': ["event.orders:read"],
'all_organizer_permissions': False, 'limit_organizer_permissions': [],
}
@@ -95,8 +100,80 @@ def test_team_create(token_client, organizer, event):
@pytest.mark.django_db
def test_team_update(token_client, organizer, event, second_team):
assert not second_team.can_change_event_settings
def test_team_update(token_client, organizer, event, team, second_team):
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_event_permission': False,
'limit_event_permissions': ["event.settings.general:write"],
},
format='json'
)
assert resp.status_code == 200
second_team.refresh_from_db()
assert second_team.limit_event_permissions == {
'event.settings.general:write': True,
}
assert not second_team.all_event_permissions
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_event_permission': False,
'limit_event_permissions': ["INVALID"],
},
format='json'
)
assert resp.status_code == 400
assert "invalid" in str(resp.data)
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, team.pk),
{
'limit_event_permissions': ["event.settings.general:write"],
},
format='json'
)
assert resp.status_code == 400
assert "Do not set both" in str(resp.data)
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_organizer_permissions': False,
'limit_organizer_permissions': ["organizer.devices:write"],
},
format='json'
)
assert resp.status_code == 400
assert ("For permission group organizer.devices, the valid combinations of actions are '' or 'read' or "
"'read,write' but you tried to set 'write'.") in str(resp.data)
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_organizer_permissions': True,
'limit_organizer_permissions': ["organizer.events:create"],
},
format='json'
)
assert resp.status_code == 400
assert "Do not set both" in str(resp.data)
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_events': True,
},
format='json'
)
assert resp.status_code == 400
assert "Do not set both" in str(resp.data)
@pytest.mark.django_db
@pytest.mark.filterwarnings("ignore")
def test_team_update_legacy_add_perm(token_client, organizer, event, second_team):
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
@@ -107,15 +184,95 @@ def test_team_update(token_client, organizer, event, second_team):
assert resp.status_code == 200
second_team.refresh_from_db()
assert second_team.can_change_event_settings
assert second_team.limit_event_permissions == {
"event.settings.general:write": True,
"event.settings.payment:write": True,
"event.settings.tax:write": True,
"event.settings.invoicing:write": True,
"event.subevents:write": True,
"event.orders:read": True,
}
@pytest.mark.django_db
@pytest.mark.filterwarnings("ignore")
def test_team_update_legacy_add_all_perms(token_client, organizer, event, second_team):
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'can_change_event_settings': True,
'can_change_items': True,
# can_view_orders omitted because already set
'can_change_orders': True,
'can_checkin_orders': True,
'can_view_vouchers': True,
'can_change_vouchers': True,
},
format='json'
)
assert resp.status_code == 200
second_team.refresh_from_db()
assert second_team.all_event_permissions
assert second_team.limit_event_permissions == {}
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'all_events': True,
'can_create_events': True,
'can_change_organizer_settings': True,
'can_change_teams': True,
'can_manage_gift_cards': True,
'can_manage_customers': True,
'can_manage_reusable_media': True,
},
format='json'
)
assert resp.status_code == 200
second_team.refresh_from_db()
assert second_team.all_organizer_permissions
assert second_team.limit_organizer_permissions == {}
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'can_change_teams': False,
},
format='json'
)
assert resp.status_code == 200
second_team.refresh_from_db()
assert not second_team.all_organizer_permissions
assert second_team.limit_organizer_permissions == {
'organizer.settings.general:write': True,
'organizer.giftcards:read': True,
'organizer.giftcards:write': True,
'organizer.events:create': True,
'organizer.customers:read': True,
'organizer.customers:write': True,
'organizer.reusablemedia:read': True,
'organizer.reusablemedia:write': True,
'organizer.devices:read': True,
'organizer.devices:write': True,
'organizer.seatingplans:write': True,
'organizer.outgoingmails:read': True,
}
assert resp.data["can_manage_customers"] is True
assert resp.data["can_change_teams"] is False
@pytest.mark.django_db
@pytest.mark.filterwarnings("ignore")
def test_team_update_legacy_and_new(token_client, organizer, event, second_team):
resp = token_client.patch(
'/api/v1/organizers/{}/teams/{}/'.format(organizer.slug, second_team.pk),
{
'can_change_event_settings': True,
'all_organizer_permissions': True,
},
format='json'
)
assert resp.status_code == 400
assert "You cannot set deprecated and current permission attributes" in str(resp.data)
@pytest.mark.django_db

View File

@@ -242,7 +242,8 @@ def test_organizer_list(token_client, team, organizer, event, order, item, taxru
assert resp.data["count"] == 0
team.all_events = True
team.can_view_orders = False
team.limit_event_permissions = {"event.vouchers:read": True}
team.all_event_permissions = False
team.save()
resp = token_client.get(