Skip to content

Commit d243e11

Browse files
committed
fix(location_manager): exclude special endpoint statuses from reactivation logic
fix(location): optimize get_or_create methods for LocationFindingReference and LocationProductReference test(import_reimport): enhance tests to skip reactivation for special statuses in endpoint and location contexts
1 parent ecb9b42 commit d243e11

3 files changed

Lines changed: 73 additions & 33 deletions

File tree

dojo/importers/location_manager.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,19 @@ def update_location_status(
127127
"""Update the list of locations from the new finding with the list that is in the old finding"""
128128
# New endpoints are already added in serializers.py / views.py (see comment "# for existing findings: make sure endpoints are present or created")
129129
# So we only need to mitigate endpoints that are no longer present
130-
# using `.all()` will mark as mitigated also `endpoint_status` with flags `false_positive`, `out_of_scope` and `risk_accepted`. This is a known issue. This is not a bug. This is a future.
131-
130+
existing_location_refs: QuerySet[LocationFindingReference] = existing_finding.locations.exclude(
131+
status__in=[
132+
FindingLocationStatus.FalsePositive,
133+
FindingLocationStatus.RiskAccepted,
134+
FindingLocationStatus.OutOfScope,
135+
],
136+
)
132137
if new_finding.is_mitigated:
133138
# New finding is mitigated, so mitigate all existing location refs
134-
self.chunk_locations_and_mitigate(existing_finding.locations.all(), user)
139+
self.chunk_locations_and_mitigate(existing_location_refs, user)
135140
else:
136141
# New finding not mitigated; so, reactivate all refs
137-
existing_location_refs: QuerySet[LocationFindingReference] = existing_finding.locations.all()
138-
139-
new_locations_values = [str(location) for location in new_finding.unsaved_locations]
140-
142+
new_locations_values = {str(location) for location in new_finding.unsaved_locations}
141143
# Reactivate endpoints in the old finding that are in the new finding
142144
location_refs_to_reactivate = existing_location_refs.filter(location__location_value__in=new_locations_values)
143145
# Mitigate endpoints in the existing finding not in the new finding

dojo/location/models.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,19 @@ def associate_with_finding(
111111
audit_time: datetime | None = None,
112112
) -> LocationFindingReference:
113113
"""
114-
Get or create a LocationFindingReference for this location and finding,
115-
updating the status each time. Also associates the related product.
114+
Get or create a LocationFindingReference for this location and finding.
115+
Also associates the related product.
116116
"""
117+
# Check if there is an existing reference for this finding and location
118+
# If this method is being used to set the status
119+
if LocationFindingReference.objects.filter(
120+
location=self,
121+
finding=finding,
122+
).exists():
123+
return LocationFindingReference.objects.get(
124+
location=self,
125+
finding=finding,
126+
)
117127
# Determine the status
118128
if status is None:
119129
status = self.status_from_finding(finding)
@@ -144,10 +154,17 @@ def associate_with_product(
144154
product: Product,
145155
status: ProductLocationStatus | None = None,
146156
) -> LocationProductReference:
147-
"""
148-
Get or create a LocationProductReference for this location and product,
149-
updating the status each time.
150-
"""
157+
"""Get or create a LocationProductReference for this location and product"""
158+
# Check if there is an existing reference for this finding and location
159+
# If this method is being used to set the status
160+
if LocationProductReference.objects.filter(
161+
location=self,
162+
product=product,
163+
).exists():
164+
return LocationProductReference.objects.get(
165+
location=self,
166+
product=product,
167+
)
151168
if status is None:
152169
status = self.status_from_product(product)
153170
# Use a transaction for safety in concurrent scenarios

unittests/test_import_reimport.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,17 +2006,42 @@ def test_import_nuclei_emptyc(self):
20062006

20072007
@parameterized.expand(
20082008
[
2009-
("Test false_positive Status", {"false_positive": True}),
2010-
("Test out_of_scope Status", {"out_of_scope": True}),
2011-
("Test risk_accepted Status", {"risk_accepted": True}),
2009+
("Test False Positive Status (Endpoint Status)", {"false_positive": True}, "status_finding"),
2010+
("Test Out of Scope Status (Endpoint Status)", {"out_of_scope": True}, "status_finding"),
2011+
("Test Risk Accepted Status (Endpoint Status)", {"risk_accepted": True}, "status_finding"),
2012+
("Test False Positive Status (Locations)", {"status": "FalsePositive"}, "locations"),
2013+
("Test Out of Scope Status (Locations)", {"status": "OutOfScope"}, "locations"),
2014+
("Test Risk Accepted Status (Locations)", {"status": "RiskAccepted"}, "locations"),
20122015
],
20132016
)
2014-
def test_import_reimport_endpoint_where_eps_reactivation_skips_special_status(self, label: str, special_status_fields: dict):
2017+
def test_import_reimport_endpoint_where_eps_reactivation_skips_special_status(self, label: str, special_status_fields: dict, m2m_key: str):
20152018
"""
20162019
When Findings are set to False Positive, Out of Scope, or Risk Accepted, they are not reactivated
20172020
because these statuses are often set by humans. The same needs to apply for the Endpoint Status as
20182021
they are an extension of the finding being partially mitigated.
20192022
"""
2023+
if settings.V3_FEATURE_LOCATIONS:
2024+
# TODO: Delete this after the move to Locations
2025+
if m2m_key == "status_finding":
2026+
# This test will fail for endpoint statuses with locations enabled
2027+
# return early here
2028+
return
2029+
context = {
2030+
"auditor": User.objects.get(username="admin"),
2031+
"audit_time": timezone.now(),
2032+
}
2033+
# TODO: Delete this after the move to Locations
2034+
else:
2035+
if m2m_key == "locations":
2036+
# This test will fail for locations with locations disabled
2037+
# return early here
2038+
return
2039+
context = {
2040+
"mitigated": True,
2041+
"mitigated_by": User.objects.get(username="admin"),
2042+
"mitigated_time": timezone.now(),
2043+
}
2044+
# Now start the test
20202045
with assertTestImportModelsCreated(self, imports=1, affected_findings=1, created=1):
20212046
import0 = self.import_scan_with_params(
20222047
self.gitlab_dast_file_name, self.scan_type_gitlab_dast, active=True, verified=True,
@@ -2025,18 +2050,12 @@ def test_import_reimport_endpoint_where_eps_reactivation_skips_special_status(se
20252050
findings = self.get_test_findings_api(test_id)
20262051
self.assert_finding_count_json(1, findings)
20272052
finding = Finding.objects.get(id=findings["results"][0]["id"])
2028-
# Get the endpoint status on the finding
2029-
endpoint_statuses = finding.status_finding.all()
2030-
self.assertEqual(len(endpoint_statuses), 1)
2031-
# Set the baseline for the endpoint status, and then use it to compare after the reimport is finished
2032-
endpoint_status_context = {
2033-
"mitigated": True,
2034-
"mitigated_by": User.objects.get(username="admin"),
2035-
"mitigated_time": timezone.now(),
2036-
**special_status_fields,
2037-
}
2038-
# Update the endpoint status with the special status fields (false_positive, out_of_scope or risk_accepted) and mitigated=True
2039-
endpoint_statuses.update(**endpoint_status_context)
2053+
# Get the related objects on the finding
2054+
related_obects = getattr(finding, m2m_key).all()
2055+
self.assertEqual(len(related_obects), 1)
2056+
# Update the related objects with the special status fields
2057+
related_objects_context = {**context, **special_status_fields}
2058+
related_obects.update(**related_objects_context)
20402059
# Reimport the same file
20412060
reimport0 = self.reimport_scan_with_params(
20422061
test_id, self.gitlab_dast_file_name, scan_type=self.scan_type_gitlab_dast,
@@ -2045,11 +2064,13 @@ def test_import_reimport_endpoint_where_eps_reactivation_skips_special_status(se
20452064
findings = self.get_test_findings_api(test_id)
20462065
self.assert_finding_count_json(1, findings)
20472066
finding = Finding.objects.get(id=findings["results"][0]["id"])
2048-
# Get the endpoint status on the finding
2049-
endpoint_status = finding.status_finding.first()
2067+
# Get the related objects on the finding
2068+
related_obects = getattr(finding, m2m_key).all()
2069+
self.assertEqual(len(related_obects), 1)
2070+
related_object = related_obects.first()
20502071
# Ensure the status is the same as the baseline
2051-
for key, value in endpoint_status_context.items():
2052-
self.assertEqual(getattr(endpoint_status, key), value)
2072+
for key, value in related_objects_context.items():
2073+
self.assertEqual(getattr(related_object, key), value)
20532074

20542075
def test_import_reimport_endpoint_where_eps_date_is_different(self):
20552076
endpoint_count_before = self.db_endpoint_count()

0 commit comments

Comments
 (0)