Skip to content

Commit 12b4d48

Browse files
dogboatMaffooch
andauthored
V3/locations Endpoint object init guards (#15142)
* guard endpoints usage * render jira templates locations instead of guarding * tests * render locations in reports when v3, tests * render locations on report api when necessary * cleanup * test fixes --------- Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com>
1 parent 98d0cc3 commit 12b4d48

9 files changed

Lines changed: 369 additions & 28 deletions

File tree

dojo/api_v2/serializers.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,6 @@ class MetaMainSerializer(serializers.Serializer):
254254
default=None,
255255
allow_null=True,
256256
)
257-
endpoint = serializers.PrimaryKeyRelatedField(
258-
queryset=Endpoint.objects.all(),
259-
required=False,
260-
default=None,
261-
allow_null=True,
262-
)
263257
finding = serializers.PrimaryKeyRelatedField(
264258
queryset=Finding.objects.all(),
265259
required=False,
@@ -268,6 +262,17 @@ class MetaMainSerializer(serializers.Serializer):
268262
)
269263
metadata = MetadataSerializer(many=True)
270264

265+
# TODO: Delete this after the move to Locations
266+
def __init__(self, *args, **kwargs):
267+
super().__init__(*args, **kwargs)
268+
if not settings.V3_FEATURE_LOCATIONS:
269+
self.fields["endpoint"] = serializers.PrimaryKeyRelatedField(
270+
queryset=Endpoint.objects.all(),
271+
required=False,
272+
default=None,
273+
allow_null=True,
274+
)
275+
271276
def validate(self, data):
272277
product_id = data.get("product", None)
273278
endpoint_id = data.get("endpoint", None)
@@ -1052,7 +1057,6 @@ class ReportGenerateSerializer(serializers.Serializer):
10521057
report_info = serializers.CharField(max_length=200)
10531058
test = TestSerializer(many=False, read_only=True)
10541059
endpoint = EndpointSerializer(many=False, read_only=True)
1055-
endpoints = EndpointSerializer(many=True, read_only=True)
10561060
findings = FindingSerializer(many=True, read_only=True)
10571061
user = UserStubSerializer(many=False, read_only=True)
10581062
team_name = serializers.CharField(max_length=200)
@@ -1063,6 +1067,22 @@ class ReportGenerateSerializer(serializers.Serializer):
10631067
many=True, allow_null=True, required=False,
10641068
)
10651069

1070+
def __init__(self, *args, **kwargs):
1071+
super().__init__(*args, **kwargs)
1072+
# Locations are the default under V3; V3EndpointCompatibleSerializer presents them with the
1073+
# same endpoint-compatible shape the V3 /endpoints route uses. Legacy Endpoints are the
1074+
# V2-only exception. The lazy import avoids a circular import: endpoint_compat imports this
1075+
# module (and its viewset references ReportGenerateSerializer in @extend_schema at
1076+
# class-definition time).
1077+
if settings.V3_FEATURE_LOCATIONS:
1078+
from dojo.location.api.endpoint_compat import ( # noqa: PLC0415
1079+
V3EndpointCompatibleSerializer,
1080+
)
1081+
self.fields["endpoints"] = V3EndpointCompatibleSerializer(many=True, read_only=True)
1082+
else:
1083+
# TODO: Delete this after the move to Locations
1084+
self.fields["endpoints"] = EndpointSerializer(many=True, read_only=True)
1085+
10661086

