Skip to content

Commit

Permalink
feat(findings): Add resource_tag filters for findings endpoint (#6587)
Browse files Browse the repository at this point in the history
Co-authored-by: Víctor Fernández Poyatos <[email protected]>
  • Loading branch information
prowler-bot and vicferpoy authored Jan 17, 2025
1 parent 690c482 commit 4c54de0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 3 deletions.
37 changes: 37 additions & 0 deletions api/src/backend/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,27 @@ class FindingFilter(FilterSet):
field_name="resources__type", lookup_expr="icontains"
)

resource_tag_key = CharFilter(field_name="resources__tags__key")
resource_tag_key__in = CharInFilter(
field_name="resources__tags__key", lookup_expr="in"
)
resource_tag_key__icontains = CharFilter(
field_name="resources__tags__key", lookup_expr="icontains"
)
resource_tag_value = CharFilter(field_name="resources__tags__value")
resource_tag_value__in = CharInFilter(
field_name="resources__tags__value", lookup_expr="in"
)
resource_tag_value__icontains = CharFilter(
field_name="resources__tags__value", lookup_expr="icontains"
)
resource_tags = CharInFilter(
method="filter_resource_tag",
lookup_expr="in",
help_text="Filter by resource tags `key:value` pairs.\nMultiple values may be "
"separated by commas.",
)

scan = UUIDFilter(method="filter_scan_id")
scan__in = UUIDInFilter(method="filter_scan_id_in")

Expand Down Expand Up @@ -353,6 +374,12 @@ class Meta:
},
}

@property
def qs(self):
# Force distinct results to prevent duplicates with many-to-many relationships
parent_qs = super().qs
return parent_qs.distinct()

# Convert filter values to UUIDv7 values for use with partitioning
def filter_scan_id(self, queryset, name, value):
try:
Expand Down Expand Up @@ -426,6 +453,16 @@ def filter_inserted_at_lte(self, queryset, name, value):

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

def filter_resource_tag(self, queryset, name, value):
overall_query = Q()
for key_value_pair in value:
tag_key, tag_value = key_value_pair.split(":", 1)
overall_query |= Q(
resources__tags__key__icontains=tag_key,
resources__tags__value__icontains=tag_value,
)
return queryset.filter(overall_query).distinct()

@staticmethod
def maybe_date_to_datetime(value):
dt = value
Expand Down
135 changes: 135 additions & 0 deletions api/src/backend/api/specs/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,51 @@ paths:
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_key]
schema:
type: string
- in: query
name: filter[resource_tag_key__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_key__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_value]
schema:
type: string
- in: query
name: filter[resource_tag_value__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_value__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tags]
schema:
type: array
items:
type: string
description: |-
Filter by resource tags `key:value` pairs.
Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_type]
schema:
Expand Down Expand Up @@ -983,6 +1028,51 @@ paths:
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_key]
schema:
type: string
- in: query
name: filter[resource_tag_key__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_key__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_value]
schema:
type: string
- in: query
name: filter[resource_tag_value__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_value__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tags]
schema:
type: array
items:
type: string
description: |-
Filter by resource tags `key:value` pairs.
Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_type]
schema:
Expand Down Expand Up @@ -1410,6 +1500,51 @@ paths:
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_key]
schema:
type: string
- in: query
name: filter[resource_tag_key__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_key__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tag_value]
schema:
type: string
- in: query
name: filter[resource_tag_value__icontains]
schema:
type: string
- in: query
name: filter[resource_tag_value__in]
schema:
type: array
items:
type: string
description: Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_tags]
schema:
type: array
items:
type: string
description: |-
Filter by resource tags `key:value` pairs.
Multiple values may be separated by commas.
explode: false
style: form
- in: query
name: filter[resource_type]
schema:
Expand Down
13 changes: 11 additions & 2 deletions api/src/backend/api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2444,6 +2444,15 @@ def test_findings_list_include(
("search", "ec2", 2),
# full text search on finding tags
("search", "value2", 2),
("resource_tag_key", "key", 2),
("resource_tag_key__in", "key,key2", 2),
("resource_tag_key__icontains", "key", 2),
("resource_tag_value", "value", 2),
("resource_tag_value__in", "value,value2", 2),
("resource_tag_value__icontains", "value", 2),
("resource_tags", "key:value", 2),
("resource_tags", "not:exists", 0),
("resource_tags", "not:exists,key:value", 2),
]
),
)
Expand Down Expand Up @@ -2592,7 +2601,7 @@ def test_findings_metadata_retrieve(self, authenticated_client, findings_fixture

expected_services = {"ec2", "s3"}
expected_regions = {"eu-west-1", "us-east-1"}
expected_tags = {"key": "value", "key2": "value2"}
expected_tags = {"key": ["value"], "key2": ["value2"]}
expected_resource_types = {"prowler-test"}

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

expected_services = {"s3"}
expected_regions = {"eu-west-1"}
expected_tags = {"key": "value", "key2": "value2"}
expected_tags = {"key": ["value"], "key2": ["value2"]}
expected_resource_types = {"prowler-test"}

assert data["data"]["type"] == "findings-metadata"
Expand Down
9 changes: 8 additions & 1 deletion api/src/backend/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,14 @@ def metadata(self, request):
if result["tags"] is None:
result["tags"] = []

result["tags"] = {t["key"]: t["value"] for t in result["tags"]}
tags_dict = {}
for t in result["tags"]:
key, value = t["key"], t["value"]
if key not in tags_dict:
tags_dict[key] = []
tags_dict[key].append(value)

result["tags"] = tags_dict

serializer = self.get_serializer(
data=result,
Expand Down

0 comments on commit 4c54de0

Please sign in to comment.