VYPR
Low severity2.7NVD Advisory· Published May 2, 2024· Updated Apr 15, 2026

CVE-2024-32882

CVE-2024-32882

Description

Wagtail is an open source content management system built on Django. In affected versions if a model has been made available for editing through the wagtail.contrib.settings module or ModelViewSet, and the permission argument on FieldPanel has been used to further restrict access to one or more fields of the model, a user with edit permission over the model but not the specific field can craft an HTTP POST request that bypasses the permission check on the individual field, allowing them to update its value. This vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, or by a user who has not been granted edit access to the model in question. The editing interfaces for pages and snippets are also unaffected. Patched versions have been released as Wagtail 6.0.3 and 6.1. Wagtail releases prior to 6.0 are unaffected. Users are advised to upgrade. Site owners who are unable to upgrade to a patched version can avoid the vulnerability as follows: 1.For models registered through ModelViewSet, register the model as a snippet instead; 2. For settings models, place the restricted fields in a separate settings model, and configure permission at the model level.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wagtailPyPI
>= 6.0.0, < 6.0.36.0.3

Patches

2
ab2a5d82b4ee

Revert "Merge pull request from GHSA-w2v8-php4-p8hc"

https://github.com/wagtail/wagtailMatt WestcottMay 1, 2024via ghsa
8 files changed · +24 324
  • wagtail/admin/tests/viewsets/test_model_viewset.py+0 29 modified
    @@ -1474,35 +1474,6 @@ def test_edit_form_rendered_with_panels(self):
             modal_workflow_script = soup.select_one(f'script[src="{modal_workflow_js}"]')
             self.assertIsNotNone(modal_workflow_script)
     
    -    def test_field_permissions(self):
    -        self.user.is_superuser = False
    -        self.user.save()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                content_type__app_label="wagtailadmin", codename="access_admin"
    -            ),
    -            Permission.objects.get(
    -                content_type__app_label=self.object._meta.app_label,
    -                codename=get_permission_codename("change", self.object._meta),
    -            ),
    -        )
    -
    -        response = self.client.get(self.url)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertEqual(list(response.context["form"].fields), ["name"])
    -
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                codename="can_set_release_date",
    -            )
    -        )
    -
    -        response = self.client.get(self.url)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertEqual(
    -            list(response.context["form"].fields), ["name", "release_date"]
    -        )
    -
     
     class TestDefaultMessages(WagtailTestUtils, TestCase):
         def setUp(self):
    
  • wagtail/admin/views/generic/models.py+0 26 modified
    @@ -27,7 +27,6 @@
     from wagtail.actions.unpublish import UnpublishAction
     from wagtail.admin import messages
     from wagtail.admin.filters import WagtailFilterSet
    -from wagtail.admin.forms.models import WagtailAdminModelForm
     from wagtail.admin.forms.search import SearchForm
     from wagtail.admin.panels import get_edit_handler
     from wagtail.admin.ui.components import Component, MediaContainer
    @@ -617,24 +616,6 @@ def get_translations(self):
                 for locale in Locale.objects.all().exclude(id=self.locale.id)
             ]
     
    -    def get_initial_form_instance(self):
    -        if self.locale:
    -            instance = self.model()
    -            instance.locale = self.locale
    -            return instance
    -
    -    def get_form_kwargs(self):
    -        if instance := self.get_initial_form_instance():
    -            # super().get_form_kwargs() will use self.object as the instance kwarg
    -            self.object = instance
    -        kwargs = super().get_form_kwargs()
    -
    -        form_class = self.get_form_class()
    -        # Add for_user support for PermissionedForm
    -        if issubclass(form_class, WagtailAdminModelForm):
    -            kwargs["for_user"] = self.request.user
    -        return kwargs
    -
         def save_instance(self):
             """
             Called after the form is successfully validated - saves the object to the db
    @@ -805,13 +786,6 @@ def get_translations(self):
                 for translation in self.object.get_translations().select_related("locale")
             ]
     
    -    def get_form_kwargs(self):
    -        kwargs = super().get_form_kwargs()
    -        form_class = self.get_form_class()
    -        if issubclass(form_class, WagtailAdminModelForm):
    -            kwargs["for_user"] = self.request.user
    -        return kwargs
    -
         def save_instance(self):
             """
             Called after the form is successfully validated - saves the object to the db.
    
  • wagtail/contrib/settings/tests/generic/test_admin.py+1 88 modified
    @@ -14,7 +14,6 @@
         PanelGenericSettings,
         TabbedGenericSettings,
         TestGenericSetting,
    -    TestPermissionedGenericSetting,
     )
     from wagtail.test.utils import WagtailTestUtils
     
    @@ -77,11 +76,6 @@ def edit_url(self, setting):
     class TestGenericSettingCreateView(BaseTestGenericSettingView):
         def setUp(self):
             self.user = self.login()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                content_type__app_label="wagtailadmin", codename="access_admin"
    -            )
    -        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -113,51 +107,14 @@ def test_file_upload_multipart(self):
             # Ensure the form supports file uploads
             self.assertContains(response, 'enctype="multipart/form-data"')
     
    -    def test_create_restricted_field_without_permission(self):
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        self.assertFalse(TestPermissionedGenericSetting.objects.exists())
    -        response = self.post(
    -            post_data={"sensitive_email": "test@example.com", "title": "test"},
    -            setting=TestPermissionedGenericSetting,
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        settings = TestPermissionedGenericSetting.objects.get()
    -        self.assertEqual(settings.title, "test")
    -        self.assertEqual(settings.sensitive_email, "")
    -
    -    def test_create_restricted_field(self):
    -        self.user.is_superuser = False
    -        self.user.save()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
    -        )
    -        self.assertFalse(TestPermissionedGenericSetting.objects.exists())
    -        response = self.post(
    -            post_data={"sensitive_email": "test@example.com", "title": "test"},
    -            setting=TestPermissionedGenericSetting,
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        settings = TestPermissionedGenericSetting.objects.get()
    -        self.assertEqual(settings.title, "test")
    -        self.assertEqual(settings.sensitive_email, "test@example.com")
    -
     
     class TestGenericSettingEditView(BaseTestGenericSettingView):
         def setUp(self):
             self.test_setting = TestGenericSetting()
             self.test_setting.title = "Setting title"
             self.test_setting.save()
     
    -        self.user = self.login()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                content_type__app_label="wagtailadmin", codename="access_admin"
    -            )
    -        )
    +        self.login()
     
         def test_get_edit(self):
             response = self.get()
    @@ -196,50 +153,6 @@ def test_for_request(self):
                 expected_url=f"{url}{TestGenericSetting.objects.first().pk}/",
             )
     
    -    def test_edit_restricted_field(self):
    -        test_setting = TestPermissionedGenericSetting()
    -        test_setting.sensitive_email = "test@example.com"
    -        test_setting.save()
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        self.user.user_permissions.add(
    -            Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
    -        )
    -
    -        response = self.get(setting=TestPermissionedGenericSetting)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertIn("sensitive_email", list(response.context["form"].fields))
    -
    -        response = self.post(
    -            setting=TestPermissionedGenericSetting,
    -            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        test_setting.refresh_from_db()
    -        self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
    -
    -    def test_edit_restricted_field_without_permission(self):
    -        test_setting = TestPermissionedGenericSetting()
    -        test_setting.sensitive_email = "test@example.com"
    -        test_setting.save()
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        response = self.get(setting=TestPermissionedGenericSetting)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertNotIn("sensitive_email", list(response.context["form"].fields))
    -
    -        response = self.post(
    -            setting=TestPermissionedGenericSetting,
    -            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        test_setting.refresh_from_db()
    -        self.assertEqual(test_setting.sensitive_email, "test@example.com")
    -
     
     class TestAdminPermission(WagtailTestUtils, TestCase):
         def test_registered_permission(self):
    
  • wagtail/contrib/settings/tests/site_specific/test_admin.py+3 92 modified
    @@ -14,7 +14,6 @@
         IconSiteSetting,
         PanelSiteSettings,
         TabbedSiteSettings,
    -    TestPermissionedSiteSetting,
         TestSiteSetting,
     )
     from wagtail.test.utils import WagtailTestUtils
    @@ -73,11 +72,6 @@ def edit_url(self, setting, site_pk=1):
     class TestSiteSettingCreateView(BaseTestSiteSettingView):
         def setUp(self):
             self.user = self.login()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                content_type__app_label="wagtailadmin", codename="access_admin"
    -            )
    -        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -109,55 +103,18 @@ def test_file_upload_multipart(self):
             # Ensure the form supports file uploads
             self.assertContains(response, 'enctype="multipart/form-data"')
     
    -    def test_create_restricted_field_without_permission(self):
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        self.assertFalse(TestPermissionedSiteSetting.objects.exists())
    -        response = self.post(
    -            post_data={"sensitive_email": "test@example.com", "title": "test"},
    -            setting=TestPermissionedSiteSetting,
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        settings = TestPermissionedSiteSetting.objects.get()
    -        self.assertEqual(settings.title, "test")
    -        self.assertEqual(settings.sensitive_email, "")
    -
    -    def test_create_restricted_field(self):
    -        self.user.is_superuser = False
    -        self.user.save()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
    -        )
    -        self.assertFalse(TestPermissionedSiteSetting.objects.exists())
    -        response = self.post(
    -            post_data={"sensitive_email": "test@example.com", "title": "test"},
    -            setting=TestPermissionedSiteSetting,
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        settings = TestPermissionedSiteSetting.objects.get()
    -        self.assertEqual(settings.title, "test")
    -        self.assertEqual(settings.sensitive_email, "test@example.com")
    -
     
     class TestSiteSettingEditView(BaseTestSiteSettingView):
         def setUp(self):
    -        self.default_site = Site.objects.get(is_default_site=True)
    +        default_site = Site.objects.get(is_default_site=True)
     
             self.test_setting = TestSiteSetting()
             self.test_setting.title = "Site title"
             self.test_setting.email = "initial@example.com"
    -        self.test_setting.site = self.default_site
    +        self.test_setting.site = default_site
             self.test_setting.save()
     
    -        self.user = self.login()
    -        self.user.user_permissions.add(
    -            Permission.objects.get(
    -                content_type__app_label="wagtailadmin", codename="access_admin"
    -            )
    -        )
    +        self.login()
     
         def test_get_edit(self):
             response = self.get()
    @@ -201,52 +158,6 @@ def test_get_redirect_to_relevant_instance_invalid(self):
             response = self.client.get(url)
             self.assertRedirects(response, status_code=302, expected_url="/admin/")
     
    -    def test_edit_restricted_field(self):
    -        test_setting = TestPermissionedSiteSetting()
    -        test_setting.sensitive_email = "test@example.com"
    -        test_setting.site = self.default_site
    -        test_setting.save()
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        self.user.user_permissions.add(
    -            Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
    -        )
    -
    -        response = self.get(setting=TestPermissionedSiteSetting)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertIn("sensitive_email", list(response.context["form"].fields))
    -
    -        response = self.post(
    -            setting=TestPermissionedSiteSetting,
    -            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        test_setting.refresh_from_db()
    -        self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
    -
    -    def test_edit_restricted_field_without_permission(self):
    -        test_setting = TestPermissionedSiteSetting()
    -        test_setting.sensitive_email = "test@example.com"
    -        test_setting.site = self.default_site
    -        test_setting.save()
    -        self.user.is_superuser = False
    -        self.user.save()
    -
    -        response = self.get(setting=TestPermissionedSiteSetting)
    -        self.assertEqual(response.status_code, 200)
    -        self.assertNotIn("sensitive_email", list(response.context["form"].fields))
    -
    -        response = self.post(
    -            setting=TestPermissionedSiteSetting,
    -            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    -        )
    -        self.assertEqual(response.status_code, 302)
    -
    -        test_setting.refresh_from_db()
    -        self.assertEqual(test_setting.sensitive_email, "test@example.com")
    -
     
     @override_settings(
         ALLOWED_HOSTS=["testserver", "example.com", "noneoftheabove.example.com"]
    
  • wagtail/snippets/views/snippets.py+19 0 modified
    @@ -235,6 +235,22 @@ def run_after_hook(self):
         def _get_action_menu(self):
             return SnippetActionMenu(self.request, view=self.view_name, model=self.model)
     
    +    def _get_initial_form_instance(self):
    +        instance = self.model()
    +
    +        # Set locale of the new instance
    +        if self.locale:
    +            instance.locale = self.locale
    +
    +        return instance
    +
    +    def get_form_kwargs(self):
    +        return {
    +            **super().get_form_kwargs(),
    +            "instance": self._get_initial_form_instance(),
    +            "for_user": self.request.user,
    +        }
    +
         def get_side_panels(self):
             side_panels = [
                 SnippetStatusSidePanel(
    @@ -294,6 +310,9 @@ def _get_action_menu(self):
                 locked_for_user=self.locked_for_user,
             )
     
    +    def get_form_kwargs(self):
    +        return {**super().get_form_kwargs(), "for_user": self.request.user}
    +
         def get_side_panels(self):
             side_panels = [
                 SnippetStatusSidePanel(
    
  • wagtail/test/testapp/migrations/0034_testpermissionedgenericsetting_and_more.py+0 42 removed
    @@ -1,42 +0,0 @@
    -# Generated by Django 4.2.11 on 2024-04-25 15:51
    -
    -from django.db import migrations, models
    -import django.db.models.deletion
    -
    -
    -class Migration(migrations.Migration):
    -
    -    dependencies = [
    -        ('wagtailcore', '0091_remove_revision_submitted_for_moderation'),
    -        ('tests', '0033_customcopyformpage'),
    -    ]
    -
    -    operations = [
    -        migrations.CreateModel(
    -            name='TestPermissionedGenericSetting',
    -            fields=[
    -                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    -                ('title', models.CharField(max_length=100)),
    -                ('sensitive_email', models.EmailField(max_length=50)),
    -            ],
    -            options={
    -                'permissions': [('can_edit_sensitive_email_generic_setting', 'Can edit sensitive email generic setting.')],
    -            },
    -        ),
    -        migrations.AlterModelOptions(
    -            name='featurecompletetoy',
    -            options={'permissions': [('can_set_release_date', 'Can set release date')]},
    -        ),
    -        migrations.CreateModel(
    -            name='TestPermissionedSiteSetting',
    -            fields=[
    -                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    -                ('title', models.CharField(max_length=100)),
    -                ('sensitive_email', models.EmailField(max_length=50)),
    -                ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
    -            ],
    -            options={
    -                'permissions': [('can_edit_sensitive_email_site_setting', 'Can edit sensitive email site setting.')],
    -            },
    -        ),
    -    ]
    
  • wagtail/test/testapp/models.py+0 46 modified
    @@ -1640,49 +1640,6 @@ class TestGenericSetting(BaseGenericSetting):
         email = models.EmailField(max_length=50)
     
     
    -@register_setting
    -class TestPermissionedGenericSetting(BaseGenericSetting):
    -    title = models.CharField(max_length=100)
    -    sensitive_email = models.EmailField(max_length=50)
    -
    -    panels = [
    -        FieldPanel("title"),
    -        FieldPanel(
    -            "sensitive_email",
    -            permission="tests.can_edit_sensitive_email_generic_setting",
    -        ),
    -    ]
    -
    -    class Meta:
    -        permissions = [
    -            (
    -                "can_edit_sensitive_email_generic_setting",
    -                "Can edit sensitive email generic setting.",
    -            ),
    -        ]
    -
    -
    -@register_setting
    -class TestPermissionedSiteSetting(BaseSiteSetting):
    -    title = models.CharField(max_length=100)
    -    sensitive_email = models.EmailField(max_length=50)
    -
    -    panels = [
    -        FieldPanel("title"),
    -        FieldPanel(
    -            "sensitive_email", permission="tests.can_edit_sensitive_email_site_setting"
    -        ),
    -    ]
    -
    -    class Meta:
    -        permissions = [
    -            (
    -                "can_edit_sensitive_email_site_setting",
    -                "Can edit sensitive email site setting.",
    -            ),
    -        ]
    -
    -
     @register_setting
     class ImportantPagesSiteSetting(BaseSiteSetting):
         sign_up_page = models.ForeignKey(
    @@ -2272,9 +2229,6 @@ def is_cool(self):
         def __str__(self):
             return f"{self.name} ({self.release_date})"
     
    -    class Meta:
    -        permissions = [("can_set_release_date", "Can set release date")]
    -
     
     class PurgeRevisionsProtectedTestModel(models.Model):
         revision = models.OneToOneField(
    
  • wagtail/test/testapp/views.py+1 1 modified
    @@ -227,7 +227,7 @@ class FeatureCompleteToyViewSet(ModelViewSet):
     
         panels = [
             FieldPanel("name"),
    -        FieldPanel("release_date", permission="tests.can_set_release_date"),
    +        FieldPanel("release_date"),
         ]
     
     
    
fa0d4829f9c8

Merge pull request from GHSA-w2v8-php4-p8hc

https://github.com/wagtail/wagtailJake HowardMay 1, 2024via ghsa
8 files changed · +324 24
  • wagtail/admin/tests/viewsets/test_model_viewset.py+29 0 modified
    @@ -1474,6 +1474,35 @@ def test_edit_form_rendered_with_panels(self):
             modal_workflow_script = soup.select_one(f'script[src="{modal_workflow_js}"]')
             self.assertIsNotNone(modal_workflow_script)
     
    +    def test_field_permissions(self):
    +        self.user.is_superuser = False
    +        self.user.save()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                content_type__app_label="wagtailadmin", codename="access_admin"
    +            ),
    +            Permission.objects.get(
    +                content_type__app_label=self.object._meta.app_label,
    +                codename=get_permission_codename("change", self.object._meta),
    +            ),
    +        )
    +
    +        response = self.client.get(self.url)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertEqual(list(response.context["form"].fields), ["name"])
    +
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                codename="can_set_release_date",
    +            )
    +        )
    +
    +        response = self.client.get(self.url)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertEqual(
    +            list(response.context["form"].fields), ["name", "release_date"]
    +        )
    +
     
     class TestDefaultMessages(WagtailTestUtils, TestCase):
         def setUp(self):
    
  • wagtail/admin/views/generic/models.py+26 0 modified
    @@ -27,6 +27,7 @@
     from wagtail.actions.unpublish import UnpublishAction
     from wagtail.admin import messages
     from wagtail.admin.filters import WagtailFilterSet
    +from wagtail.admin.forms.models import WagtailAdminModelForm
     from wagtail.admin.forms.search import SearchForm
     from wagtail.admin.panels import get_edit_handler
     from wagtail.admin.ui.components import Component, MediaContainer
    @@ -616,6 +617,24 @@ def get_translations(self):
                 for locale in Locale.objects.all().exclude(id=self.locale.id)
             ]
     
    +    def get_initial_form_instance(self):
    +        if self.locale:
    +            instance = self.model()
    +            instance.locale = self.locale
    +            return instance
    +
    +    def get_form_kwargs(self):
    +        if instance := self.get_initial_form_instance():
    +            # super().get_form_kwargs() will use self.object as the instance kwarg
    +            self.object = instance
    +        kwargs = super().get_form_kwargs()
    +
    +        form_class = self.get_form_class()
    +        # Add for_user support for PermissionedForm
    +        if issubclass(form_class, WagtailAdminModelForm):
    +            kwargs["for_user"] = self.request.user
    +        return kwargs
    +
         def save_instance(self):
             """
             Called after the form is successfully validated - saves the object to the db
    @@ -786,6 +805,13 @@ def get_translations(self):
                 for translation in self.object.get_translations().select_related("locale")
             ]
     
    +    def get_form_kwargs(self):
    +        kwargs = super().get_form_kwargs()
    +        form_class = self.get_form_class()
    +        if issubclass(form_class, WagtailAdminModelForm):
    +            kwargs["for_user"] = self.request.user
    +        return kwargs
    +
         def save_instance(self):
             """
             Called after the form is successfully validated - saves the object to the db.
    
  • wagtail/contrib/settings/tests/generic/test_admin.py+88 1 modified
    @@ -14,6 +14,7 @@
         PanelGenericSettings,
         TabbedGenericSettings,
         TestGenericSetting,
    +    TestPermissionedGenericSetting,
     )
     from wagtail.test.utils import WagtailTestUtils
     
    @@ -76,6 +77,11 @@ def edit_url(self, setting):
     class TestGenericSettingCreateView(BaseTestGenericSettingView):
         def setUp(self):
             self.user = self.login()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                content_type__app_label="wagtailadmin", codename="access_admin"
    +            )
    +        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -107,14 +113,51 @@ def test_file_upload_multipart(self):
             # Ensure the form supports file uploads
             self.assertContains(response, 'enctype="multipart/form-data"')
     
    +    def test_create_restricted_field_without_permission(self):
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        self.assertFalse(TestPermissionedGenericSetting.objects.exists())
    +        response = self.post(
    +            post_data={"sensitive_email": "test@example.com", "title": "test"},
    +            setting=TestPermissionedGenericSetting,
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        settings = TestPermissionedGenericSetting.objects.get()
    +        self.assertEqual(settings.title, "test")
    +        self.assertEqual(settings.sensitive_email, "")
    +
    +    def test_create_restricted_field(self):
    +        self.user.is_superuser = False
    +        self.user.save()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
    +        )
    +        self.assertFalse(TestPermissionedGenericSetting.objects.exists())
    +        response = self.post(
    +            post_data={"sensitive_email": "test@example.com", "title": "test"},
    +            setting=TestPermissionedGenericSetting,
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        settings = TestPermissionedGenericSetting.objects.get()
    +        self.assertEqual(settings.title, "test")
    +        self.assertEqual(settings.sensitive_email, "test@example.com")
    +
     
     class TestGenericSettingEditView(BaseTestGenericSettingView):
         def setUp(self):
             self.test_setting = TestGenericSetting()
             self.test_setting.title = "Setting title"
             self.test_setting.save()
     
    -        self.login()
    +        self.user = self.login()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                content_type__app_label="wagtailadmin", codename="access_admin"
    +            )
    +        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -153,6 +196,50 @@ def test_for_request(self):
                 expected_url=f"{url}{TestGenericSetting.objects.first().pk}/",
             )
     
    +    def test_edit_restricted_field(self):
    +        test_setting = TestPermissionedGenericSetting()
    +        test_setting.sensitive_email = "test@example.com"
    +        test_setting.save()
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        self.user.user_permissions.add(
    +            Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
    +        )
    +
    +        response = self.get(setting=TestPermissionedGenericSetting)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertIn("sensitive_email", list(response.context["form"].fields))
    +
    +        response = self.post(
    +            setting=TestPermissionedGenericSetting,
    +            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        test_setting.refresh_from_db()
    +        self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
    +
    +    def test_edit_restricted_field_without_permission(self):
    +        test_setting = TestPermissionedGenericSetting()
    +        test_setting.sensitive_email = "test@example.com"
    +        test_setting.save()
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        response = self.get(setting=TestPermissionedGenericSetting)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertNotIn("sensitive_email", list(response.context["form"].fields))
    +
    +        response = self.post(
    +            setting=TestPermissionedGenericSetting,
    +            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        test_setting.refresh_from_db()
    +        self.assertEqual(test_setting.sensitive_email, "test@example.com")
    +
     
     class TestAdminPermission(WagtailTestUtils, TestCase):
         def test_registered_permission(self):
    
  • wagtail/contrib/settings/tests/site_specific/test_admin.py+92 3 modified
    @@ -14,6 +14,7 @@
         IconSiteSetting,
         PanelSiteSettings,
         TabbedSiteSettings,
    +    TestPermissionedSiteSetting,
         TestSiteSetting,
     )
     from wagtail.test.utils import WagtailTestUtils
    @@ -72,6 +73,11 @@ def edit_url(self, setting, site_pk=1):
     class TestSiteSettingCreateView(BaseTestSiteSettingView):
         def setUp(self):
             self.user = self.login()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                content_type__app_label="wagtailadmin", codename="access_admin"
    +            )
    +        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -103,18 +109,55 @@ def test_file_upload_multipart(self):
             # Ensure the form supports file uploads
             self.assertContains(response, 'enctype="multipart/form-data"')
     
    +    def test_create_restricted_field_without_permission(self):
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        self.assertFalse(TestPermissionedSiteSetting.objects.exists())
    +        response = self.post(
    +            post_data={"sensitive_email": "test@example.com", "title": "test"},
    +            setting=TestPermissionedSiteSetting,
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        settings = TestPermissionedSiteSetting.objects.get()
    +        self.assertEqual(settings.title, "test")
    +        self.assertEqual(settings.sensitive_email, "")
    +
    +    def test_create_restricted_field(self):
    +        self.user.is_superuser = False
    +        self.user.save()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
    +        )
    +        self.assertFalse(TestPermissionedSiteSetting.objects.exists())
    +        response = self.post(
    +            post_data={"sensitive_email": "test@example.com", "title": "test"},
    +            setting=TestPermissionedSiteSetting,
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        settings = TestPermissionedSiteSetting.objects.get()
    +        self.assertEqual(settings.title, "test")
    +        self.assertEqual(settings.sensitive_email, "test@example.com")
    +
     
     class TestSiteSettingEditView(BaseTestSiteSettingView):
         def setUp(self):
    -        default_site = Site.objects.get(is_default_site=True)
    +        self.default_site = Site.objects.get(is_default_site=True)
     
             self.test_setting = TestSiteSetting()
             self.test_setting.title = "Site title"
             self.test_setting.email = "initial@example.com"
    -        self.test_setting.site = default_site
    +        self.test_setting.site = self.default_site
             self.test_setting.save()
     
    -        self.login()
    +        self.user = self.login()
    +        self.user.user_permissions.add(
    +            Permission.objects.get(
    +                content_type__app_label="wagtailadmin", codename="access_admin"
    +            )
    +        )
     
         def test_get_edit(self):
             response = self.get()
    @@ -158,6 +201,52 @@ def test_get_redirect_to_relevant_instance_invalid(self):
             response = self.client.get(url)
             self.assertRedirects(response, status_code=302, expected_url="/admin/")
     
    +    def test_edit_restricted_field(self):
    +        test_setting = TestPermissionedSiteSetting()
    +        test_setting.sensitive_email = "test@example.com"
    +        test_setting.site = self.default_site
    +        test_setting.save()
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        self.user.user_permissions.add(
    +            Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
    +        )
    +
    +        response = self.get(setting=TestPermissionedSiteSetting)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertIn("sensitive_email", list(response.context["form"].fields))
    +
    +        response = self.post(
    +            setting=TestPermissionedSiteSetting,
    +            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        test_setting.refresh_from_db()
    +        self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
    +
    +    def test_edit_restricted_field_without_permission(self):
    +        test_setting = TestPermissionedSiteSetting()
    +        test_setting.sensitive_email = "test@example.com"
    +        test_setting.site = self.default_site
    +        test_setting.save()
    +        self.user.is_superuser = False
    +        self.user.save()
    +
    +        response = self.get(setting=TestPermissionedSiteSetting)
    +        self.assertEqual(response.status_code, 200)
    +        self.assertNotIn("sensitive_email", list(response.context["form"].fields))
    +
    +        response = self.post(
    +            setting=TestPermissionedSiteSetting,
    +            post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
    +        )
    +        self.assertEqual(response.status_code, 302)
    +
    +        test_setting.refresh_from_db()
    +        self.assertEqual(test_setting.sensitive_email, "test@example.com")
    +
     
     @override_settings(
         ALLOWED_HOSTS=["testserver", "example.com", "noneoftheabove.example.com"]
    
  • wagtail/snippets/views/snippets.py+0 19 modified
    @@ -235,22 +235,6 @@ def run_after_hook(self):
         def _get_action_menu(self):
             return SnippetActionMenu(self.request, view=self.view_name, model=self.model)
     
    -    def _get_initial_form_instance(self):
    -        instance = self.model()
    -
    -        # Set locale of the new instance
    -        if self.locale:
    -            instance.locale = self.locale
    -
    -        return instance
    -
    -    def get_form_kwargs(self):
    -        return {
    -            **super().get_form_kwargs(),
    -            "instance": self._get_initial_form_instance(),
    -            "for_user": self.request.user,
    -        }
    -
         def get_side_panels(self):
             side_panels = [
                 SnippetStatusSidePanel(
    @@ -310,9 +294,6 @@ def _get_action_menu(self):
                 locked_for_user=self.locked_for_user,
             )
     
    -    def get_form_kwargs(self):
    -        return {**super().get_form_kwargs(), "for_user": self.request.user}
    -
         def get_side_panels(self):
             side_panels = [
                 SnippetStatusSidePanel(
    
  • wagtail/test/testapp/migrations/0034_testpermissionedgenericsetting_and_more.py+42 0 added
    @@ -0,0 +1,42 @@
    +# Generated by Django 4.2.11 on 2024-04-25 15:51
    +
    +from django.db import migrations, models
    +import django.db.models.deletion
    +
    +
    +class Migration(migrations.Migration):
    +
    +    dependencies = [
    +        ('wagtailcore', '0091_remove_revision_submitted_for_moderation'),
    +        ('tests', '0033_customcopyformpage'),
    +    ]
    +
    +    operations = [
    +        migrations.CreateModel(
    +            name='TestPermissionedGenericSetting',
    +            fields=[
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    +                ('title', models.CharField(max_length=100)),
    +                ('sensitive_email', models.EmailField(max_length=50)),
    +            ],
    +            options={
    +                'permissions': [('can_edit_sensitive_email_generic_setting', 'Can edit sensitive email generic setting.')],
    +            },
    +        ),
    +        migrations.AlterModelOptions(
    +            name='featurecompletetoy',
    +            options={'permissions': [('can_set_release_date', 'Can set release date')]},
    +        ),
    +        migrations.CreateModel(
    +            name='TestPermissionedSiteSetting',
    +            fields=[
    +                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    +                ('title', models.CharField(max_length=100)),
    +                ('sensitive_email', models.EmailField(max_length=50)),
    +                ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
    +            ],
    +            options={
    +                'permissions': [('can_edit_sensitive_email_site_setting', 'Can edit sensitive email site setting.')],
    +            },
    +        ),
    +    ]
    
  • wagtail/test/testapp/models.py+46 0 modified
    @@ -1640,6 +1640,49 @@ class TestGenericSetting(BaseGenericSetting):
         email = models.EmailField(max_length=50)
     
     
    +@register_setting
    +class TestPermissionedGenericSetting(BaseGenericSetting):
    +    title = models.CharField(max_length=100)
    +    sensitive_email = models.EmailField(max_length=50)
    +
    +    panels = [
    +        FieldPanel("title"),
    +        FieldPanel(
    +            "sensitive_email",
    +            permission="tests.can_edit_sensitive_email_generic_setting",
    +        ),
    +    ]
    +
    +    class Meta:
    +        permissions = [
    +            (
    +                "can_edit_sensitive_email_generic_setting",
    +                "Can edit sensitive email generic setting.",
    +            ),
    +        ]
    +
    +
    +@register_setting
    +class TestPermissionedSiteSetting(BaseSiteSetting):
    +    title = models.CharField(max_length=100)
    +    sensitive_email = models.EmailField(max_length=50)
    +
    +    panels = [
    +        FieldPanel("title"),
    +        FieldPanel(
    +            "sensitive_email", permission="tests.can_edit_sensitive_email_site_setting"
    +        ),
    +    ]
    +
    +    class Meta:
    +        permissions = [
    +            (
    +                "can_edit_sensitive_email_site_setting",
    +                "Can edit sensitive email site setting.",
    +            ),
    +        ]
    +
    +
     @register_setting
     class ImportantPagesSiteSetting(BaseSiteSetting):
         sign_up_page = models.ForeignKey(
    @@ -2229,6 +2272,9 @@ def is_cool(self):
         def __str__(self):
             return f"{self.name} ({self.release_date})"
     
    +    class Meta:
    +        permissions = [("can_set_release_date", "Can set release date")]
    +
     
     class PurgeRevisionsProtectedTestModel(models.Model):
         revision = models.OneToOneField(
    
  • wagtail/test/testapp/views.py+1 1 modified
    @@ -227,7 +227,7 @@ class FeatureCompleteToyViewSet(ModelViewSet):
     
         panels = [
             FieldPanel("name"),
    -        FieldPanel("release_date"),
    +        FieldPanel("release_date", permission="tests.can_set_release_date"),
         ]
     
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.