diff --git a/netbox/extras/api/mixins.py b/netbox/extras/api/mixins.py index 1737ff9f8..832dee607 100644 --- a/netbox/extras/api/mixins.py +++ b/netbox/extras/api/mixins.py @@ -4,6 +4,8 @@ from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.status import HTTP_400_BAD_REQUEST +from dcim.models import Device +from netbox.api.authentication import ViewOnlyPermissions from netbox.api.renderers import TextRenderer from .nested_serializers import NestedConfigTemplateSerializer @@ -61,14 +63,23 @@ class ConfigTemplateRenderMixin: class RenderConfigMixin(ConfigTemplateRenderMixin): + """ + Override initial() to save a copy of the queryset for "un-restricting" the queryset when rendering. + """ + def initial(self, request, *args, **kwargs): + self.original_qs = self.queryset + super().initial(request, *args, **kwargs) + """ Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned. """ - @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer]) + @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer], + permission_classes=[ViewOnlyPermissions]) def render_config(self, request, pk): """ Resolve and render the preferred ConfigTemplate for this Device. """ + self.queryset = self.original_queryset.restrict(request.user, 'view') instance = self.get_object() object_type = instance._meta.model_name configtemplate = instance.get_config_template() diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py index f0bd5fd27..e5dc748be 100644 --- a/netbox/netbox/api/authentication.py +++ b/netbox/netbox/api/authentication.py @@ -124,6 +124,21 @@ class TokenPermissions(DjangoObjectPermissions): return super().has_object_permission(request, view, obj) +class ViewOnlyPermissions(TokenPermissions): + """ + Override the stock perm_map to require only view permissions + """ + perms_map = { + 'GET': ['%(app_label)s.view_%(model_name)s'], + 'OPTIONS': [], + 'HEAD': ['%(app_label)s.view_%(model_name)s'], + 'POST': ['%(app_label)s.view_%(model_name)s'], + 'PUT': ['%(app_label)s.view_%(model_name)s'], + 'PATCH': ['%(app_label)s.view_%(model_name)s'], + 'DELETE': ['%(app_label)s.view_%(model_name)s'], + } + + class IsAuthenticatedOrLoginNotRequired(BasePermission): """ Returns True if the user is authenticated or LOGIN_REQUIRED is False.