aboutsummaryrefslogtreecommitdiffstats
path: root/lib/srtgui/reports.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/srtgui/reports.py')
-rw-r--r--lib/srtgui/reports.py407
1 files changed, 367 insertions, 40 deletions
diff --git a/lib/srtgui/reports.py b/lib/srtgui/reports.py
index 715c5606..3a7414c6 100644
--- a/lib/srtgui/reports.py
+++ b/lib/srtgui/reports.py
@@ -22,6 +22,10 @@ import os
import logging
from datetime import datetime, timedelta
import csv
+from openpyxl import Workbook
+from openpyxl import load_workbook
+from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
+from openpyxl.utils import get_column_letter
from orm.models import Cve, CveSource, Vulnerability, Investigation, Defect, Product
from orm.models import Package
@@ -34,7 +38,7 @@ from django.db.models import Q
logger = logging.getLogger("srt")
-SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
SRT_REPORT_DIR = '%s/reports' % SRT_BASE_DIR
# quick development/debugging support
@@ -52,6 +56,54 @@ def _log_args(msg, *args, **kwargs):
s += ')'
_log(s)
+###############################################################################
+# Excel/openpyxl common look and feel formatting objects
+#
+
+#pyxl_border_all = Border(left=thin, right=thin, top=thin, bottom=thin) # , outline=True)
+pyxl_thin = Side(border_style="thin")
+pyxl_double = Side(border_style="double")
+pyxl_border_left = Border(left=pyxl_thin)
+pyxl_border_bottom = Border(bottom=pyxl_thin)
+pyxl_border_bottom_left = Border(bottom=pyxl_thin, left=pyxl_thin)
+pyxl_alignment_left = Alignment(horizontal='left')
+pyxl_alignment_right = Alignment(horizontal='right')
+pyxl_alignment_wrap = Alignment(wrap_text=True)
+pyxl_font_bold = Font(bold=True)
+pyxl_font_red = Font(color="A00000",bold=True,size = "13")
+pyxl_font_grn = Font(color="00A000",bold=True,size = "13")
+pyxl_font_blu = Font(color="0000A0",bold=True,size = "13")
+pyxl_font_orn = Font(color="FF6600",bold=True,size = "13")
+pyxl_fill_green = PatternFill(start_color="E0FFF0", end_color="E0FFF0", fill_type = "solid")
+# Warning: the form "PatternFill(bgColor="xxxxxx", fill_type = "solid")" returns black cells
+pyxl_backcolor_red = PatternFill(start_color='FCCDBA', end_color='FCCDBA', fill_type = "solid")
+pyxl_backcolor_orn = PatternFill(start_color='FBEAAB', end_color='FBEAAB', fill_type = "solid")
+pyxl_backcolor_yel = PatternFill(start_color='FCFDC7', end_color='FCFDC7', fill_type = "solid")
+pyxl_backcolor_blu = PatternFill(start_color='C5E2FF', end_color='C5E2FF', fill_type = "solid")
+pyxl_backcolor_grn = PatternFill(start_color='D6EDBD', end_color='D6EDBD', fill_type = "solid")
+pyxl_cve_fills = [pyxl_backcolor_red,pyxl_backcolor_orn,pyxl_backcolor_yel,pyxl_backcolor_blu,None,None,None]
+
+def pyxl_write_cell(ws,row_num,column_num,value,border=None,font=None,fill=None,alignment=None):
+ cell = ws.cell(row=row_num, column=column_num)
+ try:
+ cell.value = value
+ if fill:
+ cell.fill = fill
+ if alignment:
+ cell.alignment = alignment
+ if border:
+ cell.border = border
+ if font:
+ cell.font = font
+ except Exception as e:
+ print("ERROR:(%d,%d):%s" % (row_num,column_num,e))
+ # Optional next column return value
+ return(column_num+1)
+
+###############################################################################
+# Core report support
+#
+
class Report():
def __init__(self, parent_page, *args, **kwargs):
self.parent_page = parent_page
@@ -679,6 +731,8 @@ class CvesReport(Report):
context['report_type_list'] = '\
<option value="summary">CVEs Table</option> \
+ <option value="year_pub_summary">CVE by Year Prefix Summary</option> \
+ <option value="year_summary">CVE by Publish Date Summary</option> \
<option value="cve_defects">CVE to Defects Table</option> \
'
context['report_get_title'] = ''
@@ -692,7 +746,14 @@ class CvesReport(Report):
'
context['report_format_list'] = '\
<input type="radio" name="format" value="txt" checked> Text (comma delimited)<br> \
- <input type="radio" name="format" value="csv"> CSV (tab delimited)<br> \
+ <input type="radio" name="format" value="csv"> CSV \
+ (Separator: \
+ <select name="csv_separator"> \
+ <option value="comma" checked>Comma</option> \
+ <option value="semi">Semi-colon</option> \
+ <option value="tab">Tab</option> \
+ <br> \
+ </select>) \
'
context['report_custom_list'] = '\
CVE name filter = <input type="text" placeholder="e.g. CVE-2018" name="name_filter" size="40"> <br>\
@@ -877,17 +938,20 @@ class CvesReport(Report):
request_POST = self.request.POST
- range = request_POST.get('range', '')
+ range_rec = request_POST.get('range', '')
columns = request_POST.get('columns', '')
format = request_POST.get('format', '')
title = request_POST.get('title', '')
report_type = request_POST.get('report_type', '')
record_list = request_POST.get('record_list', '')
name_filter = request_POST.get('name_filter', '').upper()
+ csv_separator = request_POST.get('csv_separator', 'semi')
report_name = '%s/cves_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
if 'csv' == format:
- delimiter = '\t'
+ delimiter = ';'
+ if csv_separator == 'comma': delimiter = ','
+ if csv_separator == 'tab': delimiter = '\t'
else:
delimiter = ','
@@ -896,14 +960,14 @@ class CvesReport(Report):
quotechar='"', quoting=csv.QUOTE_MINIMAL)
if ('summary' == report_type):
self.print_row_summary(writer,True,"all" == columns,None)
- if 'displayed' == range:
+ if 'displayed' == range_rec:
for id in record_list.split(','):
if not id:
continue
cve = Cve.objects.get(id=id)
if not name_filter or (name_filter in cve.name):
self.print_row_summary(writer,False,"all" == columns,cve)
- elif 'all' == range:
+ elif 'all' == range_rec:
if name_filter:
query = Cve.objects.filter(name__contains=name_filter).order_by('name')
else:
@@ -913,14 +977,14 @@ class CvesReport(Report):
if ('cve_defects' == report_type):
self.print_row_cve_defects(writer,'header',"all" == columns,None,None,None,None)
- if 'displayed' == range:
+ if 'displayed' == range_rec:
for id in record_list.split(','):
if not id:
continue
cve = Cve.objects.get(id=id)
if not name_filter or (name_filter in cve.name):
self.print_row_cve_defects(writer,'cve',"all" == columns,cve,None,None,None)
- elif 'all' == range:
+ elif 'all' == range_rec:
if name_filter:
query = Cve.objects.filter(name__contains=name_filter).order_by('name')
else:
@@ -928,6 +992,114 @@ class CvesReport(Report):
for cve in query:
self.print_row_cve_defects(writer,'line',"all" == columns,cve,None,None,None)
+ if report_type in ['year_summary','year_pub_summary']:
+ columns = ["Year", "CVE_Total", "CVE_HIST", "CVE_NEW", "CVE_RES", "CVE_INV", "CVE_VUL", "CVE_NVUL", "Defect_Total", "DEFECT_HIST", "DEFECT_NEW", "DEFECT_RES", "DEFECT_INV", "DEFECT_VUL", "DEFECT_NVUL","BY_PUBLISH"]
+ for i,column in enumerate(columns):
+ csvfile.write("%s%s" % (columns[i],delimiter))
+ csvfile.write("\n")
+
+ summary = {}
+ YEAR_START = 1999
+ YEAR_STOP = 2020
+ for the_year in range(YEAR_START,YEAR_STOP+1):
+ summary[the_year] = {
+ 'CVE_TOTAL':0,
+ 'CVE_HISTORICAL':0,
+ 'CVE_NEW':0,
+ 'CVE_NEW_RESERVED':0,
+ 'CVE_INVESTIGATE':0,
+ 'CVE_VULNERABLE':0,
+ 'CVE_NOT_VULNERABLE':0,
+ 'DEFECT_TOTAL':0,
+ 'DEFECT_HISTORICAL':0,
+ 'DEFECT_NEW':0,
+ 'DEFECT_NEW_RESERVED':0,
+ 'DEFECT_INVESTIGATE':0,
+ 'DEFECT_VULNERABLE':0,
+ 'DEFECT_NOT_VULNERABLE':0,
+ 'PUBLISH_DATE':0,
+ }
+
+ # Gather historgram on CVE status
+ error_count = 0
+ for cve in Cve.objects.all():
+ # Extract the year created
+ if (report_type == 'year_pub_summary') and (not cve.status in [SRTool.HISTORICAL]) and cve.publishedDate:
+ the_year = cve.publishedDate.split('-')[0]
+ summary[the_year]['PUBLISH_DATE'] += 1
+ else:
+ the_year = cve.name.split('-')[1]
+
+ if (not the_year[0].isdigit()) or (the_year < '1999') or (the_year > '2020'):
+ if 10 > error_count:
+ _log('FOO_CVE_YEARLY:%s,%s' % (cve.name, cve.publishedDate))
+ error_count += 1
+ continue
+ the_year = int(the_year)
+
+ # Register the CVE status
+ summary[the_year]['CVE_TOTAL'] += 1
+ if cve.status in [SRTool.HISTORICAL]:
+ summary[the_year]['CVE_HISTORICAL'] += 1
+ if cve.status in [SRTool.NEW,SRTool.NEW_INACTIVE]:
+ summary[the_year]['CVE_NEW'] += 1
+ if cve.status in [SRTool.NEW_RESERVED]:
+ summary[the_year]['CVE_NEW_RESERVED'] += 1
+ if cve.status in [SRTool.INVESTIGATE,SRTool.INVESTIGATE_INACTIVE]:
+ summary[the_year]['CVE_INVESTIGATE'] += 1
+ if cve.status in [SRTool.VULNERABLE,SRTool.VULNERABLE_INACTIVE]:
+ summary[the_year]['CVE_VULNERABLE'] += 1
+ if cve.status in [SRTool.NOT_VULNERABLE,SRTool.NOT_VULNERABLE_INACTIVE]:
+ summary[the_year]['CVE_NOT_VULNERABLE'] += 1
+
+ # Register the releated defects status
+ for cv in cve.cve_to_vulnerability.all():
+ for investigation in cv.vulnerability.vulnerability_investigation.all():
+ for id in investigation.investigation_to_defect.all():
+
+ # Only check defects for current and previously active products
+ if not id.product.get_product_tag('mode') in ['support','develop','eol']:
+ continue
+
+ # Register the defect status
+ summary[the_year]['DEFECT_TOTAL'] += 1
+ if id.defect.srt_status in [SRTool.HISTORICAL]:
+ summary[the_year]['DEFECT_HISTORICAL'] += 1
+ if id.defect.srt_status in [SRTool.NEW,SRTool.NEW_INACTIVE]:
+ summary[the_year]['DEFECT_NEW'] += 1
+ if id.defect.srt_status in [SRTool.NEW_RESERVED]:
+ summary[the_year]['DEFECT_NEW_RESERVED'] += 1
+ if id.defect.srt_status in [SRTool.INVESTIGATE,SRTool.INVESTIGATE_INACTIVE]:
+ summary[the_year]['DEFECT_INVESTIGATE'] += 1
+ if id.defect.srt_status in [SRTool.VULNERABLE,SRTool.VULNERABLE_INACTIVE]:
+ summary[the_year]['DEFECT_VULNERABLE'] += 1
+ if id.defect.srt_status in [SRTool.NOT_VULNERABLE,SRTool.NOT_VULNERABLE_INACTIVE]:
+ summary[the_year]['DEFECT_NOT_VULNERABLE'] += 1
+
+ # Print historgram
+ for the_year in range(YEAR_START,YEAR_STOP+1):
+ csvfile.write("%s%s" % (the_year,delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['CVE_TOTAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_HISTORICAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NEW'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NEW_RESERVED'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_INVESTIGATE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_VULNERABLE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NOT_VULNERABLE'],delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_TOTAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_HISTORICAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NEW'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NEW_RESERVED'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_INVESTIGATE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_VULNERABLE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NOT_VULNERABLE'],delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['PUBLISH_DATE'],delimiter))
+ csvfile.write("\n")
+
+
return report_name,os.path.basename(report_name)
class SelectCvesReport(Report):
@@ -1002,6 +1174,8 @@ class SelectCvesReport(Report):
file.write("%s%s" % (cve.description,tab))
file.write("\n")
+
+
return report_name,os.path.basename(report_name)
class VulnerabilitiesReport(Report):
@@ -1458,8 +1632,72 @@ class DefectsReport(Report):
return report_name,os.path.basename(report_name)
+#
+# Products Reports
+#
+
+product_summary = {}
+
+def scan_product_jira(product):
+ global product_summary
+
+ # Totals
+ critical_count = 0
+ high_count = 0
+ medium_count = 0
+ low_count = 0
+ p1_count = 0
+ p2_count = 0
+ p3_count = 0
+ p4_count = 0
+ px_count = 0
+ unresolved_count = 0
+ resolved_count = 0
+ fixed_count = 0
+ wontfix_count = 0
+ withdrawn_count = 0
+ rejected_count = 0
+
+ # Scan the registered defects
+ queryset = product.product_defect.all()
+ for defect in queryset:
+ if Defect.CRITICAL == defect.srt_priority: critical_count += 1
+ elif Defect.HIGH == defect.srt_priority: high_count += 1
+ elif Defect.MEDIUM == defect.srt_priority: medium_count += 1
+ elif Defect.LOW == defect.srt_priority: low_count += 1
+ if Defect.DEFECT_CRITICAL == defect.priority: p1_count += 1
+ elif Defect.DEFECT_HIGH == defect.priority: p2_count += 1
+ elif Defect.DEFECT_MEDIUM == defect.priority: p3_count += 1
+ elif Defect.DEFECT_LOW == defect.priority: p4_count += 1
+ if Defect.DEFECT_UNRESOLVED == defect.resolution: unresolved_count += 1
+ elif Defect.DEFECT_RESOLVED == defect.resolution: resolved_count += 1
+ elif Defect.DEFECT_FIXED == defect.resolution: fixed_count += 1
+ elif Defect.DEFECT_WILL_NOT_FIX == defect.resolution: wontfix_count += 1
+ elif Defect.DEFECT_WITHDRAWN == defect.resolution: withdrawn_count += 1
+ elif Defect.DEFECT_REJECTED == defect.resolution: rejected_count += 1
+
+ # Add this specific entry
+ product_summary[product.long_name] = [
+ critical_count,
+ high_count,
+ medium_count,
+ low_count,
+ p1_count,
+ p2_count,
+ p3_count,
+ p4_count,
+ px_count,
+ unresolved_count,
+ resolved_count,
+ fixed_count,
+ wontfix_count,
+ withdrawn_count,
+ rejected_count,
+ ]
+
class ProductsReport(Report):
"""Report for the Products Page"""
+ global product_summary
def __init__(self, parent_page, *args, **kwargs):
_log_args("REPORT_PRODUCTS_INIT(%s)" % parent_page, *args, **kwargs)
@@ -1471,6 +1709,7 @@ class ProductsReport(Report):
context['report_type_list'] = '\
<option value="summary">Products Table</option> \
+ <option value="status_jira">Product Jira Status</option> \
'
context['report_get_title'] = '1'
context['report_recordrange_list'] = '\
@@ -1480,6 +1719,7 @@ class ProductsReport(Report):
context['report_format_list'] = '\
<input type="radio" name="format" value="txt" checked> Text<br> \
<input type="radio" name="format" value="csv"> CSV<br> \
+ <input type="radio" name="excel" value="excel"> Excel<br> \
'
return context
@@ -1495,44 +1735,131 @@ class ProductsReport(Report):
report_type = request_POST.get('report_type', '')
record_list = request_POST.get('record_list', '')
- report_name = '%s/products_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
- with open(report_name, 'w') as file:
+ if 'summary' == report_type:
+ report_name = '%s/products_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
+ with open(report_name, 'w') as file:
- if 'csv' == format:
- tab = "\t"
- else:
- tab = ","
-
- if ('summary' == report_type):
if 'csv' == format:
- file.write("Name\tVersion\tProfile\tCPE\tSRT SPE\tInvestigations\tDefects\n")
- if 'txt' == format:
- file.write("Report : Products Table\n")
- file.write("\n")
- file.write("Name,Version,Profile,CPE,SRT SPE,Investigations,Defects\n")
-
- for product in Product.objects.all():
- file.write("%s%s" % (product.name,tab))
- file.write("%s%s" % (product.version,tab))
- file.write("%s%s" % (product.profile,tab))
- file.write("%s%s" % (product.cpe,tab))
- file.write("%s%s" % (product.defect_tags,tab))
- file.write("%s%s" % (product.product_tags,tab))
+ tab = "\t"
+ else:
+ tab = ","
- for i,pi in enumerate(product.product_investigation.all()):
- if i > 0:
- file.write(" ")
- file.write("%s" % (pi.name))
- file.write("%s" % tab)
- for i,pd in enumerate(product.product_defect.all()):
- if i > 0:
- file.write(" ")
- file.write("%s" % (pd.name))
- #file.write("%s" % tab)
- file.write("\n")
+ if ('summary' == report_type):
+ if 'csv' == format:
+ file.write("Name\tVersion\tProfile\tCPE\tSRT SPE\tInvestigations\tDefects\n")
+ if 'txt' == format:
+ file.write("Report : Products Table\n")
+ file.write("\n")
+ file.write("Name,Version,Profile,CPE,SRT SPE,Investigations,Defects\n")
+
+ for product in Product.objects.all():
+ file.write("%s%s" % (product.name,tab))
+ file.write("%s%s" % (product.version,tab))
+ file.write("%s%s" % (product.profile,tab))
+ file.write("%s%s" % (product.cpe,tab))
+ file.write("%s%s" % (product.defect_tags,tab))
+ file.write("%s%s" % (product.product_tags,tab))
+
+ if False:
+ for i,pi in enumerate(product.product_investigation.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pi.name))
+ file.write("%s" % tab)
+ for i,pd in enumerate(product.product_defect.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pd.name))
+ #file.write("%s" % tab)
+ file.write("\n")
+ elif 'status_jira' == report_type:
+ def resolution_color(i):
+ if 0 == i: fill = pyxl_backcolor_orn
+ elif 1 == i: fill = pyxl_backcolor_grn
+ elif 2 == i: fill = pyxl_backcolor_grn
+ elif 3 == i: fill = pyxl_backcolor_yel
+ elif 4 == i: fill = pyxl_backcolor_blu
+ elif 5 == i: fill = pyxl_backcolor_blu
+ else: fill = None
+ return(fill)
+
+ for product in Product.objects.all():
+ scan_product_jira(product)
+
+ format = "xlsx"
+ report_name = '%s/products_jira_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
+ wb = Workbook()
+ ws = wb.active
+ ws.title = "Product Jira Summary"
+ ws.column_dimensions[get_column_letter(1)].width = 30
+
+ row = 1
+ first_row = 2
+
+ col = 1
+ for header in ('Product','Critical','High','Medium','Low','P1','P2','P3','P4','Unresolved','Resolved','Fixed',"Won't Fix",'Withdrawn','Rejected'):
+ border = pyxl_border_bottom_left if (col in (2,6,10)) else pyxl_border_bottom
+ pyxl_write_cell(ws,row,col,header,border=border)
+ col += 1
+ row += 1
+
+ for product in Product.objects.order_by("order"):
+ key = product.long_name
+ scan_product_jira(product)
+ pyxl_write_cell(ws,row,1,key)
+ # CVE Severity
+ col_excel = 2
+ col_summary = 1
+ for i in range(0,4):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=pyxl_cve_fills[i] if value else None)
+
+ # Jira Priority
+ col_excel = 6
+ col_summary = 5
+ for i in range(0,4):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=pyxl_cve_fills[i] if value else None)
+ # Jira Resolution
+ col_excel = 10
+ col_summary = 9
+ for i in range(0,6):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=resolution_color(i) if value else None)
+ row += 1
+
+ # Sums
+ row -= 1
+ for i in range(1,16):
+ border = pyxl_border_bottom_left if (i in (2,6,10)) else pyxl_border_bottom
+ ws.cell(row=row,column=i).border=border
+ row += 1
+ letters = (' ','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q')
+ for col_excel in range(2,16):
+ # CVE Severity
+ col_excel = 2
+ for i in range(0,4):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=pyxl_cve_fills[i])
+ # Jira Priority
+ col_excel = 6
+ for i in range(0,4):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=pyxl_cve_fills[i])
+ # Jira Resolution
+ col_excel = 10
+ for i in range(0,6):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=resolution_color(i))
+
+ wb.save(report_name)
return report_name,os.path.basename(report_name)
+#
+# CVE Reports
+#
+
class PublishCveReport(Report):
"""Report for the Publish Cve Page"""