diff --git a/pymisp/tools/reportlab_generator.py b/pymisp/tools/reportlab_generator.py index a878a2c..ec9d44c 100644 --- a/pymisp/tools/reportlab_generator.py +++ b/pymisp/tools/reportlab_generator.py @@ -125,7 +125,7 @@ class Flowable_Tag(Flowable): # Copy of pdfexport.py moduleconfig -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description"] # == Row colors of the table (alternating) == EVEN_COLOR = colors.whitesmoke @@ -174,6 +174,22 @@ NOT_A_PICTURE_MESSAGE = "This attachment is not recognized as an image. Please a GOOD_LINK_COLOR = 'blue' BAD_LINK_COLOR = 'red' +# == Parameters for description == +LOW_THREAT_COLOR = 'green' +MEDIUM_THREAT_COLOR = 'orange' +HIGH_THREAT_COLOR = 'red' + +# == Parameters for improvement of event's metadata == + +threat_map = {"0": " undefined (0)", + "3": " Low (3)", + "2": " Medium (2)", + "1": " High (1)"} + +analysis_map = {"0": " Initial (0)", + "1": " Ongoing (1)", + "2": " Completed (2)"} + ######################################################################## # "UTILITIES" METHODS. Not meant to be used except for development purposes @@ -338,6 +354,62 @@ def get_value_link_to_event(misp_event, item, col2_style, config=None, color=Tru 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 @@ -349,7 +421,7 @@ def get_timestamp_value(misp_event, item, col2_style): :return: a Paragraph to add in the pdf, regarding the values of "timestamp" ''' if is_safe_attribute(misp_event, item[1]): - return Paragraph(str(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)), col2_style) + return Paragraph(safe_string(getattr(misp_event, item[1]).strftime(EXPORT_DATE_FORMAT)), col2_style) return Paragraph(item[2], col2_style) @@ -379,7 +451,7 @@ def get_attributes_number_value(misp_event, item, col2_style): :return: a Paragraph to add in the pdf, regarding the values of "attributes" ''' if is_safe_attribute(misp_event, item[1]): - return Paragraph(str(len(getattr(misp_event, item[1]))), col2_style) + return Paragraph(safe_string(len(getattr(misp_event, item[1]))), col2_style) return Paragraph(item[2], col2_style) @@ -443,10 +515,12 @@ 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) != "" + 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 create_flowable_table_from_one_attribute(misp_attribute): ''' Returns a table (flowalbe) representing the attribute @@ -496,6 +570,12 @@ def create_flowable_table_from_one_attribute(misp_attribute): if is_safe_attribute_table(misp_attribute, item[1]): data.append([Paragraph(item[0], col1_style), get_tag_value(misp_attribute, item, col2_style)]) + # Tags + 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)]) + return create_flowable_table_from_data(data) @@ -741,22 +821,6 @@ def create_flowable_table_from_event(misp_event, config=None): :return: a table that can be added to a pdf ''' - # 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 = [ - # ["Event ID", 'id', "None"], - ["Date", 'date', "None"], - ["Owner org", 'owner', "None"], - ["Threat level", 'threat_level_id', "None"], # TODO : improve design - ["Analysis", 'analysis', "None"], # TODO : improve design + Ask where the enum is ! - # TODO : Not present ["Email", 'email', "None"], - # TODO : ["Distribution", 'distribution', "None"], - # TODO : ["First recorded change", 'TODO', "None"], - # TODO : ["Last change", 'TODO', "None"], - # TODO : ["Modification map", 'TODO', "None"], - # TODO : ["Sightings", 'TODO', "None"] - ] - data = [] col1_style, col2_style = get_table_styles() @@ -765,19 +829,22 @@ def create_flowable_table_from_event(misp_event, config=None): item = ["UUID", 'uuid', "None"] data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) - # Automated adding of standard (python) attributes of the misp event - # Note that PEP 0363 may change the syntax in future release : https://www.python.org/dev/peps/pep-0363/ - for item in list_attr_automated: - if hasattr(misp_event, item[1]): - # The attribute exist, we fetch it and create the row - data.append( - [Paragraph(item[0], col1_style), - get_unoverflowable_paragraph(getattr(misp_event, item[1]), col2_style)]) - else: - # The attribute does not exist ,we print a default text on the row - data.append([Paragraph(item[0], col1_style), Paragraph(item[2], col2_style)]) + # 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)]) - # Manual addition # Info item = ["Info", 'info', "None"] data.append([Paragraph(item[0], col1_style), get_value_link_to_event(misp_event, item, col2_style, config)]) @@ -805,6 +872,111 @@ def create_flowable_table_from_event(misp_event, config=None): 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): ''' Returns a list of flowables representing the list of attributes of a misp event. @@ -842,7 +1014,8 @@ def create_flowable_table_from_tags(misp_event): 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 + 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)) @@ -883,6 +1056,43 @@ def create_flowable_table_from_objects(misp_event): 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 item in getattr(misp_attribute, "Sighting"): + # TODO : When Sightings will be object : if is_safe_attribute(item, "type"): + if "type" in item: + # Store the likes/dislikes depending on their types + list_sighting[int(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 + + ######################################################################## # Handling static parts drawn on the upper layer @@ -960,39 +1170,36 @@ def collect_parts(misp_event, config=None): # 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) + # Add all parts to final PDF + flowables.append(title) + + if config is not None and moduleconfig[2] in config: + description = Paragraph("Description", sample_style_sheet['Heading2']) + description_text = create_flowable_description_from_event(misp_event, config) + 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) - - event_attributes_title = Paragraph("Attributes", sample_style_sheet['Heading2']) - table_direct_attributes = create_flowable_table_from_attributes(misp_event) - - event_objects_title = Paragraph("Objects", sample_style_sheet['Heading2']) - table_objects = create_flowable_table_from_objects(misp_event) - - event_sighting_title = Paragraph("Sighting", sample_style_sheet['Heading2']) - # TODO : table_event_sightings = create_flowable_table_from_sightings(misp_event) - - # If you want to output the full json (as debug), just add next line - # paragraph_2 = Paragraph(str(misp_event.to_json()), sample_style_sheet['Code']) - - # Add all parts to final PDF - flowables.append(title) 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) 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) flowables.append(event_objects_title) flowables += table_objects - # TODO : flowables.append(PageBreak()) - # TODO : flowables.append(event_sighting_title) - # TODO : flowables += table_event_sightings + # If you want to output the full json (as debug), just add next line and add it to flowables + # paragraph_2 = Paragraph(str(misp_event.to_json()), sample_style_sheet['Code']) return flowables diff --git a/tests/reportlab_testfiles/sighting_1.json b/tests/reportlab_testfiles/sighting_1.json new file mode 100644 index 0000000..b42ccdf --- /dev/null +++ b/tests/reportlab_testfiles/sighting_1.json @@ -0,0 +1,305 @@ +{ + "Event": { + "id": "60", + "orgc_id": "5", + "org_id": "1", + "date": "2018-08-01", + "threat_level_id": "3", + "info": "Ursnif, MALWAREMESSIAGH", + "published": true, + "uuid": "5b646415-7b48-40d5-86b4-c0070acd0835", + "attribute_count": "5", + "analysis": "2", + "timestamp": "1533306089", + "distribution": "3", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1550506283", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "5", + "name": "Synovus Financial", + "uuid": "5a68c02d-959c-4c8a-a571-0dcac0a8060a" + }, + "Attribute": [ + { + "id": "8885", + "type": "domain", + "category": "Network activity", + "to_ids": true, + "uuid": "5b6464ca-e73c-4707-9b8a-d0350acd0835", + "event_id": "60", + "distribution": "5", + "timestamp": "1533306058", + "comment": "Ursnif", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "ooiasjdnqjwbeasdasd.com", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "8", + "attribute_id": "8885", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253950", + "uuid": "5c7641bf-a4e8-4d5d-a653-03240a00020f", + "source": "", + "type": "0", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464ca-e73c-4707-9b8a-d0350acd0835" + } + ] + }, + { + "id": "8886", + "type": "domain", + "category": "Network activity", + "to_ids": true, + "uuid": "5b6464ca-45f8-43d0-8b78-d0350acd0835", + "event_id": "60", + "distribution": "5", + "timestamp": "1533306058", + "comment": "Ursnif", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "eqowiesajenqweasd.com", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "9", + "attribute_id": "8886", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253959", + "uuid": "5c7641c7-f020-4643-92b4-03240a00020f", + "source": "", + "type": "1", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464ca-45f8-43d0-8b78-d0350acd0835" + } + ] + }, + { + "id": "8887", + "type": "domain", + "category": "Network activity", + "to_ids": true, + "uuid": "5b6464ca-8c84-4c2d-95d9-d0350acd0835", + "event_id": "60", + "distribution": "5", + "timestamp": "1533306058", + "comment": "Ursnif", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "dquohwdihaewqdcas.com", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "10", + "attribute_id": "8887", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253962", + "uuid": "5c7641cb-ccc0-44ee-ab75-03240a00020f", + "source": "", + "type": "1", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464ca-8c84-4c2d-95d9-d0350acd0835" + } + ] + }, + { + "id": "8888", + "type": "domain", + "category": "Network activity", + "to_ids": true, + "uuid": "5b6464ca-e0a0-40e0-8e21-d0350acd0835", + "event_id": "60", + "distribution": "5", + "timestamp": "1533306058", + "comment": "Ursnif", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "diqjwhebseqhbasdh.com", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "11", + "attribute_id": "8888", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253968", + "uuid": "5c7641d5-58bc-4d20-9a84-05f10a00020f", + "source": "honeyp", + "type": "2", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464ca-e0a0-40e0-8e21-d0350acd0835" + }, + { + "id": "12", + "attribute_id": "8888", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253976", + "uuid": "5c7641db-a9a0-49b0-b536-05f10a00020f", + "source": "dede", + "type": "1", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464ca-e0a0-40e0-8e21-d0350acd0835" + } + ] + }, + { + "id": "8889", + "type": "url", + "category": "Payload delivery", + "to_ids": true, + "uuid": "5b6464e9-e73c-484d-a0b3-c0070acd0835", + "event_id": "60", + "distribution": "5", + "timestamp": "1533306089", + "comment": "Ursnif dropped file", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "http:\/\/sistemait.it\/softaculous\/backup\/client.rar", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "7", + "attribute_id": "8889", + "event_id": "60", + "org_id": "1", + "date_sighting": "1551253943", + "uuid": "5c7641b7-b618-4e41-a9c9-03240a00020f", + "source": "", + "type": "0", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5b6464e9-e73c-484d-a0b3-c0070acd0835" + } + ] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [ + { + "id": "4", + "uuid": "59f20cce-5420-4084-afd5-0884c0a83832", + "name": "Banker", + "type": "banker", + "description": "Banking malware galaxy.", + "version": "3", + "icon": "usd", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "289", + "collection_uuid": "b9448d2a-a23c-4bf2-92a1-d860716ba2f3", + "type": "banker", + "value": "Gozi", + "tag_name": "misp-galaxy:banker=\"Gozi\"", + "description": "Banking trojan delivered primarily via email (typically malspam) and exploit kits. Gozi 1.0 source leaked in 2010", + "galaxy_id": "4", + "source": "Open Sources", + "authors": [ + "Unknown", + "raw-data" + ], + "version": "16", + "uuid": "", + "tag_id": "86", + "meta": { + "date": [ + "First seen ~ 2007" + ], + "refs": [ + "https:\/\/www.secureworks.com\/research\/gozi", + "https:\/\/www.gdatasoftware.com\/blog\/2016\/11\/29325-analysis-ursnif-spying-on-your-data-since-2007", + "https:\/\/lokalhost.pl\/gozi_tree.txt" + ], + "synonyms": [ + "Ursnif", + "CRM", + "Snifula", + "Papras" + ] + } + } + ] + } + ], + "Object": [], + "Tag": [ + { + "id": "85", + "name": "PasteBin: MALWAREMESSIAGH", + "colour": "#ab34e3", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + }, + { + "id": "86", + "name": "misp-galaxy:banker=\"Gozi\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } +} \ No newline at end of file diff --git a/tests/reportlab_testfiles/sighting_2.json b/tests/reportlab_testfiles/sighting_2.json new file mode 100644 index 0000000..b3f027e --- /dev/null +++ b/tests/reportlab_testfiles/sighting_2.json @@ -0,0 +1,221 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2019-02-18", + "threat_level_id": "2", + "info": "This is the description", + "published": false, + "uuid": "5c6ab833-676c-42f3-95d2-034f0a00020f", + "attribute_count": "2", + "analysis": "1", + "timestamp": "1551253649", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "0", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Orgc": { + "id": "1", + "name": "ORGNAME", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c" + }, + "Attribute": [ + { + "id": "1", + "type": "comment", + "category": "Antivirus detection", + "to_ids": false, + "uuid": "5c6ab897-0f68-44ca-8d62-0c150a00020f", + "event_id": "1", + "distribution": "0", + "timestamp": "1550497961", + "comment": "Contextual commentary", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "Value to test", + "Galaxy": [ + { + "id": "15", + "uuid": "90ccdf38-1649-11e8-b8bf-e7326d553087", + "name": "Botnet", + "type": "botnet", + "description": "Botnet galaxy.", + "version": "2", + "icon": "sitemap", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "2511", + "collection_uuid": "0d58f329-1356-468c-88ab-e21fbb64c02b", + "type": "botnet", + "value": "Asprox", + "tag_name": "misp-galaxy:botnet=\"Asprox\"", + "description": "The Asprox botnet (discovered around 2008), also known by its aliases Badsrc and Aseljo, is a botnet mostly involved in phishing scams and performing SQL injections into websites in order to spread malware.", + "galaxy_id": "15", + "source": "MISP Project", + "authors": [ + "Various" + ], + "version": "18", + "uuid": "", + "tag_id": "1", + "meta": { + "date": [ + "2008" + ], + "refs": [ + "https:\/\/en.wikipedia.org\/wiki\/Asprox_botnet" + ], + "synonyms": [ + "Badsrc", + "Aseljo", + "Danmec", + "Hydraflux" + ] + } + } + ] + } + ], + "ShadowAttribute": [], + "Tag": [ + { + "id": "1", + "name": "misp-galaxy:botnet=\"Asprox\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ], + "Sighting": [ + { + "id": "5", + "attribute_id": "1", + "event_id": "1", + "org_id": "1", + "date_sighting": "1551253653", + "uuid": "5c764095-129c-4e81-956d-0e1a0a00020f", + "source": "", + "type": "1", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5c6ab897-0f68-44ca-8d62-0c150a00020f" + } + ] + }, + { + "id": "242460", + "type": "target-external", + "category": "Targeting data", + "to_ids": true, + "uuid": "5c764091-273c-4821-92ad-0e1a0a00020f", + "event_id": "1", + "distribution": "2", + "timestamp": "1551253649", + "comment": "This is a contextual comment", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "value": "testvalue", + "Galaxy": [], + "ShadowAttribute": [], + "Sighting": [ + { + "id": "6", + "attribute_id": "242460", + "event_id": "1", + "org_id": "1", + "date_sighting": "1551253653", + "uuid": "5c764095-26ec-4de1-99f2-0e1a0a00020f", + "source": "", + "type": "0", + "Organisation": { + "id": "1", + "uuid": "5c6983c8-3af8-4304-869c-4800d6c1883c", + "name": "ORGNAME" + }, + "attribute_uuid": "5c764091-273c-4821-92ad-0e1a0a00020f" + } + ] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [ + { + "id": "15", + "uuid": "90ccdf38-1649-11e8-b8bf-e7326d553087", + "name": "Botnet", + "type": "botnet", + "description": "Botnet galaxy.", + "version": "2", + "icon": "sitemap", + "namespace": "misp", + "GalaxyCluster": [ + { + "id": "2511", + "collection_uuid": "0d58f329-1356-468c-88ab-e21fbb64c02b", + "type": "botnet", + "value": "Asprox", + "tag_name": "misp-galaxy:botnet=\"Asprox\"", + "description": "The Asprox botnet (discovered around 2008), also known by its aliases Badsrc and Aseljo, is a botnet mostly involved in phishing scams and performing SQL injections into websites in order to spread malware.", + "galaxy_id": "15", + "source": "MISP Project", + "authors": [ + "Various" + ], + "version": "18", + "uuid": "", + "tag_id": "1", + "meta": { + "date": [ + "2008" + ], + "refs": [ + "https:\/\/en.wikipedia.org\/wiki\/Asprox_botnet" + ], + "synonyms": [ + "Badsrc", + "Aseljo", + "Danmec", + "Hydraflux" + ] + } + } + ] + } + ], + "Object": [], + "Tag": [ + { + "id": "1", + "name": "misp-galaxy:botnet=\"Asprox\"", + "colour": "#0088cc", + "exportable": true, + "user_id": "0", + "hide_tag": false, + "numerical_value": null + } + ] + } +} \ No newline at end of file diff --git a/tests/test_reportlab.py b/tests/test_reportlab.py index 90e83aa..721cb7f 100644 --- a/tests/test_reportlab.py +++ b/tests/test_reportlab.py @@ -26,6 +26,8 @@ class TestMISPEvent(unittest.TestCase): self.test_image_folder = self.root + "image_json/" self.storage_folder = self.root + "reportlab_testoutputs/" self.storage_image_folder = self.root + "reportlab_test_image_outputs/" + self.moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description"] + def init_event(self): self.mispevent.info = 'This is a test' @@ -91,9 +93,8 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[0]] = "http://localhost:8080" - config[moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') @@ -106,8 +107,7 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[0]] = "http://localhost:8080" self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') @@ -120,8 +120,7 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'very_long_event.json') @@ -134,9 +133,8 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[0]] = "http://localhost:8080" - config[moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'image_event.json') @@ -149,9 +147,8 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[0]] = "http://localhost:8080" - config[moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'mainly_objects_1.json') @@ -164,15 +161,57 @@ class TestMISPEvent(unittest.TestCase): else: config = {} - moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] - config[moduleconfig[0]] = "http://localhost:8080" - config[moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" self.init_event() self.mispevent.load_file(self.test_folder + 'mainly_objects_2.json') reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), self.storage_folder + "mainly_objects_2.pdf") + def test_sightings_1_json(self): + if self.check_python_2(): + self.assertTrue(True) + else: + + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + + self.init_event() + self.mispevent.load_file(self.test_folder + 'sighting_1.json') + reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "sighting_1.pdf") + + def test_sightings_2_json(self): + if self.check_python_2(): + self.assertTrue(True) + else: + + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + + self.init_event() + self.mispevent.load_file(self.test_folder + 'sighting_2.json') + reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "sighting_2.pdf") + + def test_textual_json(self): + if self.check_python_2(): + self.assertTrue(True) + else: + + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True + + self.init_event() + self.mispevent.load_file(self.test_folder + 'very_long_event.json') + reportlab_generator.register_value_to_file(reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + "textual.pdf") + def test_batch_image_events(self): # Test case ONLY for manual testing. Needs to download a full list of image events ! @@ -229,3 +268,36 @@ class TestMISPEvent(unittest.TestCase): self.storage_folder + curr_file + ".pdf") print("Elapsed time : " + str(time.time() - t)) # Local run : 1958.930s for 1064 files + + def test_batch_OSINT_with_config_events(self): + # Test case ONLY for manual testing. Needs to download a full list of OSINT events ! + + if self.check_python_2(): + self.assertTrue(True) + elif not manual_testing : + self.assertTrue(True) + else: + self.init_event() + + config = {} + config[self.moduleconfig[0]] = "http://localhost:8080" + config[self.moduleconfig[1]] = "My Wonderful CERT" + config[self.moduleconfig[2]] = True + + file_nb = str(len(os.listdir(self.test_batch_folder))) + i = 0 + t = time.time() + for curr_file in os.listdir(self.test_batch_folder): + self.mispevent = MISPEvent() + file_path = self.test_batch_folder + curr_file + + print("Current file : " + file_path + " " + str(i) + " over " + file_nb) + i += 1 + + self.mispevent.load_file(file_path) + + reportlab_generator.register_value_to_file( + reportlab_generator.convert_event_in_pdf_buffer(self.mispevent, config), + self.storage_folder + curr_file + ".pdf") + print("Elapsed time : " + str(time.time() - t)) + # Local run : 1958.930s for 1064 files