Skip to content

Commit 4c54de0

Browse files
feat(findings): Add resource_tag filters for findings endpoint (#6587)
Co-authored-by: Víctor Fernández Poyatos <[email protected]>
1 parent 690c482 commit 4c54de0

File tree

4 files changed

+191
-3
lines changed

4 files changed

+191
-3
lines changed

api/src/backend/api/filters.py

+37
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,27 @@ class FindingFilter(FilterSet):
319319
field_name="resources__type", lookup_expr="icontains"
320320
)
321321

322+
resource_tag_key = CharFilter(field_name="resources__tags__key")
323+
resource_tag_key__in = CharInFilter(
324+
field_name="resources__tags__key", lookup_expr="in"
325+
)
326+
resource_tag_key__icontains = CharFilter(
327+
field_name="resources__tags__key", lookup_expr="icontains"
328+
)
329+
resource_tag_value = CharFilter(field_name="resources__tags__value")
330+
resource_tag_value__in = CharInFilter(
331+
field_name="resources__tags__value", lookup_expr="in"
332+
)
333+
resource_tag_value__icontains = CharFilter(
334+
field_name="resources__tags__value", lookup_expr="icontains"
335+
)
336+
resource_tags = CharInFilter(
337+
method="filter_resource_tag",
338+
lookup_expr="in",
339+
help_text="Filter by resource tags `key:value` pairs.\nMultiple values may be "
340+
"separated by commas.",
341+
)
342+
322343
scan = UUIDFilter(method="filter_scan_id")
323344
scan__in = UUIDInFilter(method="filter_scan_id_in")
324345

@@ -353,6 +374,12 @@ class Meta:
353374
},
354375
}
355376

377+
@property
378+
def qs(self):
379+
# Force distinct results to prevent duplicates with many-to-many relationships
380+
parent_qs = super().qs
381+
return parent_qs.distinct()
382+
356383
# Convert filter values to UUIDv7 values for use with partitioning
357384
def filter_scan_id(self, queryset, name, value):
358385
try:
@@ -426,6 +453,16 @@ def filter_inserted_at_lte(self, queryset, name, value):
426453

427454
return queryset.filter(id__lte=end).filter(inserted_at__lte=value)
428455

456+
def filter_resource_tag(self, queryset, name, value):
457+
overall_query = Q()
458+
for key_value_pair in value:
459+
tag_key, tag_value = key_value_pair.split(":", 1)
460+
overall_query |= Q(
461+
resources__tags__key__icontains=tag_key,
462+
resources__tags__value__icontains=tag_value,
463+
)
464+
return queryset.filter(overall_query).distinct()
465+
429466
@staticmethod
430467
def maybe_date_to_datetime(value):
431468
dt = value

api/src/backend/api/specs/v1.yaml

