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
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ A few things behave differently from the original Endpoint API:

- **Single status instead of flags.** Locations have one status at a time. If your code relied on a Finding being *both* `mitigated=True` *and* `false_positive=True` simultaneously on an Endpoint_Status, that is no longer representable — the migration picks the highest-priority flag (the order shown in the table above).
- **`endpoint` field on Endpoint_Status.** The legacy `endpoint` field is reconstructed by looking up the matching Asset Reference. In rare cases where a Finding's Asset no longer matches its Location's Asset references, this field may be null.
- **`mitigated` returned a date before 3.2.400.** On releases before 3.2.400 the `mitigated` field of `/api/v2/endpoint_status/` returned the record's creation date instead of a boolean. Because a date string is truthy, `mitigated_time` and `mitigated_by` answered as though every status was mitigated. From 3.2.400 the field is a boolean that is true only when the Location status is `Mitigated`. If you poll for mitigated statuses, upgrade before you trust this field.
- **Pagination and ordering.** Available ordering fields on the read-compat shim are `host`, `product`, `id`, and `active_finding_count`. If your client orders by another field, switch to one of these or move to the new Locations endpoints.

## Tags and Metadata
Expand Down
2 changes: 1 addition & 1 deletion dojo/location/api/endpoint_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def get_date(self, obj) -> datetime.date | None:
return obj.created.date() if obj.created else None

def get_mitigated(self, obj) -> bool | None:
return obj.created.date() if obj.created else None
return obj.status == FindingLocationStatus.Mitigated

def get_mitigated_time(self, obj) -> datetime.datetime | None:
return obj.audit_time if self.get_mitigated(obj) else None
Expand Down
53 changes: 53 additions & 0 deletions unittests/test_endpoint_init_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
from django.urls import reverse
from django.utils import timezone
from openpyxl import load_workbook
from parameterized import parameterized
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

from dojo.finding.deduplication import get_finding_models_for_deduplication
from dojo.finding.helper import post_process_findings_batch
from dojo.github.services import github_body
from dojo.jira.helper import jira_description
from dojo.location.api.endpoint_compat import V3EndpointStatusCompatibleSerializer
from dojo.location.models import LocationFindingReference, LocationProductReference
from dojo.location.status import FindingLocationStatus, ProductLocationStatus
from dojo.models import (
Expand Down Expand Up @@ -335,3 +337,54 @@ def test_post_process_findings_batch_with_legacy_endpoints_under_v3(self):
)

self.assertTrue(Finding.objects.filter(id=tree.finding.id).exists())

# ------------------------------------------------------------------
# endpoint_status compatibility serializer
# ------------------------------------------------------------------
# Regression: V3EndpointStatusCompatibleSerializer.get_mitigated returned obj.created.date()
# (a copy of get_date) instead of the Mitigated status, so every row reported a truthy date.
@parameterized.expand([
(FindingLocationStatus.Active, False),
(FindingLocationStatus.Mitigated, True),
(FindingLocationStatus.FalsePositive, False),
(FindingLocationStatus.RiskAccepted, False),
(FindingLocationStatus.OutOfScope, False),
])
def test_endpoint_status_mitigated_is_a_bool_tracking_the_status(self, status, expected):
"""``mitigated`` must be a bool that is True only for a Mitigated location."""
tree = self._make_tree(f"mit-{status.value}")
ref = self._add_location(tree, f"loc-{status.value.lower()}.example.com", status=status)

data = V3EndpointStatusCompatibleSerializer(ref).data

self.assertIsInstance(
data["mitigated"], bool,
msg=f"expected a bool for status={status.value}, got {type(data['mitigated']).__name__} {data['mitigated']!r}",
)
self.assertEqual(
data["mitigated"], expected,
msg=f"expected mitigated={expected} for status={status.value}, got {data['mitigated']!r}",
)

@parameterized.expand([
(FindingLocationStatus.Active, False),
(FindingLocationStatus.Mitigated, True),
])
def test_endpoint_status_mitigated_time_and_by_follow_mitigated(self, status, expected):
"""``mitigated_time``/``mitigated_by`` branch on get_mitigated, so they must follow it."""
tree = self._make_tree(f"mitby-{status.value}")
ref = self._add_location(tree, f"loc-mitby-{status.value.lower()}.example.com", status=status)
ref.auditor = self.admin
ref.audit_time = timezone.now()
ref.save()

data = V3EndpointStatusCompatibleSerializer(ref).data

self.assertEqual(
data["mitigated_time"] is not None, expected,
msg=f"expected mitigated_time set={expected} for status={status.value}, got {data['mitigated_time']!r}",
)
self.assertEqual(
data["mitigated_by"], self.admin.id if expected else None,
msg=f"expected mitigated_by set={expected} for status={status.value}, got {data['mitigated_by']!r}",
)
Loading