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

This commit is contained in:
Arthur Hanson 2026-06-12 06:16:41 -07:00 committed by GitHub
parent 9f905cf842
commit 0b192cf588
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 0 deletions

View File

@ -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):

View File

@ -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):