def document_creation(self, dataframes, page_layout, record_id):
        try:
            doc_utils = DocumentUtilities()
            document              = Document()
            section               = document.sections[-1]
            section.orientation   = WD_ORIENT.PORTRAIT
            
            section.page_width    = Cm(doc_utils.get_cms(page_layout['page_width']))
            section.page_height   = Cm(doc_utils.get_cms(page_layout['page_height']))

            section.left_margin   = Cm(1.27)
            section.right_margin  = Cm(1.27)
            section.top_margin    = Cm(1.27)
            section.bottom_margin = Cm(1.27)
            document._body.clear_content()
            
            for index, df in enumerate(dataframes):
                for index, row in df.iterrows():
                    # if row['text'] == None and row['base64'] != None:
                    #     image_path = doc_utils.get_path_from_base64(self.DOWNLOAD_FOLDER, row['base64'])           
                    #     document.add_picture(image_path, width=Cm(doc_utils.get_cms(row['text_width'])), 
                    #                     height=Cm(doc_utils.get_cms(row['text_height'])))
                    #     os.remove(image_path)
                    if row['text'] != None and row['base64'] == None:
                        paragraph                      = document.add_paragraph()
                        paragraph_format               = paragraph.paragraph_format
                        paragraph_format.left_indent   = Cm(doc_utils.get_cms(row['text_left']))
                        if index != df.index[-1] and df.iloc[index + 1]['text_top'] != row['text_top']:
                            pixel = df.iloc[index + 1]['text_top'] - row['text_top'] - row['font_size']
                            if pixel>0:
                                paragraph_format.space_after = Twips(doc_utils.pixel_to_twips(pixel))
                            else:
                                paragraph_format.space_after = Twips(0)
                        else:
                            paragraph_format.space_after = Twips(0)
                        run                            = paragraph.add_run()
                        if row['font_family'] != None and "Bold" in row['font_family']:
                            run.bold                   = True
                        font                           = run.font
                        font.name                      = 'Arial'
                        if row['font_size'] != None:
                            font.size                      = Twips(doc_utils.pixel_to_twips(row['font_size'])) 
                        run.add_text(row['text'])
                run.add_break(WD_BREAK.PAGE)
            out_filename = os.path.splitext(os.path.basename(record_id.split('|')[0]))[0] + str(uuid.uuid4()) + '_translated.docx'
            output_filepath = os.path.join(self.DOWNLOAD_FOLDER , out_filename)
            document.save(output_filepath)
            log_info("docx file formation done!! filename: %s"%out_filename, MODULE_CONTEXT)
            return out_filename
        except Exception as e:
            log_exception("dataframe to doc formation failed", MODULE_CONTEXT, e)
Esempio n. 2
0
def image_for_docx(fileref, question, tpl, width=None):
    if fileref.__class__.__name__ in ('DAFile', 'DAFileList', 'DAFileCollection', 'DALocalFile', 'DAStaticFile'):
        file_info = dict(fullpath=fileref.path())
    else:
        file_info = server.file_finder(fileref, convert={'svg': 'png'}, question=question)
    if 'fullpath' not in file_info:
        return '[FILE NOT FOUND]'
    if width is not None:
        m = re.search(r'^([0-9\.]+) *([A-Za-z]*)', str(width))
        if m:
            amount = float(m.group(1))
            units = m.group(2).lower()
            if units in ['in', 'inches', 'inch']:
                the_width = Inches(amount)
            elif units in ['pt', 'pts', 'point', 'points']:
                the_width = Pt(amount)
            elif units in ['mm', 'millimeter', 'millimeters']:
                the_width = Mm(amount)
            elif units in ['cm', 'centimeter', 'centimeters']:
                the_width = Cm(amount)
            elif units in ['twp', 'twip', 'twips']:
                the_width = Twips(amount)
            else:
                the_width = Pt(amount)
        else:
            the_width = Inches(2)
    else:
        the_width = Inches(2)
    return InlineImage(tpl, file_info['fullpath'], the_width)
Esempio n. 3
0
def getMarginFromStyle(style,side):
	margin = style._element.xpath('.//w:tblCellMar/w:%s/@w:w'%side)
	marginType = style._element.xpath('.//w:tblCellMar/w:%s/@w:type'%side)
	if not margin and not marginType and style.base_style:
		return getMarginFromStyle(style.base_style,side)
	if marginType[0] != 'dxa':
		logger.error("Ok, I don't manage this case. Open an issue : Margin type support %s" % marginType)
	return Twips(int(margin[0])) 
