diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index d7b35417d0..10a4e7fe49 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -281,13 +281,17 @@ class EventSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer): from pretix.base.plugins import get_all_plugins plugins_available = { - p.module for p in get_all_plugins(self.instance) + p.module: p for p in get_all_plugins(self.instance) if not p.name.startswith('.') and getattr(p, 'visible', True) } + settings_holder = self.instance if self.instance and self.instance.pk else self.context['organizer'] for plugin in value.get('plugins'): if plugin not in plugins_available: raise ValidationError(_('Unknown plugin: \'{name}\'.').format(name=plugin)) + if getattr(plugins_available[plugin], 'restricted', False): + if plugin not in settings_holder.settings.allowed_restricted_plugins: + raise ValidationError(_('Restricted plugin: \'{name}\'.').format(name=plugin)) return value diff --git a/src/pretix/control/views/event.py b/src/pretix/control/views/event.py index 6af1d7e3fd..5eafa1343c 100644 --- a/src/pretix/control/views/event.py +++ b/src/pretix/control/views/event.py @@ -400,7 +400,7 @@ class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, Templat if key.startswith("plugin:"): module = key.split(":")[1] if value == "enable" and module in plugins_available: - if getattr(plugins_available[module].app, 'restricted', False): + if getattr(plugins_available[module], 'restricted', False): if module not in request.event.settings.allowed_restricted_plugins: continue diff --git a/src/tests/api/test_events.py b/src/tests/api/test_events.py index 82b8ab3b4e..4c0dae74a8 100644 --- a/src/tests/api/test_events.py +++ b/src/tests/api/test_events.py @@ -763,6 +763,50 @@ def test_event_update(token_client, organizer, event, item, meta_prop): assert cnt == event.all_logentries().count() +@pytest.mark.django_db +def test_event_update_plugins_validation(token_client, organizer, event, item, meta_prop): + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug), + { + "plugins": ["pretix.plugins.paypal2", "unknown"] + }, + format='json' + ) + assert resp.status_code == 400 + assert resp.data == {"plugins": ["Unknown plugin: 'unknown'."]} + + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug), + { + "plugins": ["pretix.plugins.paypal2", "tests.testdummyhidden"] + }, + format='json' + ) + assert resp.status_code == 400 + assert resp.data == {"plugins": ["Unknown plugin: 'tests.testdummyhidden'."]} + + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug), + { + "plugins": ["pretix.plugins.paypal2", "tests.testdummyrestricted"] + }, + format='json' + ) + assert resp.status_code == 400 + assert resp.data == {"plugins": ["Restricted plugin: 'tests.testdummyrestricted'."]} + + organizer.settings.allowed_restricted_plugins = ["tests.testdummyrestricted"] + + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/'.format(organizer.slug, event.slug), + { + "plugins": ["pretix.plugins.paypal2", "tests.testdummyrestricted"] + }, + format='json' + ) + assert resp.status_code == 200 + + @pytest.mark.django_db def test_event_test_mode(token_client, organizer, event): resp = token_client.patch( diff --git a/src/tests/control/test_events.py b/src/tests/control/test_events.py index 3e90fe1aca..ada6a90d0f 100644 --- a/src/tests/control/test_events.py +++ b/src/tests/control/test_events.py @@ -299,6 +299,8 @@ class EventsTest(SoupTest): doc = self.get_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug)) self.assertIn("Stripe", doc.select(".form-plugins")[0].text) self.assertIn("Enable", doc.select("[name=\"plugin:pretix.plugins.stripe\"]")[0].text) + assert not doc.select("[name=\"plugin:tests.testdummyrestricted\"]") + assert not doc.select("[name=\"plugin:tests.testdummyhidden\"]") doc = self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug), {'plugin:pretix.plugins.stripe': 'enable'}) @@ -308,6 +310,23 @@ class EventsTest(SoupTest): {'plugin:pretix.plugins.stripe': 'disable'}) self.assertIn("Enable", doc.select("[name=\"plugin:pretix.plugins.stripe\"]")[0].text) + self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug), + {'plugin:tests.testdummyhidden': 'enable'}) + self.event1.refresh_from_db() + assert "testdummyhidden" not in self.event1.plugins + + self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug), + {'plugin:tests.testdummyrestricted': 'enable'}) + self.event1.refresh_from_db() + assert "testdummyrestricted" not in self.event1.plugins + + self.orga1.settings.allowed_restricted_plugins = ["tests.testdummyrestricted"] + + self.post_doc('/control/event/%s/%s/settings/plugins' % (self.orga1.slug, self.event1.slug), + {'plugin:tests.testdummyrestricted': 'enable'}) + self.event1.refresh_from_db() + assert "testdummyrestricted" in self.event1.plugins + def test_testmode_enable(self): self.event1.testmode = False self.event1.save() diff --git a/src/tests/settings.py b/src/tests/settings.py index 54cec5cae3..827f0a2e28 100644 --- a/src/tests/settings.py +++ b/src/tests/settings.py @@ -28,6 +28,8 @@ TEST_DIR = os.path.dirname(__file__) TEMPLATES[0]['DIRS'].append(os.path.join(TEST_DIR, 'templates')) # NOQA INSTALLED_APPS.append('tests.testdummy') # NOQA +INSTALLED_APPS.append('tests.testdummyrestricted') # NOQA +INSTALLED_APPS.append('tests.testdummyhidden') # NOQA PRETIX_AUTH_BACKENDS = [ 'pretix.base.auth.NativeAuthBackend', diff --git a/src/tests/testdummyhidden/__init__.py b/src/tests/testdummyhidden/__init__.py new file mode 100644 index 0000000000..9fd5bdc500 --- /dev/null +++ b/src/tests/testdummyhidden/__init__.py @@ -0,0 +1,21 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# diff --git a/src/tests/testdummyhidden/apps.py b/src/tests/testdummyhidden/apps.py new file mode 100644 index 0000000000..23e499b803 --- /dev/null +++ b/src/tests/testdummyhidden/apps.py @@ -0,0 +1,35 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig + + +class TestDummyHiddenApp(AppConfig): + name = 'tests.testdummyhidden' + verbose_name = 'testdummyhidden' + + class PretixPluginMeta: + name = 'testdummyhidden' + version = '1.0.0' + restricted = True + + def is_available(self, event): + return False diff --git a/src/tests/testdummyrestricted/__init__.py b/src/tests/testdummyrestricted/__init__.py new file mode 100644 index 0000000000..9fd5bdc500 --- /dev/null +++ b/src/tests/testdummyrestricted/__init__.py @@ -0,0 +1,21 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# diff --git a/src/tests/testdummyrestricted/apps.py b/src/tests/testdummyrestricted/apps.py new file mode 100644 index 0000000000..ea3d938fbd --- /dev/null +++ b/src/tests/testdummyrestricted/apps.py @@ -0,0 +1,32 @@ +# +# This file is part of pretix (Community Edition). +# +# Copyright (C) 2014-2020 Raphael Michel and contributors +# Copyright (C) 2020-2021 rami.io GmbH and contributors +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General +# Public License as published by the Free Software Foundation in version 3 of the License. +# +# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are +# applicable granting you additional permissions and placing additional restrictions on your usage of this software. +# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive +# this file, see . +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License along with this program. If not, see +# . +# +from django.apps import AppConfig + + +class TestDummyRestrictedApp(AppConfig): + name = 'tests.testdummyrestricted' + verbose_name = 'testdummyrestricted' + + class PretixPluginMeta: + name = 'testdummyrestricted' + version = '1.0.0' + restricted = True