From 6031a7d426bca4811f71aaacaf00d062da612b3a Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 28 Feb 2019 13:44:54 +0100 Subject: [PATCH] chg: [exportpdf] BIG refactoring. Classes, comments, Galaxy starting --- pymisp/tools/reportlab_generator.py | 1892 ++++++++++++++------------- 1 file changed, 1001 insertions(+), 891 deletions(-) diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index 9cf748d..eb5f6a7 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -6,12 +6,8 @@ import base64 import logging import pprint from io import BytesIO -from functools import partial - import sys -from orca.cmdnames import TABLE_CELL_FIRST - if sys.version_info.major >= 3: from html import escape # import PIL @@ -223,7 +219,44 @@ def get_sample_styles(): sample_style_sheet.list() -# "INTERNAL" METHODS. Not meant to be used outside of this class. +######################################################################## +# General Event's Attributes formater tools + +def uuid_to_url(baseurl, uuid): + ''' + Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP + :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ + :param uuid: the uuid of the event that we want to have a link to + :return: the complete URL to go to this event on this MISP instance + ''' + if baseurl[len(baseurl) - 1] != "/": + baseurl += "/" + return baseurl + "events/view/" + uuid + + +def create_flowable_table_from_data(data, col_w=COL_WIDTHS): + ''' + 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) + :return: a Table - with styles - to add to the pdf + ''' + # Create the table + curr_table = Table(data, col_w) + + # Aside notes : + # colWidths='*' does a 100% and share the space automatically + # 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) + general_style = general_style_generator() + + # Make the table nicer + curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) + + return curr_table + def alternate_colors_style_generator(data): ''' @@ -318,6 +351,7 @@ def get_table_styles(): def safe_string(bad_str): return escape(str(bad_str)) + def is_safe_attribute(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) != "" @@ -332,946 +366,1011 @@ 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 + ######################################################################## -# General attribut formater +# Functions grouped by misp object type -def get_unoverflowable_paragraph(dirty_string, curr_style, do_escape_string=True): + +class Value_Formatter(): ''' - Create a paragraph that can fit on a cell of one page. Mostly hardcoded values. - This method can be improved (get the exact size of the current frame, and limit the paragraph to this size.) - This might be worst look at KeepInFrame (which hasn't went well so far) - :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) - :param dirty_string: String to transform - :param curr_style: Style to apply to the returned paragraph - :return: + "item" parameter should be as follow, a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event"] ''' - if do_escape_string: - sanitized_str = str(escape(str(dirty_string))) - else: - sanitized_str = dirty_string + # ---------------------------------------------------------------------- + def __init__(self, config, col1_style, col2_style): + self.config = config + self.col1_style = col1_style + self.col2_style = 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) + # ---------------------------------------------------------------------- + ######################################################################## + # General attribut formater + def get_col1_paragraph(self, dirty_string): + return self.get_unoverflowable_paragraph(dirty_string, self.col1_style) - # If there is enough space, directly send back the sanitized paragraph - if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(sanitized_str, curr_style) - else: - # Otherwise, cut the content to fit the paragraph (Dichotomy) - max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) + 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.) + KeepInFrame may give a nicer solution (not for me so far) + :param do_escape_string: Activate the escaping (may be useful to add inline HTML, e.g. hyperlinks) + :param dirty_string: String to transform + :param curr_style: Style to apply to the returned paragraph + :return: + ''' + if do_escape_string: + sanitized_str = safe_string(dirty_string) + else: + sanitized_str = dirty_string - i = 0 - MAX_ITERATION = 10 - limited_string = "" - 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) - max_carac_amount = int(max_carac_amount / 2) + if curr_style is None: + 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) + + # If there is enough space, directly send back the sanitized paragraph if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: - answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) + answer_paragraph = Paragraph(sanitized_str, curr_style) else: - # We may still end with a not short enough string - answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) + # Otherwise, cut the content to fit the paragraph (Dichotomy) + max_carac_amount = int((FRAME_MAX_HEIGHT / (h * 1.0)) * len(sanitized_str)) - return answer_paragraph + i = 0 + MAX_ITERATION = 10 + limited_string = "" + 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) + max_carac_amount = int(max_carac_amount / 2) -######################################################################## -# Specific attribute formater - -def get_value_link_to_event(misp_event, item, col2_style, config=None, color=True): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event uuid, with or without link - :param config: Config dictionnary provided by MISP instance, via misp-modules (with baseurl) - :param misp_event: A misp event with or without "uuid" attributes - :param item: a list of name, in order : - ["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 "uuid" - ''' - - # Does MispEven has the attribute ? - if is_safe_attribute(misp_event, item[1]): - # 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 config is not None and moduleconfig[0] in config: - # We can build links - curr_uuid = str(getattr(misp_event, "uuid")) - curr_baseurl = config[moduleconfig[0]] - curr_url = uuid_to_url(curr_baseurl, curr_uuid) - html_url = "" + safe_string(getattr(misp_event, item[1])) + "" - - if color: - # They want fancy colors - html_url = "" + html_url + "" - - # Construct final paragraph - answer = get_unoverflowable_paragraph(html_url, col2_style, False) - - else: - # We can't build links - answer = get_unoverflowable_paragraph(getattr(misp_event, item[1]), col2_style) - - else: - # No it doesn't, so we directly give the default answer - answer = Paragraph(item[2], col2_style) - - return answer - - -def get_date_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event date - :param misp_event: A misp event with or without "date" attributes - :param item: a list of name, in order : - ["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 "date" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_owner_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event owner - :param misp_event: A misp event with or without "owner" attributes - :param item: a list of name, in order : - ["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 "owner" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_threat_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event threat - :param misp_event: A misp event with or without "threat" attributes - :param item: a list of name, in order : - ["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 "threat" - ''' - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - return Paragraph(threat_map[safe_string(getattr(misp_event, item[1]))], col2_style) - return Paragraph(item[2], col2_style) - - -def get_analysis_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event analysis - :param misp_event: A misp event with or without "analysis" attributes - :param item: a list of name, in order : - ["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 "analysis" - ''' - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - return Paragraph(analysis_map[safe_string(getattr(misp_event, item[1]))], col2_style) - return Paragraph(item[2], col2_style) - - -def get_timestamp_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event timestamp - :param misp_event: A misp event with or without "timestamp" attributes - :param item: a list of name, in order : - ["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 "timestamp" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)), col2_style) - return Paragraph(item[2], col2_style) - - -def get_creator_organisation_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event creator organisation - :param misp_event: A misp event with or without "timestamp" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event", "json property access name (second level)"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "creator organisation" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(getattr(getattr(misp_event, item[1]), item[3])), col2_style) - return Paragraph(item[2], col2_style) - - -def get_attributes_number_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event attributes - :param misp_event: A misp event with or without "attributes" attributes - :param item: a list of name, in order : - ["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 "attributes" - ''' - if is_safe_attribute(misp_event, item[1]): - return Paragraph(safe_string(len(getattr(misp_event, item[1]))), col2_style) - return Paragraph(item[2], col2_style) - - -def get_tag_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event tags - :param misp_event: A misp event with or without "tags" attributes - :param item: a list of name, in order : - ["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 "tags" - ''' - if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = create_flowable_table_from_tags(misp_event) - return table_event_tags - return Paragraph(item[2], col2_style) - - -def get_galaxy_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event galaxies - :param misp_event: A misp event with or without "galaxies" attributes - :param item: a list of name, in order : - ["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" - ''' - if is_safe_attribute_table(misp_event, item[1]): - table_event_tags = create_flowable_table_from_galaxies(misp_event) - return table_event_tags - return Paragraph(item[2], col2_style) - - -def get_published_value(misp_event, item, col2_style): - ''' - Returns a flowable paragraph to add to the pdf given the misp_event published/published_time - More information on how to play with paragraph into reportlab cells : - https://stackoverflow.com/questions/11810008/reportlab-add-two-paragraphs-into-one-table-cell - :param misp_event: A misp event with or without "published"/"publish_timestamp" attributes - :param item: a list of name, in order : - ["Name to be print in the pdf", "json property access name", - " Name to be display if no values found in the misp_event", json property access name (for timestamp")] - e.g. item = ["Published", 'published', "None", "publish_timestamp"] - :param col2_style: style to be applied on the returned paragraph - :return: a Paragraph to add in the pdf, regarding the values of "published"/"publish_timestamp" - ''' - - RED_COLOR = '#ff0000' - GREEN_COLOR = '#008000' - YES_ANSWER = " Yes (" - NO_ANSWER = "No" - - # Formatting similar to MISP Event web view - if is_safe_attribute(misp_event, item[1]): - if getattr(misp_event, item[1]): # == True - if is_safe_attribute(misp_event, item[3]): - # Published and have published date - answer = Paragraph(YES_ANSWER + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + ")", - col2_style) + if w <= FRAME_MAX_WIDTH and h <= FRAME_MAX_HEIGHT: + answer_paragraph = Paragraph(limited_string + STR_TOO_LONG_WARNING, curr_style) else: - # Published without published date - answer = Paragraph(YES_ANSWER + "no date)", col2_style) + # We may still end with a not short enough string + answer_paragraph = Paragraph(STR_TOO_LONG_WARNING, curr_style) + + return answer_paragraph + + + 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) + :param config: Config dictionnary provided by MISP instance, via misp-modules (with baseurl) + :param misp_event: A misp event with or without "uuid" 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 "uuid" + ''' + + if curr_style is None: + curr_style = self.col2_style + + # Does MispEven has the attribute ? + if is_safe_attribute(misp_event, item[1]): + # 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): + # We can build links + curr_uuid = str(getattr(misp_event, "uuid")) + curr_baseurl = self.config[moduleconfig[0]] + curr_url = uuid_to_url(curr_baseurl, curr_uuid) + html_url = "" + safe_string(getattr(misp_event, item[1])) + "" + + if color: + # They want fancy colors + html_url = "" + html_url + "" + + # Construct final paragraph + answer = self.get_unoverflowable_paragraph(html_url, curr_style=curr_style, do_escape_string=False) + + else: + # We can't build links + answer = self.get_unoverflowable_paragraph(getattr(misp_event, item[1]), curr_style=curr_style) else: - # Not published - answer = Paragraph(NO_ANSWER, col2_style) - else: - # Does not have a published attribute - answer = Paragraph(item[2], col2_style) + # No it doesn't, so we directly give the default answer + answer = self.get_unoverflowable_paragraph(item[2], curr_style=curr_style) - return answer + return answer -def create_flowable_table_from_one_attribute(misp_attribute, config=None): - ''' - Returns a table (flowalbe) representing the attribute - :param misp_attribute: A misp attribute - :return: a table representing this misp's attribute's attributes, to add to the pdf as a flowable - ''' + ######################################################################## + # Specific attribute formater - data = [] - col1_style, col2_style = get_table_styles() + def get_date_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event date + :param misp_event: A misp event with or without "date" 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 "date" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) + return self.get_unoverflowable_paragraph(item[2]) - # 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 = [["UUID", 'uuid', "None"], - ["Category", 'category', "None"], - ["Comment", 'comment', "None"], - ["Type", 'type', "None"], - ["Value", 'value', "None"]] - # Handle the special case of links - STANDARD_TYPE = True - if is_safe_attribute(misp_attribute, 'type') and (getattr(misp_attribute, 'type') in [LINK_TYPE, URL_TYPE]): - # getattr(misp_attribute, 'type') == LINK_TYPE or getattr(misp_attribute, 'type') == URL_TYPE): - # Special case for links - STANDARD_TYPE = False + def get_owner_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event owner + :param misp_event: A misp event with or without "owner" 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 "owner" + ''' + if is_safe_attribute(misp_event, item[1]): + return self.get_unoverflowable_paragraph(safe_string(getattr(misp_event, item[1]))) + return self.get_unoverflowable_paragraph(item[2]) - # Automated adding of standard (python) attributes of the misp event - for item in list_attr_automated: - if is_safe_attribute(misp_attribute, item[1]) and (STANDARD_TYPE or item[1] != 'value'): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(getattr(misp_attribute, item[1]), col2_style)]) - # 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)]) + def get_threat_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event threat + :param misp_event: A misp event with or without "threat" 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 "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(item[2]) - # 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([Paragraph(item[0], col1_style), get_good_or_bad_link(misp_attribute, item, col2_style)]) - # Handle pictures - item = ["Data", 'data', "None"] - if is_safe_attribute(misp_attribute, item[1]) and getattr(misp_attribute, 'type') == IMAGE_TYPE: - data.append([Paragraph(item[0], col1_style), get_image_value(misp_attribute, item, col2_style)]) + def get_analysis_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event analysis + :param misp_event: A misp event with or without "analysis" 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 "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(item[2]) - # Tags - item = ["Tags", 'Tag', "None"] - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), get_tag_value(misp_attribute, item, col2_style)]) - # Sighting - item = ["Sighting", 'Sighting', "None"] - if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), - create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style)]) + def get_timestamp_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event timestamp + :param misp_event: A misp event with or without "timestamp" 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 "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(item[2]) - if config is not None and moduleconfig[3] in config: - # Galaxies - item = ["Galaxies", 'Galaxy', "None"] + + def get_creator_organisation_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event creator organisation + :param misp_event: A misp event with or without "timestamp" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event", "json property access name (second level)"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "creator organisation" + ''' + if is_safe_attribute(misp_event, item[1]): + 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 + :param misp_event: A misp event with or without "attributes" 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 "attributes" + ''' + if is_safe_attribute(misp_event, item[1]): + 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 + More information on how to play with paragraph into reportlab cells : + https://stackoverflow.com/questions/11810008/reportlab-add-two-paragraphs-into-one-table-cell + :param misp_event: A misp event with or without "published"/"publish_timestamp" attributes + :param item: a list of name, in order : + ["Name to be print in the pdf", "json property access name", + " Name to be display if no values found in the misp_event", json property access name (for timestamp")] + e.g. item = ["Published", 'published', "None", "publish_timestamp"] + :param col2_style: style to be applied on the returned paragraph + :return: a Paragraph to add in the pdf, regarding the values of "published"/"publish_timestamp" + ''' + + RED_COLOR = '#ff0000' + GREEN_COLOR = '#008000' + YES_ANSWER = " Yes (" + NO_ANSWER = "No" + + # Formatting similar to MISP Event web view + if is_safe_attribute(misp_event, item[1]): + 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) + else: + # Published without published date + answer = self.get_unoverflowable_paragraph(YES_ANSWER + "no date)", do_escape_string=False) + + else: + # Not published + answer = self.get_unoverflowable_paragraph(NO_ANSWER, do_escape_string=False) + else: + # Does not have a published attribute + answer = self.get_unoverflowable_paragraph(item[2], do_escape_string=False) + + 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 + :param misp_attribute: A misp attribute with type="attachement" and data + :param item: as defined in class definition + :param col2_style: style to be applied on the returned paragraph + :return: a flowable image to add in the pdf, regarding the values of "data" + ''' + + try: + # Get the image + buf = getattr(misp_attribute, item[1]) + + # Create image within a bounded box (to allow pdf creation) + img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') + answer = img + + except OSError: + logger.error( + "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") + answer = self.get_unoverflowable_paragraph( + "" + NOT_A_PICTURE_MESSAGE + "", do_escape_string=False) + + return answer + + 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 + :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 this "link" attribute + ''' + return self.get_unoverflowable_paragraph( + "" + getattr( + misp_attribute, item[1]) + "", do_escape_string=False) + + + 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 + :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 this "url" attribute + ''' + return self.get_unoverflowable_paragraph( + "" + getattr(misp_attribute, + item[1]) + "", do_escape_string=False) + + + 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 + :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 this "link" or "url" attribute + ''' + + answer = self.get_unoverflowable_paragraph("Not an URL") + + # Handle "Good" links + if getattr(misp_attribute, 'type') == LINK_TYPE: + answer = self.get_good_link(misp_attribute, item) + # Handle "bad "links + elif getattr(misp_attribute, 'type') == URL_TYPE: + answer = self.get_bad_link(misp_attribute, item) + + return answer + + +class Event_Metadata(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.curr_val_f = value_formatter + + # ---------------------------------------------------------------------- + + ######################################################################## + # General Event's Attributes formater + + def create_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 = [] + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + # 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)]) + + 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)]) + + return create_flowable_table_from_data(data) + + def create_flowable_description_from_event(self, misp_event): + ''' + Returns a Paragraph presenting a MISP event + :param misp_event: A misp event (complete or not) + :return: a paragraph that can be added to a pdf + ''' + + ''' + The event "{EventName}" | that occurred on {EventDate}, | had been shared by {Organisation Name} | on the {Date}. + ''' + + text = "" + + item = ["Info", 'info', "None"] + if is_safe_attribute(misp_event, item[1]): + text += "The event '" + text += safe_string(getattr(misp_event, item[1])) + text += "'" + else: + text += "This event" + + item = ["Event date", 'timestamp', "None"] + if is_safe_attribute(misp_event, item[1]): + text += " that occurred on " + text += safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)) + text += "," + + item = ["Creator Org", 'Orgc', "None", "name"] + text += " had been shared by " + if is_safe_attribute(misp_event, item[1]): + text += safe_string(getattr(getattr(misp_event, item[1]), item[3])) + else: + text += " an unknown organisation" + + item = ["Date", 'date', "None"] + if is_safe_attribute(misp_event, item[1]): + text += " on the " + text += safe_string(getattr(misp_event, item[1])) + else: + text += " on an unknown date" + text += "." + + ''' + The threat level of this event is {ThreatLevel} and the analysis that was made of this event is {AnalysisLevel}. + ''' + + item = ["Threat level", 'threat_level_id', "None"] + text += " The threat level of this event is " + if is_safe_attribute(misp_event, item[1]) and safe_string(getattr(misp_event, item[1])) in threat_map: + text += threat_map[safe_string(getattr(misp_event, item[1]))] + else: + text += " unknown" + + item = ["Analysis", 'analysis', "None"] + text += " and the analysis that was made of this event is " + if is_safe_attribute(misp_event, item[1]) and safe_string(getattr(misp_event, item[1])) in analysis_map: + text += analysis_map[safe_string(getattr(misp_event, item[1]))] + else: + text += " undefined" + text += "." + + ''' + The event is currently {Published} and has associated attributes {Attribute Number}. + ''' + + item = ["Published", 'published', "None", "publish_timestamp"] + text += " The event is currently " + if is_safe_attribute(misp_event, item[1]) and getattr(misp_event, item[1]): + text += " published" + if is_safe_attribute(misp_event, item[3]): + text += " since " + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) + else: + text += " private" + + # Number of Attributes + item = ["# Attributes", 'Attribute', "None"] + text += ", has " + if is_safe_attribute_table(misp_event, item[1]): + text += safe_string(len(getattr(misp_event, item[1]))) + else: + text += " 0" + + text += " associated attributes" + + # Number of Objects + item = ["# Objects", 'Object', "None"] + text += " and has " + if is_safe_attribute_table(misp_event, item[1]): + text += safe_string(len(getattr(misp_event, item[1]))) + else: + text += " 0" + + text += " associated objects." + + ''' + For more information on the event, please consult the rest of the document + ''' + 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) + + return Paragraph(text, description_style) + + + +class Attributes(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() + # ---------------------------------------------------------------------- + + def create_flowable_table_from_attributes(self, misp_event): + ''' + Returns a list of flowables representing the list of attributes of a misp event. + The list is composed alternatively of headers and tables, to add to the pdf + :param misp_event: A misp event + :return: a table of flowables + ''' + flowable_table = [] + i = 0 + + if is_safe_attribute_table(misp_event, "Attribute"): + # There is some attributes for this object + 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)) + i += 1 + else: + # No attributes for this object + flowable_table.append(Paragraph("No attributes", self.sample_style_sheet['Heading4'])) + + return flowable_table + + def create_flowable_table_from_one_attribute(self, misp_attribute): + ''' + Returns a table (flowalbe) representing the attribute + :param misp_attribute: A misp attribute + :return: a table representing this misp's attribute's attributes, to add to the pdf as a flowable + ''' + + data = [] + col1_style, col2_style = get_table_styles() + + # 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 = [["UUID", 'uuid', "None"], + ["Category", 'category', "None"], + ["Comment", 'comment', "None"], + ["Type", 'type', "None"], + ["Value", 'value', "None"]] + + # Handle the special case of links + STANDARD_TYPE = True + if is_safe_attribute(misp_attribute, 'type') and (getattr(misp_attribute, 'type') in [LINK_TYPE, URL_TYPE]): + # getattr(misp_attribute, 'type') == LINK_TYPE or getattr(misp_attribute, 'type') == URL_TYPE): + # Special case for links + STANDARD_TYPE = False + + # Automated adding of standard (python) attributes of the misp event + for item in list_attr_automated: + if is_safe_attribute(misp_attribute, item[1]) and (STANDARD_TYPE or item[1] != 'value'): + # 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(getattr(misp_attribute, item[1]))]) + + # 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)]) + + # 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)]) + + # 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)]) + + # Tags + item = ["Tags", 'Tag', "None"] + curr_Tags = Tags(self.config, self.value_formatter) if is_safe_attribute_table(misp_attribute, item[1]): - data.append([Paragraph(item[0], col1_style), get_galaxy_value(misp_attribute, item, col2_style)]) + data.append([self.value_formatter.get_col1_paragraph(item[0]), curr_Tags.get_tag_value(misp_attribute, item)]) - return create_flowable_table_from_data(data) + # Sighting + item = ["Sighting", 'Sighting', "None"] + curr_Sighting = Sightings(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_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)]) + + return create_flowable_table_from_data(data) -def create_flowable_table_from_one_object(misp_object, config=None): - ''' - Returns a table (flowable) representing the object - :param misp_attribute: A misp object - :return: a table representing this misp's object's attributes, to add to the pdf as a flowable - ''' - data = [] - col1_style, col2_style = get_table_styles() +class Tags(): - # 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 = [["UUID", 'uuid', "None"], - ["Description", 'description', "None"], - ["Meta Category", 'meta-category', "None"], - ["Object Name", 'name', "None"], - ["Comment", 'comment', "None"], - ["Type", 'type', "None"]] + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_attribute(misp_object, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(getattr(misp_object, item[1]), col2_style)]) + # ---------------------------------------------------------------------- + def get_tag_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event tags + :param misp_event: A misp event with or without "tags" 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 "tags" + ''' + if is_safe_attribute_table(misp_event, item[1]): + table_event_tags = self.create_flowable_table_from_tags(misp_event) + return table_event_tags + return self.value_formatter.get_unoverflowable_paragraph(item[2]) + + def create_flowable_table_from_tags(self, misp_event): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of tags of an event or a misp event + :param misp_event: A misp event + :return: a table of flowable to add to the pdf + ''' + + flowable_table = [] + i = 0 + + 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)) + i += 1 + answer_tags = self.create_tags_table_from_data(flowable_table) + else: + # No tags for this object + answer_tags = [self.value_formatter.get_unoverflowable_paragraph("No tags")] + + return answer_tags + + def create_tags_table_from_data(self, data): + ''' + Given a list of flowables tags (2D/list of list), creates a Table with styles adapted to tags. + :param data: list of list of tags (flowables) + :return: a Table - with styles - to add to another table + ''' + + # Create the table + curr_table = Table(data, COL_WIDTHS, rowHeights=ROW_HEIGHT_FOR_TAGS) + + # Create styles and set parameters + general_style = general_style_generator() + + # Make the table nicer + curr_table.setStyle(TableStyle(general_style)) + + return curr_table + + +class Sightings(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + + # ---------------------------------------------------------------------- + + def create_flowable_paragraph_from_sightings(self, misp_attribute, item): + ''' + Returns a Table (flowable) to add to a pdf, representing the list of sightings of an event or a misp event + :param misp_event: A misp event + :return: a table of flowable to add to the pdf + ''' + + col1_style, col2_style = get_table_styles() + i = 0 + POSITIVE_SIGHT_COLOR = 'green' + NEGATIVE_SIGHT_COLOR = 'red' + MISC_SIGHT_COLOR = 'orange' + + list_sighting = [0, 0, 0] + if is_safe_attribute_table(misp_attribute, item[1]): + # 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"): + # Store the likes/dislikes depending on their types + list_sighting[int(curr_item["type"])] += 1 + i += 1 + + # Create the sighting text + sight_text = " Positive : " + str(list_sighting[0]) + "" + sight_text += " / " + " Negative : " + str( + list_sighting[1]) + "" + sight_text += " / " + " Misc. : " + str(list_sighting[2]) + "" + + answer_sighting = self.value_formatter.get_unoverflowable_paragraph(sight_text) + else: + # No tags for this object + answer_sighting = self.value_formatter.get_unoverflowable_paragraph("No sighting") + + return answer_sighting + +class Object(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + self.sample_style_sheet = getSampleStyleSheet() + + # ---------------------------------------------------------------------- + + def create_flowable_table_from_objects(self, misp_event, config=None): + ''' + Returns a list of flowables representing the list of objects of a misp event. + The list is composed of a serie of + [ header object, table of object information, [ header of attribute, table of attribute]*] to add to the pdf + :param misp_event: A misp event + :return: a table of flowables + ''' + + flowable_table = [] + i = 0 + + if is_safe_attribute_table(misp_event, "Object"): + + # There is a list of objects + 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(Paragraph("Object #" + str(i), self.sample_style_sheet['Heading3'])) + flowable_table += self.create_flowable_table_from_one_object(item, config) + i += 1 + else: + # No object found + flowable_table.append(Paragraph("No object", self.sample_style_sheet['Heading3'])) + + return flowable_table + + def create_flowable_table_from_one_object(self, misp_object, config=None): + ''' + Returns a table (flowable) representing the object + :param misp_attribute: A misp object + :return: a table representing this misp's object's attributes, to add to the pdf as a flowable + ''' + data = [] + col1_style, col2_style = get_table_styles() + + # 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 = [["UUID", 'uuid', "None"], + ["Description", 'description', "None"], + ["Meta Category", 'meta-category', "None"], + ["Object Name", 'name', "None"], + ["Comment", 'comment', "None"], + ["Type", 'type', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_attribute(misp_object, item[1]): + # 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(getattr(misp_object, item[1]))]) + + # 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)]) + + # Timestamp + item = ["Object date", 'timestamp', "None"] + data.append([Paragraph(item[0], col1_style), self.value_formatter.get_timestamp_value(misp_object, item)]) + + # Transform list of value in a table + data = [create_flowable_table_from_data(data)] + + # Handle all the attributes + if is_safe_attribute(misp_object, "Attribute"): + curr_attributes = Attributes(self.config, self.value_formatter) + data += curr_attributes.create_flowable_table_from_attributes(misp_object) + + # Add a page break at the end of an object + data.append(PageBreak()) + + return data + + +class Galaxy(): + + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter + + # ---------------------------------------------------------------------- + + def get_galaxy_value(self, misp_event, item): + ''' + Returns a flowable paragraph to add to the pdf given the misp_event galaxies + :param misp_event: A misp event with or without "galaxies" attributes + :param item: a list of name, in order : + ["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" + ''' + 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.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 + :param misp_event: A misp event + :return: a table of flowables to add to the pdf + ''' + + flowable_table = [] + col1_style, col2_style = get_table_styles() + i = 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%"]) + else: + # No galaxies for this object + answer_tags = [Paragraph("No galaxies", col2_style)] + + return answer_tags + + + def create_flowable_table_from_one_galaxy(self, misp_galaxy): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp 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() + + # 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"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] + + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_galaxy, item[1]): + # 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]])]) + + # 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] - # Timestamp - item = ["Object date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_object, item, col2_style)]) - # Transform list of value in a table - data = [create_flowable_table_from_data(data)] +class Galaxy_cluster(): - # Handle all the attributes - if is_safe_attribute(misp_object, "Attribute"): - data += create_flowable_table_from_attributes(misp_object, config) + # ---------------------------------------------------------------------- + def __init__(self, config, value_formatter): + self.config = config + self.value_formatter = value_formatter - # Add a page break at the end of an object - data.append(PageBreak()) + # ---------------------------------------------------------------------- + def create_flowable_table_from_galaxy_clusters(self, misp_event): + ''' + 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 + ''' - return data + flowable_table = [] + i = 0 -def create_flowable_table_from_one_galaxy(misp_galaxy): - ''' - Returns a table (flowable) representing the galaxy - :param misp_attribute: A misp 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() + 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")] - # 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"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] + return answer_tags - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_galaxy, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) + def create_flowable_table_from_one_galaxy_cluster(self, misp_cluster): + ''' + Returns a table (flowable) representing the galaxy + :param misp_attribute: A misp galaxy + :return: a table representing this misp's galaxy's attributes, to add to the pdf as a flowable + ''' + data = [] - # Clusters - item = ["Clusters", 'GalaxyCluster', "None"] - data.append([Paragraph(item[0], col1_style), create_flowable_table_from_one_galaxy_cluster(misp_galaxy, item, col2_style)]) + # 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"], + ["Type", 'type', "None"], + ["Description", 'description', "None"], + ["NameSpace", 'namespace', "None"]] - tmp_table = Table(data, ["25%","75%"]) + # Automated adding of standard (python) attributes of the misp object + for item in list_attr_automated: + if is_safe_dict_attribute(misp_cluster, item[1]): + # 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]])]) + + 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 [tmp_table] -def create_flowable_table_from_one_galaxy_cluster(misp_galaxy): - ''' - Returns a table (flowable) representing the galaxy - :param misp_attribute: A misp 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() - # 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"], - ["Type", 'type', "None"], - ["Description", 'description', "None"], - ["NameSpace", 'namespace', "None"]] - - # Automated adding of standard (python) attributes of the misp object - for item in list_attr_automated: - if is_safe_dict_attribute(misp_galaxy, item[1]): - # The attribute exists, we fetch it and create the row - data.append([Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(misp_galaxy[item[1]], col2_style)]) - - # Clusters - item = ["Object date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_object, item, col2_style)]) - - - 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] - -def get_image_value(misp_attribute, item, col2_style): - ''' - Returns a flowable image to add to the pdf given the misp attribute type and data - :param misp_attribute: A misp attribute with type="attachement" and data - :param item: a list of name, in order : - ["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 flowable image to add in the pdf, regarding the values of "data" - ''' - - try: - # Get the image - buf = getattr(misp_attribute, item[1]) - - # Create image within a bounded box (to allow pdf creation) - img = Image(buf, width=FRAME_PICTURE_MAX_WIDTH, height=FRAME_PICTURE_MAX_HEIGHT, kind='bound') - answer = img - except OSError: - logger.error( - "Trying to add an attachment during PDF export generation. Attachement joining failed. Attachement may not be an image.") - answer = get_unoverflowable_paragraph( - "" + NOT_A_PICTURE_MESSAGE + "", col2_style, False) - - return answer - - - - -######################################################################## -# General Event's Attributes formater tools - -def uuid_to_url(baseurl, uuid): - ''' - Return an url constructed from the MISP baseurl and the uuid of the event, to go to this event on this MISP - :param baseurl: the baseurl of the MISP instnce e.g. http://localhost:8080 or http://localhost:8080/ - :param uuid: the uuid of the event that we want to have a link to - :return: the complete URL to go to this event on this MISP instance - ''' - if baseurl[len(baseurl) - 1] != "/": - baseurl += "/" - return baseurl + "events/view/" + uuid - - -def create_flowable_table_from_data(data, col_w = COL_WIDTHS): - ''' - 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) - :return: a Table - with styles - to add to the pdf - ''' - # Create the table - curr_table = Table(data, col_w) - - # Aside notes : - # colWidths='*' does a 100% and share the space automatically - # 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) - general_style = general_style_generator() - - # Make the table nicer - curr_table.setStyle(TableStyle(general_style + alternate_colors_style + lines_style)) - - return curr_table - -def create_tags_table_from_data(data): - ''' - Given a list of flowables tags (2D/list of list), creates a Table with styles adapted to tags. - :param data: list of list of tags (flowables) - :return: a Table - with styles - to add to another table - ''' - - # Create the table - curr_table = Table(data, COL_WIDTHS, rowHeights=ROW_HEIGHT_FOR_TAGS) - - # Create styles and set parameters - general_style = general_style_generator() - - # Make the table nicer - curr_table.setStyle(TableStyle(general_style)) - - return curr_table - - -def get_good_link(misp_attribute, item, col2_style): - ''' - 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 - :param item: a list of name, in order : - ["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 this "link" attribute - ''' - return get_unoverflowable_paragraph( - "" + getattr( - misp_attribute, item[1]) + "", col2_style, False) - - -def get_bad_link(misp_attribute, item, col2_style): - ''' - 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 - :param item: a list of name, in order : - ["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 this "url" attribute - ''' - return get_unoverflowable_paragraph( - "" + getattr(misp_attribute, - item[1]) + "", - col2_style, False) - - -def get_good_or_bad_link(misp_attribute, item, col2_style): - ''' - 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 - :param item: a list of name, in order : - ["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 this "link" or "url" attribute - ''' - - # Handle "Good" links - if getattr(misp_attribute, 'type') == LINK_TYPE: - answer = get_good_link(misp_attribute, item, col2_style) - # Handle "bad "links - elif getattr(misp_attribute, 'type') == URL_TYPE: - answer = get_bad_link(misp_attribute, item, col2_style) - - return answer - - -######################################################################## -# General Event's Attributes formater - -def create_flowable_table_from_event(misp_event, config=None): - ''' - 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 = [] - col1_style, col2_style = get_table_styles() - - # Manual addition - # UUID - item = ["UUID", 'uuid', "None"] - data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) - - # Date - item = ["Date", 'date', "None"] - data.append([Paragraph(item[0], col1_style), get_date_value(misp_event, item, col2_style)]) - - # Owner - item = ["Owner org", 'owner', "None"] - data.append([Paragraph(item[0], col1_style), get_owner_value(misp_event, item, col2_style)]) - - # Threat - item = ["Threat level", 'threat_level_id', "None"] - data.append([Paragraph(item[0], col1_style), get_threat_value(misp_event, item, col2_style)]) - - # Analysis - item = ["Analysis", 'analysis', "None"] - data.append([Paragraph(item[0], col1_style), get_analysis_value(misp_event, item, col2_style)]) - - # Info - item = ["Info", 'info', "None"] - data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) - - # Timestamp - item = ["Event date", 'timestamp', "None"] - data.append([Paragraph(item[0], col1_style), get_timestamp_value(misp_event, item, col2_style)]) - - # Published - item = ["Published", 'published', "None", "publish_timestamp"] - data.append([Paragraph(item[0], col1_style), get_published_value(misp_event, item, col2_style)]) - - # Creator organisation - item = ["Creator Org", 'Orgc', "None", "name"] - data.append([Paragraph(item[0], col1_style), get_creator_organisation_value(misp_event, item, col2_style)]) - - # Number of Attributes - item = ["# Attributes", 'Attribute', "None"] - data.append([Paragraph(item[0], col1_style), get_attributes_number_value(misp_event, item, col2_style)]) - - # Tags - item = ["Tags", 'Tag', "None"] - data.append([Paragraph(item[0], col1_style), get_tag_value(misp_event, item, col2_style)]) - - return create_flowable_table_from_data(data) - - -def create_flowable_description_from_event(misp_event, config=None): - ''' - Returns a Paragraph presenting a MISP event - :param misp_event: A misp event (complete or not) - :return: a paragraph that can be added to a pdf - ''' - - ''' - The event "{EventName}" | that occurred on {EventDate}, | had been shared by {Organisation Name} | on the {Date}. - ''' - - text = "" - - item = ["Info", 'info', "None"] - if is_safe_attribute(misp_event, item[1]): - text += "The event '" - text += str(getattr(misp_event, item[1])) - text += "'" - else: - text += "This event" - - item = ["Event date", 'timestamp', "None"] - if is_safe_attribute(misp_event, item[1]): - text += " that occurred on " - text += str(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)) - text += "," - - item = ["Creator Org", 'Orgc', "None", "name"] - text += " had been shared by " - if is_safe_attribute(misp_event, item[1]): - text += safe_string(getattr(getattr(misp_event, item[1]), item[3])) - else: - text += " an unknown organisation" - - item = ["Date", 'date', "None"] - if is_safe_attribute(misp_event, item[1]): - text += " on the " - text += str(getattr(misp_event, item[1])) - else: - text += " on an unknown date" - text += "." - - ''' - The threat level of this event is {ThreatLevel} and the analysis that was made of this event is {AnalysisLevel}. - ''' - - item = ["Threat level", 'threat_level_id', "None"] - text += " The threat level of this event is " - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in threat_map: - text += threat_map[str(getattr(misp_event, item[1]))] - else: - text += " unknown" - - item = ["Analysis", 'analysis', "None"] - text += " and the analysis that was made of this event is " - if is_safe_attribute(misp_event, item[1]) and str(getattr(misp_event, item[1])) in analysis_map: - text += analysis_map[str(getattr(misp_event, item[1]))] - else: - text += " undefined" - text += "." - - ''' - The event is currently {Published} and has associated attributes {Attribute Number}. - ''' - - item = ["Published", 'published', "None", "publish_timestamp"] - text += " The event is currently " - if is_safe_attribute(misp_event, item[1]) and getattr(misp_event, item[1]): - text += " published" - if is_safe_attribute(misp_event, item[3]): - text += " since " + getattr(misp_event, item[3]).strftime(EXPORT_DATE_FORMAT) - else: - text += " private" - - # Number of Attributes - item = ["# Attributes", 'Attribute', "None"] - text += ", has " - if is_safe_attribute_table(misp_event, item[1]): - text += str(len(getattr(misp_event, item[1]))) - else: - text += " 0" - - text += " associated attributes" - - # Number of Objects - item = ["# Objects", 'Object', "None"] - text += " and has " - if is_safe_attribute_table(misp_event, item[1]): - text += str(len(getattr(misp_event, item[1]))) - else: - text += " 0" - - text += " associated objects." - - ''' - For more information on the event, please consult the rest of the document - ''' - text += "
For more information on the event, please consult following information." - - col1_style, col2_style = get_table_styles() - description_style = ParagraphStyle(name='Description', parent=col2_style, alignment=TA_JUSTIFY) - - return Paragraph(text, description_style) - - -def create_flowable_table_from_attributes(misp_event, config=None): - ''' - Returns a list of flowables representing the list of attributes of a misp event. - The list is composed alternatively of headers and tables, to add to the pdf - :param misp_event: A misp event - :return: a table of flowables - ''' - flowable_table = [] - sample_style_sheet = getSampleStyleSheet() - i = 0 - - if is_safe_attribute_table(misp_event, "Attribute"): - # There is some attributes for this object - 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), sample_style_sheet['Heading4'])) - flowable_table.append(create_flowable_table_from_one_attribute(item, config)) - i += 1 - else: - # No attributes for this object - flowable_table.append(Paragraph("No attributes", sample_style_sheet['Heading4'])) - - return flowable_table - - -def create_flowable_table_from_tags(misp_event): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of tags of an event or a misp event - :param misp_event: A misp event - :return: a table of flowable to add to the pdf - ''' - - flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 0 - - if is_safe_attribute_table(misp_event, - "Tag"): # and len(getattr(misp_event, "Tag")) > 1: # 'Tag' can exist and be empty - # There is some tags for this object - for item in getattr(misp_event, "Tag"): - flowable_table.append(create_flowable_tag(item)) - i += 1 - answer_tags = create_tags_table_from_data(flowable_table) - else: - # No tags for this object - answer_tags = [Paragraph("No tags", col2_style)] - - return answer_tags - -def create_flowable_table_from_objects(misp_event, config=None): - ''' - Returns a list of flowables representing the list of objects of a misp event. - The list is composed of a serie of - [ header object, table of object information, [ header of attribute, table of attribute]*] to add to the pdf - :param misp_event: A misp event - :return: a table of flowables - ''' - - flowable_table = [] - sample_style_sheet = getSampleStyleSheet() - i = 0 - - if is_safe_attribute_table(misp_event, "Object"): - - # There is a list of objects - 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(Paragraph("Object #" + str(i), sample_style_sheet['Heading3'])) - flowable_table += create_flowable_table_from_one_object(item, config) - i += 1 - else: - # No object found - flowable_table.append(Paragraph("No object", sample_style_sheet['Heading3'])) - - return flowable_table - - -def create_flowable_paragraph_from_sightings(misp_attribute, item, col2_style): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of sightings of an event or a misp event - :param misp_event: A misp event - :return: a table of flowable to add to the pdf - ''' - - col1_style, col2_style = get_table_styles() - i = 0 - POSITIVE_SIGHT_COLOR = 'green' - NEGATIVE_SIGHT_COLOR = 'red' - MISC_SIGHT_COLOR = 'orange' - - list_sighting = [0, 0, 0] - if is_safe_attribute_table(misp_attribute, "Sighting"): - # There is some tags for this object - for curr_item in getattr(misp_attribute, "Sighting"): - # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): - if "type" in curr_item: - # Store the likes/dislikes depending on their types - list_sighting[int(curr_item["type"])] += 1 - i += 1 - - # Create the sighting text - sight_text = " Positive : " + str(list_sighting[0]) + "" - sight_text += " / " + " Negative : " + str( - list_sighting[1]) + "" - sight_text += " / " + " Misc. : " + str(list_sighting[2]) + "" - - answer_sighting = Paragraph(sight_text, col2_style) - else: - # No tags for this object - answer_sighting = Paragraph("No sighting", col2_style) - - return answer_sighting - - -def create_flowable_table_from_galaxies(misp_event): - ''' - Returns a Table (flowable) to add to a pdf, representing the list of galaxies of an event or a misp event - :param misp_event: A misp event - :return: a table of flowables to add to the pdf - ''' - - flowable_table = [] - col1_style, col2_style = get_table_styles() - i = 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(create_flowable_table_from_one_galaxy(item)) - i += 1 - answer_tags = create_flowable_table_from_data(flowable_table, ["99%"]) - else: - # No galaxies for this object - answer_tags = [Paragraph("No galaxies", col2_style)] - - return answer_tags ######################################################################## # Handling static parts drawn on the upper layer -def set_template(canvas, doc, misp_event, config=None): - add_page_number(canvas, doc) - add_metadata(canvas, doc, misp_event, config) - # TODO : add_header() - # TODO : add_footer() -def add_metadata(canvas, doc, misp_event, config=None): - ''' - Allow to add metadata to the pdf. Would need deeper digging to change other metadata. - :param canvas: / Automatically filled during pdf compilation - :param doc: / Automatically filled during pdf compilation - :param misp_event: To send trough "partial", to get information to complete metadaa - :return: / Automatically filled during pdf compilation - ''' +class Statics_Drawings(): - if hasattr(misp_event, 'info'): - canvas.setTitle(getattr(misp_event, 'info')) + # ---------------------------------------------------------------------- + def __init__(self, config, misp_event): + self.config = config + self.misp_event = misp_event - if hasattr(misp_event, 'info'): - canvas.setSubject(getattr(misp_event, 'info')) + # ---------------------------------------------------------------------- - if hasattr(misp_event, 'Orgc'): - if hasattr(getattr(misp_event, 'Orgc'), 'name'): - canvas.setAuthor(getattr(getattr(misp_event, 'Orgc'), 'name')) - - if config is not None and moduleconfig[1] in config: - canvas.setCreator(config[moduleconfig[1]]) - else: - canvas.setCreator(getattr(getattr(misp_event, 'Orgc'), 'name')) - - if hasattr(misp_event, 'uuid'): - canvas.setKeywords(getattr(misp_event, 'uuid')) + def set_template(self, canvas, doc): + self.add_page_number(canvas, doc) + self.add_metadata(canvas, doc) + # TODO : add_header() + # TODO : add_footer() -def add_page_number(canvas, doc): - ''' - Draw the page number on each page - :param canvas: / Automatically filled during pdf compilation - :param doc: / Automatically filled during pdf compilation - :return: / Automatically filled during pdf compilation - ''' - canvas.saveState() - canvas.setFont('Times-Roman', 10) - page_number_text = "%d" % (doc.page) + 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 + :param doc: / Automatically filled during pdf compilation + :param misp_event: To send trough "partial", to get information to complete metadaa + :return: / Automatically filled during pdf compilation + ''' - curr_spacing = 4 * mm # 0.75 * inch + if is_safe_attribute(self.misp_event, 'info'): + canvas.setTitle(getattr(self.misp_event, 'info')) - canvas.drawCentredString( - curr_spacing, - curr_spacing, - page_number_text - ) + if is_safe_attribute(self.misp_event, 'info'): + canvas.setSubject(getattr(self.misp_event, 'info')) - canvas.restoreState() + if is_safe_attribute(self.misp_event, 'Orgc'): + 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) : + canvas.setCreator(self.config[moduleconfig[1]]) + else: + canvas.setCreator(getattr(getattr(self.misp_event, 'Orgc'), 'name')) + + if is_safe_attribute(self.misp_event, 'uuid'): + canvas.setKeywords(getattr(self.misp_event, 'uuid')) + + + def add_page_number(self,canvas, doc): + ''' + Draw the page number on each page + :param canvas: / Automatically filled during pdf compilation + :param doc: / Automatically filled during pdf compilation + :return: / Automatically filled during pdf compilation + ''' + canvas.saveState() + canvas.setFont('Times-Roman', 10) + page_number_text = "%d" % (doc.page) + + curr_spacing = 4 * mm # 0.75 * inch + + canvas.drawCentredString( + curr_spacing, + curr_spacing, + page_number_text + ) + + canvas.restoreState() ######################################################################## @@ -1285,37 +1384,45 @@ def collect_parts(misp_event, config=None): ''' # List of elements/content we want to add flowables = [] + # Get the list of available styles sample_style_sheet = getSampleStyleSheet() + col1_style, col2_style = get_table_styles() + curr_val_f = Value_Formatter(config, col1_style, col2_style) # Create stuff title_style = ParagraphStyle(name='Column_1', parent=sample_style_sheet['Heading1'], alignment=TA_CENTER) - title = get_value_link_to_event(misp_event, ["Info", 'info', "None"], title_style, config, False) + 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) - if config is not None and moduleconfig[2] in config: + # Creation of handling objects + curr_event = Event_Metadata(config, curr_val_f) + curr_attr = Attributes(config, curr_val_f) + curr_object = Object(config, curr_val_f) + + if is_in_config(config,2) : # If description is activated description = Paragraph("Description", sample_style_sheet['Heading2']) - description_text = create_flowable_description_from_event(misp_event, config) + description_text = curr_event.create_flowable_description_from_event(misp_event) flowables.append(description) flowables.append(description_text) subtitle = Paragraph("General information", sample_style_sheet['Heading2']) - table_general_metainformation = create_flowable_table_from_event(misp_event, config) + table_general_metainformation = curr_event.create_flowable_table_from_event(misp_event) flowables.append(subtitle) flowables.append(table_general_metainformation) flowables.append(PageBreak()) event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) - table_direct_attributes = create_flowable_table_from_attributes(misp_event, config) + table_direct_attributes = curr_attr.create_flowable_table_from_attributes(misp_event) flowables.append(event_attributes_title) flowables += table_direct_attributes flowables.append(PageBreak()) event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) - table_objects = create_flowable_table_from_objects(misp_event, config) + table_objects = curr_object.create_flowable_table_from_objects(misp_event) flowables.append(event_objects_title) flowables += table_objects @@ -1325,7 +1432,7 @@ def collect_parts(misp_event, config=None): return flowables -def export_flowables_to_pdf(document, misp_event, flowables, config=None): +def export_flowables_to_pdf(document, misp_event, flowables, config): ''' Export function : creates a pdf from a list of flowables, adding page numbers, etc. :param document: A document template @@ -1334,13 +1441,15 @@ def export_flowables_to_pdf(document, misp_event, flowables, config=None): :return: ''' + static_drawer = Statics_Drawings(config, misp_event) + document.build( flowables, # Partial used to set the metadata - onFirstPage=partial(set_template, misp_event=misp_event, config=config), # Pagination for first page - onLaterPages=partial(set_template, misp_event=misp_event, config=config), # Pagination for all other page + 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 ######################################################################## # "EXTERNAL" exposed METHODS. Meant to be used outside of this class. @@ -1375,6 +1484,7 @@ def convert_event_in_pdf_buffer(misp_event, config=None): return pdf_value + def get_values_from_buffer(pdf_buffer): return pdf_buffer.value()