Esempio n. 4
0
def transform_for_docx(text, question, tpl, width=None):
    if type(text) in (int, float, bool, NoneType):
        return text
    text = text_type(text)
    m = re.search(r'\[FILE ([^,\]]+), *([0-9\.]) *([A-Za-z]+) *\]', text)
    if m:
        amount = m.group(2)
        units = m.group(3).lower()
        if units in ['in', 'inches', 'inch']:
            the_width = Inches(amount)
        elif units in ['pt', 'pts', 'point', 'points']:
            the_width = Pt(amount)
        elif units in ['mm', 'millimeter', 'millimeters']:
            the_width = Mm(amount)
        elif units in ['cm', 'centimeter', 'centimeters']:
            the_width = Cm(amount)
        elif units in ['twp', 'twip', 'twips']:
            the_width = Twips(amount)
        else:
            the_width = Pt(amount)
        file_info = server.file_finder(m.group(1),
                                       convert={'svg': 'png'},
                                       question=question)
        if 'fullpath' not in file_info:
            return '[FILE NOT FOUND]'
        return InlineImage(tpl, file_info['fullpath'], the_width)
    m = re.search(r'\[FILE ([^,\]]+)\]', text)
    if m:
        file_info = server.file_finder(m.group(1),
                                       convert={'svg': 'png'},
                                       question=question)
        if 'fullpath' not in file_info:
            return '[FILE NOT FOUND]'
        return InlineImage(tpl, file_info['fullpath'], Inches(2))
    return docassemble.base.filter.docx_template_filter(text,
                                                        question=question)
class DescribeTabStop(object):

    def it_knows_its_position(self, position_get_fixture):
        tab_stop, expected_value = position_get_fixture
        assert tab_stop.position == expected_value

    def it_can_change_its_position(self, position_set_fixture):
        tab_stop, value, tabs, new_idx, expected_xml = position_set_fixture
        tab_stop.position = value
        assert tab_stop._tab is tabs[new_idx]
        assert tabs.xml == expected_xml

    def it_knows_its_alignment(self, alignment_get_fixture):
        tab_stop, expected_value = alignment_get_fixture
        assert tab_stop.alignment == expected_value

    def it_can_change_its_alignment(self, alignment_set_fixture):
        tab_stop, value, expected_xml = alignment_set_fixture
        tab_stop.alignment = value
        assert tab_stop._element.xml == expected_xml

    def it_knows_its_leader(self, leader_get_fixture):
        tab_stop, expected_value = leader_get_fixture
        assert tab_stop.leader == expected_value

    def it_can_change_its_leader(self, leader_set_fixture):
        tab_stop, value, expected_xml = leader_set_fixture
        tab_stop.leader = value
        assert tab_stop._element.xml == expected_xml

    # fixture --------------------------------------------------------

    @pytest.fixture(params=[
        ('w:tab{w:val=left}',  'LEFT'),
        ('w:tab{w:val=right}', 'RIGHT'),
    ])
    def alignment_get_fixture(self, request):
        tab_stop_cxml, member = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_value = getattr(WD_TAB_ALIGNMENT, member)
        return tab_stop, expected_value

    @pytest.fixture(params=[
        ('w:tab{w:val=left}',  'RIGHT', 'w:tab{w:val=right}'),
        ('w:tab{w:val=right}', 'LEFT',  'w:tab{w:val=left}'),
    ])
    def alignment_set_fixture(self, request):
        tab_stop_cxml, member, expected_cxml = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_xml = xml(expected_cxml)
        value = getattr(WD_TAB_ALIGNMENT, member)
        return tab_stop, value, expected_xml

    @pytest.fixture(params=[
        ('w:tab',                'SPACES'),
        ('w:tab{w:leader=none}', 'SPACES'),
        ('w:tab{w:leader=dot}',  'DOTS'),
    ])
    def leader_get_fixture(self, request):
        tab_stop_cxml, member = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        expected_value = getattr(WD_TAB_LEADER, member)
        return tab_stop, expected_value

    @pytest.fixture(params=[
        ('w:tab',                  'DOTS',   'w:tab{w:leader=dot}'),
        ('w:tab{w:leader=dot}',    'DASHES', 'w:tab{w:leader=hyphen}'),
        ('w:tab{w:leader=hyphen}', 'SPACES', 'w:tab'),
        ('w:tab{w:leader=hyphen}', None,     'w:tab'),
        ('w:tab',                  'SPACES', 'w:tab'),
        ('w:tab',                  None,     'w:tab'),
    ])
    def leader_set_fixture(self, request):
        tab_stop_cxml, new_value, expected_cxml = request.param
        tab_stop = TabStop(element(tab_stop_cxml))
        value = (
            None if new_value is None else getattr(WD_TAB_LEADER, new_value)
        )
        expected_xml = xml(expected_cxml)
        return tab_stop, value, expected_xml

    @pytest.fixture
    def position_get_fixture(self, request):
        tab_stop = TabStop(element('w:tab{w:pos=720}'))
        return tab_stop, Twips(720)

    @pytest.fixture(params=[
        ('w:tabs/w:tab{w:pos=360,w:val=left}',
         Twips(720), 0,
         'w:tabs/w:tab{w:pos=720,w:val=left}'),
        ('w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})',
         Twips(180), 0,
         'w:tabs/(w:tab{w:pos=180,w:val=left},w:tab{w:pos=720,w:val=left})'),
        ('w:tabs/(w:tab{w:pos=360,w:val=left},w:tab{w:pos=720,w:val=left})',
         Twips(960), 1,
         'w:tabs/(w:tab{w:pos=720,w:val=left},w:tab{w:pos=960,w:val=left})'),
        ('w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})',
         Twips(-48), 0,
         'w:tabs/(w:tab{w:pos=-48,w:val=left},w:tab{w:pos=-36,w:val=left})'),
        ('w:tabs/(w:tab{w:pos=-72,w:val=left},w:tab{w:pos=-36,w:val=left})',
         Twips(-16), 1,
         'w:tabs/(w:tab{w:pos=-36,w:val=left},w:tab{w:pos=-16,w:val=left})'),
    ])
    def position_set_fixture(self, request):
        tabs_cxml, value, new_idx, expected_cxml = request.param
        tabs = element(tabs_cxml)
        tab = tabs.tab_lst[0]
        tab_stop = TabStop(tab)
        expected_xml = xml(expected_cxml)
        return tab_stop, value, tabs, new_idx, expected_xml
