From 79e66363d23e643c842c746cbd3211d550bf4c5c Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 15:14:52 +0100 Subject: [PATCH] fix: [reportlab] working clusters and galaxies. Not nice however --- pymisp/tools/reportlab_generator.py | 317 +++++++++++++++++----------- 1 file changed, 192 insertions(+), 125 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index eb5f6a7..cd418e9 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -148,6 +148,7 @@ TEXT_FONT_SIZE = 8 LEADING_SPACE = 7 EXPORT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' COL_WIDTHS = ['30%', '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) @@ -234,7 +235,7 @@ def uuid_to_url(baseurl, uuid): return baseurl + "events/view/" + uuid -def create_flowable_table_from_data(data, col_w=COL_WIDTHS): +def create_flowable_table_from_data(data, col_w=COL_WIDTHS, color_alternation=None, line_alternation=None): ''' 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) @@ -248,8 +249,8 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS): # 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) - lines_style = lines_style_generator(data) + alternate_colors_style = alternate_colors_style_generator(data,color_alternation) + lines_style = lines_style_generator(data,line_alternation) general_style = general_style_generator() # Make the table nicer @@ -258,11 +259,13 @@ def create_flowable_table_from_data(data, col_w=COL_WIDTHS): return curr_table -def alternate_colors_style_generator(data): +def alternate_colors_style_generator(data, color_alternation): ''' Create a style, applicable on a table that will be built with parameter's data, with alternated background color for each line. Modified from : https://gist.github.com/chadcooper/5798392 + :param color_alternation: Allow to control the color scheme. e.g. [0,0,0,1,1,0 ... will produce 3 lines of a color, + 2 lines of another, 1 of the first one ... :param data: list of list of items (2D table) to be displayed in the pdf :return: A list of 'BACKGROUND' properties, usable in a TableStyle, with alternated colours ''' @@ -270,33 +273,60 @@ def alternate_colors_style_generator(data): data_len = len(data) color_list = [] - # For each line, generate a tuple giving to a line a color - for each in range(data_len): - if each % 2 == 0: - bg_color = EVEN_COLOR - else: - bg_color = ODD_COLOR - color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) + if color_alternation is None: + # For each line, generate a tuple giving to a line a color + for each in range(data_len): + if each % 2 == 0: + bg_color = EVEN_COLOR + else: + bg_color = ODD_COLOR + color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) + else: + if data_len > len(color_alternation) : + logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + + # 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: + bg_color = EVEN_COLOR + else: + bg_color = ODD_COLOR + color_list.append(('BACKGROUND', (0, each), (-1, each), bg_color)) return color_list -def lines_style_generator(data): +def lines_style_generator(data, line_alternation): ''' Create a style, applicable on a table that will be built with parameter's data, that draw colored lines above and below each line of the table + :param line_alternation: Allow to control the color scheme. e.g. [0,0,0,1,1,0 ... will produce with a line up it, + 2 lines without, 1 of the first one ... :param data: list of list of items (2D table) to be displayed in the pdf :return: A list of 'LINE****' properties, usable in a TableStyle, that are drawing lines ''' data_len = len(data) lines_list = [] - # For each line, generate a tuple giving to a line a color - for each in range(data_len): - lines_list.append(('LINEABOVE', (0, each), (-1, each), LINE_THICKNESS, LINE_COLOR)) + if line_alternation is None: + # For each line, generate a tuple giving to a line a color + for each in range(data_len): + 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)) + else: + if data_len > len(line_alternation) : + logger.warning("Line alternation for PDF display isn't correctly set. Looping on given values only.") + + # 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)]: + 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)) - # Last line - lines_list.append(('LINEBELOW', (0, len(data) - 1), (-1, len(data) - 1), LINE_THICKNESS, LINE_COLOR)) return lines_list @@ -366,9 +396,11 @@ def is_safe_attribute_table(curr_object, attribute_name): return hasattr(curr_object, attribute_name) and getattr(curr_object, attribute_name) is not None and getattr( curr_object, attribute_name) != [] + def is_in_config(config, index): return config is not None and moduleconfig[index] in config + ######################################################################## # Functions grouped by misp object type @@ -379,6 +411,7 @@ class Value_Formatter(): ["Name to be print in the pdf", "json property access name", " Name to be display if no values found in the misp_event"] ''' + # ---------------------------------------------------------------------- def __init__(self, config, col1_style, col2_style): self.config = config @@ -391,7 +424,7 @@ class Value_Formatter(): def get_col1_paragraph(self, dirty_string): return self.get_unoverflowable_paragraph(dirty_string, self.col1_style) - def get_unoverflowable_paragraph(self, dirty_string, curr_style = None, do_escape_string=True): + def get_unoverflowable_paragraph(self, dirty_string, curr_style=None, do_escape_string=True): ''' Create a paragraph that can fit on a cell displayed one page maximum. This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) @@ -425,7 +458,8 @@ class Value_Formatter(): while (w > FRAME_MAX_WIDTH or h > FRAME_MAX_HEIGHT) and i < MAX_ITERATION: i += 1 limited_string = sanitized_str[:max_carac_amount] # .replace("\n", "").replace("\r", "") - w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, FRAME_MAX_HEIGHT) + w, h = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style).wrap(FRAME_MAX_WIDTH, + FRAME_MAX_HEIGHT) max_carac_amount = int(max_carac_amount / 2) if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: @@ -436,8 +470,7 @@ class Value_Formatter(): return answer_paragraph - - def get_value_link_to_event(self, misp_event, item, curr_style = None, color=True): + def get_value_link_to_event(self, misp_event, item, curr_style=None, color=True): ''' Returns a flowable paragraph to add to the pdf given the misp_event uuid, with or without link :param color: Boolean to give a color or not to the generate link (good link color) @@ -456,7 +489,7 @@ class Value_Formatter(): # It has the requested attribute .. building upon it. # Does misp_object has an uuid and do we know the baseurl ? - if is_safe_attribute(misp_event, "uuid") and is_in_config(self.config,0): + if is_safe_attribute(misp_event, "uuid") and is_in_config(self.config, 0): # We can build links curr_uuid = str(getattr(misp_event, "uuid")) curr_baseurl = self.config[moduleconfig[0]] @@ -495,7 +528,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) return self.get_unoverflowable_paragraph(item[2]) - def get_owner_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event owner @@ -508,7 +540,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) return self.get_unoverflowable_paragraph(item[2]) - def get_threat_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event threat @@ -518,10 +549,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "threat" ''' if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - return self.get_unoverflowable_paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], + do_escape_string=False) return self.get_unoverflowable_paragraph(item[2]) - def get_analysis_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event analysis @@ -531,10 +562,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "analysis" ''' if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - return self.get_unoverflowable_paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], do_escape_string=False) + return self.get_unoverflowable_paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], + do_escape_string=False) return self.get_unoverflowable_paragraph(item[2]) - def get_timestamp_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event timestamp @@ -544,10 +575,10 @@ class Value_Formatter(): :return: a Paragraph to add in the pdf, regarding the values of "timestamp" ''' if is_safe_attribute(misp_event, item[1]): - return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT))) + return self.get_unoverflowable_paragraph( + safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT))) return self.get_unoverflowable_paragraph(item[2]) - def get_creator_organisation_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event creator organisation @@ -562,7 +593,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(getattr(getattr(misp_event, item[1]), item[3]))) return self.get_unoverflowable_paragraph(item[2]) - def get_attributes_number_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event attributes @@ -575,8 +605,6 @@ class Value_Formatter(): return self.get_unoverflowable_paragraph(safe_string(len(getattr(misp_event, item[1])))) return self.get_unoverflowable_paragraph(item[2]) - - def get_published_value(self, misp_event, item): ''' Returns a flowable paragraph to add to the pdf given the misp_event published/published_time @@ -601,7 +629,9 @@ class Value_Formatter(): if getattr(misp_event, item[1]): # == True if is_safe_attribute(misp_event, item[3]): # Published and have published date - answer = self.get_unoverflowable_paragraph(YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", do_escape_string=False) + answer = self.get_unoverflowable_paragraph( + YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", + do_escape_string=False) else: # Published without published date answer = self.get_unoverflowable_paragraph(YES_ANSWER + "no date)", do_escape_string=False) @@ -615,7 +645,6 @@ class Value_Formatter(): return answer - def get_image_value(self, misp_attribute, item): ''' Returns a flowable image to add to the pdf given the misp attribute type and data @@ -641,7 +670,7 @@ class Value_Formatter(): return answer - def get_good_link(self,misp_attribute, item): + def get_good_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link :param misp_attribute: A misp attribute with a link @@ -653,8 +682,7 @@ class Value_Formatter(): "" + getattr( misp_attribute, item[1]) + "", do_escape_string=False) - - def get_bad_link(self,misp_attribute, item): + def get_bad_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link :param misp_attribute: A misp event with an url @@ -664,10 +692,11 @@ class Value_Formatter(): ''' return self.get_unoverflowable_paragraph( "" + getattr(misp_attribute, - item[1]) + "", do_escape_string=False) + item[ + 1]) + "", + do_escape_string=False) - - def get_good_or_bad_link(self,misp_attribute, item): + def get_good_or_bad_link(self, misp_attribute, item): ''' Returns a flowable paragraph to add to the pdf given the misp_attribute value, if this is a link or an url :param misp_attribute: A misp attribute with a link or an url @@ -693,7 +722,8 @@ class Event_Metadata(): # ---------------------------------------------------------------------- def __init__(self, config, value_formatter): self.config = config - self.curr_val_f = value_formatter + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() # ---------------------------------------------------------------------- @@ -708,61 +738,76 @@ class Event_Metadata(): ''' data = [] + flowable_table = [] # Manual addition # UUID item = ["UUID", 'uuid', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_value_link_to_event(misp_event, item)]) # Date item = ["Date", 'date', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_date_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_date_value(misp_event, item)]) # Owner item = ["Owner org", 'owner', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_owner_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_owner_value(misp_event, item)]) # Threat item = ["Threat level", 'threat_level_id', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_threat_value(misp_event, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_threat_value(misp_event, item)]) # Analysis item = ["Analysis", 'analysis', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_analysis_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_analysis_value(misp_event, item)]) # Info item = ["Info", 'info', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_value_link_to_event(misp_event, item)]) + 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.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_timestamp_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_timestamp_value(misp_event, item)]) # Published item = ["Published", 'published', "None", "publish_timestamp"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_published_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_published_value(misp_event, item)]) # Creator organisation item = ["Creator Org", 'Orgc', "None", "name"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_creator_organisation_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_creator_organisation_value(misp_event, item)]) # Number of Attributes item = ["# Attributes", 'Attribute', "None"] - data.append([self.curr_val_f.get_col1_paragraph(item[0]), self.curr_val_f.get_attributes_number_value(misp_event, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_attributes_number_value(misp_event, item)]) # Tags item = ["Tags", 'Tag', "None"] - curr_Tags = Tags(self.config, self.curr_val_f) - data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_event, item)]) + curr_Tags = Tags(self.config, self.value_formatter) + data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_event, item)]) - if is_in_config(self.config, 3): - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] - if is_safe_attribute_table(misp_event, item[1]): - curr_Galaxy = Galaxy(self.config, self.curr_val_f) - data.append([self.curr_val_f.get_col1_paragraph(item[0]), curr_Galaxy.get_galaxy_value(misp_event, item)]) + flowable_table.append(create_flowable_table_from_data(data)) - return create_flowable_table_from_data(data) + # Galaxies + item = ["Related Galaxies", 'Galaxy', "None"] + curr_Galaxy = Galaxy(self.config, self.value_formatter) + 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']) + + flowable_table.append(galaxy_title) + flowable_table.append(curr_Galaxy.get_galaxy_value(misp_event, item)) + + return flowable_table def create_flowable_description_from_event(self, misp_event): ''' @@ -863,12 +908,12 @@ class Event_Metadata(): ''' text += "
For more information on the event, please consult following information." - description_style = ParagraphStyle(name='Description', parent=self.curr_val_f.col2_style, alignment=TA_JUSTIFY) + description_style = ParagraphStyle(name='Description', parent=self.value_formatter.col2_style, + alignment=TA_JUSTIFY) return Paragraph(text, description_style) - class Attributes(): # ---------------------------------------------------------------------- @@ -876,6 +921,7 @@ class Attributes(): self.config = config self.value_formatter = value_formatter self.sample_style_sheet = getSampleStyleSheet() + # ---------------------------------------------------------------------- def create_flowable_table_from_attributes(self, misp_event): @@ -893,7 +939,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(Paragraph("Attribute #" + str(i), self.sample_style_sheet['Heading4'])) - flowable_table.append(self.create_flowable_table_from_one_attribute(item)) + flowable_table += self.create_flowable_table_from_one_attribute(item) i += 1 else: # No attributes for this object @@ -909,7 +955,7 @@ class Attributes(): ''' data = [] - col1_style, col2_style = get_table_styles() + flowable_table = [] # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list @@ -939,18 +985,21 @@ class Attributes(): # Handle Special case for links (Value) - There were not written in the previous loop item = ["Value", 'value', "None"] if not STANDARD_TYPE and is_safe_attribute(misp_attribute, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_good_or_bad_link(misp_attribute, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_good_or_bad_link(misp_attribute, item)]) # Handle pictures item = ["Data", 'data', "None"] if is_safe_attribute(misp_attribute, item[1]) and getattr(misp_attribute, 'type') == IMAGE_TYPE: - data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_image_value(misp_attribute, item)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), + self.value_formatter.get_image_value(misp_attribute, item)]) # Tags item = ["Tags", 'Tag', "None"] curr_Tags = Tags(self.config, self.value_formatter) if is_safe_attribute_table(misp_attribute, item[1]): - data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) + data.append( + [self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) # Sighting item = ["Sighting", 'Sighting', "None"] @@ -959,14 +1008,18 @@ class Attributes(): data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Sighting.create_flowable_paragraph_from_sightings(misp_attribute, item)]) - if is_in_config(self.config, 3): - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] - curr_Galaxy = Galaxy(self.config, self.value_formatter) - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), curr_Galaxy.get_galaxy_value(misp_attribute, item)]) + flowable_table.append(create_flowable_table_from_data(data)) - return create_flowable_table_from_data(data) + # Galaxies + item = ["Related Galaxies", 'Galaxy', "None"] + curr_Galaxy = Galaxy(self.config, self.value_formatter) + if is_safe_attribute_table(misp_attribute, item[1]) and is_in_config(self.config, 3): + galaxy_title = Paragraph(item[0], self.sample_style_sheet['Heading5']) + + flowable_table.append(galaxy_title) + flowable_table.append(curr_Galaxy.get_galaxy_value(misp_attribute, item)) + + return flowable_table class Tags(): @@ -1001,7 +1054,7 @@ class Tags(): flowable_table = [] i = 0 - if is_safe_attribute_table(misp_event,"Tag"): + if is_safe_attribute_table(misp_event, "Tag"): # There is some tags for this object for item in getattr(misp_event, "Tag"): flowable_table.append(create_flowable_tag(item)) @@ -1059,7 +1112,7 @@ class Sightings(): # There is some tags for this object for curr_item in getattr(misp_attribute, item[1]): # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): - if is_safe_dict_attribute(curr_item,"type"): + if is_safe_dict_attribute(curr_item, "type"): # Store the likes/dislikes depending on their types list_sighting[int(curr_item["type"])] += 1 i += 1 @@ -1077,6 +1130,7 @@ class Sightings(): return answer_sighting + class Object(): # ---------------------------------------------------------------------- @@ -1176,13 +1230,13 @@ class Galaxy(): ["Name to be print in the pdf", "json property access name", " Name to be display if no values found in the misp_event"] :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "galaxies" + :return: a Flowable to add in the pdf, regarding the values of "galaxies" ''' if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = self.create_flowable_table_from_galaxies(misp_event) - return table_event_tags + return self.create_flowable_table_from_galaxies(misp_event) return self.value_formatter.get_unoverflowable_paragraph(item[2]) + def create_flowable_table_from_galaxies(self, misp_event): ''' Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event @@ -1191,23 +1245,39 @@ class Galaxy(): ''' flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 0 + scheme_alternation = [] + curr_color = 0 if is_safe_attribute_table(misp_event, "Galaxy"): # There is some galaxies for this object - for item in getattr(misp_event, "Galaxy"): - # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) - flowable_table.append(self.create_flowable_table_from_one_galaxy(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) + + for curr_galaxy in getattr(misp_event, "Galaxy"): + # For each galaxy of the misp object + + # Add metadata about the Galaxy + galaxy_metadata, nb_added_item = self.create_flowable_table_from_one_galaxy(curr_galaxy) + flowable_table += galaxy_metadata + + # Construct the line color scheme and line scheme + scheme_alternation += [curr_color] * nb_added_item + + # Add metadata about clusters + curr_cluster = Galaxy_cluster(self.config, self.value_formatter) + clusters_metadata, nb_added_item = curr_cluster.create_flowable_table_from_galaxy_clusters(curr_galaxy) + flowable_table += clusters_metadata + + # Construct the line color scheme and line scheme + scheme_alternation += [curr_color] * nb_added_item + curr_color += 1 if curr_color == 0 else 0 + + # Apply the scheme + answer_tags = create_flowable_table_from_data(flowable_table, color_alternation=scheme_alternation , line_alternation=scheme_alternation) else: # No galaxies for this object - answer_tags = [Paragraph("No galaxies", col2_style)] + answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxies")] return answer_tags - def create_flowable_table_from_one_galaxy(self, misp_galaxy): ''' Returns a table (flowable) representing the galaxy @@ -1215,7 +1285,7 @@ class Galaxy(): :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable ''' data = [] - col1_style, col2_style = get_table_styles() + nb_added_item = 0 # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list @@ -1230,15 +1300,11 @@ class Galaxy(): # The attribute exists, we fetch it and create the row data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_galaxy[item[1]])]) + nb_added_item += 1 - # Clusters - item = ["Clusters", 'GalaxyCluster', "None"] - # data.append([Paragraph(item[0], col1_style), create_flowable_table_from_galaxy_clusters(misp_galaxy)]) - - tmp_table = Table(data, ["25%", "75%"]) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] + return data, nb_added_item class Galaxy_cluster(): @@ -1249,40 +1315,44 @@ class Galaxy_cluster(): self.value_formatter = value_formatter # ---------------------------------------------------------------------- - def create_flowable_table_from_galaxy_clusters(self, misp_event): + def create_flowable_table_from_galaxy_clusters(self, misp_galaxy): ''' Returns a Table (flowable) to add to a pdf, representing the list of galaxy clusters of a galaxy :param misp_event: A misp event :return: a table of flowables to add to the pdf ''' - flowable_table = [] - i = 0 + data = [] + nb_added_item = 0 + + if is_safe_dict_attribute(misp_galaxy, "GalaxyCluster"): + # There is some clusters for this object + for curr_cluster in misp_galaxy["GalaxyCluster"]: + + # For each cluster + tmp_data, curr_added_items = self.create_flowable_table_from_one_galaxy_cluster(curr_cluster) + data += tmp_data + nb_added_item += curr_added_items - if is_safe_dict_attribute(misp_event, "GalaxyCluster"): - # There is some galaxies for this object - for item in misp_event["GalaxyCluster"]: - # flowable_table.append([get_unoverflowable_paragraph(item["name"],col2_style)]) - flowable_table.append(self.create_flowable_table_from_one_galaxy_cluster(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) else: # No galaxies for this object - answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] + data = [self.value_formatter.get_unoverflowable_paragraph("No galaxy cluster")] + nb_added_item += 1 - return answer_tags + return data, nb_added_item def create_flowable_table_from_one_galaxy_cluster(self, misp_cluster): ''' - Returns a table (flowable) representing the galaxy + Returns a table (flowable) representing a galaxy cluster :param misp_attribute: A misp galaxy - :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + :return: a table representing this misp's galaxy's cluster attributes, to add to the pdf as a flowable ''' data = [] + nb_added_item = 0 # To reduce code size, and automate it a bit, triplet (Displayed Name, object_attribute_name, # to_display_if_not_present) are store in the following list - list_attr_automated = [["Name", 'name', "None"], + list_attr_automated = [["Cluster #", 'tag_name', "None"], ["Type", 'type', "None"], ["Description", 'description', "None"], ["NameSpace", 'namespace', "None"]] @@ -1293,14 +1363,13 @@ class Galaxy_cluster(): # The attribute exists, we fetch it and create the row data.append([self.value_formatter.get_col1_paragraph(item[0]), self.value_formatter.get_unoverflowable_paragraph(misp_cluster[item[1]])]) + nb_added_item += 1 - tmp_table = Table(data, ["25%", "75%"]) - print(tmp_table) + # tmp_table = Table(data, ["25%", "75%"]) + # print(tmp_table) # The attribute does not exist, you may want to print a default text on the row. Then use as a else case : # data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) - return [tmp_table] - - + return data, nb_added_item ######################################################################## @@ -1322,8 +1391,7 @@ class Statics_Drawings(): # TODO : add_header() # TODO : add_footer() - - def add_metadata(self,canvas, doc): + def add_metadata(self, canvas, doc): ''' Allow to add metadata to the pdf. Would need deeper digging to change other metadata. :param canvas: / Automatically filled during pdf compilation @@ -1342,7 +1410,7 @@ class Statics_Drawings(): if is_safe_attribute(getattr(self.misp_event, 'Orgc'), 'name'): canvas.setAuthor(getattr(getattr(self.misp_event, 'Orgc'), 'name')) - if is_in_config(self.config,1) : + if is_in_config(self.config, 1): canvas.setCreator(self.config[moduleconfig[1]]) else: canvas.setCreator(getattr(getattr(self.misp_event, 'Orgc'), 'name')) @@ -1350,8 +1418,7 @@ class Statics_Drawings(): if is_safe_attribute(self.misp_event, 'uuid'): canvas.setKeywords(getattr(self.misp_event, 'uuid')) - - def add_page_number(self,canvas, doc): + def add_page_number(self, canvas, doc): ''' Draw the page number on each page :param canvas: / Automatically filled during pdf compilation @@ -1401,7 +1468,7 @@ def collect_parts(misp_event, config=None): curr_attr = Attributes(config, curr_val_f) curr_object = Object(config, curr_val_f) - if is_in_config(config,2) : # If description is activated + if is_in_config(config, 2): # If description is activated description = Paragraph("Description", sample_style_sheet['Heading2']) description_text = curr_event.create_flowable_description_from_event(misp_event) flowables.append(description) @@ -1410,7 +1477,7 @@ def collect_parts(misp_event, config=None): subtitle = Paragraph("General information", sample_style_sheet['Heading2']) table_general_metainformation = curr_event.create_flowable_table_from_event(misp_event) flowables.append(subtitle) - flowables.append(table_general_metainformation) + flowables += table_general_metainformation flowables.append(PageBreak()) @@ -1449,7 +1516,8 @@ def export_flowables_to_pdf(document, misp_event, flowables, config): onFirstPage=static_drawer.set_template, # Pagination for first page onLaterPages=static_drawer.set_template, # Pagination for all other page ) - # Old way : onLaterPages=partial(static_drawer.set_template, misp_event=misp_event), # Pagination for all other page + # Old way : onLaterPages=partial(static_drawer.set_template, misp_event=misp_event), # Pagination for all other page + ######################################################################## # "EXTERNAL" exposed METHODS. Meant to be used outside of this class. @@ -1484,7 +1552,6 @@ def convert_event_in_pdf_buffer(misp_event, config=None): return pdf_value - def get_values_from_buffer(pdf_buffer): return pdf_buffer.value()