Skip to content

Commit 57069ba

Browse files
Fix naive datetime warnings/errors (#13170)
* test filter on last_status_update * test filter on last_status_update * make datetimes timezone aware
1 parent 1a80117 commit 57069ba

7 files changed

Lines changed: 78 additions & 42 deletions

File tree

dojo/engagement/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,10 @@ def engagement_ics(request, eid):
15521552
eng = get_object_or_404(Engagement, id=eid)
15531553
start_date = datetime.combine(eng.target_start, datetime.min.time())
15541554
end_date = datetime.combine(eng.target_end, datetime.max.time())
1555+
if timezone.is_naive(start_date):
1556+
start_date = timezone.make_aware(start_date)
1557+
if timezone.is_naive(end_date):
1558+
end_date = timezone.make_aware(end_date)
15551559
uid = f"dojo_eng_{eng.id}_{eng.product.id}"
15561560
cal = get_cal_event(
15571561
start_date,

dojo/filters.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
RangeFilter,
3131
)
3232
from django_filters import rest_framework as filters
33-
from django_filters.filters import ChoiceFilter, _truncate
33+
from django_filters.filters import ChoiceFilter
3434
from drf_spectacular.types import OpenApiTypes
3535
from drf_spectacular.utils import extend_schema_field
3636
from polymorphic.base import ManagerInheritanceWarning
@@ -92,7 +92,7 @@
9292
from dojo.risk_acceptance.queries import get_authorized_risk_acceptances
9393
from dojo.test.queries import get_authorized_tests
9494
from dojo.user.queries import get_authorized_users
95-
from dojo.utils import get_system_setting, is_finding_groups_enabled
95+
from dojo.utils import get_system_setting, is_finding_groups_enabled, truncate_timezone_aware
9696

9797
logger = logging.getLogger(__name__)
9898

@@ -194,8 +194,8 @@ def filter(self, qs, value):
194194
if earliest_finding is not None:
195195
start_date = datetime.combine(
196196
earliest_finding.date, datetime.min.time()).replace(tzinfo=tzinfo())
197-
self.start_date = _truncate(start_date - timedelta(days=1))
198-
self.end_date = _truncate(now() + timedelta(days=1))
197+
self.start_date = truncate_timezone_aware(start_date - timedelta(days=1))
198+
self.end_date = truncate_timezone_aware(now() + timedelta(days=1))
199199
try:
200200
value = int(value)
201201
except (ValueError, TypeError):
@@ -654,16 +654,16 @@ class DateRangeFilter(ChoiceFilter):
654654
f"{name}__day": now().day,
655655
})),
656656
2: (_("Past 7 days"), lambda qs, name: qs.filter(**{
657-
f"{name}__gte": _truncate(now() - timedelta(days=7)),
658-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
657+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=7)),
658+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
659659
})),
660660
3: (_("Past 30 days"), lambda qs, name: qs.filter(**{
661-
f"{name}__gte": _truncate(now() - timedelta(days=30)),
662-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
661+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=30)),
662+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
663663
})),
664664
4: (_("Past 90 days"), lambda qs, name: qs.filter(**{
665-
f"{name}__gte": _truncate(now() - timedelta(days=90)),
666-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
665+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=90)),
666+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
667667
})),
668668
5: (_("Current month"), lambda qs, name: qs.filter(**{
669669
f"{name}__year": now().year,
@@ -673,8 +673,8 @@ class DateRangeFilter(ChoiceFilter):
673673
f"{name}__year": now().year,
674674
})),
675675
7: (_("Past year"), lambda qs, name: qs.filter(**{
676-
f"{name}__gte": _truncate(now() - timedelta(days=365)),
677-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
676+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=365)),
677+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
678678
})),
679679
}
680680

