mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-23 12:08:43 -06:00
Add render_config permission to ConfigTemplate render endpoint
Extend render_config permission requirement to the ConfigTemplate render endpoint per issue comments. Changes: - Add TokenWritePermission check via get_permissions() override in ConfigTemplateViewSet - Restrict queryset to render_config permission in render() method - Add explicit render_config permission check - Add tests for ConfigTemplate.render() with and without permission - Update documentation to include ConfigTemplate endpoint
This commit is contained in:
@@ -92,4 +92,8 @@ http://netbox:8000/api/extras/config-templates/123/render/ \
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! note "Permissions"
|
!!! note "Permissions"
|
||||||
Rendering device or virtual machine configurations via the REST API requires the `render_config` permission for the relevant object type. For example, to render a device's configuration via `/api/dcim/devices/{id}/render-config/`, a user must be assigned a permission for the "DCIM > Device" object type with the `render_config` action.
|
Rendering configuration templates via the REST API requires the `render_config` permission for the relevant object type:
|
||||||
|
|
||||||
|
* To render a device's configuration via `/api/dcim/devices/{id}/render-config/`, assign a permission for "DCIM > Device" with the `render_config` action
|
||||||
|
* To render a virtual machine's configuration via `/api/virtualization/virtual-machines/{id}/render-config/`, assign a permission for "Virtualization > Virtual Machine" with the `render_config` action
|
||||||
|
* To render a config template directly via `/api/extras/config-templates/{id}/render/`, assign a permission for "Extras > Config Template" with the `render_config` action
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -16,12 +17,13 @@ from rq import Worker
|
|||||||
from extras import filtersets
|
from extras import filtersets
|
||||||
from extras.jobs import ScriptJob
|
from extras.jobs import ScriptJob
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired, TokenWritePermission
|
||||||
from netbox.api.features import SyncedDataMixin
|
from netbox.api.features import SyncedDataMixin
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
from netbox.api.renderers import TextRenderer
|
from netbox.api.renderers import TextRenderer
|
||||||
from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet
|
from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet
|
||||||
from utilities.exceptions import RQWorkerNotRunningException
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
|
from utilities.permissions import get_permission_for_model
|
||||||
from utilities.request import copy_safe_request
|
from utilities.request import copy_safe_request
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from .mixins import ConfigTemplateRenderMixin
|
from .mixins import ConfigTemplateRenderMixin
|
||||||
@@ -238,13 +240,26 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
|
|||||||
serializer_class = serializers.ConfigTemplateSerializer
|
serializer_class = serializers.ConfigTemplateSerializer
|
||||||
filterset_class = filtersets.ConfigTemplateFilterSet
|
filterset_class = filtersets.ConfigTemplateFilterSet
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
# For render action, check only token write ability (not model permissions)
|
||||||
|
if self.action == 'render':
|
||||||
|
return [TokenWritePermission()]
|
||||||
|
return super().get_permissions()
|
||||||
|
|
||||||
@action(detail=True, methods=['post'], renderer_classes=[JSONRenderer, TextRenderer])
|
@action(detail=True, methods=['post'], renderer_classes=[JSONRenderer, TextRenderer])
|
||||||
def render(self, request, pk):
|
def render(self, request, pk):
|
||||||
"""
|
"""
|
||||||
Render a ConfigTemplate using the context data provided (if any). If the client requests "text/plain" data,
|
Render a ConfigTemplate using the context data provided (if any). If the client requests "text/plain" data,
|
||||||
return the raw rendered content, rather than serialized JSON.
|
return the raw rendered content, rather than serialized JSON.
|
||||||
"""
|
"""
|
||||||
|
self.queryset = self.queryset.model.objects.all().restrict(request.user, 'render_config')
|
||||||
configtemplate = self.get_object()
|
configtemplate = self.get_object()
|
||||||
|
|
||||||
|
# Check render_config permission
|
||||||
|
perm = get_permission_for_model(configtemplate, 'render_config')
|
||||||
|
if not request.user.has_perm(perm, obj=configtemplate):
|
||||||
|
raise PermissionDenied(_("This user does not have permission to render configuration templates."))
|
||||||
|
|
||||||
context = request.data
|
context = request.data
|
||||||
|
|
||||||
return self.render_configtemplate(request, configtemplate, context)
|
return self.render_configtemplate(request, configtemplate, context)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import datetime
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import make_aware, now
|
from django.utils.timezone import make_aware, now
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
from core.events import *
|
from core.events import *
|
||||||
@@ -854,6 +855,23 @@ class ConfigTemplateTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
ConfigTemplate.objects.bulk_create(config_templates)
|
ConfigTemplate.objects.bulk_create(config_templates)
|
||||||
|
|
||||||
|
def test_render(self):
|
||||||
|
configtemplate = ConfigTemplate.objects.first()
|
||||||
|
|
||||||
|
self.add_permissions('extras.render_config_configtemplate')
|
||||||
|
url = reverse('extras-api:configtemplate-detail', kwargs={'pk': configtemplate.pk}) + 'render/'
|
||||||
|
response = self.client.post(url, {'foo': 'bar'}, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['content'], 'Foo: bar')
|
||||||
|
|
||||||
|
def test_render_without_permission(self):
|
||||||
|
configtemplate = ConfigTemplate.objects.first()
|
||||||
|
|
||||||
|
# No permissions added - user has no render_config permission
|
||||||
|
url = reverse('extras-api:configtemplate-detail', kwargs={'pk': configtemplate.pk}) + 'render/'
|
||||||
|
response = self.client.post(url, {'foo': 'bar'}, format='json', **self.header)
|
||||||
|
self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
class ScriptTest(APITestCase):
|
class ScriptTest(APITestCase):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user