Skip to content

Commit

Permalink
Class based reports views (#10124)
Browse files Browse the repository at this point in the history
* class-based-reports-views Initial work on class-based views for reports

* class-based-reports-views Refactor a bit

* class-based-reports-views move reports navbar entry into a block

* class-based-reports-views clean up some variables

* class-based-reports-views ReportFindingFilter add a few new filter fields

* class-based-reports-views linting fix
  • Loading branch information
dogboat authored May 6, 2024
1 parent 5b69a00 commit e420d02
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 100 deletions.
7 changes: 5 additions & 2 deletions dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2881,6 +2881,7 @@ class ReportFindingFilter(FindingTagFilter):
queryset=Product_Type.objects.none(),
label="Product Type")
test__engagement__product__lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES, label="Product Lifecycle")
test__engagement = ModelMultipleChoiceFilter(queryset=Engagement.objects.none(), label="Engagement")
severity = MultipleChoiceFilter(choices=SEVERITY_CHOICES)
active = ReportBooleanFilter()
is_mitigated = ReportBooleanFilter()
Expand All @@ -2901,8 +2902,8 @@ class Meta:
model = Finding
# exclude sonarqube issue as by default it will show all without checking permissions
exclude = ['date', 'cwe', 'url', 'description', 'mitigation', 'impact',
'references', 'test', 'sonarqube_issue',
'thread_id', 'notes', 'endpoints',
'references', 'sonarqube_issue',
'thread_id', 'notes',
'numerical_severity', 'reporter', 'last_reviewed',
'jira_creation', 'jira_change', 'files']

Expand Down Expand Up @@ -2952,6 +2953,8 @@ def __init__(self, *args, **kwargs):
if 'test__engagement__product' in self.form.fields:
self.form.fields[
'test__engagement__product'].queryset = get_authorized_products(Permissions.Product_View)
if 'test__engagement' in self.form.fields:
self.form.fields['test__engagement'].queryset = get_authorized_engagements(Permissions.Engagement_View)

@property
def qs(self):
Expand Down
4 changes: 2 additions & 2 deletions dojo/reports/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
re_path(r'^reports/cover$',
views.report_cover_page, name='report_cover_page'),
re_path(r'^reports/builder$',
views.report_builder, name='report_builder'),
views.ReportBuilder.as_view(), name='report_builder'),
re_path(r'^reports/findings$',
views.report_findings, name='report_findings'),
re_path(r'^reports/endpoints$',
views.report_endpoints, name='report_endpoints'),
re_path(r'^reports/custom$',
views.custom_report, name='custom_report'),
views.CustomReport.as_view(), name='custom_report'),
re_path(r'^reports/quick$',
views.QuickReportView.as_view(), name='quick_report'),
re_path(r'^reports/csv_export$',
Expand Down
155 changes: 88 additions & 67 deletions dojo/reports/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import re
from datetime import datetime
from tempfile import NamedTemporaryFile
from typing import List

from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponse, QueryDict
from django.http import Http404, HttpRequest, HttpResponse, QueryDict
from django.shortcuts import get_object_or_404, render
from django.utils import timezone
from django.views import View
Expand All @@ -30,6 +31,7 @@
PageBreak,
ReportOptions,
TableOfContents,
Widget,
WYSIWYGContent,
report_widget_factory,
)
Expand Down Expand Up @@ -64,75 +66,94 @@ def report_url_resolver(request):
return url_resolver + ":" + request.META['SERVER_PORT']


def report_builder(request):
add_breadcrumb(title="Report Builder", top_level=True, request=request)
findings = get_authorized_findings(Permissions.Finding_View)
findings = ReportFindingFilter(request.GET, queryset=findings)
endpoints = Endpoint.objects.filter(finding__active=True,
finding__verified=True,
finding__false_p=False,
finding__duplicate=False,
finding__out_of_scope=False,
).distinct()
filter_string_matching = get_system_setting("filter_string_matching", False)
filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
endpoints = filter_class(request.GET, queryset=endpoints, user=request.user)

in_use_widgets = [ReportOptions(request=request)]
available_widgets = [CoverPage(request=request),
TableOfContents(request=request),
WYSIWYGContent(request=request),
FindingList(request=request, findings=findings),
EndpointList(request=request, endpoints=endpoints),
PageBreak()]
return render(request,
'dojo/report_builder.html',
{"available_widgets": available_widgets,
"in_use_widgets": in_use_widgets})


