Skip to content

Commit 4a77d08

Browse files
Merge branch 'dev' into valentijnscholten-patch-6
2 parents 2cd9118 + 37b3e8e commit 4a77d08

41 files changed

Lines changed: 2978 additions & 774 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/gh-pages.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@ jobs:
4646

4747
- name: Install dependencies
4848
run: cd docs && npm ci
49-
49+
5050
- name: Build production website
5151
env:
5252
HUGO_ENVIRONMENT: production
5353
HUGO_ENV: production
5454
run: cd docs && hugo --minify --gc --config config/production/hugo.toml
55-
5655
- name: Deploy
5756
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
5857
if: github.repository == 'DefectDojo/django-DefectDojo' # Deploy docs only in core repo, not in forks - it would just fail in fork

docs/content/en/open_source/upgrading/2.54.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: 'Upgrading to DefectDojo Version 2.54.x'
33
toc_hide: true
44
weight: -20251226
5-
description: Removal of django-auditlog & Dropped support for DD_PARSER_EXCLUDE & Reimport performance improvements
5+
description: Removal of django-auditlog & Dropped support for DD_PARSER_EXCLUDE & Reimport performance improvements & Removal of Finding Template Matching
66
---
77

88
## Breaking Change: Removal of django-auditlog
@@ -55,5 +55,9 @@ DefectDojo 2.54.x includes performance improvements for reimporting scan results
5555

5656
No action is required after upgrading. (Optional tuning knobs exist via `DD_IMPORT_REIMPORT_MATCH_BATCH_SIZE` and `DD_IMPORT_REIMPORT_DEDUPE_BATCH_SIZE`.)
5757

58+
## Finding Template enhancements and removal of CWE matching
59+
60+
As communicated in the [2025Q1 community update](https://github.com/DefectDojo/django-DefectDojo/discussions/12153) the automated matching of Finding Templates based on `CWE` and/or `title` has now been removed.
61+
5862
There are other instructions for upgrading to 2.54.x. Check the Release Notes for the contents of the release: `https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.54.0`
5963
Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.54.0) for the contents of the release.

dojo/api_v2/serializers.py

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from dojo.authorization.roles_permissions import Permissions
3030
from dojo.endpoint.utils import endpoint_filter, endpoint_meta_import
3131
from dojo.finding.helper import (
32+
save_endpoints_template,
3233
save_vulnerability_ids,
3334
save_vulnerability_ids_template,
3435
)
@@ -111,7 +112,6 @@
111112
User,
112113
UserContactInfo,
113114
Vulnerability_Id,
114-
Vulnerability_Id_Template,
115115
get_current_date,
116116
)
117117
from dojo.notifications.helper import create_notification
@@ -2029,57 +2029,80 @@ def validate_severity(self, value: str) -> str:
20292029
return value
20302030

20312031

2032-
class VulnerabilityIdTemplateSerializer(serializers.ModelSerializer):
2033-
class Meta:
2034-
model = Vulnerability_Id_Template
2035-
fields = ["vulnerability_id"]
2036-
2037-
20382032
class FindingTemplateSerializer(serializers.ModelSerializer):
20392033
tags = TagListSerializerField(required=False)
2040-
vulnerability_ids = VulnerabilityIdTemplateSerializer(
2041-
source="vulnerability_id_template_set", many=True, required=False,
2042-
)
2034+
vulnerability_ids = serializers.SerializerMethodField()
2035+
endpoints = serializers.SerializerMethodField()
20432036

20442037
class Meta:
20452038
model = Finding_Template
2046-
exclude = ("cve",)
2039+
exclude = ("cve", "vulnerability_ids_text")
2040+
2041+
@extend_schema_field(serializers.ListField(child=serializers.CharField()))
2042+
def get_vulnerability_ids(self, obj):
2043+
"""Return vulnerability IDs as a list of strings."""
2044+
return obj.vulnerability_ids
2045+
2046+
@extend_schema_field(serializers.ListField(child=serializers.CharField()))
2047+
def get_endpoints(self, obj):
2048+
"""Return endpoints as a list of URL strings."""
2049+
return obj.endpoints if hasattr(obj, "endpoints") else []
20472050

20482051
def create(self, validated_data):
20492052

