Skip to content

Commit

Permalink
Release 1.4.0
Browse files Browse the repository at this point in the history
Includes template for docx export
  • Loading branch information
TheGroundZero committed Sep 27, 2018
1 parent 5b442e6 commit 330f63e
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 88 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Changelog
=========

1.4.0 - Use Word template for report building

1.3.1 - Add charts to Word document using matplotlib. Some code clean-up and small lay-out changes in Excel.

1.3.0 - Fix retrieval of description and other useful info by parsing <tags> instead of <description>
Expand Down
22 changes: 16 additions & 6 deletions openvasreporting/libs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

"""This file contains data structures"""

from pathlib import Path


class Config(object):
def __init__(self, input_files, output_file="openvas_report", min_level="none", filetype="xlsx"):
def __init__(self, input_files, output_file="openvas_report", min_level="none", filetype="xlsx",
template="openvasreporting/src/openvas-template.docx"):
"""
:param input_files: input file path
:type input_files: list(str)
Expand All @@ -22,26 +25,33 @@ def __init__(self, input_files, output_file="openvas_report", min_level="none",
:param filetype: output file filetype
:type filetype: str
:param template: template to use
:type filetype: str
:raises: TypeError, ValueError
"""
if not isinstance(input_files, list):
raise TypeError("Expected list, got '{}' instead".format(type(input_files)))
else:
for i in input_files:
if not isinstance(i, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(i)))
raise TypeError("Expected str, got '{}' instead".format(type(i)))

if not isinstance(output_file, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(output_file)))
raise TypeError("Expected str, got '{}' instead".format(type(output_file)))
if not isinstance(min_level, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(min_level)))
raise TypeError("Expected str, got '{}' instead".format(type(min_level)))
if not isinstance(filetype, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(filetype)))
raise TypeError("Expected str, got '{}' instead".format(type(filetype)))
if not isinstance(template, str):
raise TypeError("Expected str, got '{}' instead".format(type(template)))

self.input_files = input_files
self.output_file = output_file
self.output_file = "{}.{}".format(output_file, filetype) if output_file.split(".")[-1] != filetype \
else output_file
self.min_level = min_level
self.filetype = filetype
self.template = Path(template).resolve()

@staticmethod
def colors():
Expand Down
131 changes: 54 additions & 77 deletions openvasreporting/libs/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,37 @@ def exporters():
}


def export_to_excel(vuln_info, output_file="openvas_report"):
def export_to_excel(vuln_info, template=None, output_file='openvas_report'):
"""
Export vulnerabilities info in an Excel file.
:param vuln_info: Vulnerability list info
:type vuln_info: list(Vulnerability)
:param template: Not supported in xlsx-output
:type template: NoneType
:param output_file: Filename of the Excel file
:type output_file: str
:raises: TypeError
:raises: TypeError, NotImplementedError
"""

import xlsxwriter

if not isinstance(vuln_info, list):
raise TypeError("Expected list, got '{}' instead".format(type(vuln_info)))
else:
for x in vuln_info:
if not isinstance(x, Vulnerability):
raise TypeError("Expected Vulnerability, got '{}' instead".format(type(x)))
if not isinstance(output_file, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(output_file)))
raise TypeError("Expected str, got '{}' instead".format(type(output_file)))
else:
if not output_file:
raise ValueError("output_file must have a valid name.")

if output_file.split(".")[-1] != "xlsx":
output_file = "{}.xlsx".format(output_file)

import xlsxwriter
if template is not None:
raise NotImplementedError("Use of template is not supported in XSLX-output.")

# ====================
# FUNCTIONS
Expand Down Expand Up @@ -292,7 +295,7 @@ def __row_height(text, width):
workbook.close()


def export_to_word(vuln_info, output_file="openvas_report"):
def export_to_word(vuln_info, template, output_file='openvas_report'):
"""
Export vulnerabilities info in a Word file.
Expand All @@ -301,35 +304,38 @@ def export_to_word(vuln_info, output_file="openvas_report"):
:param output_file: Filename of the Excel file
:type output_file: str
:param template: Path to Docx template
:type template: PosixPath, str
:raises: NotImplementedError
:raises: TypeError
"""
if not isinstance(vuln_info, list):
raise TypeError("Expected list, got '{}' instead".format(type(vuln_info)))
else:
for x in vuln_info:
if not isinstance(x, Vulnerability):
raise TypeError("Expected Vulnerability, got '{}' instead".format(type(x)))
if not isinstance(output_file, str):
raise TypeError("Expected basestring, got '{}' instead".format(type(output_file)))
else:
if not output_file:
raise ValueError("output_file must have a valid name.")

