Skip to content
Open
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
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>
<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