fix: [reportlab] working clusters and galaxies. Not nice however

pull/358/head
Falconieri 2019-02-28 15:14:52 +01:00
parent 6031a7d426
commit 79e66363d2
1 changed files with 192 additions and 125 deletions

View File

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