Skip to content

Commit c827ab8

Browse files
authored
Some Reporting Updates (#10563)
* reports-fixes Update cover page widget to have page break after entry, use heading attribute for heading * reports-fixes Add classes for widgets; add css for dealing with page breaks on print * reports-fixes some addtional classes to help distinguish reporting sections * reports-fixes additional classes on findings/endpoints for reports * reports-fixes update report widgets to specify a widget_class instead of generating one based on title; fix wysiwyg issue, add delete button for in-use widgets * reports-fixes for reports widgets, use "header" field for header on rendered reports, instead of "title" (excepting findings/endpoints lists, which do not accept a custom heading) * reports-fixes add back a newline * reports-fixes remove extra space * report-fixes add dojo css for availability in reports * reports-fixes undo some template/css changes, move report break stuff to report_base * reports-fixes newline on end of file, remove changed css * reports-fixes remove unused css from custom_html_report * reports-fixes change "WYSIWYG Content" to "Custom Content" * reports-fixes on finding/endpoint filter/clear, run selectpicker on returned selects so the ui (select) elements do not change suddenly * reports-fixes work on page break changes * reports-fixes typo in style names * reports-fixes remove margins on page break widget * reports-fixes add optional page break after custom content * reports-fixes optional follwoing page break for wysiwyg * reports-fixes first pass at removing asciidoc support * reports-fixes more asciidoc removal updates * reports-fixes fix wysiwyg widget options loading * reports-fixes page break after toc * reports-fixes linter fixes * trigger GitHub actions
1 parent 4a710d4 commit c827ab8

23 files changed

Lines changed: 250 additions & 1416 deletions

docs/content/en/usage/features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ Custom reports, generated with the Report Builder, allow you to select specific
540540
5. Vulnerable Endpoints
541541
6. Page Breaks
542542

543-
DefectDojo's reports can be generated in HTML and AsciiDoc.
543+
DefectDojo's reports can be generated in HTML.
544544

545545
## Metrics
546546

dojo/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2286,15 +2286,15 @@ class ReportOptionsForm(forms.Form):
22862286
include_executive_summary = forms.ChoiceField(choices=yes_no, label="Executive Summary")
22872287
include_table_of_contents = forms.ChoiceField(choices=yes_no, label="Table of Contents")
22882288
include_disclaimer = forms.ChoiceField(choices=yes_no, label="Disclaimer")
2289-
report_type = forms.ChoiceField(choices=(('HTML', 'HTML'), ('AsciiDoc', 'AsciiDoc')))
2289+
report_type = forms.ChoiceField(choices=(('HTML', 'HTML'),))
22902290

22912291

22922292
class CustomReportOptionsForm(forms.Form):
22932293
yes_no = (('0', 'No'), ('1', 'Yes'))
22942294
report_name = forms.CharField(required=False, max_length=100)
22952295
include_finding_notes = forms.ChoiceField(required=False, choices=yes_no)
22962296
include_finding_images = forms.ChoiceField(choices=yes_no, label="Finding Images")
2297-
report_type = forms.ChoiceField(choices=(('HTML', 'HTML'), ('AsciiDoc', 'AsciiDoc')))
2297+
report_type = forms.ChoiceField(choices=(('HTML', 'HTML'),))
22982298

22992299

23002300
class DeleteFindingForm(forms.ModelForm):

dojo/reports/views.py

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from dojo.finding.queries import get_authorized_findings
2929
from dojo.finding.views import BaseListFindings
3030
from dojo.forms import ReportOptionsForm
31-
from dojo.models import Dojo_User, Endpoint, Engagement, Finding, Product, Product_Type, Risk_Acceptance, Test
31+
from dojo.models import Dojo_User, Endpoint, Engagement, Finding, Product, Product_Type, Test
3232
from dojo.reports.widgets import (
3333
CoverPage,
3434
CustomReportJsonForm,
@@ -140,7 +140,7 @@ def get_selected_widgets(self, request):
140140
self.finding_notes = (options.include_finding_notes == '1')
141141
self.finding_images = (options.include_finding_images == '1')
142142
else:
143-
self.report_format = 'AsciiDoc'
143+
self.report_format = 'HTML'
144144
self.finding_notes = True
145145
self.finding_images = True
146146

@@ -152,9 +152,7 @@ def get_form(self, request):
152152
return CustomReportJsonForm(request.POST)
153153

154154
def get_template(self):
155-
if self.report_format == 'AsciiDoc':
156-
return 'dojo/custom_asciidoc_report.html',
157-
elif self.report_format == 'HTML':
155+
if self.report_format == 'HTML':
158156
return 'dojo/custom_html_report.html'
159157
else:
160158
raise PermissionDenied
@@ -277,7 +275,7 @@ def product_endpoint_report(request, pid):
277275
endpoints = EndpointReportFilter(request.GET, queryset=endpoints)
278276

279277
paged_endpoints = get_page_items(request, endpoints.qs, 25)
280-
report_format = request.GET.get('report_type', 'AsciiDoc')
278+
report_format = request.GET.get('report_type', 'HTML')
281279
include_finding_notes = int(request.GET.get('include_finding_notes', 0))
282280
include_finding_images = int(request.GET.get('include_finding_images', 0))
283281
include_executive_summary = int(request.GET.get('include_executive_summary', 0))
@@ -291,65 +289,9 @@ def product_endpoint_report(request, pid):
291289
report_form = ReportOptionsForm()
292290
template = "dojo/product_endpoint_pdf_report.html"
293291

294-
try:
295-
start_date = Finding.objects.filter(endpoints__in=endpoints.qs).order_by('date')[:1][0].date
296-
except:
297-
start_date = timezone.now()
298-
299-
end_date = timezone.now()
300-
301-
risk_acceptances = Risk_Acceptance.objects.filter(engagement__test__finding__endpoints__in=endpoints.qs)
302-
303-
accepted_findings = [finding for ra in risk_acceptances
304-
for finding in ra.accepted_findings.filter(endpoints__in=endpoints.qs)]
305-
306-
verified_findings = Finding.objects.filter(endpoints__in=endpoints.qs,
307-
date__range=[start_date, end_date],
308-
false_p=False,
309-
verified=True,
310-
duplicate=False,
311-
out_of_scope=False)
312-
313-
open_findings = Finding.objects.filter(endpoints__in=endpoints.qs,
314-
false_p=False,
315-
verified=True,
316-
duplicate=False,
317-
out_of_scope=False,
318-
active=True,
319-
mitigated__isnull=True)
320-
321-
closed_findings = Finding.objects.filter(endpoints__in=endpoints.qs,
322-
false_p=False,
323-
verified=True,
324-
duplicate=False,
325-
out_of_scope=False,
326-
mitigated__isnull=False)
327292
if generate:
328293
report_form = ReportOptionsForm(request.GET)
329-
if report_format == 'AsciiDoc':
330-
return render(request,
331-
'dojo/asciidoc_report.html',
332-
{'product_type': None,
333-
'product': product,
334-
'accepted_findings': accepted_findings,
335-
'open_findings': open_findings,
336-
'closed_findings': closed_findings,
337-
'verified_findings': verified_findings,
338-
'engagement': None,
339-
'test': None,
340-
'endpoints': endpoints.qs,
341-
'endpoint': None,
342-
'findings': None,
343-
'include_finding_notes': include_finding_notes,
344-
'include_finding_images': include_finding_images,
345-
'include_executive_summary': include_executive_summary,
346-
'include_table_of_contents': include_table_of_contents,
347-
'include_disclaimer': include_disclaimer,
348-
'disclaimer': disclaimer,
349-
'user': request.user,
350-
'title': 'Generate Report',
351-
})
352-
elif report_format == 'HTML':
294+
if report_format == 'HTML':
353295
return render(request,
354296
template,
355297
{'product_type': None,
@@ -413,7 +355,7 @@ def generate_report(request, obj, host_view=False):
413355
msg = f'Report cannot be generated for object of type {type(obj).__name__}'
414356
raise Exception(msg)
415357

416-
report_format = request.GET.get('report_type', 'AsciiDoc')
358+
report_format = request.GET.get('report_type', 'HTML')
417359
include_finding_notes = int(request.GET.get('include_finding_notes', 0))
418360
include_finding_images = int(request.GET.get('include_finding_images', 0))
419361
include_executive_summary = int(request.GET.get('include_executive_summary', 0))
@@ -615,30 +557,7 @@ def generate_report(request, obj, host_view=False):
615557

616558
if generate:
617559
report_form = ReportOptionsForm(request.GET)
618-
if report_format == 'AsciiDoc':
619-
return render(request,
620-
'dojo/asciidoc_report.html',
621-
{'product_type': product_type,
622-
'product': product,
623-
'engagement': engagement,
624-
'test': test,
625-
'endpoint': endpoint,
626-
'findings': findings.qs.distinct().order_by('numerical_severity'),
627-
'include_finding_notes': include_finding_notes,
628-
'include_finding_images': include_finding_images,
629-
'include_executive_summary': include_executive_summary,
630-
'include_table_of_contents': include_table_of_contents,
631-
'include_disclaimer': include_disclaimer,
632-
'disclaimer': disclaimer,
633-
'user': user,
634-
'team_name': settings.TEAM_NAME,
635-
'title': report_title,
636-
'user_id': request.user.id,
637-
'host': report_url_resolver(request),
638-
'host_view': host_view,
639-
'context': context,
640-
})
641-
elif report_format == 'HTML':
560+
if report_format == 'HTML':
642561
return render(request,
643562
template,
644563
{'product_type': product_type,

dojo/reports/widgets.py

Lines changed: 31 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class Meta:
5151

5252
class TableOfContentsForm(forms.Form):
5353
heading = forms.CharField(max_length=200, required=False, initial="Table of Contents")
54-
depth = forms.IntegerField(min_value=1, required=False, max_value=6, initial=4)
5554

5655
class Meta:
5756
exclude = []
@@ -106,9 +105,10 @@ def render(self, name, value, attrs=None, renderer=None):
106105

107106

108107
class WYSIWYGContentForm(forms.Form):
109-
heading = forms.CharField(max_length=200, required=False, initial="WYSIWYG Content")
108+
heading = forms.CharField(max_length=200, required=False, initial="Custom Content")
110109
content = forms.CharField(required=False, widget=Div(attrs={'class': 'editor'}))
111110
hidden_content = forms.CharField(widget=forms.HiddenInput(), required=True)
111+
page_break_after = forms.BooleanField(required=False, help_text='Include a page break after the custom content.')
112112

113113
class Meta:
114114
exclude = []
@@ -120,15 +120,12 @@ def __init__(self, *args, **kwargs):
120120
self.title = 'Base Widget'
121121
self.form = None
122122
self.multiple = "false"
123+
self.widget_class = 'widget'
123124

124125
@abc.abstractmethod
125126
def get_html(self, request):
126127
return
127128

128-
@abc.abstractmethod
129-
def get_asciidoc(self):
130-
return
131-
132129
@abc.abstractmethod
133130
def get_option_form(self):
134131
return
@@ -138,14 +135,12 @@ class PageBreak(Widget):
138135
def __init__(self, *args, **kwargs):
139136
super().__init__(*args, **kwargs)
140137
self.title = 'Page Break'
138+
self.widget_class = 'page-break'
141139
self.form = None
142140
self.multiple = "true"
143141

144142
def get_html(self):
145-
return mark_safe('<hr title="Page Break" class="report-page-break"/>')
146-
147-
def get_asciidoc(self):
148-
return mark_safe('<br/><<<<br/>')
143+
return mark_safe('<div class="report-page-break">Page Break</div>')
149144

150145
def get_option_form(self):
151146
return mark_safe(
@@ -160,10 +155,8 @@ def __init__(self, *args, **kwargs):
160155
super().__init__(*args, **kwargs)
161156
self.title = 'Report Options'
162157
self.form = CustomReportOptionsForm()
163-
self.extra_help = "Choose additional report options. These will apply to the overall report."
164-
165-
def get_asciidoc(self):
166-
return mark_safe('')
158+
self.extra_help = "Choose additional report options. These will apply to the overall report."
159+
self.widget_class = 'report-options'
167160

168161
def get_html(self):
169162
return mark_safe('')
@@ -182,16 +175,12 @@ def __init__(self, *args, **kwargs):
182175
self.title = 'Cover Page'
183176
self.form = CoverPageForm()
184177
self.help_text = "The cover page includes a page break after its content."
178+
self.widget_class = 'cover-page'
185179

186180
def get_html(self):
187-
return render_to_string("dojo/custom_html_report_cover_page.html", {"heading": self.title,
188-
"sub_heading": self.sub_heading,
189-
"meta_info": self.meta_info})
190-
191-
def get_asciidoc(self):
192-
return render_to_string("dojo/custom_asciidoc_report_cover_page.html", {"heading": self.title,
193-
"sub_heading": self.sub_heading,
194-
"meta_info": self.meta_info})
181+
return render_to_string("dojo/custom_html_report_cover_page.html", {"heading": self.heading,
182+
"sub_heading": self.sub_heading,
183+
"meta_info": self.meta_info})
195184

196185
def get_option_form(self):
197186
html = render_to_string("dojo/report_widget.html", {"form": self.form,
@@ -205,16 +194,12 @@ class TableOfContents(Widget):
205194
def __init__(self, *args, **kwargs):
206195
super().__init__(*args, **kwargs)
207196
self.title = 'Table Of Contents'
197+
self.widget_class = 'table-of-contents'
208198
self.form = TableOfContentsForm()
209199
self.help_text = "The table of contents includes a page break after its content."
210200

211201
def get_html(self):
212-
return render_to_string("dojo/custom_html_toc.html", {"title": self.title,
213-
"depth": self.depth})
214-
215-
def get_asciidoc(self):
216-
return render_to_string("dojo/custom_asciidoc_toc.html", {"title": self.title,
217-
"depth": self.depth})
202+
return render_to_string("dojo/custom_html_toc.html", {"heading": self.heading})
218203

219204
def get_option_form(self):
220205
html = render_to_string("dojo/report_widget.html", {"form": self.form,
@@ -227,20 +212,21 @@ def get_option_form(self):
227212
class WYSIWYGContent(Widget):
228213
def __init__(self, *args, **kwargs):
229214
super().__init__(*args, **kwargs)
230-
self.title = 'WYSIWYG Content'
215+
self.title = 'Custom Content'
216+
self.widget_class = 'wysiwyg-content'
231217
self.form = WYSIWYGContentForm()
232218
self.multiple = 'true'
233219

234220
def get_html(self):
235-
html = render_to_string("dojo/custom_html_report_wysiwyg_content.html", {"title": self.title,
236-
"content": self.content})
221+
html = render_to_string(
222+
"dojo/custom_html_report_wysiwyg_content.html",
223+
{
224+
"heading": self.heading,
225+
"content": self.content,
226+
"page_break_after": self.page_break_after,
227+
})
237228
return mark_safe(html)
238229

239-
def get_asciidoc(self):
240-
asciidoc = render_to_string("dojo/custom_asciidoc_report_wysiwyg_content.html", {"title": self.title,
241-
"content": self.content})
242-
return mark_safe(asciidoc)
243-
244230
def get_option_form(self):
245231
html = render_to_string("dojo/report_widget.html", {"form": self.form,
246232
"multiple": self.multiple,
@@ -282,6 +268,7 @@ def __init__(self, *args, **kwargs):
282268
else:
283269
self.form = None
284270
self.multiple = 'true'
271+
self.widget_class = 'finding-list'
285272
self.extra_help = "You can use this form to filter findings and select only the ones to be included in the " \
286273
"report."
287274
self.title_words = get_words_for_field(Finding, 'title')
@@ -292,15 +279,6 @@ def __init__(self, *args, **kwargs):
292279
else:
293280
self.paged_findings = self.findings
294281

295-
def get_asciidoc(self):
296-
asciidoc = render_to_string("dojo/custom_asciidoc_report_findings.html",
297-
{"findings": self.findings.qs,
298-
"host": self.host,
299-
"include_finding_notes": self.finding_notes,
300-
"include_finding_images": self.finding_images,
301-
"user_id": self.user_id})
302-
return mark_safe(asciidoc)
303-
304282
def get_html(self):
305283
html = render_to_string("dojo/custom_html_report_finding_list.html",
306284
{"title": self.title,
@@ -355,6 +333,7 @@ def __init__(self, *args, **kwargs):
355333
self.title = 'Endpoint List'
356334
self.form = self.endpoints.form
357335
self.multiple = 'false'
336+
self.widget_class = 'endpoint-list'
358337
if self.request is not None:
359338
self.paged_endpoints = get_page_items(self.request, self.endpoints.qs, 25)
360339
else:
@@ -373,15 +352,6 @@ def get_html(self):
373352
"user_id": self.user_id})
374353
return mark_safe(html)
375354

376-
def get_asciidoc(self):
377-
asciidoc = render_to_string("dojo/custom_asciidoc_report_endpoints.html",
378-
{"endpoints": self.endpoints.qs,
379-
"host": self.host,
380-
"include_finding_notes": self.finding_notes,
381-
"include_finding_images": self.finding_images,
382-
"user_id": self.user_id})
383-
return mark_safe(asciidoc)
384-
385355
def get_option_form(self):
386356
html = render_to_string('dojo/report_endpoints.html',
387357
{"endpoints": self.paged_endpoints,
@@ -441,12 +411,15 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes
441411
finding_images=finding_images,
442412
host=host, user_id=user_id)
443413

444-
if list(widget.keys())[0] == 'wysiwyg-content':
414+
if list(widget.keys())[0] == 'custom-content':
445415
wysiwyg_content = WYSIWYGContent(request=request)
446-
wysiwyg_content.title = \
416+
wysiwyg_content.heading = \
447417
next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'heading'), None)['value']
448418
wysiwyg_content.content = \
449419
next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'hidden_content'), None)['value']
420+
wysiwyg_content.page_break_after = \
421+
next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'page_break_after'),
422+
{'value': False})['value']
450423
selected_widgets[list(widget.keys())[0] + '-' + str(idx)] = wysiwyg_content
451424
if list(widget.keys())[0] == 'report-options':
452425
options = ReportOptions(request=request)
@@ -463,14 +436,12 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes
463436
selected_widgets[list(widget.keys())[0]] = options
464437
if list(widget.keys())[0] == 'table-of-contents':
465438
toc = TableOfContents(request=request)
466-
toc.title = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'heading'), None)[
439+
toc.heading = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'heading'), None)[
467440
'value']
468-
toc.depth = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'depth'), None)['value']
469-
toc.depth = int(toc.depth) + 1
470441
selected_widgets[list(widget.keys())[0]] = toc
471442
if list(widget.keys())[0] == 'cover-page':
472443
cover_page = CoverPage(request=request)
473-
cover_page.title = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'heading'), None)[
444+
cover_page.heading = next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'heading'), None)[
474445
'value']
475446
cover_page.sub_heading = \
476447
next((item for item in widget.get(list(widget.keys())[0]) if item["name"] == 'sub_heading'), None)['value']

0 commit comments

Comments
 (0)