def custom_report(request):
# saving the report
form = CustomReportJsonForm(request.POST)
host = report_url_resolver(request)
if form.is_valid():
selected_widgets = report_widget_factory(json_data=request.POST['json'], request=request, user=request.user,
finding_notes=False, finding_images=False, host=host)
report_format = 'AsciiDoc'
finding_notes = True
finding_images = True

if 'report-options' in selected_widgets:
options = selected_widgets['report-options']
report_format = options.report_type
finding_notes = (options.include_finding_notes == '1')
finding_images = (options.include_finding_images == '1')

selected_widgets = report_widget_factory(json_data=request.POST['json'], request=request, user=request.user,
finding_notes=finding_notes, finding_images=finding_images, host=host)
class ReportBuilder(View):
def get(self, request: HttpRequest) -> HttpResponse:
add_breadcrumb(title="Report Builder", top_level=True, request=request)
return render(request, self.get_template(), self.get_context(request))

def get_findings(self, request: HttpRequest):
findings = get_authorized_findings(Permissions.Finding_View)
return ReportFindingFilter(self.request.GET, queryset=findings)

def get_endpoints(self, request: HttpRequest):
endpoints = Endpoint.objects.filter(finding__active=True,
finding__verified=True,
finding__false_p=False,
finding__duplicate=False,
finding__out_of_scope=False,
).distinct()
filter_string_matching = get_system_setting("filter_string_matching", False)
filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
return filter_class(request.GET, queryset=endpoints, user=request.user)

def get_available_widgets(self, request: HttpRequest) -> List[Widget]:
return [
CoverPage(request=request),
TableOfContents(request=request),
WYSIWYGContent(request=request),
FindingList(request=request, findings=self.get_findings(request)),
EndpointList(request=request, endpoints=self.get_endpoints(request)),
PageBreak()]

def get_in_use_widgets(self, request):
return [ReportOptions(request=request)]

if report_format == 'AsciiDoc':
widgets = list(selected_widgets.values())
return render(request,
'dojo/custom_asciidoc_report.html',
{"widgets": widgets,
"host": host,
"finding_notes": finding_notes,
"finding_images": finding_images,
"user_id": request.user.id})
elif report_format == 'HTML':
widgets = list(selected_widgets.values())
return render(request,
'dojo/custom_html_report.html',
{"widgets": widgets,
"host": "",
"finding_notes": finding_notes,
"finding_images": finding_images,
"user_id": request.user.id})
def get_template(self):
return 'dojo/report_builder.html'

def get_context(self, request: HttpRequest) -> dict:
return {
"available_widgets": self.get_available_widgets(request),
"in_use_widgets": self.get_in_use_widgets(request), }


class CustomReport(View):
def post(self, request: HttpRequest) -> HttpResponse:
# saving the report
form = self.get_form(request)
if form.is_valid():
self._set_state(request)
return render(request, self.get_template(), self.get_context())
else:
raise PermissionDenied()
else:
raise PermissionDenied()

def _set_state(self, request: HttpRequest):
self.request = request
self.selected_widgets = self.get_selected_widgets(request)
self.widgets = list(self.selected_widgets.values())

def get_selected_widgets(self, request):
selected_widgets = report_widget_factory(json_data=request.POST['json'], request=request, finding_notes=False,
finding_images=False)

if options := selected_widgets.get('report-options', None):
self.report_format = options.report_type
self.finding_notes = (options.include_finding_notes == '1')
self.finding_images = (options.include_finding_images == '1')
else:
self.report_format = 'AsciiDoc'
self.finding_notes = True
self.finding_images = True

return report_widget_factory(json_data=request.POST['json'], request=request, finding_notes=self.finding_notes,
finding_images=self.finding_images)

def get_form(self, request):
return CustomReportJsonForm(request.POST)

def get_template(self):
if self.report_format == 'AsciiDoc':
return 'dojo/custom_asciidoc_report.html',
elif self.report_format == 'HTML':
return 'dojo/custom_html_report.html'
else:
raise PermissionDenied()

def get_context(self):
return {
"widgets": self.widgets,
"finding_notes": self.finding_notes,
"finding_images": self.finding_images, }


def report_findings(request):
Expand Down
36 changes: 7 additions & 29 deletions dojo/reports/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,6 @@ class FindingList(Widget):
def __init__(self, *args, **kwargs):
if 'request' in kwargs:
self.request = kwargs.get('request')
if 'user_id' in kwargs:
self.user_id = kwargs.get('user_id')

