Skip to content

Commit d86f471

Browse files
committed
refine implementation
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent c5087aa commit d86f471

4 files changed

Lines changed: 66 additions & 27 deletions

File tree

product_portfolio/api.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,11 @@ class TriageRulesetAssignmentSerializer(serializers.Serializer):
396396
assigned = serializers.BooleanField(read_only=True)
397397

398398

399+
class AssignTriageRulesetSerializer(serializers.Serializer):
400+
ruleset = serializers.UUIDField()
401+
assigned = serializers.BooleanField()
402+
403+
399404
class ProductViewSet(
400405
ObjectPermissionsMixin,
401406
SendAboutFilesMixin,
@@ -484,7 +489,12 @@ def triage_records(self, request, uuid):
484489
serializer = TriageRecordSerializer(records, many=True)
485490
return Response(serializer.data)
486491

487-
@action(detail=True, methods=["get", "post"], url_path="manage_triage_rulesets")
492+
@action(
493+
detail=True,
494+
methods=["get", "post"],
495+
url_path="manage_triage_rulesets",
496+
serializer_class=AssignTriageRulesetSerializer,
497+
)
488498
def manage_triage_rulesets(self, request, uuid):
489499
"""
490500
GET: list every enabled triage ruleset in this product's dataspace, each flagged
@@ -496,22 +506,16 @@ def manage_triage_rulesets(self, request, uuid):
496506
product = self.get_object()
497507

498508
if request.method == "POST":
499-
if not isinstance(request.data, dict):
500-
return Response(
501-
{"error": "Expected a JSON object with 'ruleset' and 'assigned'."},
502-
status=status.HTTP_400_BAD_REQUEST,
503-
)
504-
ruleset_uuid = request.data.get("ruleset")
505-
assigned = request.data.get("assigned")
506-
if ruleset_uuid is None or assigned is None:
507-
return Response(
508-
{"error": "Both 'ruleset' and 'assigned' are required."},
509-
status=status.HTTP_400_BAD_REQUEST,
510-
)
509+
serializer = AssignTriageRulesetSerializer(data=request.data)
510+
if not serializer.is_valid():
511+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
512+
511513
ruleset = get_object_or_404(
512514
TriageRuleset.objects.scope(product.dataspace).filter(enabled=True),
513-
uuid=ruleset_uuid,
515+
uuid=serializer.validated_data["ruleset"],
514516
)
517+
assigned = serializer.validated_data["assigned"]
518+
515519
with transaction.atomic():
516520
assignment = ProductTriageRuleset.objects.filter(
517521
product=product, ruleset=ruleset
@@ -520,10 +524,11 @@ def manage_triage_rulesets(self, request, uuid):
520524
ProductTriageRuleset.objects.create(
521525
product=product, ruleset=ruleset, dataspace=product.dataspace
522526
)
527+
reevaluate_product_rulesets(product)
523528
elif not assigned and assignment:
524529
assignment.delete()
525530
delete_triage_records_for_assignment(ruleset=ruleset, product=product)
526-
reevaluate_product_rulesets(product)
531+
reevaluate_product_rulesets(product)
527532
return Response(status=status.HTTP_200_OK)
528533

529534
assigned_ruleset_ids = set(

product_portfolio/tests/test_api.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,25 @@ def test_api_product_endpoint_manage_triage_rulesets_post_unassigns(self):
868868
ProductTriageRuleset.objects.filter(product=self.product1, ruleset=ruleset).exists()
869869
)
870870

871+
def test_api_product_endpoint_manage_triage_rulesets_post_unassigns_form_encoded(self):
872+
# Regression: a form-encoded "false" string must not be treated as truthy.
873+
url = reverse("api_v2:product-manage-triage-rulesets", args=[self.product1.uuid])
874+
self.client.login(username=self.base_user.username, password="secret")
875+
add_perm(self.base_user, "add_product")
876+
assign_perm("view_product", self.base_user, self.product1)
877+
assign_perm("change_product", self.base_user, self.product1)
878+
879+
ruleset = make_triage_ruleset(self.dataspace)
880+
make_product_triage_ruleset(self.product1, ruleset=ruleset)
881+
882+
data = {"ruleset": str(ruleset.uuid), "assigned": "false"}
883+
response = self.client.post(url, data=data)
884+
885+
self.assertEqual(status.HTTP_200_OK, response.status_code)
886+
self.assertFalse(
887+
ProductTriageRuleset.objects.filter(product=self.product1, ruleset=ruleset).exists()
888+
)
889+
871890
def test_api_product_endpoint_manage_triage_rulesets_post_requires_both_fields(self):
872891
url = reverse("api_v2:product-manage-triage-rulesets", args=[self.product1.uuid])
873892
self.client.login(username=self.base_user.username, password="secret")

vulnerabilities/triage/api.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from rest_framework import serializers
1111

1212
from dje.api import CreateRetrieveUpdateListViewSet
13+
from dje.api import DataspacedHyperlinkedRelatedField
1314
from dje.api import DataspacedSerializer
1415
from dje.api import ExtraPermissionsViewSetMixin
1516
from dje.api_custom import TabPermission
@@ -73,6 +74,19 @@ class AnalysisPresetViewSet(
7374

7475

7576
class TriageRulesetSerializer(DataspacedSerializer):
77+
analysis_preset = DataspacedHyperlinkedRelatedField(
78+
view_name="api_v2:analysispreset-detail",
79+
lookup_field="uuid",
80+
required=False,
81+
allow_null=True,
82+
)
83+
request_template = DataspacedHyperlinkedRelatedField(
84+
view_name="api_v2:requesttemplate-detail",
85+
lookup_field="uuid",
86+
required=False,
87+
allow_null=True,
88+
)
89+
7690
class Meta:
7791
model = TriageRuleset
7892
fields = (
@@ -92,18 +106,6 @@ class Meta:
92106
"view_name": "api_v2:triageruleset-detail",
93107
"lookup_field": "uuid",
94108
},
95-
"analysis_preset": {
96-
"view_name": "api_v2:analysispreset-detail",
97-
"lookup_field": "uuid",
98-
"required": False,
99-
"allow_null": True,
100-
},
101-
"request_template": {
102-
"view_name": "api_v2:requesttemplate-detail",
103-
"lookup_field": "uuid",
104-
"required": False,
105-
"allow_null": True,
106-
},
107109
}
108110

109111
def validate_request_template(self, value):

vulnerabilities/triage/tests/test_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ def test_api_triageruleset_endpoint_create_rejects_request_template_with_no_crea
144144
msg = "This request template has no creator and cannot be used to open requests."
145145
self.assertIn(msg, response.data["request_template"])
146146

147+
def test_api_triageruleset_endpoint_create_rejects_cross_dataspace_analysis_preset(self):
148+
self.client.login(username="super_user", password="secret")
149+
other_preset = make_analysis_preset(self.alternate, name="OtherPreset")
150+
other_preset_url = reverse("api_v2:analysispreset-detail", args=[other_preset.uuid])
151+
data = {
152+
"name": "New Ruleset",
153+
"precedence": 200,
154+
"analysis_preset": other_preset_url,
155+
}
156+
response = self.client.post(self.list_url, data=data, content_type="application/json")
157+
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
158+
self.assertIn("analysis_preset", response.data)
159+
147160
def test_api_triageruleset_endpoint_update(self):
148161
self.client.login(username="super_user", password="secret")
149162
data = {"enabled": False}

0 commit comments

Comments
 (0)