+135
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,51 @@ paths:
477477
description: Multiple values may be separated by commas.
478478
explode: false
479479
style: form
480+
- in: query
481+
name: filter[resource_tag_key]
482+
schema:
483+
type: string
484+
- in: query
485+
name: filter[resource_tag_key__icontains]
486+
schema:
487+
type: string
488+
- in: query
489+
name: filter[resource_tag_key__in]
490+
schema:
491+
type: array
492+
items:
493+
type: string
494+
description: Multiple values may be separated by commas.
495+
explode: false
496+
style: form
497+
- in: query
498+
name: filter[resource_tag_value]
499+
schema:
500+
type: string
501+
- in: query
502+
name: filter[resource_tag_value__icontains]
503+
schema:
504+
type: string
505+
- in: query
506+
name: filter[resource_tag_value__in]
507+
schema:
508+
type: array
509+
items:
510+
type: string
511+
description: Multiple values may be separated by commas.
512+
explode: false
513+
style: form
514+
- in: query
515+
name: filter[resource_tags]
516+
schema:
517+
type: array
518+
items:
519+
type: string
520+
description: |-
521+
Filter by resource tags `key:value` pairs.
522+
Multiple values may be separated by commas.
523+
explode: false
524+
style: form
480525
- in: query
481526
name: filter[resource_type]
482527
schema:
@@ -983,6 +1028,51 @@ paths:
9831028
description: Multiple values may be separated by commas.
9841029
explode: false
9851030
style: form
1031+
- in: query
1032+
name: filter[resource_tag_key]
1033+
schema:
1034+
type: string
1035+
- in: query
1036+
name: filter[resource_tag_key__icontains]
1037+
schema:
1038+
type: string
1039+
- in: query
1040+
name: filter[resource_tag_key__in]
1041+
schema:
1042+
type: array
1043+
items:
1044+
type: string
1045+
description: Multiple values may be separated by commas.
1046+
explode: false
1047+
style: form
1048+
- in: query
1049+
name: filter[resource_tag_value]
1050+
schema:
1051+
type: string
1052+
- in: query
1053+
name: filter[resource_tag_value__icontains]
1054+
schema:
1055+
type: string
1056+
- in: query
1057+
name: filter[resource_tag_value__in]
1058+
schema:
1059+
type: array
1060+
items:
1061+
type: string
1062+
description: Multiple values may be separated by commas.
1063+
explode: false
1064+
style: form
1065+
- in: query
1066+
name: filter[resource_tags]
1067+
schema:
1068+
type: array
1069+
items:
1070+
type: string
1071+
description: |-
1072+
Filter by resource tags `key:value` pairs.
1073+
Multiple values may be separated by commas.
1074+
explode: false
1075+
style: form
9861076
- in: query
9871077
name: filter[resource_type]
9881078
schema:
@@ -1410,6 +1500,51 @@ paths:
14101500
description: Multiple values may be separated by commas.
14111501
explode: false
14121502
style: form
1503+
- in: query
1504+
name: filter[resource_tag_key]
1505+
schema:
1506+
type: string
1507+
- in: query
1508+
name: filter[resource_tag_key__icontains]
1509+
schema:
1510+
type: string
1511+
- in: query
1512+
name: filter[resource_tag_key__in]
1513+
schema:
1514+
type: array
1515+
items:
1516+
type: string
1517+
description: Multiple values may be separated by commas.
1518+
explode: false
1519+
style: form
1520+
- in: query
1521+
name: filter[resource_tag_value]
1522+
schema:
1523+
type: string
1524+
- in: query
1525+
name: filter[resource_tag_value__icontains]
1526+
schema:
1527+
type: string
1528+
- in: query
1529+
name: filter[resource_tag_value__in]
1530+
schema:
1531+
type: array
1532+
items:
1533+
type: string
1534+
description: Multiple values may be separated by commas.
1535+
explode: false
1536+
style: form
1537+
- in: query
1538+
name: filter[resource_tags]
1539+
schema:
1540+
type: array
1541+
items:
1542+
type: string
1543+
description: |-
1544+
Filter by resource tags `key:value` pairs.
1545+
Multiple values may be separated by commas.
1546+
explode: false
1547+
style: form
14131548
- in: query
14141549
name: filter[resource_type]
14151550
schema:

api/src/backend/api/tests/test_views.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -2444,6 +2444,15 @@ def test_findings_list_include(
24442444
("search", "ec2", 2),
24452445
# full text search on finding tags
24462446
("search", "value2", 2),
2447+
("resource_tag_key", "key", 2),
2448+
("resource_tag_key__in", "key,key2", 2),
2449+
("resource_tag_key__icontains", "key", 2),
2450+
("resource_tag_value", "value", 2),
2451+
("resource_tag_value__in", "value,value2", 2),
2452+
("resource_tag_value__icontains", "value", 2),
2453+
("resource_tags", "key:value", 2),
2454+
("resource_tags", "not:exists", 0),
2455+
("resource_tags", "not:exists,key:value", 2),
24472456
]
24482457
),
24492458
)
@@ -2592,7 +2601,7 @@ def test_findings_metadata_retrieve(self, authenticated_client, findings_fixture
25922601

25932602
expected_services = {"ec2", "s3"}
25942603
expected_regions = {"eu-west-1", "us-east-1"}
2595-
expected_tags = {"key": "value", "key2": "value2"}
2604+
expected_tags = {"key": ["value"], "key2": ["value2"]}
25962605
expected_resource_types = {"prowler-test"}
25972606

25982607
assert data["data"]["type"] == "findings-metadata"
@@ -2619,7 +2628,7 @@ def test_findings_metadata_severity_retrieve(
26192628

26202629
expected_services = {"s3"}
26212630
expected_regions = {"eu-west-1"}
2622-
expected_tags = {"key": "value", "key2": "value2"}
2631+
expected_tags = {"key": ["value"], "key2": ["value2"]}
26232632
expected_resource_types = {"prowler-test"}
26242633

26252634
assert data["data"]["type"] == "findings-metadata"

api/src/backend/api/v1/views.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1414,7 +1414,14 @@ def metadata(self, request):
14141414
if result["tags"] is None:
14151415
result["tags"] = []
14161416

1417-
result["tags"] = {t["key"]: t["value"] for t in result["tags"]}
1417+
tags_dict = {}
1418+
for t in result["tags"]:
1419+
key, value = t["key"], t["value"]
1420+
if key not in tags_dict:
1421+
tags_dict[key] = []
1422+
tags_dict[key].append(value)
1423+
1424+
result["tags"] = tags_dict
14181425

14191426
serializer = self.get_serializer(
14201427
data=result,

0 commit comments

Comments
 (0)