Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ def get_duplicate_cluster(self, request, pk):
)
@action(detail=True, methods=["post"], url_path=r"duplicate/reset", permission_classes=(IsAuthenticated, permissions.UserHasFindingRelatedObjectPermission))
def reset_finding_duplicate_status(self, request, pk):
self.get_object()
checked_duplicate_id = reset_finding_duplicate_status_internal(
request.user, pk,
)
Expand All @@ -1566,6 +1567,7 @@ def reset_finding_duplicate_status(self, request, pk):
detail=True, methods=["post"], url_path=r"original/(?P<new_fid>\d+)", permission_classes=(IsAuthenticated, permissions.UserHasFindingRelatedObjectPermission),
)
def set_finding_as_original(self, request, pk, new_fid):
self.get_object()
success = set_finding_as_original_internal(request.user, pk, new_fid)
if not success:
return Response(status=status.HTTP_400_BAD_REQUEST)
Expand Down
12 changes: 5 additions & 7 deletions unittests/test_permissions_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1571,18 +1571,16 @@ def test_finding_metadata_reader_allowed(self):
self.assertEqual(response.status_code, 200, response.content)

# ── Finding: duplicate status actions ──────────────────────────────
# NOTE: reset_finding_duplicate_status and set_finding_as_original
# bypass self.get_object(), so DRF object-level permission checks
# (has_object_permission) never fire. The internal helpers do their
# own permission checks. These tests verify current behaviour.
# reset_finding_duplicate_status and set_finding_as_original call
# self.get_object() so DRF's object-level permission check runs via
# UserHasFindingRelatedObjectPermission (POST -> Finding_Edit).

def test_finding_reset_duplicate_reader(self):
"""View bypasses get_object()internal helper checks permissions."""
"""Reader lacks Finding_EditPOST must be denied before the helper runs."""
client = self._client_for_user(self.reader_user)
url = reverse("finding-reset-finding-duplicate-status", args=(self.finding.id,))
response = client.post(url, format="json")
# Returns 400 (not a duplicate) — internal helper runs before perm check
self.assertEqual(response.status_code, 400, response.content)
self.assertEqual(response.status_code, 403, response.content)

def test_finding_reset_duplicate_writer(self):
client = self._client_for_user(self.writer_user)
Expand Down
64 changes: 64 additions & 0 deletions unittests/test_rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,70 @@ def test_post_without_finding_returns_4xx(self):
self.assertLess(response.status_code, 500)


@versioned_fixtures
class FindingActionAuthzTest(DojoAPITestCase):

fixtures = ["dojo_testdata.json"]

def _client_for(self, username):
user = User.objects.get(username=username)
token = Token.objects.get(user=user)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
return client

def test_admin_can_reset_finding_duplicate_status(self):
client = self._client_for("admin")
# Mark finding 2 as a duplicate of finding 3 first, then reset.
set_response = client.post("/api/v2/findings/2/original/3/")
self.assertEqual(set_response.status_code, status.HTTP_204_NO_CONTENT, set_response.content[:500])
reset_response = client.post("/api/v2/findings/2/duplicate/reset/")
self.assertEqual(reset_response.status_code, status.HTTP_204_NO_CONTENT, reset_response.content[:500])
refreshed = Finding.objects.get(pk=2)
self.assertFalse(refreshed.duplicate)
self.assertIsNone(refreshed.duplicate_finding)

def test_admin_can_set_finding_as_original(self):
client = self._client_for("admin")
response = client.post("/api/v2/findings/2/original/3/")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT, response.content[:500])
refreshed = Finding.objects.get(pk=2)
self.assertTrue(refreshed.duplicate)
self.assertEqual(refreshed.duplicate_finding_id, 3)

def test_unrelated_user_cannot_reset_finding_duplicate_status(self):
client = self._client_for("user2")
# Sanity: finding 7 is not visible to this user.
self.assertEqual(client.get("/api/v2/findings/7/").status_code, 404)

before = Finding.objects.get(pk=7)
before_duplicate = before.duplicate
before_duplicate_finding_id = before.duplicate_finding_id

response = client.post("/api/v2/findings/7/duplicate/reset/")
self.assertIn(response.status_code, (403, 404), response.content[:500])

after = Finding.objects.get(pk=7)
self.assertEqual(after.duplicate, before_duplicate)
self.assertEqual(after.duplicate_finding_id, before_duplicate_finding_id)

def test_unrelated_user_cannot_set_finding_as_original(self):
client = self._client_for("user2")
# Sanity: finding 7 is not visible to this user.
self.assertEqual(client.get("/api/v2/findings/7/").status_code, 404)

before = Finding.objects.get(pk=7)
before_duplicate = before.duplicate
before_duplicate_finding_id = before.duplicate_finding_id

response = client.post("/api/v2/findings/7/original/2/")
self.assertIn(response.status_code, (403, 404), response.content[:500])

after = Finding.objects.get(pk=7)
self.assertEqual(after.duplicate, before_duplicate)
self.assertEqual(after.duplicate_finding_id, before_duplicate_finding_id)


@versioned_fixtures
class FilesTest(DojoAPITestCase):
fixtures = ["dojo_testdata.json"]
Expand Down
Loading