class DescribeTabStops(object):

    def it_knows_its_length(self, len_fixture):
        tab_stops, expected_value = len_fixture
        assert len(tab_stops) == expected_value

    def it_can_iterate_over_its_tab_stops(self, iter_fixture):
        tab_stops, expected_count, tab_stop_, TabStop_, expected_calls = (
            iter_fixture
        )
        count = 0
        for tab_stop in tab_stops:
            assert tab_stop is tab_stop_
            count += 1
        assert count == expected_count
        assert TabStop_.call_args_list == expected_calls

    def it_can_get_a_tab_stop_by_index(self, index_fixture):
        tab_stops, idx, TabStop_, tab, tab_stop_ = index_fixture
        tab_stop = tab_stops[idx]
        TabStop_.assert_called_once_with(tab)
        assert tab_stop is tab_stop_

    def it_raises_on_indexed_access_when_empty(self):
        tab_stops = TabStops(element('w:pPr'))
        with pytest.raises(IndexError):
            tab_stops[0]

    def it_can_add_a_tab_stop(self, add_tab_fixture):
        tab_stops, position, kwargs, expected_xml = add_tab_fixture
        tab_stops.add_tab_stop(position, **kwargs)
        assert tab_stops._element.xml == expected_xml

    def it_can_delete_a_tab_stop(self, del_fixture):
        tab_stops, idx, expected_xml = del_fixture
        del tab_stops[idx]
        assert tab_stops._element.xml == expected_xml

    def it_raises_on_del_idx_invalid(self, del_raises_fixture):
        tab_stops, idx = del_raises_fixture
        with pytest.raises(IndexError) as exc:
            del tab_stops[idx]
        assert exc.value.args[0] == 'tab index out of range'

    def it_can_clear_all_its_tab_stops(self, clear_all_fixture):
        tab_stops, expected_xml = clear_all_fixture
        tab_stops.clear_all()
        assert tab_stops._element.xml == expected_xml

    # fixture --------------------------------------------------------

    @pytest.fixture(params=[
        'w:pPr',
        'w:pPr/w:tabs/w:tab{w:pos=42}',
        'w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})',
    ])
    def clear_all_fixture(self, request):
        pPr_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml('w:pPr')
        return tab_stops, expected_xml

    @pytest.fixture(params=[
        ('w:pPr/w:tabs/w:tab{w:pos=42}',                   0,
         'w:pPr'),
        ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 0,
         'w:pPr/w:tabs/w:tab{w:pos=42}'),
        ('w:pPr/w:tabs/(w:tab{w:pos=24},w:tab{w:pos=42})', 1,
         'w:pPr/w:tabs/w:tab{w:pos=24}'),
    ])
    def del_fixture(self, request):
        pPr_cxml, idx, expected_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml(expected_cxml)
        return tab_stops, idx, expected_xml

    @pytest.fixture(params=[
        ('w:pPr',                        0),
        ('w:pPr/w:tabs/w:tab{w:pos=42}', 1),
    ])
    def del_raises_fixture(self, request):
        tab_stops_cxml, idx = request.param
        tab_stops = TabStops(element(tab_stops_cxml))
        return tab_stops, idx

    @pytest.fixture(params=[
        ('w:pPr', Twips(42), {},
         'w:pPr/w:tabs/w:tab{w:pos=42,w:val=left}'),
        ('w:pPr', Twips(72), {'alignment': WD_TAB_ALIGNMENT.RIGHT},
         'w:pPr/w:tabs/w:tab{w:pos=72,w:val=right}'),
        ('w:pPr', Twips(24),
         {'alignment': WD_TAB_ALIGNMENT.CENTER,
          'leader': WD_TAB_LEADER.DOTS},
         'w:pPr/w:tabs/w:tab{w:pos=24,w:val=center,w:leader=dot}'),
        ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(72), {},
         'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=72,w:val=left})'),
        ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(24), {},
         'w:pPr/w:tabs/(w:tab{w:pos=24,w:val=left},w:tab{w:pos=42})'),
        ('w:pPr/w:tabs/w:tab{w:pos=42}', Twips(42), {},
         'w:pPr/w:tabs/(w:tab{w:pos=42},w:tab{w:pos=42,w:val=left})'),
    ])
    def add_tab_fixture(self, request):
        pPr_cxml, position, kwargs, expected_cxml = request.param
        tab_stops = TabStops(element(pPr_cxml))
        expected_xml = xml(expected_cxml)
        return tab_stops, position, kwargs, expected_xml

    @pytest.fixture(params=[
        ('w:pPr/w:tabs/w:tab{w:pos=0}',                                 0),
        ('w:pPr/w:tabs/(w:tab{w:pos=1},w:tab{w:pos=2},w:tab{w:pos=3})', 1),
        ('w:pPr/w:tabs/(w:tab{w:pos=4},w:tab{w:pos=5},w:tab{w:pos=6})', 2),
    ])
    def index_fixture(self, request, TabStop_, tab_stop_):
        pPr_cxml, idx = request.param
        pPr = element(pPr_cxml)
        tab = pPr.xpath('./w:tabs/w:tab')[idx]
        tab_stops = TabStops(pPr)
        return tab_stops, idx, TabStop_, tab, tab_stop_

    @pytest.fixture(params=[
        ('w:pPr',                                              0),
        ('w:pPr/w:tabs/w:tab{w:pos=2880}',                     1),
        ('w:pPr/w:tabs/(w:tab{w:pos=2880},w:tab{w:pos=5760})', 2),
    ])
    def iter_fixture(self, request, TabStop_, tab_stop_):
        pPr_cxml, expected_count = request.param
        pPr = element(pPr_cxml)
        tab_elms = pPr.xpath('//w:tab')
        tab_stops = TabStops(pPr)
        expected_calls = [call(tab) for tab in tab_elms]
        return tab_stops, expected_count, tab_stop_, TabStop_, expected_calls

    @pytest.fixture(params=[
        ('w:pPr',                          0),
        ('w:pPr/w:tabs/w:tab{w:pos=2880}', 1),
    ])
    def len_fixture(self, request):
        tab_stops_cxml, expected_value = request.param
        tab_stops = TabStops(element(tab_stops_cxml))
        return tab_stops, expected_value

    # fixture components ---------------------------------------------

    @pytest.fixture
    def TabStop_(self, request, tab_stop_):
        return class_mock(
            request, 'docx.text.tabstops.TabStop', return_value=tab_stop_
        )

    @pytest.fixture
    def tab_stop_(self, request):
        return instance_mock(request, TabStop)
 def position_get_fixture(self, request):
     tab_stop = TabStop(element('w:tab{w:pos=720}'))
     return tab_stop, Twips(720)