@@ -700,43 +700,43 @@ class DateRangeOmniFilter(ChoiceFilter):
700700
f"{name}__day": now().day,
701701
})),
702702
2: (_("Next 7 days"), lambda qs, name: qs.filter(**{
703-
f"{name}__gte": _truncate(now() + timedelta(days=1)),
704-
f"{name}__lt": _truncate(now() + timedelta(days=7)),
703+
f"{name}__gte": truncate_timezone_aware(now() + timedelta(days=1)),
704+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=7)),
705705
})),
706706
3: (_("Next 30 days"), lambda qs, name: qs.filter(**{
707-
f"{name}__gte": _truncate(now() + timedelta(days=1)),
708-
f"{name}__lt": _truncate(now() + timedelta(days=30)),
707+
f"{name}__gte": truncate_timezone_aware(now() + timedelta(days=1)),
708+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=30)),
709709
})),
710710
4: (_("Next 90 days"), lambda qs, name: qs.filter(**{
711-
f"{name}__gte": _truncate(now() + timedelta(days=1)),
712-
f"{name}__lt": _truncate(now() + timedelta(days=90)),
711+
f"{name}__gte": truncate_timezone_aware(now() + timedelta(days=1)),
712+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=90)),
713713
})),
714714
5: (_("Past 7 days"), lambda qs, name: qs.filter(**{
715-
f"{name}__gte": _truncate(now() - timedelta(days=7)),
716-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
715+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=7)),
716+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
717717
})),
718718
6: (_("Past 30 days"), lambda qs, name: qs.filter(**{
719-
f"{name}__gte": _truncate(now() - timedelta(days=30)),
720-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
719+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=30)),
720+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
721721
})),
722722
7: (_("Past 90 days"), lambda qs, name: qs.filter(**{
723-
f"{name}__gte": _truncate(now() - timedelta(days=90)),
724-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
723+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=90)),
724+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
725725
})),
726726
8: (_("Current month"), lambda qs, name: qs.filter(**{
727727
f"{name}__year": now().year,
728728
f"{name}__month": now().month,
729729
})),
730730
9: (_("Past year"), lambda qs, name: qs.filter(**{
731-
f"{name}__gte": _truncate(now() - timedelta(days=365)),
732-
f"{name}__lt": _truncate(now() + timedelta(days=1)),
731+
f"{name}__gte": truncate_timezone_aware(now() - timedelta(days=365)),
732+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=1)),
733733
})),
734734
10: (_("Current year"), lambda qs, name: qs.filter(**{
735735
f"{name}__year": now().year,
736736
})),
737737
11: (_("Next year"), lambda qs, name: qs.filter(**{
738-
f"{name}__gte": _truncate(now() + timedelta(days=1)),
739-
f"{name}__lt": _truncate(now() + timedelta(days=365)),
738+
f"{name}__gte": truncate_timezone_aware(now() + timedelta(days=1)),
739+
f"{name}__lt": truncate_timezone_aware(now() + timedelta(days=365)),
740740
})),
741741
}
742742

@@ -818,8 +818,8 @@ def any(self, qs, name):
818818
if earliest_finding is not None:
819819
start_date = datetime.combine(
820820
earliest_finding.date, datetime.min.time()).replace(tzinfo=tzinfo())
821-
self.start_date = _truncate(start_date - timedelta(days=1))
822-
self.end_date = _truncate(now() + timedelta(days=1))
821+
self.start_date = truncate_timezone_aware(start_date - timedelta(days=1))
822+
self.end_date = truncate_timezone_aware(now() + timedelta(days=1))
823823
return qs.all()
824824
return None
825825

@@ -839,8 +839,8 @@ def current_year(self, qs, name):
839839
})
840840

