Skip to content

Commit e52a7dd

Browse files
committed
wip
1 parent 541e956 commit e52a7dd

5 files changed

Lines changed: 127 additions & 23 deletions

File tree

dojo/api_v2/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2976,6 +2976,11 @@ def validate(self, data):
29762976
return data
29772977

29782978

2979+
class FindingVerifySerializer(serializers.Serializer):
2980+
note = serializers.CharField(required=False, allow_blank=True)
2981+
note_type = serializers.PrimaryKeyRelatedField(required=False, allow_null=True, queryset=Note_Type.objects.all())
2982+
2983+
29792984
class ReportGenerateOptionSerializer(serializers.Serializer):
29802985
include_finding_notes = serializers.BooleanField(default=False)
29812986
include_finding_images = serializers.BooleanField(default=False)

dojo/api_v2/views.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,32 @@ def close(self, request, pk=None):
10141014
serialized_finding = serializers.FindingCloseSerializer(finding, context={"request": request})
10151015
return Response(serialized_finding.data)
10161016

1017+
@extend_schema(
1018+
methods=["POST"],
1019+
request=serializers.FindingVerifySerializer,
1020+
responses={status.HTTP_200_OK: serializers.FindingSerializer},
1021+
)
1022+
@action(detail=True, methods=["post"], permission_classes=(IsAuthenticated, permissions.UserHasFindingRelatedObjectPermission))
1023+
def verify(self, request, pk=None):
1024+
finding = self.get_object()
1025+
1026+
serializer = serializers.FindingVerifySerializer(data=request.data)
1027+
if not serializer.is_valid():
1028+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
1029+
1030+
# Remove prefetched tags to keep queryset state in sync
1031+
finding.tags._remove_prefetched_objects()
1032+
1033+
finding_helper.verify_finding(
1034+
finding=finding,
1035+
user=request.user,
1036+
note_entry=serializer.validated_data.get("note"),
1037+
note_type=serializer.validated_data.get("note_type"),
1038+
)
1039+
1040+
serialized_finding = serializers.FindingSerializer(finding, context={"request": request})
1041+
return Response(serialized_finding.data)
1042+
10171043
@extend_schema(
10181044
methods=["GET"],
10191045
responses={status.HTTP_200_OK: serializers.TagSerializer},

dojo/finding/helper.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,31 @@ def normalize_datetime(value):
10121012
return value
10131013

10141014

1015+
def _create_note_if_provided(
1016+
*,
1017+
finding,
1018+
note_entry,
1019+
user=None,
1020+
note_type=None,
1021+
note_date=None,
1022+
):
1023+
"""
1024+
Create a note for the finding when content is provided. Returns the note or None.
1025+
Note author defaults to finding.last_reviewed_by
1026+
"""
1027+
if not note_entry:
1028+
return None
1029+
1030+
new_note = Notes.objects.create(
1031+
entry=note_entry,
1032+
author=user or finding.last_reviewed_by,
1033+
note_type=note_type,
1034+
date=note_date,
1035+
)
1036+
finding.notes.add(new_note)
1037+
return new_note
1038+
1039+
10151040
def close_finding(
10161041
*,
10171042
finding,
@@ -1046,15 +1071,12 @@ def close_finding(
10461071
finding.last_reviewed_by = user
10471072

10481073
# Create note if provided
1049-
new_note = None
1050-
if note_entry:
1051-
new_note = Notes.objects.create(
1052-
entry=note_entry,
1053-
author=user,
1054-
note_type=note_type,
1055-
date=mitigated_date,
1056-
)
1057-
finding.notes.add(new_note)
1074+
new_note = _create_note_if_provided(
1075+
finding,
1076+
note_entry,
1077+
note_type=note_type,
1078+
note_date=mitigated_date,
1079+
)
10581080

10591081
if settings.V3_FEATURE_LOCATIONS:
10601082
# Related locations
@@ -1105,3 +1127,29 @@ def close_finding(
11051127
description=f'The finding "{finding.title}" was closed by {user}',
11061128
url=reverse("view_finding", args=(finding.id,)),
11071129
)
1130+
1131+
1132+
def verify_finding(
1133+
*,
1134+
finding,
1135+
user,
1136+
note_entry=None,
1137+
note_type=None,
1138+
) -> None:
1139+
"""Shared verify logic used by UI and API."""
1140+
1141+
verification_time = now()
1142+
1143+
finding.verified = True
1144+
finding.last_reviewed = verification_time
1145+
finding.last_reviewed_by = user
1146+
finding.last_status_update = verification_time
1147+
1148+
_create_note_if_provided(
1149+
finding,
1150+
note_entry,
1151+
note_type=note_type,
1152+
note_date=verification_time,
1153+
)
1154+
1155+
finding.save(push_to_jira=False)

dojo/finding/views.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,19 +1247,12 @@ def verify_finding(request, fid):
12471247
form.fields["entry"].label = _("Comment (optional)")
12481248

12491249
if request.method == "POST" and form.is_valid():
1250-
entry = form.cleaned_data.get("entry", "").strip()
1251-
if entry:
1252-
note = form.save(commit=False)
1253-
note.author = request.user
1254-
note.save()
1255-
finding.notes.add(note)
1256-
1257-
now_time = timezone.now()
1258-
finding.verified = True
1259-
finding.last_reviewed = now_time
1260-
finding.last_reviewed_by = request.user
1261-
finding.last_status_update = now_time
1262-
finding.save(push_to_jira=False)
1250+
entry = form.cleaned_data.get("entry", "")
1251+
finding_helper.verify_finding(
1252+
finding=finding,
1253+
user=request.user,
1254+
note_entry=entry,
1255+
)
12631256

12641257
messages.add_message(
12651258
request,

unittests/test_rest_framework.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,39 @@ def test_close_finding_pushes_note_to_jira_when_configured(self):
10111011
}
10121012
response = self.client.post(self._close_url(finding.id), payload, format="json")
10131013
self.assertEqual(200, response.status_code, response.content[:1000])
1014-
self.assertTrue(add_comment_mock.called)
1014+
self.assertTrue(add_comment_mock.called)
1015+
1016+
1017+
@versioned_fixtures
1018+
class FindingVerifyAPITest(DojoAPITestCase):
1019+
fixtures = ["dojo_testdata.json"]
1020+
1021+
def setUp(self):
1022+
testuser = User.objects.get(username="admin")
1023+
token = Token.objects.get(user=testuser)
1024+
self.client = APIClient()
1025+
self.client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}")
1026+
self.admin = testuser
1027+
1028+
def _verify_url(self, finding_id: int) -> str:
1029+
return f"/api/v2/findings/{finding_id}/verify/"
1030+
1031+
def test_verify_finding_basic(self):
1032+
finding = Finding.objects.get(id=7)
1033+
response = self.client.post(self._verify_url(finding.id), {"note": "Marked verified"}, format="json")
1034+
self.assertEqual(200, response.status_code, response.content[:1000])
1035+
1036+
finding.refresh_from_db()
1037+
self.assertTrue(finding.verified)
1038+
self.assertEqual(finding.last_reviewed_by, self.admin)
1039+
self.assertTrue(finding.notes.filter(entry__icontains="Marked verified").exists())
1040+
1041+
def test_verify_finding_invalid_payload(self):
1042+
finding = Finding.objects.get(id=7)
1043+
# note_type specified but invalid id
1044+
response = self.client.post(self._verify_url(finding.id), {"note_type": 9999}, format="json")
1045+
self.assertEqual(400, response.status_code, response.content[:1000])
1046+
10151047

10161048

10171049
@versioned_fixtures

0 commit comments

Comments
 (0)