From b8759673b91e733c307698abdc0d5ed82fd7e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 4 Mar 2019 16:06:38 -0800 Subject: [PATCH] chg: Add i8n for pdfexport, without all the fonts in the main repo --- .gitmodules | 3 + pymisp/tools/pdf_fonts | 1 + pymisp/tools/reportlab_generator.py | 249 ++++++++++++++----- setup.py | 3 +- tests/reportlab_testfiles/japanese_test.json | 156 ++++++++++++ tests/test_reportlab.py | 56 ++++- 6 files changed, 408 insertions(+), 60 deletions(-) create mode 160000 pymisp/tools/pdf_fonts create mode 100644 tests/reportlab_testfiles/japanese_test.json diff --git a/.gitmodules b/.gitmodules index d0e9062..a1fe528 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "pymisp/data/misp-objects"] path = pymisp/data/misp-objects url = https://github.com/MISP/misp-objects +[submodule "pymisp/tools/pdf_fonts"] + path = pymisp/tools/pdf_fonts + url = https://github.com/MISP/pdf_fonts diff --git a/pymisp/tools/pdf_fonts b/pymisp/tools/pdf_fonts new file mode 160000 index 0000000..7ff2220 --- /dev/null +++ b/pymisp/tools/pdf_fonts @@ -0,0 +1 @@ +Subproject commit 7ff222022e6ad99e11a201f62d57da4bff1337ee diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 1cab9c2..b8bb403 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -8,6 +8,7 @@ import pprint from io import BytesIO import sys +import os if sys.version_info.major >= 3: from html import escape @@ -21,13 +22,16 @@ logger = logging.getLogger('pymisp') # Potentially not installed imports try: from reportlab.pdfgen import canvas - from reportlab.pdfbase.pdfmetrics import stringWidth + from reportlab.pdfbase.pdfmetrics import stringWidth, registerFont, registerFontFamily from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFInfo + from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib import colors from reportlab.lib.utils import ImageReader from reportlab.lib.pagesizes import A4 + from reportlab.lib.fonts import addMapping - from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image, Indenter + from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak, Spacer, Table, TableStyle, Flowable, Image, \ + Indenter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import mm @@ -125,7 +129,7 @@ class Flowable_Tag(Flowable): # Copy of pdfexport.py moduleconfig moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", - "Activate_galaxy_description"] + "Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"] # == Row colors of the table (alternating) == EVEN_COLOR = colors.whitesmoke @@ -138,7 +142,6 @@ LINE_COLOR = colors.lightslategray LINE_THICKNESS = 0.75 # == Columns colors, aligment, fonts, space, size, width, heights == -# FIRST_COL_FONT_COLOR = colors.darkslateblue # Test purposes FIRST_COL_FONT_COLOR = colors.HexColor("#333333") # Same as GUI FIRST_COL_FONT = 'Helvetica-Bold' FIRST_COL_ALIGNEMENT = TA_CENTER @@ -158,24 +161,23 @@ SMALL_COL2_ALIGMENT = TA_JUSTIFY EXPORT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' COL_WIDTHS = ['25%', '75%'] # colWidths='*' # Not documented but does exist -# COL_WIDTHS = ['20%', '80%'] # colWidths='*' # Not documented but does exist ROW_HEIGHT = 5 * mm # 4.5 * mm (a bit too short to allow vertical align TODO : Fix it) ROW_HEIGHT_FOR_TAGS = 4 * mm # 4.5 * mm (a bit too short to allow vertical align TODO : Fix it) # == Whole document margins and size == -PAGESIZE = A4 # (140 * mm, 216 * mm) # width, height +PAGESIZE = A4 # (140 * mm, 216 * mm) # width, height BASE_MARGIN = 5 * mm # Create a list here to specify each row separately -INDENT_SIZE = 6 * mm # The Indentation of attribute from object, etc. -INDENT_SIZE_HEADING = 3 * mm # The Indentation of attribute from object, etc. +INDENT_SIZE = 6 * mm # The Indentation of attribute from object, etc. +INDENT_SIZE_HEADING = 3 * mm # The Indentation of attribute from object, etc. # == Parameters for error handling for content too long to fit on a page == -FRAME_MAX_HEIGHT = 200*mm # 500 # Ad hoc value for a A4 page -FRAME_MAX_WIDTH = 145*mm - INDENT_SIZE # 356 +FRAME_MAX_HEIGHT = 200 * mm # 500 # Ad hoc value for a A4 page +FRAME_MAX_WIDTH = 145 * mm - INDENT_SIZE # 356 STR_TOO_LONG_WARNING = "
[Too long to fit on a single page. Cropped]" # == Parameters for error handling for image too big to fit on a page == -FRAME_PICTURE_MAX_HEIGHT = 200*mm # 195 * mm -FRAME_PICTURE_MAX_WIDTH = 145*mm - INDENT_SIZE # 88 * mm +FRAME_PICTURE_MAX_HEIGHT = 200 * mm # 195 * mm +FRAME_PICTURE_MAX_WIDTH = 145 * mm - INDENT_SIZE # 88 * mm # == Parameters for links management == LINK_TYPE = "link" # Name of the type that define 'good' links @@ -190,6 +192,7 @@ BAD_LINK_COLOR = 'red' LOW_THREAT_COLOR = 'green' MEDIUM_THREAT_COLOR = 'orange' HIGH_THREAT_COLOR = 'red' +EXTERNAL_ANALYSIS_PREFIX = "External analysis from an attribute : " # == Parameters for improvement of event's metadata == @@ -209,9 +212,9 @@ MISC_SIGHT_COLOR = 'orange' # == Parameters for galaxies == DO_SMALL_GALAXIES = True -FIRST_LEVEL_GALAXY_WIDTHS = ["15%","85%"] -SECOND_LEVEL_GALAXY_WIDTHS = ["20%","80%"] -CLUSTER_COLORS = [0] # or 1 +FIRST_LEVEL_GALAXY_WIDTHS = ["15%", "85%"] +SECOND_LEVEL_GALAXY_WIDTHS = ["20%", "80%"] +CLUSTER_COLORS = [0] # or 1 OFFSET = 1 ######################################################################## @@ -258,7 +261,8 @@ def uuid_to_url(baseurl, uuid): return baseurl + "events/view/" + uuid -def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None, galaxy_colors=False): +def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None, + galaxy_colors=False): ''' Given a list of flowables items (2D/list of list), creates a Table with styles. :param data: list of list of items (flowables is better) @@ -272,8 +276,8 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=No # rowHeights=ROW_HEIGHT if you want a fixed height. /!\ Problems with paragraphs that are spreading everywhere # Create styles and set parameters - alternate_colors_style = alternate_colors_style_generator(data,color_alternation, galaxy_colors) - lines_style = lines_style_generator(data,line_alternation) + alternate_colors_style = alternate_colors_style_generator(data, color_alternation, galaxy_colors) + lines_style = lines_style_generator(data, line_alternation) general_style = general_style_generator() # Make the table nicer @@ -310,7 +314,7 @@ def alternate_colors_style_generator(data, color_alternation, galaxy_colors=True # For each line, generate a tuple giving to a line a color for each in range(data_len): - if color_alternation[each%len(color_alternation)] % 2 == 0: + if color_alternation[each % len(color_alternation)] % 2 == 0: bg_color = EVEN_COLOR if not galaxy_colors else EVEN_COLOR_GALAXY else: bg_color = ODD_COLOR if not galaxy_colors else ODD_COLOR_GALAXY @@ -338,7 +342,7 @@ def lines_style_generator(data, line_alternation): # Last line lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR)) - elif line_alternation == [] : + elif line_alternation == []: # Do nothing return lines_list else: @@ -347,13 +351,13 @@ def lines_style_generator(data, line_alternation): # For each line, generate a tuple giving to a line a color for each in range(data_len): - if each == 0 or line_alternation[each%len(line_alternation)] != line_alternation[(each-1)%len(line_alternation)]: + if each == 0 or line_alternation[each % len(line_alternation)] != line_alternation[ + (each - 1) % len(line_alternation)]: lines_list.append(('LINEABOVE', (0, each), (-1, each), LINE_THICKNESS, LINE_COLOR)) # Last line lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR)) - return lines_list @@ -375,6 +379,36 @@ def general_style_generator(): return lines_list +def internationalize_font(): + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/tools" + + global FIRST_COL_FONT + global SECOND_COL_FONT + + ''' # Available fonts : + NotoSansCJKtc - DemiLight.ttf + NotoSansCJKtc - Regular.ttf + NotoSansCJKtc - Black.ttf + NotoSansCJKtc - Light.ttf + NotoSansCJKtc - Thin.ttf + NotoSansCJKtc - Bold.ttf + NotoSansCJKtc - Medium.ttf + ''' + + noto_bold = BASE_DIR + "/pdf_fonts/Noto_TTF/NotoSansCJKtc-Bold.ttf" + noto = BASE_DIR + "/pdf_fonts/Noto_TTF/NotoSansCJKtc-DemiLight.ttf" + + if os.path.isfile(noto_bold) and os.path.isfile(noto): + registerFont(TTFont("Noto", noto)) + registerFont(TTFont("Noto-bold", noto_bold)) + + FIRST_COL_FONT = 'Noto-bold' + SECOND_COL_FONT = 'Noto' + else: + logger.error( + "Trying to load a custom (internationalization) font, unable to access the file : " + noto_bold) + + def get_table_styles(): ''' Create and returns the two mains styles for the columns of the document. @@ -426,6 +460,7 @@ def get_clusters_table_styles(): return custom_body_style_col_1, custom_body_style_col_2 + ######################################################################## # Checks @@ -474,8 +509,8 @@ class Value_Formatter(): # ---------------------------------------------------------------------- ######################################################################## # General attribut formater - def get_col1_paragraph(self, dirty_string, do_small=False): - if do_small : + def get_col1_paragraph(self, dirty_string, do_small=False): + if do_small: return self.get_unoverflowable_paragraph(dirty_string, self.col1_small_style, do_small=do_small) return self.get_unoverflowable_paragraph(dirty_string, self.col1_style, do_small=do_small) @@ -494,13 +529,12 @@ class Value_Formatter(): else: sanitized_str = dirty_string - if curr_style is None : - if do_small : + if curr_style is None: + if do_small: curr_style = self.col2_small_style - else : + else: curr_style = self.col2_style - # Get the space that the paragraph needs to be printed w, h = Paragraph(sanitized_str, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) @@ -780,7 +814,8 @@ class Value_Formatter(): if is_safe_dict_attribute(misp_galaxy, item[1]): return self.get_unoverflowable_paragraph(safe_string(misp_galaxy[item[1]]) + " from " + safe_string(misp_galaxy[item[3]]) + ":" - + safe_string(misp_galaxy[item[4]]), do_escape_string=False, do_small=True) + + safe_string(misp_galaxy[item[4]]), do_escape_string=False, + do_small=True) return self.get_unoverflowable_paragraph(item[2], do_small=True) def get_galaxy_cluster_name_value(self, misp_cluster, do_small=False): @@ -790,19 +825,20 @@ class Value_Formatter(): if is_safe_dict_attribute(misp_cluster, item[1]): tmp_text += safe_string(misp_cluster[item[1]]) - #if is_safe_dict_attribute(misp_cluster, item[3]) : - # tmp_text += "
Source : " + misp_cluster[item[3]] + # if is_safe_dict_attribute(misp_cluster, item[3]) : + # tmp_text += "
Source : " + misp_cluster[item[3]] if is_safe_dict_attribute(misp_cluster, item[4]) and is_safe_dict_attribute(misp_cluster[item[4]], item[5]): tmp_text += "
Synonyms : " - for i, synonyme in enumerate(misp_cluster[item[4]][item[5]]) : - if i != 0 : + for i, synonyme in enumerate(misp_cluster[item[4]][item[5]]): + if i != 0: tmp_text += " / " tmp_text += safe_string(synonyme) return self.get_unoverflowable_paragraph(tmp_text, do_escape_string=False, do_small=do_small) return self.get_unoverflowable_paragraph(item[2], do_small=do_small) + class Event_Metadata(): # ---------------------------------------------------------------------- @@ -884,6 +920,10 @@ class Event_Metadata(): flowable_table.append(create_flowable_table_from_data(data)) + # Correlation + item = ["Related Events", 'RelatedEvent', "None"] + if is_safe_attribute_table(misp_event, item[1]) and is_in_config(self.config, 4): + flowable_table += self.get_correlation_values(misp_event, item) # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] @@ -894,6 +934,36 @@ class Event_Metadata(): return flowable_table + def create_reduced_flowable_table_from_event(self, misp_event): + ''' + Returns Table presenting a MISP event + :param misp_event: A misp event (complete or not) + :return: a table that can be added to a pdf + ''' + + data = [] + flowable_table = [] + + # Manual addition + # UUID + item = ["UUID", 'uuid', "None"] + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_value_link_to_event(misp_event, item)]) + + # Info + item = ["Info", 'info', "None"] + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_value_link_to_event(misp_event, item)]) + + # Timestamp + item = ["Event date", 'timestamp', "None"] + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_timestamp_value(misp_event, item)]) + + flowable_table.append(create_flowable_table_from_data(data)) + + return flowable_table + def create_flowable_description_from_event(self, misp_event): ''' Returns a Paragraph presenting a MISP event @@ -988,6 +1058,14 @@ class Event_Metadata(): text += " associated objects." + curr_attributes = Attributes(self.config, self.value_formatter) + tmp_text = curr_attributes.get_external_analysis(misp_event) + + if tmp_text != "": + text += "
" + text += tmp_text + text += "
" + ''' For more information on the event, please consult the rest of the document ''' @@ -998,6 +1076,33 @@ class Event_Metadata(): return Paragraph(text, description_style) + def get_correlation_values(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event correlated events + :param misp_event: A misp event with or without "RelatedEvent" attributes + :param item: as defined in class definition + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "RelatedEvent" + ''' + flowable_table = [] + + flowable_table.append(PageBreak()) + flowable_table.append(Paragraph("Related Events", self.sample_style_sheet['Heading3'])) + + if is_safe_attribute_table(misp_event, item[1]): + for i, evt in enumerate(getattr(misp_event, item[1])): + flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) + flowable_table.append( + Paragraph("Related Event #" + str(i + OFFSET), self.sample_style_sheet['Heading4'])) + flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) + + flowable_table += self.create_reduced_flowable_table_from_event(evt) + i += 1 + else: + return flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2])) + + return flowable_table + class Attributes(): @@ -1024,7 +1129,7 @@ class Attributes(): for item in getattr(misp_event, "Attribute"): # you can use a spacer instead of title to separate paragraph: flowable_table.append(Spacer(1, 5 * mm)) flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) - flowable_table.append(Paragraph("Attribute #" + str(i+OFFSET), self.sample_style_sheet['Heading4'])) + flowable_table.append(Paragraph("Attribute #" + str(i + OFFSET), self.sample_style_sheet['Heading4'])) flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) flowable_table += self.create_flowable_table_from_one_attribute(item) @@ -1098,10 +1203,9 @@ class Attributes(): flowable_table.append(create_flowable_table_from_data(data)) - # Galaxies item = ["Related Galaxies", 'Galaxy', "None"] - if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3) : + if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3): curr_Galaxy = Galaxy(self.config, self.value_formatter) flowable_table.append(Indenter(left=INDENT_SIZE)) flowable_table += curr_Galaxy.get_galaxy_value(misp_attribute, item) @@ -1109,6 +1213,27 @@ class Attributes(): return flowable_table + def get_external_analysis(self, misp_event): + ''' + Returns a string representing the list of external analysis comments of a misp event. + :param misp_event: A misp event + :return: a table of flowables + ''' + text = "" + + if is_safe_attribute_table(misp_event, "Attribute"): + # There is some attributes for this object + for attribute in getattr(misp_event, "Attribute"): + # If the current event is an external analysis and a comment + if is_safe_attribute(attribute, "value") and is_safe_attribute(attribute, + "category") and is_safe_attribute( + attribute, "type") and getattr(attribute, "category") == "External analysis" and getattr( + attribute, "type") == "comment": + # We add it to the description + text += "
" + EXTERNAL_ANALYSIS_PREFIX + safe_string(getattr(attribute, "value")) + + return text + class Tags(): @@ -1215,7 +1340,6 @@ class Sightings(): return answer_sighting - class Object(): # ---------------------------------------------------------------------- @@ -1244,7 +1368,7 @@ class Object(): for item in getattr(misp_event, "Object"): # you can use a spacer instead of title to separate paragraph: flowable_table.append(Spacer(1, 5 * mm)) flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) - flowable_table.append(Paragraph("Object #" + str(i+OFFSET), self.sample_style_sheet['Heading3'])) + flowable_table.append(Paragraph("Object #" + str(i + OFFSET), self.sample_style_sheet['Heading3'])) flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) flowable_table += self.create_flowable_table_from_one_object(item, config) i += 1 @@ -1254,7 +1378,6 @@ class Object(): return flowable_table - def create_flowable_table_from_one_object(self, misp_object, config=None): ''' Returns a table (flowable) representing the object @@ -1284,7 +1407,8 @@ class Object(): # Timestamp item = ["Object date", 'timestamp', "None"] - data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_timestamp_value(misp_object, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_timestamp_value(misp_object, item)]) # Transform list of value in a table data = [create_flowable_table_from_data(data)] @@ -1328,12 +1452,12 @@ class Galaxy(): # Galaxies # item = ["Related Galaxies", 'Galaxy', "None"] if is_safe_attribute_table(misp_event, item[1]) and is_in_config(self.config, 3): - galaxy_title = Paragraph(item[0], self.sample_style_sheet['Heading5']) + galaxy_title = Paragraph(safe_string(item[0]), self.sample_style_sheet['Heading5']) flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) flowable_table.append(galaxy_title) flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) flowable_table += self.create_flowable_table_from_galaxies(misp_event) - else : + else: flowable_table.append(self.value_formatter.get_unoverflowable_paragraph(item[2])) return flowable_table @@ -1348,7 +1472,8 @@ class Galaxy(): scheme_alternation = [] curr_color = 0 i = 0 - + small_title_style = ParagraphStyle(name='Column_1', parent=self.sample_style_sheet['Heading6'], + fontName=FIRST_COL_FONT, alignment=TA_LEFT) if is_safe_attribute_table(misp_event, "Galaxy"): # There is some galaxies for this object @@ -1356,8 +1481,8 @@ class Galaxy(): for curr_galaxy in getattr(misp_event, "Galaxy"): # For each galaxy of the misp object - txt_title = "Galaxy #" + str(i+OFFSET) + " - " + safe_string(curr_galaxy["name"]) - galaxy_title = Paragraph(txt_title, self.sample_style_sheet['Heading6']) + txt_title = "Galaxy #" + str(i + OFFSET) + " - " + safe_string(curr_galaxy["name"]) + galaxy_title = Paragraph(txt_title, small_title_style) flowable_table.append(Indenter(left=INDENT_SIZE_HEADING)) flowable_table.append(galaxy_title) flowable_table.append(Indenter(left=-INDENT_SIZE_HEADING)) @@ -1403,7 +1528,8 @@ class Galaxy(): item = ["Description", 'description', "None"] if is_safe_dict_attribute(misp_galaxy, item[1]): data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES), - self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]], do_small=DO_SMALL_GALAXIES)]) + self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]], + do_small=DO_SMALL_GALAXIES)]) nb_added_item += 1 flowable_table = [] @@ -1412,7 +1538,6 @@ class Galaxy(): return flowable_table, nb_added_item - class Galaxy_cluster(): # ---------------------------------------------------------------------- @@ -1435,26 +1560,29 @@ class Galaxy_cluster(): if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"): # There is some clusters for this object for i, curr_cluster in enumerate(misp_galaxy["GalaxyCluster"]): - # If title is needed : # galaxy_title = [Paragraph("Cluster #" + str(i), self.sample_style_sheet['Heading6'])] # data.append(galaxy_title) - item[0] = "Cluster #" + str(i + OFFSET) # For each cluster tmp_data = self.create_flowable_table_from_one_galaxy_cluster(curr_cluster) tmp_flowable_table = [] - tmp_flowable_table.append(create_flowable_table_from_data(tmp_data, col_w=SECOND_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS, line_alternation=[], galaxy_colors=True)) - data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES), tmp_flowable_table]) # Cluster #X - 3 lines + tmp_flowable_table.append(create_flowable_table_from_data(tmp_data, col_w=SECOND_LEVEL_GALAXY_WIDTHS, + color_alternation=CLUSTER_COLORS, + line_alternation=[], galaxy_colors=True)) + data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=DO_SMALL_GALAXIES), + tmp_flowable_table]) # Cluster #X - 3 lines else: # No galaxies for this object data = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster", do_small=DO_SMALL_GALAXIES)] flowable_table = [] - flowable_table.append(create_flowable_table_from_data(data, col_w=FIRST_LEVEL_GALAXY_WIDTHS, color_alternation = CLUSTER_COLORS, galaxy_colors=True)) + flowable_table.append( + create_flowable_table_from_data(data, col_w=FIRST_LEVEL_GALAXY_WIDTHS, color_alternation=CLUSTER_COLORS, + galaxy_colors=True)) return flowable_table @@ -1469,13 +1597,13 @@ class Galaxy_cluster(): # Name item = ["Name", 'name', "None"] data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=True), - self.value_formatter.get_galaxy_cluster_name_value(misp_cluster, do_small=True)]) + self.value_formatter.get_galaxy_cluster_name_value(misp_cluster, do_small=True)]) - if misp_cluster['value'] != misp_cluster['description'] : # Prevent name that are same as description + if misp_cluster['value'] != misp_cluster['description']: # Prevent name that are same as description # Description item = ["Description", 'description', "None"] data.append([self.value_formatter.get_col1_paragraph(item[0], do_small=True), - self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]], do_small=True)]) + self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]], do_small=True)]) # Refs ? # item = ["Description", 'description', "None"] @@ -1572,7 +1700,8 @@ def collect_parts(misp_event, config=None): curr_val_f = Value_Formatter(config, col1_style, col2_style, col1_small_style, col2_small_style) # Create stuff - title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], alignment=TA_CENTER) + title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], fontName=FIRST_COL_FONT, + alignment=TA_CENTER) title = curr_val_f.get_value_link_to_event(misp_event, ["Info", 'info', "None"], title_style, color=False) # Add all parts to final PDF flowables.append(title) @@ -1593,7 +1722,7 @@ def collect_parts(misp_event, config=None): flowables.append(subtitle) flowables += table_general_metainformation - if is_safe_attribute_table(misp_event, "Attribute") : + if is_safe_attribute_table(misp_event, "Attribute"): flowables.append(PageBreak()) event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) @@ -1601,7 +1730,7 @@ def collect_parts(misp_event, config=None): flowables.append(event_attributes_title) flowables += table_direct_attributes - if is_safe_attribute_table(misp_event, "Object") : + if is_safe_attribute_table(misp_event, "Object"): flowables.append(PageBreak()) event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) @@ -1647,6 +1776,10 @@ def convert_event_in_pdf_buffer(misp_event, config=None): # Create a document buffer pdf_buffer = BytesIO() + if is_in_config(config, 5): # We want internationalization + logger.info("Internationalization of fonts during pdf export activated. CJK-fonts supported.") + internationalize_font() + # DEBUG / TO DELETE : curr_document = SimpleDocTemplate('myfile.pdf') curr_document = SimpleDocTemplate(pdf_buffer, pagesize=PAGESIZE, diff --git a/setup.py b/setup.py index 27ea70f..ab6e4aa 100644 --- a/setup.py +++ b/setup.py @@ -59,5 +59,6 @@ setup( 'data/misp-objects/schema_objects.json', 'data/misp-objects/schema_relationships.json', 'data/misp-objects/objects/*/definition.json', - 'data/misp-objects/relationships/definition.json']}, + 'data/misp-objects/relationships/definition.json', + 'tools/pdf_fonts/Noto_TTF/*']}, ) diff --git a/tests/reportlab_testfiles/japanese_test.json b/tests/reportlab_testfiles/japanese_test.json new file mode 100644 index 0000000..13764fa --- /dev/null +++ b/tests/reportlab_testfiles/japanese_test.json @@ -0,0 +1,156 @@ +{ + "Event": { + "id": "1208", + "orgc_id": "1", + "org_id": "1", + "date": "2019-03-04", + "threat_level_id": "2", + "info": "Japanese Lorem Ipsum 条イ音態ぞゃご法説イシ技", + "published": false, + "uuid": "5c7cdc3b-2f40-4dca-8200-276c0a00020f", + "attribute_count": "1", + "analysis": "1", + "timestamp": "1551686770", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "0", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "event_creator_email": "admin@admin.test 条イ音態ぞゃご法説イシ技", + "Org": { + "id": "1", + "name": "ORGNAME 条イ音態ぞゃご法説イシ技", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "1", + "name": "ORGNAME 条イ音態ぞゃご法説イシ技", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Attribute": [ + { + "id": "242461", + "type": "text", + "category": "Antivirus detection", + "to_ids": false, + "uuid": "5c7cdc72-c9a4-4075-acb5-0e1b0a00020f", + "event_id": "1208", + "distribution": "5", + "timestamp": "1551686770", + "comment": "This is a contextual comment 条イ音態ぞゃご法説イシ技", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "\u6761\u30a4\u97f3\u614b\u305e\u3083\u3054\u6cd5\u8aac\u30a4\u30b7\u6280\u4f4d\u30fc\u30a4\u80fd\u76ee\u9580\u521d\u30f1\u5909\u6574\u3054\u3052\u5c55\u540c\u30bd\u30d8\u590954\u62ab\u30c8\u3093\u30d5\u8a18\u66ae\u30ce\u30e9\u6c17\u6708\u8cea\u3093\u3092\u554f\u63b2\u5f8c\u3076\u30e9\u306a\u8ee2\u6b69\u30ea\u30e4\u30f2\u88fd\u51fa\u30ed\u30a2\u7d42\u4ed6\u8f38\u3084\u3057\u3002\u672a\u554f\u30bb\u30b5\u30ea\u5357\u6587\u306f\u3076\u66ae\u6b69\u30a6\u30b1\u30ef\u53ef\u9ad8\u3084\u793e\u4f1a\u30a2\u5c53\u91d1\u30f2\u30cd\u902374\u9023\u3074\u53ef\u6b73\u30ce\u30b3\u30a8\u30bb\u73fe\u5e02\u305c\u304b\u3072\u308b\u54e1\u89e3\u5916\u55b6\u4f0a\u5e2b\u53df\u305a\u3002\u969b\u304e\u3071\u3055\u8077\u6587\u308c\u304e\u30ec\u30c3\u4e8b\u8457\u5834\u307b\u3086\u3066\u70b9\u8cbb\u307e\u30b9\u3055\u4ef0\u62d3\u753a\u30e2\u30eb\u30cf\u30df\u534a\u539f\u30ec\u4f1a\u5e73\u30cf\u30cc\u65c5\u56f2\u30b5\u30d2\u30a2\u30b1\u587e\u4e2d\u3059\u9928\u8a9e\u307f\u611b8\u4f1a\u304f\u3042\u305f\u7a3f27\u8a13\u3059\u30e9\u305e\u308a\u3002\r\n\r\n\u9014\u3053\u306f\u8abf\u53ef\u30cd\u30ab\u97628\u7981\u3086\u3060\u3044\u5e74\u4eba\u30d5\u5faa\u90e8\u306e\u3076\u30f3\u672a\u4e8b\u30c1\u30a8\u30f2\u4f9b\u671d\u30f3\u3065\u59cb\u5207\u3066\u30b9\u30af\u3061\u6458\u4e0d\u3061\u3086\u305a\u5de8\u7121\u30d5\u30db\u9686\u5177\u30e9\u30b9\u30e6\u5ea7\u61f8\u308f\u4eac\u7389\u306b\u3079\u3075\u3089\u6307\u6a80\u30a6\u30b3\u30cd\u6d17\u9762\u30b5\u30c8\u30bb\u534170\u5347\u5fcd\u66c7\u690e\u307b\u300236\u5eb7\u3070\u3050\u8ca1\u6709\u793e\u30ea\u8ad6\u5f92\u30e2\u30b1\u30aa\u5854\u98db\u8868\u5fdc\u30c8\u30db\u4e0a\u5bfe\u5404\u30d5\u962a\u8fce\u30db\u30bf\u6599\u4e07\u306e\u307d\u8981\u52a9\u63a8\u308d\u304a\u3071\u5408\u584a\u30db\u8005\u653f\u30ec\u305f\u3052\u3048\u6d3b\u7d19\u30aa\u30ab\u5374\u80b2\u304b\u6c37\u5348\u4ef0\u5de3\u6bc5\u3079\u30af\u3072\u3002\r\n\r\n\u516d\u30aa\u62c9\u885d\u30b7\u30e0\u30bd\u30c6\u624b\u76f8\u30ec\u990a\u53e4\u30e6\u30cb\u30f1\u4e2d\u63d0\u30c9\u3060\u4e0a\u66ff\u304f\u307e\u30c9\u4efb\u8a00\u30cc\u652f\u7279\u30ca\u30ed\u30a2\u30df\u653f\u8aad\u304f\u306b\u3084\u3048\u5bb9\u826f\u3052\u308b\u305e\u3067\u5e7b\u901f\u30fc\u9178\u7acb\u308f\u30f3\u70b9\u53f7\u30b5\u30e2\u4e88\u969b\u3082\u3080\u751f\u6cbf\u3054\u3053\u30e9\u3057\u7d9a\u8a18\u3093\u307c\u30af\u305b\u60c5\u61b2\u63f4\u5091\u307c\u305c\u3002\u53d7\u30c8\u30db\u65e593\u5b9f\u30b7\u30eb\u9000\u4e2d\u30eb\u30d2\u30e6\u30b5\u5c0f\u60d1\u30d5\u30c9\u3042\u5712\u7dbf\u30e2\u30b1\u9707\u4ea4\u5065\u3050\u3086\u3081\u3066\u8aad\u65e53\u5e38\u79fb\u9700\u667a\u30db\u30e4\u30d2\u52b9\u53e4\u4e26\u52d9\u9ed2\u7b46\u62f3\u3050\u3086\u3002\u5357\u30c3\u4f5c3\u547c\u305f\u7528\u52d9\u5a18\u30db\u82b8\u672c\u3068\u304b\u3080\u30af\u4efb\u5199\u30cd\u30a6\u30b1\u6700\u66f4\u304d\u77f3\u7981\u3076\u30c3\u65ec\u88d5\u5b57\u307d\u6226\u6a2a\u30f2\u602765\u4e57\u304a\u6a29\u518d\u6676\u90ca\u9673\u308b\u3066\u3002\r\n\r\n\u7a7a\u30db\u30c4\u30b3\u7881\u8f09\u30a4\u30c4\u30ec\u518d\u5e74\u30e0\u30cb\u30e2\u4e8c\u96a0\u30f1\u30d8\u30a4\u901f\u66f4\u6bd2\u3068\u3073\u30f3\u65706\u9078\u3084\u305e\u3080\u7279\u5168\u304b\u30fc\u3080\u6d6e\u666f\u30b9\u30c4\u82e5\u4e8b\u518d\u30bb\u30e4\u66f4\u8a2d\u539f\u3083\u3048\u306e\u3002\u4e92\u3064\u308c\u554640\u5411\u306b\u55b6\u6e29\u5909\u30b7\u7d22\u5199\u307f\u30b9\u6c34\u4fee\u30b5\u30d8\u30cd\u512a\u5e745\u53f0\u306f\u3061\u8fba\u8302\u30c9\u30af\u5b88\u969b\u74b0\u3070\u3044\u30f3\u5b58\u72b6\u30a4\u30e2\u30d5\u30db\u516c\u4e21\u3064\u3069\u6c17\u7d44\u305b\u3088\u5b58\u6cc1\u30cf\u56f0\u4e2d\u3086\u3067\u307f\u5eb7\u96ea\u30c8\u30cc\u30eb\u6b4c\u4eee\u3057\u3055\u305e\u3002\u56fd\u611b\u30d5\u30e8\u30b3\u30cb\u712139\u7d50\u30cb\u30cd\u30e8\u30b7\u592787\u9593\u3055\u30ec\u3058\u3083\u65e5\u793a\u30b5\u30b3\u54c1\u65c5\u307e\u3081\u3066\u983c\u5236\u3068\u5143\u89b3\u305d\u3073\u3088\u30f3\u6d88\u73fe\u30af\u3079\u60d1\u8a71\u30c3\u3086\u8996\u66ae\u660e\u4e00\u30e8\u30e6\u30ed\u4fdd\u9662\u696d\u7406\u304c\u305e\u3050\u3002\r\n\r\n\u76df\u30eb\u4e00\u5186\u3052\u8981\u4ed8\u3046\u535a\u5f53\u308d\u3051\u3065\u6d3b\u7d44\u30ed\u671d\u52a0\u30db\u30e0\u81ea\u6a5f\u30ec\u898b\u65ad\u3060\u6587\u5185\u30a4\u578b\u592e\u30f1\u6bba\u5927\u304d\u305b\u3050\u5909\u5149\u3075\u3048\u3051\u672c\u826f\u3087\u3046\u5927\u6cbb\u898b\u30ad\u30b7\u901a\u7dca\u306f\u3042\u67087\u9078\u3058\u5224\u65e5\u30bf\u30a6\u30aa\u30cb\u5916\u805e\u30cf\u30eb\u30c6\u30e2\u897f\u8ca0\u3064\u307e\u304d\u3002\u7a4d\u805e\u308d\u90a3\u8a18\u307d\u653f57\u5473\u5f62\u5869\u62b591\u7269\u4e8b\u30ce\u30d5\u30d8\u30eb\u8005\u6d41\u30bf\u30ef\u30e8\u30d5\u8853\u635c\u904e\u308a\u3085\u305a\u308c\u9686\u7b11\u3080\u3071\u304d\u98f2\u88ab\u30f1\u30d5\u5149\u51b7\u52e7\u304d\u3093\u3002\u7881\u8a18\u8f09\u30cb\u6025\u5272\u3067\u305f\u884c\u9023\u305e\u3052\u30eb\u3073\u7d22\u962a\u5c06\u30e8\u9332\u85e44\u8868\u90e8\u30aa\u30ef\u30df\u8cfc\u540c\u3060\u3076\u304d\u3080\u5fb395\u5b99\u59d4\u3085\u3089\u3002\r\n\r\n\u80fd\u30b7\u30a8\u30c6\u77e5\u610f\u5929\u7d9a\u3079\u7a3f\u79d1\u304c\u3083\u3052\u5b50\u4f5c\u30d5\u30c8\u5f69\u6fc0\u30ea\u30ad\u30a8\u592b\u515a\u30ef\u30cb\u30b9\u30cd\u544a\u5c0f\u304a\u30a4\u305b\u8239\u540d\u7a3f\u3063\u6771\u4e88\u30eb\u3058\u3067\u8003\u9577\u307f\u306f\u65b0\u7e3e\u3076\u308a\u958b6\u6bba\u30eb\u3088\u3063\u3067\u533b\u62ab\u6691\u68da\u8c46\u3048\u3002\u56fd\u307d\u3076\u30af\u3056\u80017\u518d\u30cb\u30de\u610f\u7d9a\u3071\u3080\u590975\u8a55\u30e4\u30cb\u5c0e\u540d\u30ad\u30a2\u30c1\u4ed6\u6226\u82b1\u306a\u3069\u7e045\u5404\u30d5\u5e73\u5f69\u691c\u3074\u3053\u6c34\u91d1\u3075\u3051\u5354\u6b21\u7fd2\u30c6\u30ad\u30b7\u30e1\u8a66\u4e94\u8b70\u3058\u3002\u76ee\u30c3\u307c\u30af\u3089\u624b\u95a2\u30e4\u640d\u52e2\u30b7\u30a8\u30b1\u554f\u6620\u30c1\u5ea7\u5272\u56de\u306d\u3044\u3065\u3072\u58eb\u7dda\u30b5\u624b\u6982\u30c3\u5e73\u6e2f\u3074\u30d5\u3065\u4eac\u6642\u30ca\u554f\u6b62\u52d5\u3076\u3048\u5bfe\u4ef2\u30c8\u30ec\u3002\r\n\r\n\u4eac\u30e6\u30cb\u65e5\u5f85\u5b89\u3075\u9032\u5f37\u307e\u3063\u56f2\u9023\u30ca\u4e3b\u751f\u3093\u5c02\u7a42\u30c1\u30d5\u30b9\u30e6\u66428\u597d\u307c\u4e00\u57cb\u30c9\u4e2d\u81ea\u6cd5\u4f1a\u7c73\u3067\u8cea\u7121\u308d\u3081\u308b\u3084\u5f85\u63d0\u5916\u3088\u3076\u30c9\u3002\u9aa8\u30e2\u30de\u30c6\u5bb9\u82b8\u308d\u591a\u7de8\u3086\u304b\u30a4\u3072\u500b\u81f4\u30e1\u5317\u5831\u30de\u5e74\u884c\u30fc\u3075\u30d5\u3080\u8db31\u6606\u30ed\u30a2\u5348\u5915\u30d8\u30ce\u4f8b\u6848\u30c8\u6297\u82b8\u5730\u30e2\u30b5\u30bd\u8981\u8aad\u305e\u306d\u304f\u3082\u66ae8\u6e96\u8b66\u59cb\u306a\u305a\u3042\u3002\u68a8\u304c\u308c\u5831\u92ad\u3054\u3086\u30ec\u5bfe\u7d4c\u30e0\u30ce\u5c4b\u5bfe\u30df\u30e0\u30c1\u653f\u76f8\u3072\u30a4\u3079\u304a\u78ba\u63a2\u5408\u304d\u5b58\u677e\u30c6\u4eca6\u6e08\u306a\u3063\u6e1b\u5fc5\u30e6\u30cd\u6a2a\u753b\u7af9\u691c\u65b0\u899a\u6fc0\u305d\u305c\u307c\u304e\u3002\r\n\r\n\u66f4\u6b664\u65ad\u884c\u3061\u305d\u3067\u305c\u7a3f\u990a\u30af\u304c\u3070\u3092\u611b6\u7121\u30c8\u30ec\u30cd\u5ca9\u6d88\u308c\u3089\u307b\u592b\u5883\u308c\u3084\u305d\u3052\u6761\u6d45\u307d\u305d\u306e\u304a\u8df3\u6539\u30e9\u3073\u3056\u5e744\u6642\u30ec\u30f1\u9580\u5265\u3066\u30a4\u305b\u3085\u8457\u5f35\u30ad\u6b21\u5584\u306a\u304b\u3072\u3064\u4e0a\u4e1e\u4e91\u3092\u3002\u5408\u30cd\u30ec\u6027\u65ad\u30ea\u30d8\u4e00\u5c0f\u30ea\u30e0\u5168\u5bfe\u6ca1\u30cc\u5883\u969b\u3063\u30c9\u304e\u56fd\u6297\u672c\u8edf\u307c\u6cd5\u4eca\u56f3\u5e2b\u3063\u3058\u304f\u3051\u5b50\u6c34\u30ef\u30de\u77e5\u5468\u30cf\u6642\u8a18\u30b9\u30e4\u7cfb\u51fa\u307f\u6728\u5354\u3088\u30d5\u3068\u56e3\u5c5e\u305c\u30a4\u5831\u508d\u3088\u307e\u3052\u3069\u3002\u4eca\u30a8\u6765\u90ce\u30d5\u5199\u541b\u3089\u308b\u534137\u9593\u30ec\u7cd6\u7dda\u9055\u30e6\u30ea\u30b3\u8f09\u672c\u3067\u307d\u30eb\u63d0\u6607\u3058\u305b\u753047\u898b\u3044\u30d5\u3065\u707d\u5fc5\u30f2\u30c1\u30ed\u30b1\u89e3\u73b2\u3068\u3086\u308b\u3002\r\n\r\n\u6a29\u53d6\u3084\u884c\u5d8b\u3078\u3074\u8abf\u7a7a\u3088\u5f15\u5ea7\u30fc\u8fd4\u6cbb\u305f\u3076\u30c8\u305c\u5d0e\u6b8b\u826f\u30ea\u671f\u5c55\u3067\u307e\u307d\u535a\u7121\u3079\u307f\u308d\u308b\u7ffc\u6a21\u3086\u7d9a\u96f2\u958b\u61f8\u3078\u6b6610\u88dc\u5c5e\u30af\u3055\u30c9\u30f3\u653f\u6295\u30cc\u30af\u30b5\u90fd\u660e\u7a1a\u85e4\u5f79\u3065\u3002\u4f4f\u30d2\u30b5\u5fc535\u5e02\u7559\u30bb\u30cc\u30a2\u5f53\u4f1a\u5cf6\u30c1\u30de\u30bf\u30ab\u591529\u88dc\u4e0a\u66ae\u3069\u3076\u5104\u591a\u30b1\u30ce\u8457\u5973\u30b5\u50be\u7a74\u30e4\u30d5\u30e2\u7b2c\u793e\u3088\u30b9\u7a3f\u6020\u6b8a\u6edd\u307f\u3084\u3068\u3055\u3002\u7b54\u30eb\u30f1\u30a2\u30df\u66f4\u6d77\u304d\u3060\u3085\u30b9\u6163\u6ede\u30a2\u30b5\u30a4\u30e0\u6d3b\u6c60\u30a4\u30d8\u30db\u523a6\u707d\u30bb\u7f8e\u5238\u30ec\u9023\u76df\u30cf\u30bf\u30ca\u30f2\u6cbb\u6f14\u30ed\u30ab\u30a6\u5929\u60c5\u3048\u307d\u3057\u8eab\u6539\u305b\u3053\u304f\u30eb\u53d7\u8fba\u30a6\u30cc\u30b9\u30b1\u6027\u8853\u3060\u307d\u3044\u6cbb\u544a\u30cc\u30cb\u30a8\u82b8\u65b0\u9000\u5236\u95a2\u3056\u306b\u305f\u3080\u3002\r\n\r\n\u8fbc\u4ed5\u91d1\u30de\u30a4\u30c6\u969b3\u5c5e\u30b5\u30df\u30cb\u7981\u9762\u305c\u30b9\u3063\u5fc3\u6750\u5e33\u3083\u30b9\u5148\u5dde\u3064\u636e\u4f9b\u30ad\u30bf\u30c8\u6587\u843d\u3085\u307f\u725f\u8ee2\u3072\u56f3\u7a0e\u30af\u30bd\u30d2\u539f\u6b62\u30f2\u30e9\u66f4\u540c\u5343\u3075\u3087\u307f\u50249\u6765\u696d\u7530\u6d88\u4e0d\u7ba1\u304a\u30eb\u3002\u8ee2\u3069\u30ea\u3042\u5f3e\u5fd8\u30cd\u30de\u6797\u5e73\u3051\u3058\u6c17\u6848\u30e8\u30e1\u5206\u533a\u990a\u3044\u307c\u3080\u691c\u5bc4\u3069\u82e66\u5927\u30b3\u30cd\u53f2\u7279\u30b9\u30af\u30e8\u6c5f\u8fbc\u30b9\u30af\u8a73\u4f50\u3051\u304f\u66ae\u4fc3\u30c4\u30e1\u30e2\u8ac7\u9762\u304c\u30c3\u3087\u8f0994\u96e3\u533b\u5bb9\u4fdd\u7403\u307b\u30025\u5fc5\u30a4\u984d\u5c5e\u30ec\u30b5\u30c8\u30b7\u88ab\u7981\u30b3\u30de\u30bb\u30e1\u554f\u5149\u30b7\u5ec9\u4e21\u306a\u8457\u5e02\u30e8\u8cc3\u9662\u30d5\u306f\u3051\u5199\u6b7b\u30eb\u3053\u672c\u5fc5\u3082\u3057\u5e38\u8b77\u30d2\u30ce\u7834\u5c5e\u7dda\u5348\u3048\u308b\u3002", + "Galaxy": [], + "ShadowAttribute": [] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [ + { + "id": "10", + "uuid": "fb5a36c0-1707-11e8-81f5-d732b22a4982", + "name": "Enterprise Attack 条イ音態ぞゃご法説イシ技 - Course of Action", + "type": "mitre-enterprise- 条イ音態ぞゃご法説イシ技 attack-course-of-action", + "description": "ATT&CK 条イ音態ぞゃご法説イシ技 Mitigation", + "version": "5", + "icon": "chain", + "namespace": "deprecated", + "GalaxyCluster": [ + { + "id": "1524", + "collection_uuid": "95c29444-49f9-49f7-8b20-bcd68d8fcaa6", + "type": "mitre-enterprise- 条イ音態ぞゃご法説イシ技attack-course-of-action", + "value": "AppCert DLLs 条イ音態ぞゃご法説イシ技 Mitigation - T1182", + "tag_name": "misp-galaxy:mitre-e 条イ音態ぞゃご法説イシ技nterprise-attack-course-of-action=\"AppCert DLLs Mitigation - T1182\" 条イ音態ぞゃご法説イシ技", + "description": "Identify and block 条イ音態ぞゃご法説イシ技 potentially malicious software that may be executed through AppCert DLLs by using whitelisting (Citation: Beechey 2010) tools, like AppLocker, (Citation: Windows Commands JPCERT) (Citation: NSA MS AppLocker) that are capable of auditing and\/or blocking unknown DLLs.", + "galaxy_id": "10", + "source": "https:\/\/git条イ音態ぞゃご法説イシ技hub.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "5", + "uuid": "", + "tag_id": "730", + "meta": { + "external_id": [ + "T1182" + ] + } + } + ] + }, + { + "id": "30", + "uuid": "1023f364-7831-11e7-8318-43b5531983ab", + "name": "Intrusion Set", + "type": "mitre-intrusion-set", + "description": "Name of ATT&CK Group", + "version": "8", + "icon": "user-secret", + "namespace": "mitre-attack", + "GalaxyCluster": [ + { + "id": "4015", + "collection_uuid": "247cb30b-955f-42eb-97a5-a89fef69341e", + "type": "mitre-intrusion-set", + "value": "APT32 - G0050", + "tag_name": "misp-galaxy:mitre-intrusion-set=\"APT32 - G0050\"", + "description": "[APT32](https:\/\/attack.mitre.org\/groups\/G0050) is a threat group that has been active since at least 2014. The group has targeted multiple private sector industries as well as with foreign governments, dissidents, and journalists with a strong focus on Southeast Asian countries like Vietnam, Phillipines, Laos, and Cambodia. They have extensively used strategic web compromises to compromise victims. \nThe group is believed to be Vietnam-based. (Citation: FireEye APT32 May 2017) (Citation: Volexity OceanLotus Nov 2017) (Citation: ESET OceanLotus)", + "galaxy_id": "30", + "source": "https:\/\/github.com\/mitre\/cti", + "authors": [ + "MITRE" + ], + "version": "12", + "uuid": "", + "tag_id": "731", + "meta": { + "external_id": [ + "G0050" + ], + "refs": [ + "https:\/\/attack.mitre.org\/groups\/G0050", + "https:\/\/www.fireeye.com\/blog\/threat-research\/2017\/05\/cyber-espionage-apt32.html", + "https:\/\/www.volexity.com\/blog\/2017\/11\/06\/oceanlotus-blossoms-mass-digital-surveillance-and-exploitation-of-asean-nations-the-media-human-rights-and-civil-society\/", + "https:\/\/www.welivesecurity.com\/2018\/03\/13\/oceanlotus-ships-new-backdoor\/" + ], + "synonyms": [ + "APT32", + "OceanLotus Group", + "APT-C-00" + ] + } + } + ] + } + ], + "Object": [], + "Tag": [ + { + "id": "730", + "name": "misp-galaxy:mitre-enterprise条イ音態ぞゃご法説イシ技-attack-course-of-action=\"AppCert DLLs Mitigation - T1182\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "731", + "name": "misp-galaxy:mitre-intrusion-条イ音態ぞゃご法説イシ技set=\"APT32 - G0050\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } +} \ No newline at end of file diff --git a/tests/test_reportlab.py b/tests/test_reportlab.py index 6ceaa92..e6afe01 100644 --- a/tests/test_reportlab.py +++ b/tests/test_reportlab.py @@ -33,7 +33,7 @@ class TestMISPEvent(unittest.TestCase): self.storage_folder = self.root + "reportlab_testoutputs/" self.storage_image_folder = self.root + "reportlab_test_image_outputs/" self.moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", - "Activate_galaxy_description"] + "Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"] def init_event(self): @@ -244,6 +244,58 @@ class TestMISPEvent(unittest.TestCase): reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), self.storage_folder + "galaxy_1.pdf") + def test_related_events(self): + if self.check_python_2(): + self.assertTrue(True) + else: + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True + config[self.moduleconfig[3]] = True + config[self.moduleconfig[4]] = True + + self.init_event() + self.mispevent.load_file(self.test_folder + 'galaxy_1.json') + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "related_events.pdf") + + def test_related_events_too_simple(self): + if self.check_python_2(): + self.assertTrue(True) + else: + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True + config[self.moduleconfig[3]] = True + config[self.moduleconfig[4]] = True + + self.init_event() + self.mispevent.load_file(self.test_folder + 'to_delete1.json') + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "related_events_no_related.pdf") + + def test_utf(self): + if self.check_python_2(): + self.assertTrue(True) + else: + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True + config[self.moduleconfig[3]] = True + config[self.moduleconfig[4]] = True + config[self.moduleconfig[5]] = True + + self.init_event() + self.mispevent.load_file(self.test_folder + 'japanese_test.json') + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "japanese_test.pdf") + def test_batch_image_events(self): # Test case ONLY for manual testing. Needs to download a full list of image events ! @@ -315,6 +367,8 @@ class TestMISPEvent(unittest.TestCase): config[self.moduleconfig[1]] = "My Wonderful CERT" config[self.moduleconfig[2]] = True config[self.moduleconfig[3]] = True + config[self.moduleconfig[4]] = True + config[self.moduleconfig[5]] = True file_nb = str(len(os.listdir(self.test_batch_folder)))