From e6510f9b3cd22147bc1d7cedd61debc384ade3c1 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:52:41 -0700 Subject: [PATCH 1/2] Add notes management to RiskAcceptanceViewSet with GET and POST actions --- dojo/api_v2/serializers.py | 7 ++++ dojo/api_v2/views.py | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 9a17e4be98..bfd84005ca 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1607,6 +1607,13 @@ class Meta: fields = "__all__" +class RiskAcceptanceToNotesSerializer(serializers.Serializer): + risk_acceptance_id = serializers.PrimaryKeyRelatedField( + queryset=Risk_Acceptance.objects.all(), many=False, allow_null=True, + ) + notes = NoteSerializer(many=True) + + class FindingMetaSerializer(serializers.ModelSerializer): class Meta: model = DojoMeta diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index a083535ef4..e5eef14e16 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -762,6 +762,78 @@ def download_proof(self, request, pk=None): return response + @extend_schema( + methods=["GET"], + responses={ + status.HTTP_200_OK: serializers.RiskAcceptanceToNotesSerializer, + }, + ) + @extend_schema( + methods=["POST"], + request=serializers.AddNewNoteOptionSerializer, + responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, + ) + @action(detail=True, methods=["get", "post"], permission_classes=[IsAuthenticated, permissions.UserHasRiskAcceptanceRelatedObjectPermission]) + def notes(self, request, pk=None): + risk_acceptance = self.get_object() + if request.method == "POST": + new_note = serializers.AddNewNoteOptionSerializer( + data=request.data, + ) + if new_note.is_valid(): + entry = new_note.validated_data["entry"] + private = new_note.validated_data.get("private", False) + note_type = new_note.validated_data.get("note_type", None) + else: + return Response( + new_note.errors, status=status.HTTP_400_BAD_REQUEST, + ) + + notes = risk_acceptance.notes.filter(note_type=note_type).first() + if notes and note_type and note_type.is_single: + return Response("Only one instance of this note_type allowed on a risk acceptance.", status=status.HTTP_400_BAD_REQUEST) + + author = request.user + note = Notes( + entry=entry, + author=author, + private=private, + note_type=note_type, + ) + note.save() + risk_acceptance.notes.add(note) + # Attempt to get a link to the risk acceptance + if hasattr(risk_acceptance, "engagement") and risk_acceptance.engagement is not None: + risk_acceptance_url = request.build_absolute_uri( + reverse("view_risk_acceptance", args=(risk_acceptance.engagement.id, risk_acceptance.id)), + ) + # Attempt to intuit the engagement from the findings + elif (finding := risk_acceptance.accepted_findings.first()) is not None: + risk_acceptance_url = request.build_absolute_uri( + reverse("view_risk_acceptance", args=(finding.test.engagement.id, risk_acceptance.id)), + ) + else: + # Not much we can do here... + risk_acceptance_url = "/" + # Determine if we need to send any notifications for user mentioned + process_tag_notifications( + request=request, + note=note, + parent_url=risk_acceptance_url, + parent_title=f"Risk Acceptance: {risk_acceptance.name}", + ) + + serialized_note = serializers.NoteSerializer( + {"author": author, "entry": entry, "private": private}, + ) + return Response( + serialized_note.data, status=status.HTTP_201_CREATED, + ) + notes = risk_acceptance.notes.all() + serialized_notes = serializers.RiskAcceptanceToNotesSerializer( + {"risk_acceptance_id": risk_acceptance, "notes": notes}, + ) + return Response(serialized_notes.data, status=status.HTTP_200_OK) # These are technologies in the UI and the API! # Authorization: object-based From 2d8774096051e758317bae6ed4538e987852cd11 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:54:13 -0700 Subject: [PATCH 2/2] Fix ruff --- dojo/api_v2/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index e5eef14e16..90b6f4ea5c 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -835,6 +835,7 @@ def notes(self, request, pk=None): ) return Response(serialized_notes.data, status=status.HTTP_200_OK) + # These are technologies in the UI and the API! # Authorization: object-based @extend_schema_view(**schema_with_prefetch())