Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 110 additions & 17 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,33 @@ def filter_mitigated_on(self, queryset, name, value):
return queryset.filter(mitigated=value)


def get_finding_group_queryset_for_context(pid=None, eid=None, tid=None):
"""
Helper function to build finding group queryset based on context hierarchy.
Context priority: test > engagement > product > global

Args:
pid: Product ID (least specific)
eid: Engagement ID
tid: Test ID (most specific)

Returns:
QuerySet of Finding_Group filtered by context

"""
if tid is not None:
# Most specific: filter by test
return Finding_Group.objects.filter(test_id=tid).only("id", "name")
if eid is not None:
# Filter by engagement's tests
return Finding_Group.objects.filter(test__engagement_id=eid).only("id", "name")
if pid is not None:
# Filter by product's tests
return Finding_Group.objects.filter(test__engagement__product_id=pid).only("id", "name")
# Global: return all (authorization will be applied separately)
return Finding_Group.objects.all().only("id", "name")


class FindingFilterWithoutObjectLookups(FindingFilterHelper, FindingTagStringFilter):
test__engagement__product__prod_type = NumberFilter(widget=HiddenInput())
test__engagement__product = NumberFilter(widget=HiddenInput())
Expand Down Expand Up @@ -2111,20 +2138,45 @@ class Meta:
def __init__(self, *args, **kwargs):
self.user = None
self.pid = None
self.eid = None
self.tid = None
if "user" in kwargs:
self.user = kwargs.pop("user")

if "pid" in kwargs:
self.pid = kwargs.pop("pid")
if "eid" in kwargs:
self.eid = kwargs.pop("eid")
if "tid" in kwargs:
self.tid = kwargs.pop("tid")
super().__init__(*args, **kwargs)
# Set some date fields
self.set_date_fields(*args, **kwargs)
# Don't show the product filter on the product finding view
if self.pid:
del self.form.fields["test__engagement__product__name"]
del self.form.fields["test__engagement__product__name_contains"]
del self.form.fields["test__engagement__product__prod_type__name"]
del self.form.fields["test__engagement__product__prod_type__name_contains"]
# Don't show the product/engagement/test filter fields when in specific context
if self.tid or self.eid or self.pid:
if "test__engagement__product__name" in self.form.fields:
del self.form.fields["test__engagement__product__name"]
if "test__engagement__product__name_contains" in self.form.fields:
del self.form.fields["test__engagement__product__name_contains"]
if "test__engagement__product__prod_type__name" in self.form.fields:
del self.form.fields["test__engagement__product__prod_type__name"]
if "test__engagement__product__prod_type__name_contains" in self.form.fields:
del self.form.fields["test__engagement__product__prod_type__name_contains"]
# Also hide engagement and test fields if in test or engagement context
if self.tid:
if "test__engagement__name" in self.form.fields:
del self.form.fields["test__engagement__name"]
if "test__engagement__name_contains" in self.form.fields:
del self.form.fields["test__engagement__name_contains"]
if "test__name" in self.form.fields:
del self.form.fields["test__name"]
if "test__name_contains" in self.form.fields:
del self.form.fields["test__name_contains"]
elif self.eid:
if "test__engagement__name" in self.form.fields:
del self.form.fields["test__engagement__name"]
if "test__engagement__name_contains" in self.form.fields:
del self.form.fields["test__engagement__name_contains"]


class FindingFilter(FindingFilterHelper, FindingTagFilter):
Expand Down Expand Up @@ -2163,38 +2215,79 @@ class Meta:
def __init__(self, *args, **kwargs):
self.user = None
self.pid = None
self.eid = None
self.tid = None
if "user" in kwargs:
self.user = kwargs.pop("user")

if "pid" in kwargs:
self.pid = kwargs.pop("pid")
if "eid" in kwargs:
self.eid = kwargs.pop("eid")
if "tid" in kwargs:
self.tid = kwargs.pop("tid")
super().__init__(*args, **kwargs)
# Set some date fields
self.set_date_fields(*args, **kwargs)
# Don't show the product filter on the product finding view
self.set_related_object_fields(*args, **kwargs)

def set_related_object_fields(self, *args: list, **kwargs: dict):
finding_group_query = Finding_Group.objects.all()
if self.pid is not None:
del self.form.fields["test__engagement__product"]
del self.form.fields["test__engagement__product__prod_type"]
# Use helper to get contextual finding group queryset
finding_group_query = get_finding_group_queryset_for_context(
pid=self.pid,
eid=self.eid,
tid=self.tid,
)

