Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions dojo/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,7 @@ def update(self, instance, validated_data):

class EndpointSerializer(serializers.ModelSerializer):
tags = TagListSerializerField(required=False)
active_finding_count = serializers.IntegerField(read_only=True)

class Meta:
model = Endpoint
Expand Down
11 changes: 10 additions & 1 deletion dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from django.contrib.auth.models import Permission
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.db.models import OuterRef, Value
from django.db.models.functions import Coalesce
from django.db.models.query import QuerySet as DjangoQuerySet
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404
Expand Down Expand Up @@ -166,6 +168,7 @@
get_authorized_product_type_members,
get_authorized_product_types,
)
from dojo.query_utils import build_count_subquery
from dojo.reports.views import (
prefetch_related_findings_for_report,
report_url_resolver,
Expand Down Expand Up @@ -345,7 +348,13 @@ class EndPointViewSet(
)

def get_queryset(self):
return get_authorized_endpoints(Permissions.Location_View).distinct()
active_finding_subquery = build_count_subquery(
Finding.objects.filter(endpoints=OuterRef("pk"), active=True),
group_field="endpoints",
)
return get_authorized_endpoints(Permissions.Location_View).annotate(
active_finding_count=Coalesce(active_finding_subquery, Value(0)),
).distinct()

@extend_schema(
request=serializers.ReportGenerateOptionSerializer,
Expand Down
8 changes: 7 additions & 1 deletion dojo/endpoint/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ def process_endpoints_view(request, *, host_view=False, vulnerable=False):
else:
endpoints = Endpoint.objects.all()

endpoints = endpoints.prefetch_related("product", "product__tags", "tags").distinct()
active_finding_subquery = build_count_subquery(
Finding.objects.filter(endpoints=OuterRef("pk"), active=True),
group_field="endpoints",
)
endpoints = endpoints.prefetch_related("product", "product__tags", "tags").annotate(
active_finding_count=Coalesce(active_finding_subquery, Value(0)),
).distinct()
endpoints = get_authorized_endpoints_for_queryset(Permissions.Location_View, endpoints, request.user)
filter_string_matching = get_system_setting("filter_string_matching", False)
filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
Expand Down
8 changes: 8 additions & 0 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2866,7 +2866,11 @@ class EndpointFilterHelper(FilterSet):
("product", "product"),
("host", "host"),
("id", "id"),
("active_finding_count", "active_finding_count"),
),
field_labels={
"active_finding_count": "Active Findings Count",
},
)


Expand Down Expand Up @@ -3108,7 +3112,11 @@ class ApiEndpointFilter(DojoFilter):
("host", "host"),
("product", "product"),
("id", "id"),
("active_finding_count", "active_finding_count"),
),
field_labels={
"active_finding_count": "Active Findings Count",
},
)