if 'host' in kwargs:
self.host = kwargs.get('host')

if 'findings' in kwargs:
self.findings = kwargs.get('findings')
Expand Down Expand Up @@ -290,20 +285,16 @@ def __init__(self, *args, **kwargs):
def get_asciidoc(self):
asciidoc = render_to_string("dojo/custom_asciidoc_report_findings.html",
{"findings": self.findings.qs,
"host": self.host,
"include_finding_notes": self.finding_notes,
"include_finding_images": self.finding_images,
"user_id": self.user_id})
"include_finding_images": self.finding_images, })
return mark_safe(asciidoc)

def get_html(self):
html = render_to_string("dojo/custom_html_report_finding_list.html",
{"title": self.title,
"findings": self.findings.qs,
"include_finding_notes": self.finding_notes,
"include_finding_images": self.finding_images,
"host": self.host,
"user_id": self.user_id})
"include_finding_images": self.finding_images, })
return mark_safe(html)

def get_option_form(self):
Expand All @@ -323,11 +314,6 @@ class EndpointList(Widget):
def __init__(self, *args, **kwargs):
if 'request' in kwargs:
self.request = kwargs.get('request')
if 'user_id' in kwargs:
self.user_id = kwargs.get('user_id')

if 'host' in kwargs:
self.host = kwargs.get('host')

if 'endpoints' in kwargs:
self.endpoints = kwargs.get('endpoints')
Expand Down Expand Up @@ -363,18 +349,14 @@ def get_html(self):
{"title": self.title,
"endpoints": self.endpoints.qs,
"include_finding_notes": self.finding_notes,
"include_finding_images": self.finding_images,
"host": self.host,
"user_id": self.user_id})
"include_finding_images": self.finding_images, })
return mark_safe(html)

def get_asciidoc(self):
asciidoc = render_to_string("dojo/custom_asciidoc_report_endpoints.html",
{"endpoints": self.endpoints.qs,
"host": self.host,
"include_finding_notes": self.finding_notes,
"include_finding_images": self.finding_images,
"user_id": self.user_id})
"include_finding_images": self.finding_images, })
return mark_safe(asciidoc)

def get_option_form(self):
Expand All @@ -388,8 +370,7 @@ def get_option_form(self):
return mark_safe(html)


def report_widget_factory(json_data=None, request=None, user=None, finding_notes=False, finding_images=False,
host=None):
def report_widget_factory(json_data=None, request=None, finding_notes=False, finding_images=False):
selected_widgets = OrderedDict()
widgets = json.loads(json_data)
for idx, widget in enumerate(widgets):
Expand All @@ -413,9 +394,8 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes
filter_string_matching = get_system_setting("filter_string_matching", False)
filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
endpoints = filter_class(d, queryset=endpoints, user=request.user)
user_id = user.id if user is not None else None
endpoints = EndpointList(request=request, endpoints=endpoints, finding_notes=finding_notes,
finding_images=finding_images, host=host, user_id=user_id)
finding_images=finding_images)

selected_widgets[list(widget.keys())[0] + '-' + str(idx)] = endpoints

Expand All @@ -429,11 +409,9 @@ def report_widget_factory(json_data=None, request=None, user=None, finding_notes
d[item['name']] = item['value']

findings = ReportFindingFilter(d, queryset=findings)
user_id = user.id if user is not None else None
selected_widgets[list(widget.keys())[0] + '-' + str(idx)] = FindingList(request=request, findings=findings,
finding_notes=finding_notes,
finding_images=finding_images,
host=host, user_id=user_id)
finding_images=finding_images)

if list(widget.keys())[0] == 'wysiwyg-content':
wysiwyg_content = WYSIWYGContent(request=request)
Expand Down
2 changes: 2 additions & 0 deletions dojo/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@
{% endif %}
</ul>
</li>
{% block reports_tab %}
<li>
<a href="{% url 'report_builder' %}" aria-expanded="false" aria-label="Reports">
<i class="fa-solid fa-file-lines fa-fw"></i>
Expand All @@ -380,6 +381,7 @@
</a>
<!-- /.nav-second-level -->
</li>
{% endblock %}
<li>
<a href="{% url 'metrics' %}?date=5&view=dashboard" aria-expanded="false" aria-label="Metrics">
<i class="fa-solid fa-chart-column fa-fw"></i>
Expand Down

0 comments on commit e420d02

Please sign in to comment.