2050-
# Save vulnerability ids and pop them
2051-
if "vulnerability_id_template_set" in validated_data:
2052-
vulnerability_id_set = validated_data.pop(
2053-
"vulnerability_id_template_set",
2054-
)
2055-
else:
2056-
vulnerability_id_set = None
2053+
# Handle vulnerability_ids if provided as list
2054+
vulnerability_ids = None
2055+
if "vulnerability_ids" in self.initial_data:
2056+
vulnerability_ids = self.initial_data.get("vulnerability_ids", [])
2057+
if isinstance(vulnerability_ids, str):
2058+
# If it's a string, split by newlines
2059+
vulnerability_ids = [vid.strip() for vid in vulnerability_ids.split("\n") if vid.strip()]
2060+
elif not isinstance(vulnerability_ids, list):
2061+
vulnerability_ids = []
2062+
2063+
# Handle endpoints if provided as list
2064+
endpoint_urls = None
2065+
if "endpoints" in self.initial_data:
2066+
endpoint_urls = self.initial_data.get("endpoints", [])
2067+
if isinstance(endpoint_urls, str):
2068+
# If it's a string, split by newlines
2069+
endpoint_urls = [url.strip() for url in endpoint_urls.split("\n") if url.strip()]
2070+
elif not isinstance(endpoint_urls, list):
2071+
endpoint_urls = []
20572072

20582073
new_finding_template = super().create(
20592074
validated_data,
20602075
)
20612076

2062-
if vulnerability_id_set:
2063-
vulnerability_ids = [vulnerability_id["vulnerability_id"] for vulnerability_id in vulnerability_id_set]
2064-
validated_data["cve"] = vulnerability_ids[0]
2065-
save_vulnerability_ids_template(
2066-
new_finding_template, vulnerability_ids,
2067-
)
2068-
new_finding_template.save()
2077+
# Save vulnerability IDs using helper
2078+
if vulnerability_ids:
2079+
save_vulnerability_ids_template(new_finding_template, vulnerability_ids)
2080+
2081+
# Save endpoints using helper
2082+
if endpoint_urls:
2083+
save_endpoints_template(new_finding_template, endpoint_urls)
20692084

20702085
return new_finding_template
20712086

20722087
def update(self, instance, validated_data):
2073-
# Save vulnerability ids and pop them
2074-
if "vulnerability_id_template_set" in validated_data:
2075-
vulnerability_id_set = validated_data.pop(
2076-
"vulnerability_id_template_set",
2077-
)
2078-
vulnerability_ids = []
2079-
if vulnerability_id_set:
2080-
vulnerability_ids.extend(vulnerability_id["vulnerability_id"] for vulnerability_id in vulnerability_id_set)
2088+
# Handle vulnerability_ids if provided
2089+
if "vulnerability_ids" in self.initial_data:
2090+
vulnerability_ids = self.initial_data.get("vulnerability_ids", [])
2091+
if isinstance(vulnerability_ids, str):
2092+
vulnerability_ids = [vid.strip() for vid in vulnerability_ids.split("\n") if vid.strip()]
2093+
elif not isinstance(vulnerability_ids, list):
2094+
vulnerability_ids = []
20812095
save_vulnerability_ids_template(instance, vulnerability_ids)
20822096

2097+
# Handle endpoints if provided
2098+
if "endpoints" in self.initial_data:
2099+
endpoint_urls = self.initial_data.get("endpoints", [])
2100+
if isinstance(endpoint_urls, str):
2101+
endpoint_urls = [url.strip() for url in endpoint_urls.split("\n") if url.strip()]
2102+
elif not isinstance(endpoint_urls, list):
2103+
endpoint_urls = []
2104+
save_endpoints_template(instance, endpoint_urls)
2105+
20832106
return super().update(instance, validated_data)
20842107

20852108

dojo/api_v2/views.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from datetime import datetime
55
from pathlib import Path
66

7+
import pghistory
78
import tagulous
89
from crum import get_current_user
910
from dateutil.relativedelta import relativedelta
@@ -2530,7 +2531,17 @@ def perform_create(self, serializer):
25302531
if jira_project := (jira_helper.get_jira_project(jira_driver) if jira_driver else None):
25312532
push_to_jira = push_to_jira or jira_project.push_all_issues
25322533

