Преглед изворни кода

Fixes #20929: Require render_config permission for UI config rendering (#20975)

* Closes #20929: Require render_config permission for UI config rendering

- Modified `ObjectRenderConfigView.has_permission()` to require both view and render_config permissions
- Added `remove_permissions()` test helper to remove permissions from existing ObjectPermission objects
- Added regression tests for Device and VirtualMachine render-config permission enforcement

The `render_config` permission action was introduced in #16681 for API endpoints. This extends PR_7604_description
to the UI render-config tabs, preventing users from viewing rendered configurations without explicit permission.

* Address PR feedback

* Address PR feedback
Jason Novinger пре 1 месец
родитељ
комит
a364ee832d

+ 23 - 0
netbox/dcim/tests/test_views.py

@@ -11,6 +11,7 @@ from core.models import ObjectType
 from dcim.choices import *
 from dcim.choices import *
 from dcim.constants import *
 from dcim.constants import *
 from dcim.models import *
 from dcim.models import *
+from extras.models import ConfigTemplate
 from ipam.models import ASN, RIR, VLAN, VRF
 from ipam.models import ASN, RIR, VLAN, VRF
 from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, WeightUnitChoices
 from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, WeightUnitChoices
 from tenancy.models import Tenant
 from tenancy.models import Tenant
@@ -2339,6 +2340,28 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
         url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
         self.assertHttpStatus(self.client.get(url), 200)
         self.assertHttpStatus(self.client.get(url), 200)
 
 
+    def test_device_renderconfig(self):
+        configtemplate = ConfigTemplate.objects.create(
+            name='Test Config Template',
+            template_code='Config for device {{ device.name }}'
+        )
+        device = Device.objects.first()
+        device.config_template = configtemplate
+        device.save()
+        url = reverse('dcim:device_render-config', kwargs={'pk': device.pk})
+
+        # User with only view permission should NOT be able to render config
+        self.add_permissions('dcim.view_device')
+        self.assertHttpStatus(self.client.get(url), 403)
+
+        # With render_config permission added should be able to render config
+        self.add_permissions('dcim.render_config_device')
+        self.assertHttpStatus(self.client.get(url), 200)
+
+        # With view permission removed should NOT be able to render config
+        self.remove_permissions('dcim.view_device')
+        self.assertHttpStatus(self.client.get(url), 403)
+
 
 
 class ModuleTestCase(
 class ModuleTestCase(
     # Module does not support bulk renaming (no name field) or
     # Module does not support bulk renaming (no name field) or

+ 1 - 0
netbox/dcim/views.py

@@ -2682,6 +2682,7 @@ class DeviceConfigContextView(ObjectConfigContextView):
 class DeviceRenderConfigView(ObjectRenderConfigView):
 class DeviceRenderConfigView(ObjectRenderConfigView):
     queryset = Device.objects.all()
     queryset = Device.objects.all()
     base_template = 'dcim/device/base.html'
     base_template = 'dcim/device/base.html'
+    additional_permissions = ['dcim.render_config_device']
     tab = ViewTab(
     tab = ViewTab(
         label=_('Render Config'),
         label=_('Render Config'),
         weight=2100,
         weight=2100,

+ 10 - 0
netbox/utilities/testing/base.py

@@ -67,6 +67,16 @@ class TestCase(_TestCase):
             obj_perm.users.add(self.user)
             obj_perm.users.add(self.user)
             obj_perm.object_types.add(object_type)
             obj_perm.object_types.add(object_type)
 
 
+    def remove_permissions(self, *names):
+        """
+        Remove a set of permissions from the test user. Accepts permission names in the form <app>.<action>_<model>.
+        """
+        for name in names:
+            object_type, action = resolve_permission_type(name)
+            ObjectPermission.objects.filter(
+                actions__contains=[action], object_types=object_type, users=self.user
+            ).delete()
+
     #
     #
     # Custom assertions
     # Custom assertions
     #
     #

+ 23 - 0
netbox/virtualization/tests/test_views.py

@@ -4,6 +4,7 @@ from django.urls import reverse
 
 
 from dcim.choices import InterfaceModeChoices
 from dcim.choices import InterfaceModeChoices
 from dcim.models import DeviceRole, Platform, Site
 from dcim.models import DeviceRole, Platform, Site
+from extras.models import ConfigTemplate
 from ipam.models import VLAN, VRF
 from ipam.models import VLAN, VRF
 from utilities.testing import ViewTestCases, create_tags, create_test_device, create_test_virtualmachine
 from utilities.testing import ViewTestCases, create_tags, create_test_device, create_test_virtualmachine
 from virtualization.choices import *
 from virtualization.choices import *
@@ -326,6 +327,28 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
         url = reverse('virtualization:virtualmachine_interfaces', kwargs={'pk': virtualmachine.pk})
         self.assertHttpStatus(self.client.get(url), 200)
         self.assertHttpStatus(self.client.get(url), 200)
 
 
+    def test_virtualmachine_renderconfig(self):
+        configtemplate = ConfigTemplate.objects.create(
+            name='Test Config Template',
+            template_code='Config for VM {{ virtualmachine.name }}'
+        )
+        vm = VirtualMachine.objects.first()
+        vm.config_template = configtemplate
+        vm.save()
+        url = reverse('virtualization:virtualmachine_render-config', kwargs={'pk': vm.pk})
+
+        # User with only view permission should NOT be able to render config
+        self.add_permissions('virtualization.view_virtualmachine')
+        self.assertHttpStatus(self.client.get(url), 403)
+
+        # With render_config permission added should be able to render config
+        self.add_permissions('virtualization.render_config_virtualmachine')
+        self.assertHttpStatus(self.client.get(url), 200)
+
+        # With view permission removed should NOT be able to render config
+        self.remove_permissions('virtualization.view_virtualmachine')
+        self.assertHttpStatus(self.client.get(url), 403)
+
 
 
 class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
 class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
     model = VMInterface
     model = VMInterface

+ 1 - 0
netbox/virtualization/views.py

@@ -405,6 +405,7 @@ class VirtualMachineConfigContextView(ObjectConfigContextView):
 class VirtualMachineRenderConfigView(ObjectRenderConfigView):
 class VirtualMachineRenderConfigView(ObjectRenderConfigView):
     queryset = VirtualMachine.objects.all()
     queryset = VirtualMachine.objects.all()
     base_template = 'virtualization/virtualmachine/base.html'
     base_template = 'virtualization/virtualmachine/base.html'
+    additional_permissions = ['virtualization.render_config_virtualmachine']
     tab = ViewTab(
     tab = ViewTab(
         label=_('Render Config'),
         label=_('Render Config'),
         weight=2100,
         weight=2100,