Skip to content

Commit 04c3d89

Browse files
Maffoochclaude
andauthored
Use a dedicated permission class for BurpRawRequestResponseViewSet (#14838)
* Use a dedicated permission class for BurpRawRequestResponseViewSet The top-level /api/v2/request_response_pairs/ viewset reused UserHasFindingRelatedObjectPermission, which is shaped for @action(detail=True) endpoints where DRF resolves the parent finding from the URL. On a top-level POST there is no parent object resolved yet, so the create flow only ran has_object_permission against the not-yet-saved row and effectively skipped any check on the client-supplied "finding" foreign key. Introduce UserHasBurpRawRequestResponsePermission, which validates the parent finding against Finding_Edit on POST via check_post_permission, mirroring the pattern already used by UserHasFindingPermission, UserHasProductPermission, and the other parent-keyed viewsets. has_object_permission dereferences obj.finding for retrieve/update/delete so list/detail/PUT/PATCH/DELETE behavior is unchanged. Add regression coverage in unittests/test_rest_framework.py asserting the positive control still works, that an authenticated user without membership cannot create a pair on a hidden finding, and that POSTs missing the finding key are rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use versioned_fixtures for RequestResponsePairsAuthzTest The dojo_testdata.json fixture contains Endpoint rows, which raise NotImplementedError in Endpoint.__init__ when V3_FEATURE_LOCATIONS is enabled. Mirror the surrounding API test classes by applying the @versioned_fixtures decorator so the locations-aware fixture is loaded on the V3 matrix leg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b70c293 commit 04c3d89

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

dojo/api_v2/permissions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,23 @@ class UserHasFindingNotePermission(BaseRelatedObjectPermission):
455455
}
456456

457457

458+
class UserHasBurpRawRequestResponsePermission(permissions.BasePermission):
459+
def has_permission(self, request, view):
460+
return check_post_permission(
461+
request, Finding, "finding", Permissions.Finding_Edit,
462+
)
463+
464+
def has_object_permission(self, request, view, obj):
465+
return check_object_permission(
466+
request,
467+
obj.finding,
468+
Permissions.Finding_View,
469+
Permissions.Finding_Edit,
470+
Permissions.Finding_Edit,
471+
Permissions.Finding_Edit,
472+
)
473+
474+
458475
class UserHasImportPermission(permissions.BasePermission):
459476
def has_permission(self, request, view):
460477
# permission check takes place before validation, so we don't have access to serializer.validated_data()

dojo/api_v2/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3011,7 +3011,7 @@ class BurpRawRequestResponseViewSet(
30113011
filterset_fields = ["finding"]
30123012
permission_classes = (
30133013
IsAuthenticated,
3014-
permissions.UserHasFindingRelatedObjectPermission,
3014+
permissions.UserHasBurpRawRequestResponsePermission,
30153015
)
30163016

30173017
def get_queryset(self):

unittests/test_rest_framework.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,67 @@ def test_request_response_get(self):
17881788
self.assertEqual(200, response.status_code, response.content[:1000])
17891789

17901790

1791+
@versioned_fixtures
1792+
class RequestResponsePairsAuthzTest(DojoAPITestCase):
1793+
1794+
fixtures = ["dojo_testdata.json"]
1795+
1796+
def _client_for(self, username):
1797+
user = User.objects.get(username=username)
1798+
token = Token.objects.get(user=user)
1799+
client = APIClient()
1800+
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
1801+
return client
1802+
1803+
def test_admin_can_create_request_response_pair_positive_control(self):
1804+
client = self._client_for("admin")
1805+
before = BurpRawRequestResponse.objects.filter(finding_id=7).count()
1806+
response = client.post(
1807+
"/api/v2/request_response_pairs/",
1808+
dumps({
1809+
"finding": 7,
1810+
"burpRequestBase64": "cmVxdWVzdAo=",
1811+
"burpResponseBase64": "cmVzcG9uc2UK",
1812+
}),
1813+
content_type="application/json",
1814+
)
1815+
self.assertEqual(response.status_code, 201, response.content[:1000])
1816+
self.assertEqual(BurpRawRequestResponse.objects.filter(finding_id=7).count(), before + 1)
1817+
1818+
def test_unrelated_user_cannot_create_request_response_pair_on_hidden_finding(self):
1819+
client = self._client_for("user2")
1820+
# Sanity: the victim finding is genuinely hidden from this user.
1821+
get_response = client.get("/api/v2/findings/7/")
1822+
self.assertEqual(get_response.status_code, 404)
1823+
1824+
before = BurpRawRequestResponse.objects.filter(finding_id=7).count()
1825+
response = client.post(
1826+
"/api/v2/request_response_pairs/",
1827+
dumps({
1828+
"finding": 7,
1829+
"burpRequestBase64": "cmVxdWVzdAo=",
1830+
"burpResponseBase64": "cmVzcG9uc2UK",
1831+
}),
1832+
content_type="application/json",
1833+
)
1834+
self.assertIn(response.status_code, (403, 404), response.content[:1000])
1835+
self.assertEqual(BurpRawRequestResponse.objects.filter(finding_id=7).count(), before)
1836+
1837+
def test_post_without_finding_returns_4xx(self):
1838+
client = self._client_for("user2")
1839+
response = client.post(
1840+
"/api/v2/request_response_pairs/",
1841+
dumps({
1842+
"burpRequestBase64": "cmVxdWVzdAo=",
1843+
"burpResponseBase64": "cmVzcG9uc2UK",
1844+
}),
1845+
content_type="application/json",
1846+
)
1847+
# check_post_permission raises ParseError (400) when "finding" is omitted.
1848+
self.assertGreaterEqual(response.status_code, 400)
1849+
self.assertLess(response.status_code, 500)
1850+
1851+
17911852
@versioned_fixtures
17921853
class FilesTest(DojoAPITestCase):
17931854
fixtures = ["dojo_testdata.json"]

0 commit comments

Comments
 (0)