841841
def past_x_days(self, qs, name, days):
842-
self.start_date = _truncate(now() - timedelta(days=days))
843-
self.end_date = _truncate(now() + timedelta(days=1))
842+
self.start_date = truncate_timezone_aware(now() - timedelta(days=days))
843+
self.end_date = truncate_timezone_aware(now() + timedelta(days=1))
844844
return qs.filter(**{
845845
f"{name}__gte": self.start_date,
846846
f"{name}__lt": self.end_date,
@@ -884,8 +884,8 @@ def filter(self, qs, value):
884884
if earliest_finding is not None:
885885
start_date = datetime.combine(
886886
earliest_finding.date, datetime.min.time()).replace(tzinfo=tzinfo())
887-
self.start_date = _truncate(start_date - timedelta(days=1))
888-
self.end_date = _truncate(now() + timedelta(days=1))
887+
self.start_date = truncate_timezone_aware(start_date - timedelta(days=1))
888+
self.end_date = truncate_timezone_aware(now() + timedelta(days=1))
889889
try:
890890
value = int(value)
891891
except (ValueError, TypeError):

dojo/metrics/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ def js_epoch(
404404
"""
405405
if isinstance(d, date):
406406
d = datetime.combine(d, datetime.min.time())
407+
if timezone.is_naive(d):
408+
d = timezone.make_aware(d)
407409
return int(d.timestamp()) * 1000
408410

409411

dojo/test/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,10 @@ def test_ics(request, tid):
405405
test = get_object_or_404(Test, id=tid)
406406
start_date = datetime.combine(test.target_start, datetime.min.time())
407407
end_date = datetime.combine(test.target_end, datetime.max.time())
408+
if timezone.is_naive(start_date):
409+
start_date = timezone.make_aware(start_date)
410+
if timezone.is_naive(end_date):
411+
end_date = timezone.make_aware(end_date)
408412
uid = f"dojo_test_{test.id}_{test.engagement.id}_{test.engagement.product.id}"
409413
cal = get_cal_event(
410414
start_date,

dojo/tools/cyberwatch_galeax/parser.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import cvss.parser
77
from cvss.cvss3 import CVSS3
8+
from django.utils import timezone
89

910
from dojo.models import Endpoint, Endpoint_Status, Finding
1011

@@ -202,7 +203,7 @@ def build_findings_for_cve(self, cve_code, c_data, test):
202203
products = c_data["products"]
203204

204205
if not products:
205-
mitigated_date = datetime.now()
206+
mitigated_date = timezone.now()
206207
mitigation = f"Fixed At: {mitigated_date}"
207208
endpoints = [Endpoint(host=e) for e in c_data["no_product_endpoints"]]
208209

@@ -269,7 +270,7 @@ def determine_product_finding_state(self, p_data):
269270
active_status = any(am[0] for am in p_data["active_mitigated_data"])
270271
mitigated_date = (max(am[1] for am in p_data["active_mitigated_data"] if am[1])
271272
if [am[1] for am in p_data["active_mitigated_data"] if am[1]] and not active_status
272-
else (datetime.now() if not active_status else None))
273+
else (timezone.now() if not active_status else None))
273274
return component_version_str, active_status, mitigated_date
274275

275276
def create_finding(
@@ -466,7 +467,7 @@ def process_servers_for_security_issue(self, servers):
466467
active_status = True
467468
mitigated_date = None
468469
else:
469-
mitigated_date = datetime.now()
470+
mitigated_date = timezone.now()
470471
mitigated_dates.append(mitigated_date)
471472

472473
detected_at_str = server.get("detected_at")
@@ -483,15 +484,15 @@ def process_servers_for_security_issue(self, servers):
483484
)
484485
unsaved_endpoint_status.append(endpoint_status)
485486

486-
mitigated_date = (max(mitigated_dates) if mitigated_dates else datetime.now()) if not active_status else None
487+
mitigated_date = (max(mitigated_dates) if mitigated_dates else timezone.now()) if not active_status else None
487488
return unsaved_endpoints, unsaved_endpoint_status, active_status, mitigated_date
488489

489490
def parse_detected_at(self, detected_at_str):
490491
"""Parse the detected_at field for a security issue server."""
491492
try:
492493
return datetime.strptime(detected_at_str, "%Y-%m-%dT%H:%M:%S.%fZ")
493494
except (ValueError, TypeError):
494-
return datetime.now()
495+
return timezone.now()
495496

496497
def parse_fixed_at(self, fixed_at_str):
497498
"""Parse fixed_at datetime, defaulting to now if parsing fails."""
@@ -500,7 +501,7 @@ def parse_fixed_at(self, fixed_at_str):
500501
return datetime.strptime(fixed_at_str, "%Y-%m-%dT%H:%M:%S.%f%z")
501502
except ValueError as e:
502503
logger.error(f'Error parsing fixed_at date "{fixed_at_str}": {e}')
503-
return datetime.now()
504+
return timezone.now()
504505

505506
def parse_datetime(self, dt_str):
506507
"""Parse a datetime string with fallback to now on error."""
@@ -509,7 +510,7 @@ def parse_datetime(self, dt_str):
509510
return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%f%z")
510511
except (ValueError, TypeError):
511512
logger.error(f'Error parsing datetime "{dt_str}"')
512-
return datetime.now()
513+
return timezone.now()
513514

514515
def parse_cvss(self, cvss_v3_vector, json_data):
515516
if cvss_v3_vector:

dojo/utils.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2470,7 +2470,8 @@ def get_open_findings_burndown(product):
24702470
findings = Finding.objects.filter(test__engagement__product=product, duplicate=False)
24712471
f_list = list(findings)
24722472

2473-
curr_date = datetime.combine(datetime.now(), datetime.min.time())
2473+
curr_date = datetime.combine(timezone.now().date(), datetime.min.time())
2474+
curr_date = timezone.make_aware(curr_date)
24742475
start_date = curr_date - timedelta(days=90)
24752476

24762477
critical_count = 0
@@ -2708,3 +2709,21 @@ def parse_cvss_data(cvss_vector_string: str) -> dict:
27082709
}
27092710
logger.debug("No valid CVSS3 or CVSS4 vector found in %s", cvss_vector_string)
27102711
return {}
2712+
2713+
2714+
def truncate_timezone_aware(dt):
2715+
"""
2716+
Truncate datetime to date and make it timezone-aware.
2717+
This replaces the django_filters._truncate function which creates naive datetimes.
2718+
"""
2719+
if dt is None:
2720+
return None
2721+
2722+
# Get the date part and create a new datetime at midnight
2723+
truncated = datetime.combine(dt.date(), datetime.min.time())
2724+
2725+
# Make it timezone-aware if it isn't already
2726+
if timezone.is_naive(truncated):
2727+
truncated = timezone.make_aware(truncated)
2728+
2729+
return truncated

tests/check_various_pages.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def test_finding_group_open_filtered_status(self):
3232
driver = self.driver
3333
driver.get(self.base_url + "finding_group/open?name=CVE&severity=Medium&engagement=14&product=6")
3434

35+
def test_date_filter(self):
36+
driver = self.driver
37+
# can result in an error about date not having timezone information
38+
driver.get(self.base_url + "finding/open?last_status_update=2")
39+
3540

3641
def suite():
3742
suite = unittest.TestSuite()
@@ -42,6 +47,7 @@ def suite():
4247
suite.addTest(VariousPagesTest("test_finding_group_all_status"))
4348
suite.addTest(VariousPagesTest("test_finding_group_closed_status"))
4449
suite.addTest(VariousPagesTest("test_finding_group_open_filtered_status"))
50+
suite.addTest(VariousPagesTest("test_date_filter"))
4551
return suite
4652

4753

0 commit comments

Comments
 (0)