dusty/reporters/html/presenter.py (270 lines of code) (raw):
#!/usr/bin/python3
# coding=utf-8
# pylint: disable=I0011,R0903,W0702
# Copyright 2019 getcarrier.io
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
HTML report presenter
"""
from dusty.models.finding import DastFinding, SastFinding
from dusty.constants import SEVERITIES
from dusty.tools import markdown, log
from .models import HTMLReportMeta, HTMLReportAlert, HTMLReportFinding, HTMLReportError
class HTMLPresenter:
""" HTML presenter """
def __init__(self, context, config):
self.context = context
self.config = config
@staticmethod
def _item_to_finding(item):
if isinstance(item, DastFinding):
return HTMLReportFinding(
tool=item.get_meta("tool", ""),
title=item.title,
severity=item.get_meta("severity", SEVERITIES[-1]),
description=markdown.markdown_to_html(item.description)
)
if isinstance(item, SastFinding):
return HTMLReportFinding(
tool=item.get_meta("tool", ""),
title=item.title,
severity=item.get_meta("severity", SEVERITIES[-1]),
description=markdown.markdown_to_html("\n\n".join(item.description))
)
raise ValueError("Unsupported item type")
def _group_findings_by_endpoints(self, items):
result = list()
try:
endpoint_map = dict()
ungrouped = list()
# Prepare endpoint mapping
for item in items:
endpoints = item.get_meta("endpoints", None)
if not endpoints:
ungrouped.append(item)
continue
for endpoint in endpoints:
if endpoint not in endpoint_map:
endpoint_map[endpoint] = list()
endpoint_map[endpoint].append(item)
# Make HTMLReportFinding instances
for endpoint in sorted(endpoint_map.keys(), key=lambda item: item.raw):
group = HTMLReportFinding(
tool="-",
title=f'Findings on {endpoint.raw}',
severity="-",
description=""
)
for finding in sorted(
endpoint_map[endpoint],
key=lambda item: (
SEVERITIES.index(item.get_meta("severity", SEVERITIES[-1])),
item.get_meta("tool", ""),
item.title
)
):
try:
group.findings.append(self._item_to_finding(finding))
except:
log.exception("Failed to create finding item")
result.append(group)
if ungrouped:
group = HTMLReportFinding(
tool="-",
title="Findings with no endpoint",
severity="-",
description=""
)
for finding in sorted(
ungrouped,
key=lambda item: (
SEVERITIES.index(item.get_meta("severity", SEVERITIES[-1])),
item.get_meta("tool", ""),
item.title
)
):
try:
group.findings.append(self._item_to_finding(finding))
except:
log.exception("Failed to create finding item")
result.append(group)
except:
log.exception("Error during findings grouping")
# Done
return result
@property
def project_name(self):
""" Returns project name """
return self.context.get_meta("project_name", "Unnamed Project")
@property
def project_meta(self):
""" Returns project meta """
result = list()
result.append(HTMLReportMeta(
name="Project name",
value=self.context.get_meta("project_name", "Unnamed Project")
))
if self.context.get_meta("project_description", None):
result.append(HTMLReportMeta(
name="Application name",
value=self.context.get_meta("project_description")
))
if self.context.get_meta("environment_name", None):
result.append(HTMLReportMeta(
name="Environment",
value=self.context.get_meta("environment_name")
))
if self.context.get_meta("testing_type", None):
result.append(HTMLReportMeta(
name="Testing type",
value=self.context.get_meta("testing_type")
))
if self.context.get_meta("dast_target", None):
result.append(HTMLReportMeta(
name="DAST target",
value=self.context.get_meta("dast_target")
))
if self.context.get_meta("sast_code", None):
result.append(HTMLReportMeta(
name="SAST code",
value=self.context.get_meta("sast_code")
))
if self.context.get_meta("scan_type", None):
result.append(HTMLReportMeta(
name="Scan type",
value=self.context.get_meta("scan_type")
))
if self.context.get_meta("build_id", None):
result.append(HTMLReportMeta(
name="Build ID",
value=self.context.get_meta("build_id")
))
if self.context.get_meta("dusty_version", None):
result.append(HTMLReportMeta(
name="Dusty version",
value=self.context.get_meta("dusty_version")
))
testing_time = self.context.performers["reporting"].get_module_meta(
"time_meta", "testing_run_time", None
)
if testing_time:
result.append(HTMLReportMeta(
name="Testing time",
value=f"{testing_time} second(s)"
))
result.append(HTMLReportMeta(
name="Total findings",
value=str(len(self.project_findings))
))
result.append(HTMLReportMeta(
name="Total false positives",
value=str(len(self.project_false_positive_findings))
))
result.append(HTMLReportMeta(
name="Total information findings",
value=str(len(self.project_information_findings))
))
result.append(HTMLReportMeta(
name="Total excluded findings",
value=str(len(self.project_excluded_findings))
))
result.append(HTMLReportMeta(
name="Total errors",
value=str(len(self.project_errors))
))
return result
@property
def project_alerts(self):
""" Returns project alerts """
result = list()
if self.project_errors:
result.append(HTMLReportAlert(
type_="warning",
text=f"Errors occurred during testing, result may be incomplete"
))
if self.project_findings:
result.append(HTMLReportAlert(
type_="danger",
text=f"Security testing FAILED, found {len(self.project_findings)} findings"
))
else:
result.append(HTMLReportAlert(
type_="success",
text=f"Security testing PASSED"
))
return result
@property
def project_findings(self):
""" Returns project findings """
result = list()
if self.config.get("group_by_endpoint", False):
result.extend(self._group_findings_by_endpoints([
item for item in self.context.findings \
if not item.get_meta("information_finding", False) \
and not item.get_meta("false_positive_finding", False) \
and not item.get_meta("excluded_finding", False)
]))
else:
for item in self.context.findings:
if item.get_meta("information_finding", False) or \
item.get_meta("false_positive_finding", False) or \
item.get_meta("excluded_finding", False):
continue
try:
result.append(self._item_to_finding(item))
except:
log.exception("Failed to create finding item")
result.sort(key=lambda item: (SEVERITIES.index(item.severity), item.tool, item.title))
return result
@property
def project_information_findings(self):
""" Returns project information findings """
result = list()
if self.config.get("group_by_endpoint", False):
result.extend(self._group_findings_by_endpoints([
item for item in self.context.findings \
if item.get_meta("information_finding", False) \
and not item.get_meta("false_positive_finding", False) \
and not item.get_meta("excluded_finding", False)
]))
else:
for item in self.context.findings:
if item.get_meta("information_finding", False) and \
not item.get_meta("false_positive_finding", False) and \
not item.get_meta("excluded_finding", False):
try:
result.append(self._item_to_finding(item))
except:
log.exception("Failed to create finding item")
result.sort(key=lambda item: (SEVERITIES.index(item.severity), item.tool, item.title))
return result
@property
def project_false_positive_findings(self):
""" Returns project false positive findings """
result = list()
if self.config.get("group_by_endpoint", False):
result.extend(self._group_findings_by_endpoints([
item for item in self.context.findings \
if item.get_meta("false_positive_finding", False) and \
not item.get_meta("excluded_finding", False)
]))
else:
for item in self.context.findings:
if item.get_meta("false_positive_finding", False) and \
not item.get_meta("excluded_finding", False):
try:
result.append(self._item_to_finding(item))
except:
log.exception("Failed to create finding item")
result.sort(key=lambda item: (SEVERITIES.index(item.severity), item.tool, item.title))
return result
@property
def project_excluded_findings(self):
""" Returns project excluded findings """
result = list()
if self.config.get("group_by_endpoint", False):
result.extend(self._group_findings_by_endpoints([
item for item in self.context.findings \
if item.get_meta("excluded_finding", False)
]))
else:
for item in self.context.findings:
if item.get_meta("excluded_finding", False):
try:
result.append(self._item_to_finding(item))
except:
log.exception("Failed to create finding item")
result.sort(key=lambda item: (SEVERITIES.index(item.severity), item.tool, item.title))
return result
@property
def project_errors(self):
""" Returns project errors """
result = list()
for item in self.context.errors:
result.append(HTMLReportError(
tool=item.tool,
title=item.error,
description=markdown.markdown_to_html(item.details)
))
result.sort(key=lambda item: (item.tool, item.title))
return result