10671087
from dojo.jira.api.serializers import ( # noqa: E402, F401 backward compat
10681088
EngagementUpdateJiraEpicSerializer,

dojo/api_v2/views.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from django.contrib.auth.models import Permission
88
from django.core.exceptions import ValidationError
99
from django.db import IntegrityError
10+
from django.db.models import OuterRef, Value
11+
from django.db.models.functions import Coalesce
1012
from django.db.models.query import QuerySet as DjangoQuerySet
1113
from django.utils import timezone
1214
from django_filters.rest_framework import DjangoFilterBackend
@@ -45,6 +47,8 @@
4547
from dojo.importers.auto_create_context import AutoCreateContextManager
4648
from dojo.jira import services as jira_services
4749
from dojo.labels import get_labels
50+
from dojo.location.models import LocationFindingReference, LocationProductReference
51+
from dojo.location.status import FindingLocationStatus
4852
from dojo.models import (
4953
App_Analysis,
5054
Dojo_User,
@@ -67,11 +71,13 @@
6771
get_authorized_languages,
6872
get_authorized_products,
6973
)
74+
from dojo.query_utils import build_count_subquery
7075
from dojo.reports.ui.views import (
7176
prefetch_related_findings_for_report,
7277
report_url_resolver,
7378
)
7479
from dojo.test.queries import get_authorized_tests
80+
from dojo.url.models import URL
7581
from dojo.user.utils import get_configuration_permissions_codenames
7682
from dojo.utils import (
7783
get_celery_queue_details,
@@ -261,11 +267,13 @@ def _fetch_and_authorize_parents(self, request, permission_map):
261267
"""Fetch parent objects and verify the user has the required permissions."""
262268
data = request.data
263269
parents = {}
264-
for field, (model, permission) in permission_map.items():
265-
obj = model.objects.filter(id=data.get(field)).first()
266-
if obj:
267-
user_has_permission_or_403(request.user, obj, permission)
268-
parents[field] = obj
270+
# TODO: Delete this after the move to Locations
271+
with Endpoint.allow_endpoint_init():
272+
for field, (model, permission) in permission_map.items():
273+
obj = model.objects.filter(id=data.get(field)).first()
274+
if obj:
275+
user_has_permission_or_403(request.user, obj, permission)
276+
parents[field] = obj
269277
return parents
270278

271279
def process_post(self, request):
@@ -519,6 +527,29 @@ def perform_create(self, serializer):
519527
from dojo.notes.api.views import NotesViewSet # noqa: E402, F401 -- re-export; urls.py imports by name
520528

521529

530+
def _report_url_location_refs(product):
531+
"""
532+
URL LocationProductReferences for a product, shaped for V3EndpointCompatibleSerializer.
533+
534+
Mirrors V3EndpointCompatibleViewSet.get_queryset so the report's ``endpoints`` field matches
535+
the V3 ``/endpoints`` route. Non-URL locations (e.g. dependencies) are excluded because the
536+
compat serializer only understands URL-backed locations.
537+
"""
538+
active_finding_subquery = build_count_subquery(
539+
LocationFindingReference.objects.filter(
540+
location=OuterRef("location"),
541+
status=FindingLocationStatus.Active,
542+
),
543+
group_field="location",
544+
)
545+
return LocationProductReference.objects.filter(
546+
product=product,
547+
location__location_type=URL.LOCATION_TYPE,
548+
).annotate(
549+
active_finding_count=Coalesce(active_finding_subquery, Value(0)),
550+
).distinct()
551+
552+
522553
def report_generate(request, obj, options):
523554
user = Dojo_User.objects.get(id=request.user.id)
524555
product_type = None
@@ -589,10 +620,14 @@ def report_generate(request, obj, options):
589620
Finding.objects.filter(test__engagement__product=product),
590621
),
591622
)
592-
ids = get_endpoint_ids(
593-
Endpoint.objects.filter(product=product).distinct(),
594-
)
595-
endpoints = Endpoint.objects.filter(id__in=ids)
623+
if settings.V3_FEATURE_LOCATIONS:
624+
endpoints = _report_url_location_refs(product)
625+
else:
626+
# TODO: Delete this after the move to Locations
627+
ids = get_endpoint_ids(
628+
Endpoint.objects.filter(product=product).distinct(),
629+
)
630+
endpoints = Endpoint.objects.filter(id__in=ids)
596631

597632
elif type(obj).__name__ == "Engagement":
598633
engagement = obj
@@ -605,11 +640,14 @@ def report_generate(request, obj, options):
605640
)
606641
report_name = "Engagement Report: " + str(engagement)
607642

608-
ids = set(finding.id for finding in findings.qs) # noqa: C401
609-
ids = get_endpoint_ids(
610-
Endpoint.objects.filter(product=engagement.product).distinct(),
611-
)
612-
endpoints = Endpoint.objects.filter(id__in=ids)
643+
if settings.V3_FEATURE_LOCATIONS:
644+
endpoints = _report_url_location_refs(engagement.product)
645+
else:
646+
# TODO: Delete this after the move to Locations
647+
ids = get_endpoint_ids(
648+
Endpoint.objects.filter(product=engagement.product).distinct(),
649+
)
650+
endpoints = Endpoint.objects.filter(id__in=ids)
613651

614652
elif type(obj).__name__ == "Test":
615653
test = obj

dojo/auditlog/ui/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ def action_history(request, cid, oid):
7373
# TODO: Delete this after the move to Locations
7474
elif ct.model == "endpoint":
7575
user_has_permission_or_403(request.user, obj, "view")
76-
object_value = Endpoint.objects.get(id=obj.id)
76+
with Endpoint.allow_endpoint_init():
77+
object_value = Endpoint.objects.get(id=obj.id)
7778
product_id = object_value.product.id
7879
active_tab = "endpoints"
7980
elif ct.model == "risk_acceptance":

dojo/github/services.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import sys
33

4+
from django.conf import settings
45
from django.template.loader import render_to_string
56
from github import Auth, Github
67

@@ -160,4 +161,5 @@ def github_body(find):
160161
template = "issue-trackers/jira_full/jira-description.tpl"
161162
kwargs = {}
162163
kwargs["finding"] = find
164+
kwargs["V3_FEATURE_LOCATIONS"] = settings.V3_FEATURE_LOCATIONS
163165
return render_to_string(template, kwargs)

dojo/jira/helper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,7 @@ def jira_description(obj, **kwargs):
705705
elif isinstance(obj, Finding_Group):
706706
kwargs["finding_group"] = obj
707707

