mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-23 12:08:43 -06:00
Remove the add permission requirement from the render-config API endpoint while maintaining token write_enabled enforcement as specified in #16681. Changes: - Add TokenWritePermission class to check token write ability without requiring specific model permissions - Override get_permissions() in RenderConfigMixin to use TokenWritePermission instead of TokenPermissions for render_config action - Replace queryset restriction: use render_config instead of add - Remove add permissions from tests - render_config permission now sufficient - Update tests to expect 404 when permission denied (NetBox standard pattern) Per #16681: 'requirement for write permission makes sense for API calls (because we're accepting and processing arbitrary user data), the specific permission for creating devices does not'
104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
from django.utils.translation import gettext_lazy as _
|
|
from jinja2.exceptions import TemplateError
|
|
from rest_framework.decorators import action
|
|
from rest_framework.exceptions import PermissionDenied
|
|
from rest_framework.renderers import JSONRenderer
|
|
from rest_framework.response import Response
|
|
from rest_framework.status import HTTP_400_BAD_REQUEST
|
|
|
|
from netbox.api.authentication import TokenWritePermission
|
|
from netbox.api.renderers import TextRenderer
|
|
from utilities.permissions import get_permission_for_model
|
|
from .serializers import ConfigTemplateSerializer
|
|
|
|
__all__ = (
|
|
'ConfigContextQuerySetMixin',
|
|
'ConfigTemplateRenderMixin',
|
|
'RenderConfigMixin',
|
|
)
|
|
|
|
|
|
class ConfigContextQuerySetMixin:
|
|
"""
|
|
Used by views that work with config context models (device and virtual machine).
|
|
Provides a get_queryset() method which deals with adding the config context
|
|
data annotation or not.
|
|
"""
|
|
def get_queryset(self):
|
|
"""
|
|
Build the proper queryset based on the request context
|
|
|
|
If the `brief` query param equates to True or the `exclude` query param
|
|
includes `config_context` as a value, return the base queryset.
|
|
|
|
Else, return the queryset annotated with config context data
|
|
"""
|
|
queryset = super().get_queryset()
|
|
request = self.get_serializer_context()['request']
|
|
if self.brief or 'config_context' in request.query_params.get('exclude', []):
|
|
return queryset
|
|
return queryset.annotate_config_context_data()
|
|
|
|
|
|
class ConfigTemplateRenderMixin:
|
|
"""
|
|
Provides a method to return a rendered ConfigTemplate as REST API data.
|
|
"""
|
|
def render_configtemplate(self, request, configtemplate, context):
|
|
try:
|
|
output = configtemplate.render(context=context)
|
|
except TemplateError as e:
|
|
return Response({
|
|
'detail': f"An error occurred while rendering the template (line {e.lineno}): {e}"
|
|
}, status=500)
|
|
|
|
# If the client has requested "text/plain", return the raw content.
|
|
if request.accepted_renderer.format == 'txt':
|
|
return Response(output)
|
|
|
|
template_serializer = ConfigTemplateSerializer(configtemplate, nested=True, context={'request': request})
|
|
|
|
return Response({
|
|
'configtemplate': template_serializer.data,
|
|
'content': output
|
|
})
|
|
|
|
|
|
class RenderConfigMixin(ConfigTemplateRenderMixin):
|
|
"""
|
|
Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned.
|
|
"""
|
|
|
|
def get_permissions(self):
|
|
# For render_config action, check only token write ability (not model permissions)
|
|
if self.action == 'render_config':
|
|
return [TokenWritePermission()]
|
|
return super().get_permissions()
|
|
|
|
@action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
|
|
def render_config(self, request, pk):
|
|
"""
|
|
Resolve and render the preferred ConfigTemplate for this Device.
|
|
"""
|
|
self.queryset = self.queryset.model.objects.all().restrict(request.user, 'render_config')
|
|
instance = self.get_object()
|
|
|
|
# Check render_config permission
|
|
perm = get_permission_for_model(instance, 'render_config')
|
|
if not request.user.has_perm(perm, obj=instance):
|
|
raise PermissionDenied(_("This user does not have permission to render configurations for this object."))
|
|
|
|
object_type = instance._meta.model_name
|
|
configtemplate = instance.get_config_template()
|
|
if not configtemplate:
|
|
return Response({
|
|
'error': f'No config template found for this {object_type}.'
|
|
}, status=HTTP_400_BAD_REQUEST)
|
|
|
|
# Compile context data
|
|
context_data = instance.get_config_context()
|
|
context_data.update(request.data)
|
|
context_data.update({object_type: instance})
|
|
|
|
return self.render_configtemplate(request, configtemplate, context_data)
|