if output_file.split(".")[-1] != "docx":
output_file = "{}.docx".format(output_file)

import matplotlib.pyplot as plt
import numpy as np
import tempfile
import os

from pathlib import PosixPath
from docx import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.oxml.shared import qn, OxmlElement
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
from docx.shared import Cm, Pt, RGBColor
from docx.shared import Cm

if not isinstance(vuln_info, list):
raise TypeError("Expected list, got '{}' instead".format(type(vuln_info)))
else:
for x in vuln_info:
if not isinstance(x, Vulnerability):
raise TypeError("Expected Vulnerability, got '{}' instead".format(type(x)))
if not isinstance(output_file, str):
raise TypeError("Expected str, got '{}' instead".format(type(output_file)))
else:
if not output_file:
raise ValueError("output_file must have a valid name.")
if not isinstance(template, (str, PosixPath)):
raise TypeError("Expected str or PosixPath, got '{}' instead".format(type(template)))

# TODO Move to function to de-duplicate this
vuln_info.sort(key=lambda key: key.cvss, reverse=True)
Expand All @@ -345,64 +351,26 @@ def export_to_word(vuln_info, output_file="openvas_report"):
# ====================
# DOCUMENT PROPERTIES
# ====================
document = Document()
document = Document(template)

doc_prop = document.core_properties
doc_prop.title = "OpenVAS Report"
doc_prop.category = "Report"

# MARGINS
# --------------------
for section in document.sections:
section.top_margin = Cm(2.54)
section.bottom_margin = Cm(2.54)
section.left_margin = Cm(2.54)
section.right_margin = Cm(2.54)

# --------------------
# FONTS
# --------------------
styles = document.styles

color_blue = RGBColor.from_string(Config.colors()['blue'][1:])

font_normal = styles['Normal'].font
font_normal.name = 'Tahoma'
font_normal.size = Pt(10)

def add_style(new_style_name, base_style_name, font_size, font_color, font_bold, widow_ctrl):
style = styles.add_style(new_style_name, WD_STYLE_TYPE.PARAGRAPH)
style.base_style = styles[base_style_name]
style.font.name = 'Tahoma'
style.font.size = font_size
style.font.bold = font_bold
style.font.color.rgb = font_color
style.paragraph_format.widow_control = widow_ctrl
style.next_paragraph_style = styles['Body Text']
style.hidden = False
style.quick_style = True

add_style('Report Title', 'Title', Pt(36), color_blue, True, True)
add_style('Report Heading TOC', 'Normal', Pt(16), color_blue, True, True)
add_style('Report Heading 1', 'Heading 1', Pt(16), color_blue, True, True)
add_style('Report Heading 2', 'Heading 2', Pt(14), color_blue, True, True)
add_style('Report Heading 3', 'Heading 3', Pt(13), color_blue, True, True)

document.add_paragraph('OpenVAS Report', style='Report Title')
document.add_paragraph('OpenVAS Report', style='Title')

# ====================
# TABLE OF CONTENTS
# ====================
# TODO Use ToC Header so it doesn't end up in the ToC
document.add_paragraph('Table of Contents', style='Report Heading TOC')
document.add_paragraph('Table of Contents', style='Heading 1')

par = document.add_paragraph()
run = par.add_run()
fld_char = OxmlElement('w:fldChar') # creates a new element
fld_char.set(qn('w:fldCharType'), 'begin') # sets attribute on element
instr_text = OxmlElement('w:instrText')
instr_text.set(qn('xml:space'), 'preserve') # sets attribute on element
instr_text.text = r'TOC \o 1-2 \h \z \u' # change "1-2" depending on heading levels you need
instr_text.text = r'TOC \h \z \t "OV-H1toc;1;OV-H2toc;2;OV-H3toc;3;OV-Finding;3"'

