Просмотр исходного кода

Fixes #5470: Fix exception when making OPTIONS request for a REST API list endpoint

Jeremy Stretch 5 лет назад
Родитель
Сommit
c835ec5102

+ 1 - 0
docs/release-notes/version-2.10.md

@@ -12,6 +12,7 @@
 * [#5463](https://github.com/netbox-community/netbox/issues/5463) - Back-to-back Circuit Termination throws AttributeError exception
 * [#5465](https://github.com/netbox-community/netbox/issues/5465) - Correct return URL when disconnecting a cable from a device
 * [#5466](https://github.com/netbox-community/netbox/issues/5466) - Fix validation for required custom fields
+* [#5470](https://github.com/netbox-community/netbox/issues/5470) - Fix exception when making `OPTIONS` request for a REST API list endpoint
 
 ---
 

+ 36 - 1
netbox/netbox/api/metadata.py

@@ -1,10 +1,45 @@
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
 from django.utils.encoding import force_str
+from rest_framework import exceptions
 from rest_framework.metadata import SimpleMetadata
+from rest_framework.request import clone_request
 
 from netbox.api import ContentTypeField
 
 
-class ContentTypeMetadata(SimpleMetadata):
+class BulkOperationMetadata(SimpleMetadata):
+
+    def determine_actions(self, request, view):
+        """
+        Replace the stock determine_actions() method to assess object permissions only
+        when viewing a specific object. This is necessary to support OPTIONS requests
+        with bulk update in place (see #5470).
+        """
+        actions = {}
+        for method in {'PUT', 'POST'} & set(view.allowed_methods):
+            view.request = clone_request(request, method)
+            try:
+                # Test global permissions
+                if hasattr(view, 'check_permissions'):
+                    view.check_permissions(view.request)
+                # Test object permissions (if viewing a specific object)
+                if method == 'PUT' and view.lookup_url_kwarg and hasattr(view, 'get_object'):
+                    view.get_object()
+            except (exceptions.APIException, PermissionDenied, Http404):
+                pass
+            else:
+                # If user has appropriate permissions for the view, include
+                # appropriate metadata about the fields that should be supplied.
+                serializer = view.get_serializer()
+                actions[method] = self.get_serializer_info(serializer)
+            finally:
+                view.request = request
+
+        return actions
+
+
+class ContentTypeMetadata(BulkOperationMetadata):
 
     def get_field_info(self, field):
         field_info = super().get_field_info(field)

+ 1 - 0
netbox/netbox/settings.py

@@ -467,6 +467,7 @@ REST_FRAMEWORK = {
     'DEFAULT_FILTER_BACKENDS': (
         'django_filters.rest_framework.DjangoFilterBackend',
     ),
+    'DEFAULT_METADATA_CLASS': 'netbox.api.metadata.BulkOperationMetadata',
     'DEFAULT_PAGINATION_CLASS': 'netbox.api.pagination.OptionalLimitOffsetPagination',
     'DEFAULT_PERMISSION_CLASSES': (
         'netbox.api.authentication.TokenPermissions',

+ 17 - 0
netbox/utilities/testing/api.py

@@ -109,6 +109,15 @@ class APIViewTestCases:
             url = self._get_detail_url(instance2)
             self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_404_NOT_FOUND)
 
+        @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+        def test_options_object(self):
+            """
+            Make an OPTIONS request for a single object.
+            """
+            url = self._get_detail_url(self._get_queryset().first())
+            response = self.client.options(url, **self.header)
+            self.assertHttpStatus(response, status.HTTP_200_OK)
+
     class ListObjectsViewTestCase(APITestCase):
         brief_fields = []
 
@@ -174,6 +183,14 @@ class APIViewTestCases:
             self.assertHttpStatus(response, status.HTTP_200_OK)
             self.assertEqual(len(response.data['results']), 2)
 
+        @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+        def test_options_objects(self):
+            """
+            Make an OPTIONS request for a list endpoint.
+            """
+            response = self.client.options(self._get_list_url(), **self.header)
+            self.assertHttpStatus(response, status.HTTP_200_OK)
+
     class CreateObjectViewTestCase(APITestCase):
         create_data = []
         validation_excluded_fields = []