class Meta:
Expand Down
21 changes: 20 additions & 1 deletion dojo/location/api/endpoint_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"""
import datetime

from django.db.models import OuterRef, Value
from django.db.models.functions import Coalesce
from django_filters import BooleanFilter, CharFilter, NumberFilter
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from drf_spectacular.utils import extend_schema
Expand All @@ -32,6 +34,7 @@
from dojo.location.models import LocationFindingReference, LocationProductReference
from dojo.location.queries import get_authorized_location_finding_reference, get_authorized_location_product_reference
from dojo.location.status import FindingLocationStatus
from dojo.query_utils import build_count_subquery
from dojo.url.models import URL

##########
Expand Down Expand Up @@ -101,7 +104,11 @@ class V3EndpointCompatibleFilterSet(FilterSet):
("location__url__host", "host"),
("product__id", "product"),
("id", "id"),
("active_finding_count", "active_finding_count"),
),
field_labels={
"active_finding_count": "Active Findings Count",
},
)


Expand All @@ -118,6 +125,7 @@ class V3EndpointCompatibleSerializer(ModelSerializer):
fragment = CharField(source="location.url.fragment")
tags = TagListSerializerField(source="location.tags")
location_id = IntegerField(source="location.id")
active_finding_count = IntegerField(read_only=True)

class Meta:
model = LocationProductReference
Expand All @@ -141,7 +149,18 @@ class V3EndpointCompatibleViewSet(PrefetchListMixin, PrefetchRetrieveMixin, view

def get_queryset(self):
"""Get authorized URLs using Endpoint authorization logic."""
return get_authorized_location_product_reference(Permissions.Location_View).filter(location__location_type=URL.LOCATION_TYPE).distinct()
active_finding_subquery = build_count_subquery(
LocationFindingReference.objects.filter(
location=OuterRef("location"),
status=FindingLocationStatus.Active,
),
group_field="location",
)
return get_authorized_location_product_reference(Permissions.Location_View).filter(
location__location_type=URL.LOCATION_TYPE,
).annotate(
active_finding_count=Coalesce(active_finding_subquery, Value(0)),
).distinct()

@extend_schema(
request=serializers.ReportGenerateOptionSerializer,
Expand Down
4 changes: 2 additions & 2 deletions dojo/templates/dojo/endpoints.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ <h3 class="has-filters">
{% comment %} The display field is translated in the function. No need to translate here as well{% endcomment %}
<th scope="col">{% dojo_sort request 'Product' 'product' 'asc' %}</th>
{% endif %}
<th class="text-center" nowrap="nowrap" scope="col">Active (Verified) Findings</th>
<th class="text-center" nowrap="nowrap" scope="col">{% dojo_sort request 'Active (Verified) Findings' 'active_finding_count' %}</th>
<th scope="col">Status</th>
</tr>

Expand Down Expand Up @@ -119,7 +119,7 @@ <h3 class="has-filters">
{% if host_view %}
{{ e.host_active_findings_count }} ({{ e.host_active_verified_findings_count }})
{% else %}
<a href="{% url 'open_findings' %}?endpoints={{ e.id }}">{{ e.active_findings_count }}</a>
<a href="{% url 'open_findings' %}?endpoints={{ e.id }}">{{ e.active_finding_count }}</a>
<a href="{% url 'verified_findings' %}?endpoints={{ e.id }}">({{ e.active_verified_findings_count }})</a>
{% endif %}
</td>
Expand Down
2 changes: 1 addition & 1 deletion dojo/templates/dojo/url/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ <h3 class="has-filters">
<th>Endpoint</th>
{% endif %}
{% if not product_tab %}<th class="text-center" nowrap="nowrap">Active (Total) Products</th>{% endif %}
<th class="text-center" nowrap="nowrap">Active (Total) Findings</th>
<th class="text-center" nowrap="nowrap">{% dojo_sort request 'Active (Total) Findings' 'active_findings' %}</th>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one didn't work for me; from what I can tell, while the table does request the correct URL per dojo_sort, the URLFilter used by the view that renders this snippet expects the ordering parameter instead of o. In other words, clicking the listing here doesn't reorder, but changing the queryparam to e.g. ordering=-active_findings does.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't get a notification about the edited comment, but i pushed some changes as the idea was that it would also work for Locations/URLS. But I don't a have working dataset with that locally. Can you review the latest changes?

<th class="text-center" nowrap="nowrap">Overall Status</th>
</tr>
{% for location in locations %}
Expand Down
1 change: 1 addition & 0 deletions dojo/url/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class URLFilter(StaticMethodFilters):
"url__fragment",
"created_at",
"updated_at",
"active_findings",
),
)

Expand Down
37 changes: 37 additions & 0 deletions tests/endpoint_extended_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import sys
import time
import unittest
Expand Down Expand Up @@ -27,6 +28,38 @@ def test_endpoint_host_list(self):
driver.get(self.base_url + "endpoint/host")
self.assertTrue(self.is_text_present_on_page(text="All Hosts"))

def _active_findings_sort_field(self):
v3 = os.environ.get("DD_V3_FEATURE_LOCATIONS", "false").lower() == "true"
return "active_findings" if v3 else "active_finding_count"

@on_exception_html_source_logger
def test_endpoint_list_sort_by_active_findings_asc(self):
driver = self.driver
field = self._active_findings_sort_field()
driver.get(self.base_url + f"endpoint?o={field}")
self.assertTrue(self.is_text_present_on_page(text="Endpoint"))

@on_exception_html_source_logger
def test_endpoint_list_sort_by_active_findings_desc(self):
driver = self.driver
field = self._active_findings_sort_field()
driver.get(self.base_url + f"endpoint?o=-{field}")
self.assertTrue(self.is_text_present_on_page(text="Endpoint"))

@on_exception_html_source_logger
def test_endpoint_host_list_sort_by_active_findings_asc(self):
driver = self.driver
field = self._active_findings_sort_field()
driver.get(self.base_url + f"endpoint/host?o={field}")
self.assertTrue(self.is_text_present_on_page(text="Hosts"))

@on_exception_html_source_logger
def test_endpoint_host_list_sort_by_active_findings_desc(self):
driver = self.driver
field = self._active_findings_sort_field()
driver.get(self.base_url + f"endpoint/host?o=-{field}")
self.assertTrue(self.is_text_present_on_page(text="Hosts"))

@on_exception_html_source_logger
def test_add_endpoint_meta_data(self):
driver = self.driver
Expand Down Expand Up @@ -92,6 +125,10 @@ def suite():
suite.addTest(EndpointExtendedTest("test_vulnerable_endpoints_page"))
suite.addTest(EndpointExtendedTest("test_vulnerable_endpoint_hosts_page"))
suite.addTest(EndpointExtendedTest("test_endpoint_host_list"))
suite.addTest(EndpointExtendedTest("test_endpoint_list_sort_by_active_findings_asc"))
suite.addTest(EndpointExtendedTest("test_endpoint_list_sort_by_active_findings_desc"))
suite.addTest(EndpointExtendedTest("test_endpoint_host_list_sort_by_active_findings_asc"))
suite.addTest(EndpointExtendedTest("test_endpoint_host_list_sort_by_active_findings_desc"))
suite.addTest(EndpointExtendedTest("test_add_endpoint_meta_data"))
suite.addTest(EndpointExtendedTest("test_edit_endpoint_meta_data"))
suite.addTest(ProductTest("test_delete_product"))
Expand Down