fld_char2 = OxmlElement('w:fldChar')
fld_char2.set(qn('w:fldCharType'), 'separate')
Expand All @@ -422,19 +390,28 @@ def add_style(new_style_name, base_style_name, font_size, font_color, font_bold,
document.add_page_break()

# ====================
# SUMMARY
# MANAGEMENT SUMMARY
# ====================
document.add_paragraph('Summary', style='Report Heading 1')
document.add_paragraph('Summary info, with graphs, will come here')
document.add_paragraph('Management Summary', style='OV-H1toc')
document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >')
document.add_page_break()

# ====================
# TECHNICAL FINDINGS
# ====================
document.add_paragraph('Technical Findings', style='OV-H1toc')
document.add_paragraph('The section below discusses the technical findings.')

# --------------------
# SUMMARY TABLE
# --------------------
document.add_paragraph('Summary', style='OV-H2toc')

colors_sum = []
labels_sum = []
vuln_sum = []
aff_sum = []

# --------------------
# SUMMARY TABLE
# --------------------
table_summary = document.add_table(rows=1, cols=3)
hdr_cells = table_summary.rows[0].cells
hdr_cells[0].paragraphs[0].add_run('Risk level').bold = True
Expand Down Expand Up @@ -527,13 +504,13 @@ def __label_bars(barcontainer):

if level != cur_level:
document.add_paragraph(
level.capitalize(), style='Report Heading 1').paragraph_format.page_break_before = True
level.capitalize(), style='OV-H2toc').paragraph_format.page_break_before = True
cur_level = level
else:
document.add_page_break()

title = "[{}] {}".format(level.upper(), vuln.name)
document.add_paragraph(title, style='Report Heading 2')
document.add_paragraph(title, style='OV-Finding')

table_vuln = document.add_table(rows=7, cols=3)
table_vuln.autofit = False
Expand Down Expand Up @@ -583,7 +560,7 @@ def __label_bars(barcontainer):

# VULN HOSTS
# --------------------
document.add_paragraph('Vulnerable hosts', style='Report Heading 3')
document.add_paragraph('Vulnerable hosts', style='Heading 4')

table_hosts = document.add_table(cols=4, rows=(len(vuln.hosts) + 1))
hdr_cells = table_hosts.rows[0].cells
Expand Down
8 changes: 6 additions & 2 deletions openvasreporting/openvasreporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def main():
parser.add_argument("-l", "--level", dest="min_level", help="Minimal level (c, h, m, l, n)", required=False,
default="n")
parser.add_argument("-f", "--format", dest="filetype", help="Output format (xlsx)", required=False, default="xlsx")
parser.add_argument("-t", "--template", dest="template", help="Template file for docx export", required=False)

args = parser.parse_args()

Expand All @@ -38,7 +39,10 @@ def main():
raise ValueError("Filetype not supported, got {}, expecting one of {}".format(args.filetype,
exporters().keys()))

config = Config(args.input_files, args.output_file, min_lvl, args.filetype)
if args.template is not None:
config = Config(args.input_files, args.output_file, min_lvl, args.filetype, args.template)
else:
config = Config(args.input_files, args.output_file, min_lvl, args.filetype)

convert(config)

Expand All @@ -61,4 +65,4 @@ def convert(config):

openvas_info = openvas_parser(config.input_files, config.min_level)

exporters()[config.filetype](openvas_info, config.output_file)
exporters()[config.filetype](openvas_info, config.template, config.output_file)
5 changes: 5 additions & 0 deletions openvasreporting/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
#
#
# Project name: OpenVAS Reporting: A tool to convert OpenVAS XML reports into Excel files.
# Project URL: https://github.com/TheGroundZero/openvas_to_report
Binary file added openvasreporting/src/openvas-template.docx
Binary file not shown.
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
XlsxWriter>=1.0.0
XlsxWriter>=1.1.1
python-docx>=0.8.7
matplotlib>=2.2.2
matplotlib>=3.0.0
numpy>=1.15.2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
setup(
name='OpenVAS Reporting',
description='A tool to convert OpenVAS XML into reports.',
version='1.3.1',
version='1.4.0',
long_description=long_description,
long_description_content_type='text/markdown',
author='TheGroundZero (@DezeStijn)',
Expand Down

0 comments on commit 330f63e

Please sign in to comment.