# Filter by most specific context: test > engagement > product
if self.tid is not None:
# Test context: filter finding groups by test
if "test__engagement__product" in self.form.fields:
del self.form.fields["test__engagement__product"]
if "test__engagement__product__prod_type" in self.form.fields:
del self.form.fields["test__engagement__product__prod_type"]
if "test__engagement" in self.form.fields:
del self.form.fields["test__engagement"]
if "test" in self.form.fields:
del self.form.fields["test"]
elif self.eid is not None:
# Engagement context: filter finding groups by engagement
if "test__engagement__product" in self.form.fields:
del self.form.fields["test__engagement__product"]
if "test__engagement__product__prod_type" in self.form.fields:
del self.form.fields["test__engagement__product__prod_type"]
if "test__engagement" in self.form.fields:
del self.form.fields["test__engagement"]
# Filter tests by engagement - get_authorized_tests doesn't support engagement param
engagement = Engagement.objects.filter(id=self.eid).select_related("product").first()
if engagement:
self.form.fields["test"].queryset = get_authorized_tests(Permissions.Test_View, product=engagement.product).filter(engagement_id=self.eid).prefetch_related("test_type")
elif self.pid is not None:
# Product context: filter finding groups by product
if "test__engagement__product" in self.form.fields:
del self.form.fields["test__engagement__product"]
if "test__engagement__product__prod_type" in self.form.fields:
del self.form.fields["test__engagement__product__prod_type"]
# TODO: add authorized check to be sure
self.form.fields["test__engagement"].queryset = Engagement.objects.filter(
product_id=self.pid,
).all()
self.form.fields["test"].queryset = get_authorized_tests(Permissions.Test_View, product=self.pid).prefetch_related("test_type")
finding_group_query = Finding_Group.objects.filter(test__engagement__product_id=self.pid)
if "test__engagement" in self.form.fields:
self.form.fields["test__engagement"].queryset = Engagement.objects.filter(
product_id=self.pid,
).all()
if "test" in self.form.fields:
self.form.fields["test"].queryset = get_authorized_tests(Permissions.Test_View, product=self.pid).prefetch_related("test_type")
else:
# Global context: show all authorized finding groups
self.form.fields[
"test__engagement__product__prod_type"].queryset = get_authorized_product_types(Permissions.Product_Type_View)
self.form.fields["test__engagement"].queryset = get_authorized_engagements(Permissions.Engagement_View)
del self.form.fields["test"]
if "test" in self.form.fields:
del self.form.fields["test"]

if self.form.fields.get("test__engagement__product"):
self.form.fields["test__engagement__product"].queryset = get_authorized_products(Permissions.Product_View)
if self.form.fields.get("finding_group", None):
self.form.fields["finding_group"].queryset = get_authorized_finding_groups_for_queryset(Permissions.Finding_Group_View, finding_group_query)
self.form.fields["finding_group"].queryset = get_authorized_finding_groups_for_queryset(Permissions.Finding_Group_View, finding_group_query, user=self.user)
self.form.fields["reporter"].queryset = get_authorized_users(Permissions.Finding_View)
self.form.fields["reviewers"].queryset = self.form.fields["reporter"].queryset

Expand Down
35 changes: 19 additions & 16 deletions dojo/finding/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ def filter_findings_by_form(self, request: HttpRequest, findings: QuerySet[Findi
kwargs = {
"user": request.user,
"pid": self.get_product_id(),
"eid": self.get_engagement_id(),
"tid": self.get_test_id(),
}

filter_string_matching = get_system_setting("filter_string_matching", False)
Expand Down Expand Up @@ -360,10 +362,11 @@ def add_breadcrumbs(self, request: HttpRequest, context: dict):

return request, context

def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
# Store the product and engagement ids
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
# Store the product, engagement, and test ids
self.product_id = product_id
self.engagement_id = engagement_id
self.test_id = test_id
# Get the initial context
request, context = self.get_initial_context(request)
# Get the filtered findings
Expand All @@ -386,46 +389,46 @@ def get(self, request: HttpRequest, product_id: int | None = None, engagement_id


class ListOpenFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Open"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListVerifiedFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Verified"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListOutOfScopeFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Out of Scope"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListFalsePositiveFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "False Positive"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListInactiveFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Inactive"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListAcceptedFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Accepted"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ListClosedFindings(ListFindings):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None):
def get(self, request: HttpRequest, product_id: int | None = None, engagement_id: int | None = None, test_id: int | None = None):
self.filter_name = "Closed"
self.order_by = "-mitigated"
return super().get(request, product_id=product_id, engagement_id=engagement_id)
return super().get(request, product_id=product_id, engagement_id=engagement_id, test_id=test_id)


class ViewFinding(View):
Expand Down
2 changes: 1 addition & 1 deletion dojo/test/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def get_findings(self, request: HttpRequest, test: Test):
findings = Finding.objects.filter(test=test).order_by("numerical_severity")
filter_string_matching = get_system_setting("filter_string_matching", False)
finding_filter_class = FindingFilterWithoutObjectLookups if filter_string_matching else FindingFilter
findings = finding_filter_class(request.GET, pid=test.engagement.product.id, queryset=findings)
findings = finding_filter_class(request.GET, pid=test.engagement.product.id, eid=test.engagement.id, tid=test.id, queryset=findings)
paged_findings = get_page_items_and_count(request, prefetch_for_findings(findings.qs), 25, prefix="findings")
fix_available_count = findings.qs.filter(fix_available=True).count()

Expand Down
Loading