Kaynağa Gözat

Exclude ObjectPermissions API endpoint from EXEMPT_VIEW_PERMISSIONS

Jeremy Stretch 5 yıl önce
ebeveyn
işleme
3e6b257fa0

+ 3 - 0
docs/configuration/optional-settings.md

@@ -172,6 +172,9 @@ To exempt _all_ models from view permission enforcement, set the following. (Not
 EXEMPT_VIEW_PERMISSIONS = ['*']
 ```
 
+!!! note
+    Using a wildcard will not affect certain potentially sensitive models, such as user permissions. If there is a need to exempt these models, they must be specified individually.
+
 ---
 
 ## ENFORCE_GLOBAL_UNIQUE

+ 17 - 1
netbox/users/tests/test_api.py

@@ -1,9 +1,11 @@
 from django.contrib.auth.models import Group, User
 from django.contrib.contenttypes.models import ContentType
+from django.test import override_settings
 from django.urls import reverse
+from rest_framework import status
 
 from users.models import ObjectPermission
-from utilities.testing import APIViewTestCases, APITestCase
+from utilities.testing import APIViewTestCases, APITestCase, disable_warnings
 
 
 class AppTest(APITestCase):
@@ -72,3 +74,17 @@ class ObjectPermissionTest(APIViewTestCases.APIViewTestCase):
                 'constraints': {'name': 'TEST6'},
             },
         ]
+
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+    def test_list_objects_anonymous(self):
+        # Endpoint should never be exposed via EXEMPT_VIEW_PERMISSIONS
+        url = self._get_list_url()
+        with disable_warnings('django.request'):
+            self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)
+
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+    def test_get_object_anonymous(self):
+        # Endpoint should never be exposed via EXEMPT_VIEW_PERMISSIONS
+        url = self._get_detail_url(self._get_queryset().first())
+        with disable_warnings('django.request'):
+            self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_403_FORBIDDEN)

+ 9 - 3
netbox/utilities/permissions.py

@@ -1,6 +1,12 @@
 from django.conf import settings
 from django.contrib.contenttypes.models import ContentType
 
+# Exclude potentially sensitive models from wild view exemption. These may still be exempted
+# by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
+EXEMPT_EXCLUDE_MODELS = (
+    ('users', 'objectpermission'),
+)
+
 
 def get_permission_for_model(model, action):
     """
@@ -63,11 +69,11 @@ def permission_is_exempt(name):
 
     if action == 'view':
         if (
-            # All models are exempt from view permission enforcement
-            '*' in settings.EXEMPT_VIEW_PERMISSIONS
+            # All models (excluding those in EXEMPT_EXCLUDE_MODELS) are exempt from view permission enforcement
+            '*' in settings.EXEMPT_VIEW_PERMISSIONS and (app_label, model_name) not in EXEMPT_EXCLUDE_MODELS
         ) or (
             # This specific model is exempt from view permission enforcement
-            '{}.{}'.format(app_label, model_name) in settings.EXEMPT_VIEW_PERMISSIONS
+            f'{app_label}.{model_name}' in settings.EXEMPT_VIEW_PERMISSIONS
         ):
             return True
 

+ 4 - 3
netbox/utilities/testing/api.py

@@ -103,14 +103,15 @@ class APIViewTestCases:
             url = self._get_list_url()
             response = self.client.get(url, **self.header)
 
-            self.assertEqual(len(response.data['results']), self._get_queryset().count())
             self.assertHttpStatus(response, status.HTTP_200_OK)
+            self.assertEqual(len(response.data['results']), self._get_queryset().count())
 
-        @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+        @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_list_objects_brief(self):
             """
-            GET a list of objects using the "brief" parameter as an unauthenticated user.
+            GET a list of objects using the "brief" parameter.
             """
+            self.add_permissions(f'{self.model._meta.app_label}.view_{self.model._meta.model_name}')
             url = f'{self._get_list_url()}?brief=1'
             response = self.client.get(url, **self.header)