708+
kwargs["V3_FEATURE_LOCATIONS"] = settings.V3_FEATURE_LOCATIONS
708709
description = render_to_string(template, kwargs)
709710
defect_dojo_obj_url = get_full_url(obj.get_absolute_url())
710711
max_length = getattr(settings, "JIRA_DESCRIPTION_MAX_LENGTH", 32767)

dojo/reports/ui/views.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -917,8 +917,12 @@ def get(self, request):
917917
fields.append(finding.test.engagement.product.name)
918918

919919
endpoint_value = ""
920-
for endpoint in finding.endpoints.all():
921-
endpoint_value += f"{endpoint}; "
920+
if settings.V3_FEATURE_LOCATIONS:
921+
for location_ref in finding.locations.all():
922+
endpoint_value += f"{location_ref.location}; "
923+
else:
924+
for endpoint in finding.endpoints.all():
925+
endpoint_value += f"{endpoint}; "
922926
endpoint_value = endpoint_value.removesuffix("; ")
923927
if len(endpoint_value) > EXCEL_CHAR_LIMIT:
924928
endpoint_value = endpoint_value[:EXCEL_CHAR_LIMIT - 3] + "..."
@@ -1082,8 +1086,12 @@ def get(self, request):
10821086
col_num += 1
10831087

10841088
endpoint_value = ""
1085-
for endpoint in finding.endpoints.all():
1086-
endpoint_value += f"{endpoint}; \n"
1089+
if settings.V3_FEATURE_LOCATIONS:
1090+
for location_ref in finding.locations.all():
1091+
endpoint_value += f"{location_ref.location}; \n"
1092+
else:
1093+
for endpoint in finding.endpoints.all():
1094+
endpoint_value += f"{endpoint}; \n"
10871095
endpoint_value = endpoint_value.removesuffix("; \n")
10881096
if len(endpoint_value) > EXCEL_CHAR_LIMIT:
10891097
endpoint_value = endpoint_value[:EXCEL_CHAR_LIMIT - 3] + "..."

dojo/templates/issue-trackers/jira_full/jira-description.tpl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,23 @@
4343
*Commit hash:* {{ finding.test.engagement.commit_hash }}
4444
{% endif %}
4545

46+
{% if V3_FEATURE_LOCATIONS %}
47+
{% if finding.locations.all %}
48+
*Systems/Locations*:
49+
||Location||Status||
50+
{% for location_ref in finding.locations.all %}|{{ location_ref.location }}|{{ location_ref.get_status_display }}|
51+
{% endfor %}
52+
{% endif %}
53+
{% else %}
54+
{% comment %} TODO: Delete this after the move to Locations {% endcomment %}
4655
{% if finding.endpoints.all %}
4756
*Systems/Endpoints*:
4857
||System/Endpoint||Status||
4958
{% for endpoint in finding|get_vulnerable_endpoints %}|{{ endpoint }}|{{ endpoint|endpoint_display_status:finding }}|
5059
{% endfor %}{% for endpoint in finding|get_mitigated_endpoints %}|{{ endpoint }}|{{ endpoint|endpoint_display_status:finding }}|
5160
{% endfor %}
52-
{%endif%}
61+
{% endif %}
62+
{% endif %}
5363

5464

5565
{% if finding.component_name %}

dojo/templates/issue-trackers/jira_full/jira-finding-group-description.tpl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,23 @@ h3. [{{ finding.title|jiraencode}}|{{ finding_url|full_url }}]
4747
{% if finding.cve %}*CVE:* [{{ finding.cve }}|{{ finding.cve|vulnerability_url }}]{% else %}*CVE:* Unknown{% endif %}
4848
{% if finding.cvssv3_score %} *CVSSv3 Score:* {{ finding.cvssv3_score }} {% endif %}
4949

50+
{% if V3_FEATURE_LOCATIONS %}
51+
{% if finding.locations.all %}
52+
*Systems/Locations*:
53+
||Location||Status||
54+
{% for location_ref in finding.locations.all %}|{{ location_ref.location }}|{{ location_ref.get_status_display }}|
55+
{% endfor %}
56+
{% endif %}
57+
{% else %}
58+
{% comment %} TODO: Delete this after the move to Locations {% endcomment %}
5059
{% if finding.endpoints.all %}
5160
*Systems/Endpoints*:
5261
||System/Endpoint||Status||
5362
{% for endpoint in finding|get_vulnerable_endpoints %}|{{ endpoint }}|{{ endpoint|endpoint_display_status:finding }}|
5463
{% endfor %}{% for endpoint in finding|get_mitigated_endpoints %}|{{ endpoint }}|{{ endpoint|endpoint_display_status:finding }}|
5564
{% endfor %}
56-
{%endif%}
65+
{% endif %}
66+
{% endif %}
5767

5868
{% if finding.sast_source_object %}
5969
*Source Object*: {{ finding.sast_source_object }}

0 commit comments

Comments
 (0)