Ver código fonte

Closes #22411:Enforce token write ability when executing custom scripts via the REST API

Arthur Hanson 3 semanas atrás
pai
commit
0b192cf588
2 arquivos alterados com 46 adições e 0 exclusões
  1. 7 0
      netbox/extras/api/views.py
  2. 39 0
      netbox/extras/tests/test_api.py

+ 7 - 0
netbox/extras/api/views.py

@@ -21,6 +21,7 @@ from netbox.api.metadata import ContentTypeMetadata
 from netbox.api.renderers import TextRenderer
 from netbox.api.viewsets import BaseViewSet, NetBoxModelViewSet
 from netbox.api.viewsets.mixins import ObjectValidationMixin
+from users.models import Token
 from utilities.exceptions import RQWorkerNotRunningException
 from utilities.request import copy_safe_request
 from utilities.rqworker import any_workers_for_queue
@@ -332,6 +333,12 @@ class ScriptViewSet(ModelViewSet):
         Run a Script identified by its numeric PK or module & name and return the pending Job as the result
         """
 
+        # Running a script is a state-changing operation. If token authentication is in use, enforce the token's
+        # write ability before performing any object lookup. Session-authenticated requests are unaffected
+        # (request.auth is not a Token).
+        if isinstance(request.auth, Token) and not request.auth.write_enabled:
+            raise PermissionDenied("This token does not permit write operations.")
+
         script = self._get_script(pk)
 
         if not request.user.has_perm('extras.run_script', obj=script):

+ 39 - 0
netbox/extras/tests/test_api.py

@@ -1239,6 +1239,45 @@ class ScriptTestCase(APITestCase):
             # Restore the original setting for other tests
             self.TestScriptClass.Meta.scheduling_enabled = original
 
+    def test_run_token_write_enabled(self):
+        """
+        Running a script is an unsafe (state-changing) action and must be rejected when the calling token has
+        write_enabled=False, even if the user holds the run_script permission.
+        """
+        self.add_permissions('extras.run_script')
+        payload = {
+            'data': {'var1': 'hello', 'var2': 1, 'var3': False},
+            'commit': True,
+        }
+
+        # A token with write_enabled=False should be rejected
+        token = Token.objects.create(version=2, user=self.user, write_enabled=False)
+        token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}'
+        response = self.client.post(self.url, payload, format='json', HTTP_AUTHORIZATION=token_header)
+        self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
+
+        # Enabling write ability on the token should allow the script to run
+        token.write_enabled = True
+        token.save()
+        response = self.client.post(self.url, payload, format='json', HTTP_AUTHORIZATION=token_header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+    def test_run_session_auth(self):
+        """
+        The token write-ability check applies only to token authentication. Session-authenticated requests
+        (where request.auth is not a Token) must still be allowed to run scripts.
+        """
+        self.add_permissions('extras.run_script')
+        payload = {
+            'data': {'var1': 'hello', 'var2': 1, 'var3': False},
+            'commit': True,
+        }
+
+        # Authenticate via session rather than a token; request.auth is None
+        self.client.force_authenticate(user=self.user)
+        response = self.client.post(self.url, payload, format='json')
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
 
 class CreatedUpdatedFilterTestCase(APITestCase):