2534+
# Add pghistory context for audit trail (adds to existing middleware context).
2535+
# /api/vue is the Pro UI
2536+
source = "import_vue" if "/api/vue/" in self.request.path else "import_api"
2537+
pghistory.context(
2538+
source=source,
2539+
scan_type=serializer.validated_data.get("scan_type"),
2540+
)
25332541
serializer.save(push_to_jira=push_to_jira)
2542+
# Add test_id to pghistory context now that test is created
2543+
if test_id := serializer.data.get("test"):
2544+
pghistory.context(test_id=test_id)
25342545

25352546
def get_queryset(self):
25362547
return get_authorized_tests(Permissions.Import_Scan_Result)
@@ -2678,7 +2689,22 @@ def perform_create(self, serializer):
26782689
if jira_project := (jira_helper.get_jira_project(jira_driver) if jira_driver else None):
26792690
push_to_jira = push_to_jira or jira_project.push_all_issues
26802691
logger.debug("push_to_jira: %s", push_to_jira)
2692+
# Add pghistory context for audit trail (adds to existing middleware context)
2693+
# For reimport, test may already exist or be created during save
2694+
test_id = test.id if test else serializer.validated_data.get("test", {})
2695+
if hasattr(test_id, "id"):
2696+
test_id = test_id.id
2697+
# /api/vue is the Pro UI
2698+
source = "reimport_vue" if "/api/vue/" in self.request.path else "reimport_api"
2699+
pghistory.context(
2700+
source=source,
2701+
test_id=test_id if isinstance(test_id, int) else None,
2702+
scan_type=serializer.validated_data.get("scan_type"),
2703+
)
26812704
serializer.save(push_to_jira=push_to_jira)
2705+
# Update test_id if it wasn't available before save
2706+
if test_id_from_response := serializer.data.get("test"):
2707+
pghistory.context(test_id=test_id_from_response)
26822708

26832709

26842710
# Authorization: configuration

dojo/celery.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
from logging.config import dictConfig
44

5-
from celery import Celery
5+
from celery import Celery, Task
66
from celery.signals import setup_logging
77
from django.conf import settings
88

@@ -11,7 +11,31 @@
1111
# set the default Django settings module for the 'celery' program.
1212
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dojo.settings.settings")
1313

14-
app = Celery("dojo")
14+
15+
class PgHistoryTask(Task):
16+
17+
"""
18+
Custom Celery base task that automatically applies pghistory context.
19+
20+
When a task is dispatched via dojo_async_task, the current pghistory
21+
context is captured and passed in kwargs as "_pgh_context". This base
22+
class extracts that context and applies it before running the task,
23+
ensuring all database events share the same context as the original
24+
request.
25+
"""
26+
27+
def __call__(self, *args, **kwargs):
28+
# Import here to avoid circular imports during Celery startup
29+
from dojo.pghistory_utils import get_pghistory_context_manager # noqa: PLC0415
30+
31+
# Extract context from kwargs (won't be passed to task function)
32+
pgh_context = kwargs.pop("_pgh_context", None)
33+
34+
with get_pghistory_context_manager(pgh_context):
35+
return super().__call__(*args, **kwargs)
36+
37+
38+
app = Celery("dojo", task_cls=PgHistoryTask)
1539

1640
# Using a string here means the worker will not have to
1741
# pickle the object when using Windows.

dojo/context_processors.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
# import the settings file
55
from django.conf import settings
6+
from django.contrib import messages
67

78
from dojo.labels import get_labels
89
from dojo.models import Alerts, System_Settings, UserAnnouncement
@@ -39,7 +40,26 @@ def globalize_vars(request):
3940

4041

4142
def bind_system_settings(request):
42-
return {"system_settings": System_Settings.objects.get()}
43+
"""Load system settings and display warning if there's a database error."""
44+
try:
45+
system_settings = System_Settings.objects.get()
46+
# Check if there was an error stored on the request (from middleware)
47+
if hasattr(request, "system_settings_error"):
48+
error_msg = request.system_settings_error
49+
messages.add_message(
50+
request,
51+
messages.WARNING,
52+
f"Warning: Unable to load system settings from database: {error_msg}. "
53+
"Default values are being used. Please check your database configuration and run migrations if needed.",
54+
extra_tags="alert-warning",
55+
)
56+
# Clear after adding message
57+
delattr(request, "system_settings_error")
58+
except Exception:
59+
# If we can't get settings, return empty dict (will cause errors elsewhere, but that's expected)
60+
return {}
61+
62+
return {"system_settings": system_settings}
4363

4464

4565
def bind_alert_count(request):

0 commit comments

Comments
 (0)