def convert_twips_to_cm(twips):
    width = Twips(int(twips))
    return round(width.mm / 10, 2)
Esempio n. 9
0
def export_to_word(vuln_info, template, output_file='openvas_report.docx'):
    """
    Export vulnerabilities info in a Word file.

    :param vuln_info: Vulnerability list info
    :type vuln_info: list(Vulnerability)

    :param output_file: Filename of the Excel file
    :type output_file: str
    
    :param template: Path to Docx template
    :type template: str

    :raises: TypeError
    """

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

    from docx import Document
    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, Twips
    from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
    from docx.enum.section import WD_ORIENT
    from docx.enum.style import WD_STYLE_TYPE
    from docx.enum.table import WD_ALIGN_VERTICAL
    from docx.shared import RGBColor

    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 template is not None:
        if not isinstance(template, str):
            raise TypeError("Expected str, got '{}' instead".format(
                type(template)))

    vuln_info, vuln_levels, vuln_host_by_level, vuln_by_family = _get_collections(
        vuln_info)

    # ====================
    # DOCUMENT PROPERTIES
    # ====================
    # Create new doc
    if template is None:
        document = Document()
        doc_section = document.sections[0]
        # Set A4 Format
        doc_section.page_width = Cm(21.0)
        doc_section.page_height = Cm(29.7)
        # Shrink margins almost to 0
        doc_section.left_margin = Cm(1.5)
        doc_section.right_margin = doc_section.left_margin
        doc_section.top_margin = Cm(1.0)
        doc_section.bottom_margin = Cm(1.0)
        # Force portrait
        doc_section.orientation = WD_ORIENT.PORTRAIT
    # use template
    else:
        document = Document(template)
        doc_section = document.sections[0]

    # Defining styles (if not defined already)
    # All used style will be custom, and with 'OR-' prefix.
    # In this way, the template can still define styles.
    doc_styles = document.styles

    # Base paragraph
    if 'OR-base' not in doc_styles:
        style_pr_base = doc_styles.add_style('OR-base',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_base.font.name = 'Ubuntu'
        style_pr_base.font.size = Pt(8)
        style_pr_base.font.color.rgb = RGBColor.from_string('080808')
        style_pr_base.paragraph_format.left_indent = Cm(0)
        style_pr_base.paragraph_format.right_indent = Cm(0)
        style_pr_base.paragraph_format.first_line_indent = Cm(0)
        style_pr_base.paragraph_format.space_before = Cm(0)
        style_pr_base.paragraph_format.space_after = Cm(0)
        style_pr_base.paragraph_format.line_spacing_rule = WD_LINE_SPACING.SINGLE
        style_pr_base.paragraph_format.widow_control = True
        style_pr_base.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY_LOW
    # Base Styles modification
    if 'OR-base_bold' not in doc_styles:
        style_pr_body = doc_styles.add_style('OR-base_bold',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_body.base_style = doc_styles['OR-base']
        style_pr_body.font.bold = True
    # Section headers
    if 'OR-Heading_base' not in doc_styles:
        style_pr_or_head_base = doc_styles.add_style('OR-Heading_base',
                                                     WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_head_base.base_style = doc_styles['OR-base_bold']
        style_pr_or_head_base.font.color.rgb = RGBColor.from_string('183868')
        style_pr_or_head_base.paragraph_format.space_after = Pt(4)
    # - Titles
    if 'OR-Title' not in doc_styles:
        style_pr_or_title = doc_styles.add_style('OR-Title',
                                                 WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_title.base_style = doc_styles['OR-Heading_base']
        style_pr_or_title.font.size = Pt(36)
        style_pr_or_title.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
        style_pr_or_head_base.paragraph_format.space_after = Pt(8)
    # - Headers
    if 'OR-Heading_1' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Heading_1',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(20)
    if 'OR-Heading_2' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Heading_2',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(16)
    if 'OR-Heading_3' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Vuln_title',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(12)
    # - Vulnerabilities Titles
    for name, rgb in Config.colors().items():
        if 'OR-Vuln_title_' + name not in doc_styles:
            style_pr_or_header = doc_styles.add_style('OR-Vuln_title_' + name,
                                                      WD_STYLE_TYPE.PARAGRAPH)
            style_pr_or_header.base_style = doc_styles['OR-Vuln_title']
            style_pr_or_header.font.color.rgb = RGBColor.from_string(rgb[1:])
    # - Host with vulnerabilities title
    if 'OR-Vuln_hosts' not in doc_styles:
        style_pr_or_header = doc_styles.add_style('OR-Vuln_hosts',
                                                  WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_header.base_style = doc_styles['OR-Heading_base']
        style_pr_or_header.font.size = Pt(10)
    # TOC specific
    if 'OR-TOC_base' not in doc_styles:
        style_pr_or_toc_base = doc_styles.add_style('OR-TOC_base',
                                                    WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc_base.base_style = doc_styles['OR-base']
        style_pr_or_toc_base.font.color.rgb = RGBColor.from_string('183868')
    if 'OR-TOC_1' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_1',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
        style_pr_or_toc.font.bold = True
    if 'OR-TOC_2' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_2',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
    if 'OR-TOC_3' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-Toc_3',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
    if 'OR-TOC_4' not in doc_styles:
        style_pr_or_toc = doc_styles.add_style('OR-TOC_4',
                                               WD_STYLE_TYPE.PARAGRAPH)
        style_pr_or_toc.base_style = doc_styles['OR-TOC_base']
        style_pr_or_toc.font.italic = True
    # Tables style
    # - Specific paragraph style to allow space before and after
    if 'OR-cell' not in doc_styles:
        style_pr_cell = doc_styles.add_style('OR-cell',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_cell.base_style = doc_styles['OR-base']
        style_pr_cell.paragraph_format.space_before = Pt(1.5)
        style_pr_cell.paragraph_format.space_after = Pt(1.5)
    if 'OR-cell_bold' not in doc_styles:
        style_pr_cell = doc_styles.add_style('OR-cell_bold',
                                             WD_STYLE_TYPE.PARAGRAPH)
        style_pr_cell.base_style = doc_styles['OR-cell']
        style_pr_cell.font.bold = True

    # Clear all contents
    document._body.clear_content()

    # Set doc title
    doc_prop = document.core_properties
    doc_title = 'OpenVAS/GreenBone Report'
    doc_prop.title = doc_title
    doc_prop.category = "Report"

    # Effective writeable width
    # If margins set are float, try to fix (issue in python-docx: expected an int)
    # In this case, they _should be_ in twentieths of a point, so
    # multiply Twips helper
    try:
        doc_section.left_margin
    except ValueError as e:
        fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1))
        doc_section.left_margin = Twips(fixed_margin)
    try:
        doc_section.right_margin
    except ValueError as e:
        fixed_margin = float(re.search(": '(.+?)'", str(e)).group(1))
        doc_section.right_margin = Twips(fixed_margin)

    page_width = doc_section.page_width - (doc_section.left_margin +
                                           doc_section.right_margin)

    ## Start actual document writing ##
    document.add_paragraph(doc_title, style='OR-Title')

    # ====================
    # TABLE OF CONTENTS
    # ====================
    # WARNING -Not working with LibreOffice
    document.add_paragraph('Table of Contents', style='OR-Heading_1')
    # keep the title as cover of the report
    document.add_page_break()

    par = document.add_paragraph(style='OR-base')
    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 \h \z \t "OR-TOC_1;1;OR-OR-TOC_1;2;OR-TOC_3;3;OR-TOC_4;3"'

    fld_char2 = OxmlElement('w:fldChar')
    fld_char2.set(qn('w:fldCharType'), 'separate')
    fld_char3 = OxmlElement('w:t')
    fld_char3.text = "# Right-click to update field. #"
    fld_char2.append(fld_char3)

    fld_char4 = OxmlElement('w:fldChar')
    fld_char4.set(qn('w:fldCharType'), 'end')

    r_element = run._r
    r_element.append(fld_char)
    r_element.append(instr_text)
    r_element.append(fld_char2)
    r_element.append(fld_char4)

    document.add_page_break()

    # ====================
    # MANAGEMENT SUMMARY
    # ====================
    document.add_paragraph('Management Summary', style='OR-Heading_1')
    document.add_paragraph('< TYPE YOUR MANAGEMENT SUMMARY HERE >',
                           style='OR-base')
    document.add_page_break()

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

    # --------------------
    # SUMMARY TABLE
    # --------------------
    document.add_paragraph('Summary', style='OR-Heading_2')

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

    table_summary = document.add_table(rows=1, cols=3)

    # TABLE HEADERS
    # --------------------
    hdr_cells = table_summary.rows[0].cells
    hdr_cells[0].paragraphs[0].add_run('Risk level')
    hdr_cells[1].paragraphs[0].add_run('Vulns number')
    hdr_cells[2].paragraphs[0].add_run('Affected hosts')
    # FIELDS
    # --------------------
    # Provide data to table and charts
    for level in Config.levels().values():
        row_cells = table_summary.add_row().cells
        row_cells[0].text = level.capitalize()
        row_cells[1].text = str(vuln_levels[level])
        row_cells[2].text = str(vuln_host_by_level[level])
        colors_sum.append(Config.colors()[level])
        labels_sum.append(level)
        vuln_sum.append(vuln_levels[level])
        aff_sum.append(vuln_host_by_level[level])

    # Apply styles
    # --------------------
    for h in hdr_cells:
        h.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
        for p in h.paragraphs:
            p.style = doc_styles['OR-cell_bold']

    for r in table_summary.rows[1:]:
        for c in r.cells:
            c.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

    # --------------------
    # CHART
    # --------------------
    fd, path = tempfile.mkstemp(suffix='.png')
    chart_dpi = 144
    chart_height = Cm(8)
    par_chart = document.add_paragraph(style='OR-base')
    par_chart.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run_chart = par_chart.add_run()

    bar_chart, bar_axis = plt.subplots(dpi=chart_dpi)
    bar_axis.set_title('Vulnerability summary by risk level', fontsize=10)

    pos = np.arange(len(labels_sum))
    width = 0.35

    bar_axis.set_xticks(pos)
    bar_axis.set_xticklabels(labels_sum)
    bar_chart.gca().spines['left'].set_visible(False)
    bar_chart.gca().spines['right'].set_visible(False)
    bar_chart.gca().spines['top'].set_visible(False)
    bar_chart.gca().spines['bottom'].set_position('zero')
    bar_axis.tick_params(top=False,
                         bottom=True,
                         left=False,
                         right=False,
                         labelleft=False,
                         labelbottom=True)
    bars_vuln = plt.bar(pos - width / 2,
                        vuln_sum,
                        width,
                        align='center',
                        label='Vulnerabilities',
                        color=colors_sum,
                        edgecolor='black')
    bars_aff = plt.bar(pos + width / 2,
                       aff_sum,
                       width,
                       align='center',
                       label='Affected hosts',
                       color=colors_sum,
                       edgecolor='black',
                       hatch='//')
    for barcontainer in (bars_vuln, bars_aff):
        for bar in barcontainer:
            height = bar.get_height()
            bar_chart.gca().text(bar.get_x() + bar.get_width() / 2,
                                 bar.get_height() + 0.3,
                                 str(int(height)),
                                 ha='center',
                                 color='black',
                                 fontsize=8)
    bar_chart.legend()

    bar_chart.savefig(path)

    # plt.show()  # DEBUG

    bar_height = chart_height
    run_chart.add_picture(path, height=bar_height)
    os.remove(path)

    pie_chart, pie_axis = plt.subplots(dpi=chart_dpi,
                                       subplot_kw=dict(aspect="equal"))
    pie_axis.set_title('Vulnerability by family', fontsize=10)

    values = list(vuln_by_family.values())
    pie, tx, autotexts = pie_axis.pie(values,
                                      labels=vuln_by_family.keys(),
                                      autopct='',
                                      textprops=dict(fontsize=8))
    for i, txt in enumerate(autotexts):
        txt.set_text('{}'.format(values[i]))
    pie_chart.savefig(path)

    # plt.show()  # DEBUG
    pie_height = chart_height
    run_chart.add_picture(path, height=pie_height)
    os.remove(path)

    # ====================
    # VULN PAGES
    # ====================
    cur_level = ""

    for i, vuln in enumerate(vuln_info, 1):
        # --------------------
        # GENERAL
        # --------------------
        level = vuln.level.lower()

        if level != cur_level:
            document.add_paragraph(
                level.capitalize(),
                style='OR-Heading_2').paragraph_format.page_break_before = True
            cur_level = level
        else:
            document.add_page_break()

        title = "[{}] {}".format(level.upper(), vuln.name)
        par = document.add_paragraph(title,
                                     style='OR-Vuln_title_' + vuln.level)

        table_vuln = document.add_table(rows=8, cols=3)
        table_vuln.autofit = False
        table_vuln.columns[0].width = Cm(0.35)
        table_vuln.columns[1].width = Cm(2.85)
        table_vuln.columns[-1].width = page_width
        for c in range(len(table_vuln.columns) - 1):
            table_vuln.columns[-1].width -= table_vuln.columns[c].width

        # COLOR
        # --------------------
        col_cells = table_vuln.columns[0].cells
        col_cells[0].merge(col_cells[7])
        color_fill = parse_xml(r'<w:shd {} w:fill="{}"/>'.format(
            nsdecls('w'),
            Config.colors()[vuln.level][1:]))
        col_cells[0]._tc.get_or_add_tcPr().append(color_fill)

        # TABLE HEADERS
        # --------------------
        hdr_cells = table_vuln.columns[1].cells
        hdr_cells[0].paragraphs[0].add_run('Description')
        hdr_cells[1].paragraphs[0].add_run('Impact')
        hdr_cells[2].paragraphs[0].add_run('Recommendation')
        hdr_cells[3].paragraphs[0].add_run('Details')
        hdr_cells[4].paragraphs[0].add_run('CVSS')
        hdr_cells[5].paragraphs[0].add_run('CVEs')
        hdr_cells[6].paragraphs[0].add_run('Family')
        hdr_cells[7].paragraphs[0].add_run('References')

        # FIELDS
        # --------------------
        cves = ", ".join(vuln.cves)
        cves = cves.upper() if cves != "" else "No CVE"

        cvss = str(vuln.cvss) if vuln.cvss != -1.0 else "No CVSS"

        txt_cells = table_vuln.columns[2].cells
        txt_cells[0].text = vuln.description
        txt_cells[1].text = vuln.impact
        txt_cells[2].text = vuln.solution
        txt_cells[3].text = vuln.insight
        txt_cells[4].text = cvss
        txt_cells[5].text = cves
        txt_cells[6].text = vuln.family
        txt_cells[7].text = vuln.references
        for c in txt_cells:
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

        # Apply styles
        # --------------------
        for h in hdr_cells:
            for p in h.paragraphs:
                p.style = doc_styles['OR-cell_bold']

        for c in txt_cells:
            for p in c.paragraphs:
                p.style = doc_styles['OR-cell']

        # VULN HOSTS
        # --------------------
        document.add_paragraph('Vulnerable hosts', style='OR-Vuln_hosts')

        # add coloumn for result per port and resize columns
        table_hosts = document.add_table(cols=5, rows=(len(vuln.hosts) + 1))

        table_hosts.columns[0].width = Cm(2.8)
        table_hosts.columns[1].width = Cm(3.0)
        table_hosts.columns[2].width = Cm(2.0)
        table_hosts.columns[3].width = Cm(2.0)
        table_hosts.columns[-1].width = page_width
        for c in range(len(table_hosts.columns) - 1):
            table_hosts.columns[-1].width -= table_hosts.columns[c].width

        # TABLE HEADERS
        # --------------------
        hdr_cells = table_hosts.rows[0].cells
        hdr_cells[0].paragraphs[0].add_run('IP')
        hdr_cells[1].paragraphs[0].add_run('Host name')
        hdr_cells[2].paragraphs[0].add_run('Port number')
        hdr_cells[3].paragraphs[0].add_run('Port protocol')
        hdr_cells[4].paragraphs[0].add_run('Port result')
        # FIELDS
        # --------------------
        for j, (host, port) in enumerate(vuln.hosts, 1):
            cells = table_hosts.rows[j].cells
            cells[0].text = host.ip
            cells[1].text = host.host_name if host.host_name else "-"
            if port and port is not None:
                cells[2].text = "-" if port.number == 0 else str(port.number)
                cells[3].text = port.protocol
                cells[4].text = port.result
            else:
                cells[2].text = "No port info"

        # Apply styles
        # --------------------
        for h in hdr_cells:
            for p in h.paragraphs:
                p.style = doc_styles['OR-cell_bold']
        for r in table_hosts.rows[1:]:
            for c in r.cells:
                for p in c.paragraphs:
                    p.style = doc_styles['OR-cell']

    document.save(output_file)
Esempio n. 10
0
 def __init__(self, path: str, caption=None, width=Twips(5000), style=None):
     self.path = path
     self.caption = caption
     self.width = width
     self.style = style