Skip to content

Commit 06596fc

Browse files
authored
Django AuditLog: Upgrade to 3.x (#11592)
* Django AuditLog: Upgrade to 3.x * Accommodate auditlog copy restrictions * ruff fixes * Ruff Autofix Bite * Take Kiblik's advice * Fixing ruff * Try with running tests with no deps
1 parent 89e17c7 commit 06596fc

File tree

13 files changed

+104
-90
lines changed

13 files changed

+104
-90
lines changed

.github/workflows/fetch-oas.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
docker images
3434
3535
- name: Start Dojo
36-
run: docker compose up -d postgres nginx uwsgi
36+
run: docker compose up --no-deps -d postgres nginx uwsgi
3737
env:
3838
DJANGO_VERSION: ${{ env.release_version }}-alpine
3939
NGINX_VERSION: ${{ env.release_version }}-alpine

.github/workflows/integration-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,21 @@ jobs:
6363
run: ln -s docker-compose.override.integration_tests.yml docker-compose.override.yml
6464

6565
- name: Start Dojo
66-
run: docker compose up -d postgres nginx celerybeat celeryworker mailhog uwsgi redis
66+
run: docker compose up --no-deps -d postgres nginx celerybeat celeryworker mailhog uwsgi redis
6767
env:
6868
DJANGO_VERSION: ${{ matrix.os }}
6969
NGINX_VERSION: ${{ matrix.os }}
7070

7171
- name: Initialize
7272
timeout-minutes: 10
73-
run: docker compose up --exit-code-from initializer initializer
73+
run: docker compose up --no-deps --exit-code-from initializer initializer
7474
env:
7575
DJANGO_VERSION: ${{ matrix.os }}
7676
NGINX_VERSION: ${{ matrix.os }}
7777

7878
- name: Integration tests
7979
timeout-minutes: 10
80-
run: docker compose up --exit-code-from integration-tests integration-tests
80+
run: docker compose up --no-deps --exit-code-from integration-tests integration-tests
8181
env:
8282
DD_INTEGRATION_TEST_FILENAME: ${{ matrix.test-case }}
8383
INTEGRATION_TESTS_VERSION: debian

.github/workflows/rest-framework-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ jobs:
3939

4040
# phased startup so we can use the exit code from unit test container
4141
- name: Start Postgres and webhook.endpoint
42-
run: docker compose up -d postgres webhook.endpoint
42+
run: docker compose up --no-deps -d postgres webhook.endpoint
4343

4444
# no celery or initializer needed for unit tests
4545
- name: Unit tests
4646
timeout-minutes: 10
47-
run: docker compose up --exit-code-from uwsgi uwsgi
47+
run: docker compose up --no-deps --exit-code-from uwsgi uwsgi
4848
env:
4949
DJANGO_VERSION: ${{ matrix.os }}
5050

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,34 @@
22
title: 'Upgrading to DefectDojo Version 2.43.x'
33
toc_hide: true
44
weight: -20250106
5-
description: Disclaimer field renamed/split and removal of `dc-` scripts.
5+
description: Disclaimer field renamed/split, removal of `dc-` scripts, audit log updates, and hash codes updates.
66
---
77

8+
### Audit log migration
9+
10+
As part of the upgrade to django-auditlog 3.x, there is a migration of
11+
existing records from json-text to json. Depending on the number of
12+
LogEntry objects in your database, this migration could take a long time
13+
to fully execute. If you believe this period of time will be disruptive
14+
to your operations, please consult the [migration guide](https://django-auditlog.readthedocs.io/en/latest/upgrade.html#upgrading-to-version-3)
15+
for making this migration a two step process.
16+
17+
---
18+
19+
### Removal of "dc" helper scripts
20+
21+
In the past, when DefectDojo supported different database and message brokers, `dc-` scripts have been added to simplify start of Dojo stack. As these backends are not supported, mentioned scripts are not needed anymore. From now we recommend to use standard `docker compose` (or `docker-compose`) commands as they are described on [README.md](https://github.com/DefectDojo/django-DefectDojo/blob/master/README.md)
22+
23+
---
24+
25+
### Diversification of Disclaimers
26+
827
[Pull request #10902](https://github.com/DefectDojo/django-DefectDojo/pull/10902) introduced different kinds of disclaimers within the DefectDojo instance. The original content of the disclaimer was copied to all new fields where it had been used until now (so this change does not require any action on the user's side). However, if users were managing the original disclaimer via API (endpoint `/api/v2/system_settings/1/`, field `disclaimer`), be aware that the fields are now called `disclaimer_notifications` and `disclaimer_reports` (plus there is one additional, previously unused field called `disclaimer_notes`).
928

10-
In the past, when DefectDojo supported different database and message brokers, `dc-` scripts have been added to simplify start of Dojo stack. As these backends are not supported, mentioned scripts are not needed anymore. From now we recommend to use standard `docker compose` (or `docker-compose`) commands as they are described on [README.md](https://github.com/DefectDojo/django-DefectDojo/blob/master/README.md)
29+
---
30+
31+
### Hash Code changes
1132

12-
**Hash Code changes**
1333
The Rusty Hog parser has been [updated](https://github.com/DefectDojo/django-DefectDojo/pull/11433) to populate more fields. Some of these fields are part of the hash code calculation. To recalculate the hash code and deduplicate existing Rusty Hog findings, please execute the following command:
1434

1535
`docker compose exec uwsgi /bin/bash -c "python manage.py dedupe.py --parser "Essex Hog Scan (Rusty Hog Scan)" --hash_code_only`

dojo/models.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def _manage_inherited_tags(obj, incoming_inherited_tags, potentially_existing_ta
123123
obj.tags.set(cleaned_tag_list)
124124

125125

126+
def _copy_model_util(model_in_database, exclude_fields: list[str] = []):
127+
new_model_instance = model_in_database.__class__()
128+
for field in model_in_database._meta.fields:
129+
if field.name not in ["id", *exclude_fields]:
130+
setattr(new_model_instance, field.name, getattr(model_in_database, field.name))
131+
return new_model_instance
132+
133+
126134
@deconstructible
127135
class UniqueUploadNameProvider:
128136

@@ -703,9 +711,7 @@ class NoteHistory(models.Model):
703711
current_editor = models.ForeignKey(Dojo_User, editable=False, null=True, on_delete=models.CASCADE)
704712

705713
def copy(self):
706-
copy = self
707-
copy.pk = None
708-
copy.id = None
714+
copy = _copy_model_util(self)
709715
copy.save()
710716
return copy
711717

@@ -731,12 +737,9 @@ def __str__(self):
731737
return self.entry
732738

733739
def copy(self):
734-
copy = self
740+
copy = _copy_model_util(self)
735741
# Save the necessary ManyToMany relationships
736742
old_history = list(self.history.all())
737-
# Wipe the IDs of the new object
738-
copy.pk = None
739-
copy.id = None
740743
# Save the object before setting any ManyToMany relationships
741744
copy.save()
742745
# Copy the history
@@ -751,10 +754,7 @@ class FileUpload(models.Model):
751754
file = models.FileField(upload_to=UniqueUploadNameProvider("uploaded_files"))
752755

753756
def copy(self):
754-
copy = self
755-
# Wipe the IDs of the new object
756-
copy.pk = None
757-
copy.id = None
757+
copy = _copy_model_util(self)
758758
# Add unique modifier to file name
759759
copy.title = f"{self.title} - clone-{str(uuid4())[:8]}"
760760
# Create new unique file name
@@ -1538,16 +1538,13 @@ def get_absolute_url(self):
15381538
return reverse("view_engagement", args=[str(self.id)])
15391539

15401540
def copy(self):
1541-
copy = self
1541+
copy = _copy_model_util(self)
15421542
# Save the necessary ManyToMany relationships
15431543
old_notes = list(self.notes.all())
15441544
old_files = list(self.files.all())
15451545
old_tags = list(self.tags.all())
15461546
old_risk_acceptances = list(self.risk_acceptance.all())
15471547
old_tests = list(Test.objects.filter(engagement=self))
1548-
# Wipe the IDs of the new object
1549-
copy.pk = None
1550-
copy.id = None
15511548
# Save the object before setting any ManyToMany relationships
15521549
copy.save()
15531550
# Copy the notes
@@ -1658,10 +1655,8 @@ def __str__(self):
16581655
return f"'{self.finding}' on '{self.endpoint}'"
16591656

16601657
def copy(self, finding=None):
1661-
copy = self
1658+
copy = _copy_model_util(self)
16621659
current_endpoint = self.endpoint
1663-
copy.pk = None
1664-
copy.id = None
16651660
if finding:
16661661
copy.finding = finding
16671662
copy.endpoint = current_endpoint
@@ -2127,15 +2122,12 @@ def get_breadcrumbs(self):
21272122
return bc
21282123

21292124
def copy(self, engagement=None):
2130-
copy = self
2125+
copy = _copy_model_util(self)
21312126
# Save the necessary ManyToMany relationships
21322127
old_notes = list(self.notes.all())
21332128
old_files = list(self.files.all())
21342129
old_tags = list(self.tags.all())
21352130
old_findings = list(Finding.objects.filter(test=self))
2136-
# Wipe the IDs of the new object
2137-
copy.pk = None
2138-
copy.id = None
21392131
if engagement:
21402132
copy.engagement = engagement
21412133
# Save the object before setting any ManyToMany relationships
@@ -2748,7 +2740,7 @@ def get_absolute_url(self):
27482740
return reverse("view_finding", args=[str(self.id)])
27492741

27502742
def copy(self, test=None):
2751-
copy = self
2743+
copy = _copy_model_util(self)
27522744
# Save the necessary ManyToMany relationships
27532745
old_notes = list(self.notes.all())
27542746
old_files = list(self.files.all())
@@ -2757,8 +2749,6 @@ def copy(self, test=None):
27572749
old_found_by = list(self.found_by.all())
27582750
old_tags = list(self.tags.all())
27592751
# Wipe the IDs of the new object
2760-
copy.pk = None
2761-
copy.id = None
27622752
if test:
27632753
copy.test = test
27642754
# Save the object before setting any ManyToMany relationships
@@ -3744,13 +3734,10 @@ def engagement(self):
37443734
return None
37453735

37463736
def copy(self, engagement=None):
3747-
copy = self
3737+
copy = _copy_model_util(self)
37483738
# Save the necessary ManyToMany relationships
37493739
old_notes = list(self.notes.all())
37503740
old_accepted_findings_hash_codes = [finding.hash_code for finding in self.accepted_findings.all()]
3751-
# Wipe the IDs of the new object
3752-
copy.pk = None
3753-
copy.id = None
37543741
# Save the object before setting any ManyToMany relationships
37553742
copy.save()
37563743
# Copy the notes

dojo/settings/settings.dist.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,9 @@ def saml2_attrib_map_format(dict):
17981798
# ------------------------------------------------------------------------------
17991799
AUDITLOG_FLUSH_RETENTION_PERIOD = env("DD_AUDITLOG_FLUSH_RETENTION_PERIOD")
18001800
ENABLE_AUDITLOG = env("DD_ENABLE_AUDITLOG")
1801+
AUDITLOG_TWO_STEP_MIGRATION = False
1802+
AUDITLOG_USE_TEXT_CHANGES_IF_JSON_IS_NOT_PRESENT = False
1803+
18011804
USE_FIRST_SEEN = env("DD_USE_FIRST_SEEN")
18021805
USE_QUALYS_LEGACY_SEVERITY_PARSING = env("DD_QUALYS_LEGACY_SEVERITY_PARSING")
18031806

dojo/templatetags/display_tags.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import logging
55
import mimetypes
6+
from ast import literal_eval
67
from itertools import chain
78

89
import bleach
@@ -323,8 +324,7 @@ def display_index(data, index):
323324
@register.filter(is_safe=True, needs_autoescape=False)
324325
@stringfilter
325326
def action_log_entry(value, autoescape=None):
326-
import json
327-
history = json.loads(value)
327+
history = literal_eval(value)
328328
text = ""
329329
for k in history:
330330
if isinstance(history[k], dict):

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ bleach[css]
55
celery==5.4.0
66
defusedxml==0.7.1
77
django_celery_results==2.5.1
8-
django-auditlog==2.3.0
8+
django-auditlog==3.0.0
99
django-dbbackup==4.2.1
1010
django-environ==0.12.0
1111
django-filter==24.3

unittests/test_copy_model.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def test_duplicate_finding_with_notes(self):
7878
# Make sure the copy was made without error
7979
self.assertEqual(current_finding_count + 1, Finding.objects.filter(test=test).count())
8080
# Do the notes match
81-
self.assertEqual(finding.notes, finding_copy.notes)
81+
self.assertQuerySetEqual(finding.notes.all(), finding_copy.notes.all())
8282

8383
def test_duplicate_finding_with_tags_and_notes(self):
8484
# Set the scene
@@ -98,9 +98,9 @@ def test_duplicate_finding_with_tags_and_notes(self):
9898
# Make sure the copy was made without error
9999
self.assertEqual(current_finding_count + 1, Finding.objects.filter(test=test).count())
100100
# Do the tags match
101-
self.assertEqual(finding.notes, finding_copy.notes)
101+
self.assertEqual(finding.tags, finding_copy.tags)
102102
# Do the notes match
103-
self.assertEqual(finding.notes, finding_copy.notes)
103+
self.assertQuerySetEqual(finding.notes.all(), finding_copy.notes.all())
104104

105105
def test_duplicate_finding_with_endpoints(self):
106106
# Set the scene
@@ -173,7 +173,9 @@ def test_duplicate_tests_different_engagements(self):
173173
# Do the enagements have the same number of findings
174174
self.assertEqual(Finding.objects.filter(test__engagement=engagement1).count(), Finding.objects.filter(test__engagement=engagement2).count())
175175
# Are the tests equal
176-
self.assertEqual(test, test_copy)
176+
self.assertEqual(test.title, test_copy.title)
177+
self.assertEqual(test.scan_type, test_copy.scan_type)
178+
self.assertEqual(test.test_type, test_copy.test_type)
177179
# Does the product thave more findings
178180
self.assertEqual(product_finding_count + 1, Finding.objects.filter(test__engagement__product=product).count())
179181

@@ -213,7 +215,7 @@ def test_duplicate_test_with_notes(self):
213215
# Make sure the copy was made without error
214216
self.assertEqual(current_test_count + 1, Test.objects.filter(engagement=engagement).count())
215217
# Do the notes match
216-
self.assertEqual(test.notes, test_copy.notes)
218+
self.assertQuerySetEqual(test.notes.all(), test_copy.notes.all())
217219

218220
def test_duplicate_test_with_tags_and_notes(self):
219221
# Set the scene
@@ -233,7 +235,7 @@ def test_duplicate_test_with_tags_and_notes(self):
233235
# Make sure the copy was made without error
234236
self.assertEqual(current_test_count + 1, Test.objects.filter(engagement=engagement).count())
235237
# Do the notes match
236-
self.assertEqual(test.notes, test_copy.notes)
238+
self.assertQuerySetEqual(test.notes.all(), test_copy.notes.all())
237239
# Do the tags match
238240
self.assertEqual(test.tags, test_copy.tags)
239241

@@ -297,7 +299,7 @@ def test_duplicate_engagement_with_notes(self):
297299
# Make sure the copy was made without error
298300
self.assertEqual(current_engagement_count + 1, Engagement.objects.filter(product=product).count())
299301
# Do the notes match
300-
self.assertEqual(engagement.notes, engagement_copy.notes)
302+
self.assertQuerySetEqual(engagement.notes.all(), engagement_copy.notes.all())
301303

302304
def test_duplicate_engagement_with_tags_and_notes(self):
303305
# Set the scene
@@ -317,6 +319,6 @@ def test_duplicate_engagement_with_tags_and_notes(self):
317319
# Make sure the copy was made without error
318320
self.assertEqual(current_engagement_count + 1, Engagement.objects.filter(product=product).count())
319321
# Do the notes match
320-
self.assertEqual(engagement.notes, engagement_copy.notes)
322+
self.assertQuerySetEqual(engagement.notes.all(), engagement_copy.notes.all())
321323
# Do the tags match
322324
self.assertEqual(engagement.tags, engagement_copy.tags)

unittests/test_deduplication_logic.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@
44
from crum import impersonate
55
from django.conf import settings
66

7-
from dojo.models import Endpoint, Endpoint_Status, Engagement, Finding, Product, System_Settings, Test, User
7+
from dojo.models import (
8+
Endpoint,
9+
Endpoint_Status,
10+
Engagement,
11+
Finding,
12+
Product,
13+
System_Settings,
14+
Test,
15+
User,
16+
_copy_model_util,
17+
)
818

919
from .dojo_test_case import DojoTestCase
1020

@@ -1189,8 +1199,7 @@ def log_summary(self, product=None, engagement=None, test=None):
11891199

11901200
def copy_and_reset_finding(self, id):
11911201
org = Finding.objects.get(id=id)
1192-
new = org
1193-
new.pk = None
1202+
new = _copy_model_util(org)
11941203
new.duplicate = False
11951204
new.duplicate_finding = None
11961205
new.active = True
@@ -1227,15 +1236,13 @@ def copy_and_reset_finding_add_endpoints(self, id, static=False, dynamic=True):
12271236

12281237
def copy_and_reset_test(self, id):
12291238
org = Test.objects.get(id=id)
1230-
new = org
1231-
new.pk = None
1239+
new = _copy_model_util(org)
12321240
# return unsaved new finding and reloaded existing finding
12331241
return new, Test.objects.get(id=id)
12341242

12351243
def copy_and_reset_engagement(self, id):
12361244
org = Engagement.objects.get(id=id)
1237-
new = org
1238-
new.pk = None
1245+
new = _copy_model_util(org)
12391246
# return unsaved new finding and reloaded existing finding
12401247
return new, Engagement.objects.get(id=id)
12411248

0 commit comments

Comments
 (0)