aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Reyna <David.Reyna@windriver.com>2020-01-21 20:16:21 -0800
committerDavid Reyna <David.Reyna@windriver.com>2020-01-21 20:16:21 -0800
commit6a235c90acf6c8902488569b8d2cdfefd218d5d7 (patch)
treebf73b76e8ed4af45b387514d1d1f5a646d3ae5eb
parentd9ba6051e173df1f5049e8df274c5e7f09fec3be (diff)
downloadsrtool-6a235c90acf6c8902488569b8d2cdfefd218d5d7.tar.gz
srtool-6a235c90acf6c8902488569b8d2cdfefd218d5d7.tar.bz2
srtool-6a235c90acf6c8902488569b8d2cdfefd218d5d7.zip
srtool: add publishing, update backup scanning, MITRE init fix
Add CVE publishing features, specifically add a method to generate CVE status across the releases, filterable by CVE status. Add dynamic schema calculations for the backup database snapshots, to enable difference scanning even when the schema has been reordered after a migration. Add first part of database difference scanning code migration. Fix MITRE scanning for new source files. [YOCTO #13734] Signed-off-by: David Reyna <David.Reyna@windriver.com>
-rwxr-xr-xbin/common/srtool_common.py22
-rwxr-xr-xbin/common/srtool_utils.py250
-rwxr-xr-xbin/mitre/srtool_mitre.py41
-rw-r--r--bin/nist/datasource.json4
-rw-r--r--lib/srtgui/tables.py7
-rw-r--r--lib/srtgui/templates/publish.html361
-rw-r--r--lib/srtgui/templates/publish_diff_snapshot.html327
-rw-r--r--lib/srtgui/urls.py6
-rw-r--r--lib/srtgui/views.py184
-rwxr-xr-xlib/yp/reports.py381
-rwxr-xr-xlib/yp/urls.py4
-rwxr-xr-xlib/yp/views.py52
12 files changed, 1276 insertions, 363 deletions
diff --git a/bin/common/srtool_common.py b/bin/common/srtool_common.py
index d9fbd341..2a92333a 100755
--- a/bin/common/srtool_common.py
+++ b/bin/common/srtool_common.py
@@ -40,8 +40,10 @@ from datetime import datetime
# Load the srt.sqlite schema index file
# Since it is generated from this script
# it may not exist on the first pass
+dir_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.insert(0, dir_path)
try:
- from srt_schema import ORM
+ from common.srt_schema import ORM
except:
# Do a pass so that '--generate-schema-header' can fix it
print("Warning: srt_schema not yet created or bad format")
@@ -857,24 +859,29 @@ def update_cve_status_tree(cve_list,update_skip_history):
# CREATE TABLE "orm_notifycategories" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "category" varchar(50) NULL);
# ...
-def gen_schema_header():
+
+def gen_schema_header(database_dir,schema_dir):
+
+ database_file = os.path.join(database_dir, 'srt.sqlite')
+ schema_file = os.path.join(schema_dir, 'srt_schema.py')
+
create_re = re.compile(r"CREATE TABLE[A-Z ]* \"(\w+)\" \((.+)\);")
try:
- cmd = ('sqlite3', os.path.join(srtool_basepath, 'srt.sqlite'), '.schema')
+ cmd = ('sqlite3', database_file, '.schema')
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
print("ERROR(%d): %s" % (e.returncode, e.output))
return
# Fetch USER_SRTOOL_ID
- conn = sqlite3.connect(srtDbName)
+ conn = sqlite3.connect(database_file)
cur = conn.cursor()
USER_SRTOOL_NAME = 'SRTool'
user = cur.execute("SELECT * FROM users_srtuser where username = '%s'" % USER_SRTOOL_NAME).fetchone()
USER_SRTOOL_ID = user[0] # Hardcoded 'ORM.USERS_SRTUSER_ID'
conn.close()
- with open(os.path.join(srtool_basepath,'bin/common/srt_schema.py'), 'w') as fd:
+ with open(schema_file, 'w') as fd:
fd.write("# SRTool database table schema indexes\n")
fd.write("# Generated by: './bin/common/srtool_common.py --generate-schema-header'\n")
fd.write("# Should be run after any schema changes to sync commandline tools\n")
@@ -1089,6 +1096,7 @@ def main(argv):
parser.add_argument('--init-notify-categories', '-n', action='store_const', const='init_notify_categories', dest='command', help='Initialize notify categories')
parser.add_argument('--score-new-cves', '-s', dest='score_new_cves', help='Score CVEs for triage [NEW|CVE-1234]')
parser.add_argument('--generate-schema-header', '-g', action='store_const', const='gen_schema_header', dest='command', help='Generate database schema header')
+ parser.add_argument('--generate-schema-header-dir', dest='gen_schema_header_dir', help='Generate database schema header for a give database directory')
parser.add_argument('--update-cve-status-tree', '-S', dest='update_cve_status_tree', help="Update CVEs and their children's cumulative status")
@@ -1126,7 +1134,9 @@ def main(argv):
elif args.score_new_cves:
score_new_cves(args.score_new_cves)
elif 'gen_schema_header' == args.command:
- gen_schema_header()
+ gen_schema_header(srtool_basepath,os.path.join(srtool_basepath,'bin/common'))
+ elif args.gen_schema_header_dir:
+ gen_schema_header(args.gen_schema_header_dir,args.gen_schema_header_dir)
elif args.update_cve_status_tree:
update_cve_status_tree(args.update_cve_status_tree, update_skip_history)
diff --git a/bin/common/srtool_utils.py b/bin/common/srtool_utils.py
index ac65d42d..e3f574f6 100755
--- a/bin/common/srtool_utils.py
+++ b/bin/common/srtool_utils.py
@@ -28,6 +28,7 @@ import sqlite3
from datetime import datetime, date
import time
import re
+import subprocess
# load the srt.sqlite schema indexes
dir_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
@@ -57,6 +58,32 @@ def _log(msg):
f1.write("|" + msg + "|\n" )
f1.close()
+# Sub Process calls
+def execute_process(*args):
+ cmd_list = []
+ for arg in args:
+ if isinstance(arg, (list, tuple)):
+ # Flatten all the way down
+ for a in arg:
+ cmd_list.append(a)
+ else:
+ cmd_list.append(arg)
+
+ # Python < 3.5 compatible
+ if sys.version_info < (3,5):
+ process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ try:
+ stdout, stderr = process.communicate(input)
+ except:
+ process.kill()
+ process.wait()
+ raise
+ retcode = process.poll()
+ return retcode, stdout, stderr
+ else:
+ result = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return result.returncode,result.stdout,result.stderr
+
#################################
# reset sources
#
@@ -873,6 +900,180 @@ def fix_defects_to_products():
conn.commit()
#################################
+# fix_bad_mitre_init
+#
+
+#
+def fix_bad_mitre_init():
+ conn = sqlite3.connect(srtDbName)
+ cur = conn.cursor()
+ cur_ds = conn.cursor()
+ cur_cve = conn.cursor()
+ cur_del = conn.cursor()
+
+ new_count = 0
+ mitre_count = 0
+ cve_name = ''
+
+ nist_source_list = []
+ # Find NIST data sources
+ cur.execute('SELECT * FROM orm_datasource WHERE source = "nist"')
+ for i,ds in enumerate(cur):
+ nist_source_list.append(ds[ORM.DATASOURCE_ID])
+ print('NIST DataSource List=[%s]' % nist_source_list)
+
+ mitre_source_list = []
+ # Find MITRE data sources
+ cur.execute('SELECT * FROM orm_datasource WHERE source = "mitre"')
+ for i,ds in enumerate(cur):
+ mitre_source_list.append(ds[ORM.DATASOURCE_ID])
+ print('MITRE DataSource List=[%s]' % mitre_source_list)
+
+ # Find all bad MITRE reserved CVEs
+ cur.execute('SELECT * FROM orm_cve WHERE description = "" AND status = %d' % ORM.STATUS_NEW)
+# cur.execute('SELECT * FROM orm_cve WHERE description = ""')
+ for i,cve in enumerate(cur):
+ new_count += 1
+
+ cur_ds.execute('SELECT * FROM orm_cvesource WHERE cve_id = %d' % cve[ORM.CVE_ID])
+ is_mitre = False
+ is_nist = False
+ for cvesource in cur_ds:
+ if cvesource[ORM.CVESOURCE_DATASOURCE_ID] in mitre_source_list:
+ is_mitre = True
+ if cvesource[ORM.CVESOURCE_DATASOURCE_ID] in nist_source_list:
+ is_nist = True
+
+ if is_mitre and not is_nist:
+ mitre_count += 1
+ cve_name = cve[ORM.CVE_NAME]
+
+ if force:
+ sql = ''' UPDATE orm_cve
+ SET status = ?
+ WHERE id = ?'''
+ cur_cve.execute(sql, (ORM.STATUS_NEW_RESERVED,cve[ORM.CVE_ID],))
+ conn.commit()
+
+ # Progress indicator support
+ if 19 == i % 20:
+ print('%05d: %-20s\r' % (i,cve[ORM.CVE_NAME]), end='')
+ pass
+ if (0 == i % 200):
+# conn.commit()
+ #print('')
+ pass
+ # Development/debug support
+ if cmd_skip and (i < cmd_skip): continue
+ if cmd_count and ((i - cmd_skip) > cmd_count): break
+
+ print("3CVE NEW_COUNT=%d, mitre=%d, name=%s, database=%s" % (new_count,mitre_count,cve_name,srtDbName))
+# conn.commit()
+
+
+
+
+#
+def foo_fix_bad_mitre_init():
+ conn = sqlite3.connect(srtDbName)
+ cur = conn.cursor()
+ cur_ds = conn.cursor()
+ cur_cve = conn.cursor()
+ cur_del = conn.cursor()
+
+ fix_count = 0
+ reserved_count = 0
+ mitre_count = 0
+ nosource_count = 0
+
+ mitre_source_list = []
+ mitre_lookup = {}
+
+ # Find MITRE data sources
+ cur.execute('SELECT * FROM orm_datasource WHERE source = "mitre"')
+ for i,ds in enumerate(cur):
+ mitre_source_list.append(ds[ORM.DATASOURCE_ID])
+ mitre_lookup[ds[ORM.DATASOURCE_ID]] = ds[ORM.DATASOURCE_LOOKUP]
+ print('MITRE DataSource List=[%s]' % mitre_source_list)
+
+ # Find all bad MITRE reserved CVEs
+ cur.execute('SELECT * FROM orm_cve WHERE description = ""')
+ for i,cve in enumerate(cur):
+ fix_count += 1
+
+# reserved_pos = cve[ORM.CVE_DESCRIPTION].find('** RESERVED **')
+# if (0 <= reserved_pos) and (20 > reserved_pos):
+# reserved_count += 1
+
+ if ORM.STATUS_NEW == cve[ORM.CVE_STATUS]:
+ reserved_count += 1
+
+ cur_ds.execute('SELECT * FROM orm_cvesource WHERE cve_id = %d' % cve[ORM.CVE_ID])
+ is_mitre = False
+ mitre_ds = 0
+ ds_list = []
+ ds_count = 0
+ for cvesource in cur_ds:
+ ds_count += 1
+ if cvesource[ORM.CVESOURCE_DATASOURCE_ID] in mitre_source_list:
+ is_mitre = True
+ mitre_ds = cvesource[ORM.CVESOURCE_DATASOURCE_ID]
+ break
+ ds_list.append(cvesource[ORM.CVESOURCE_DATASOURCE_ID])
+
+ if True:
+ print('%05d: %-20s, SourceList=%s' % (i,cve[ORM.CVE_NAME],ds_list))
+
+ if False:
+ if is_mitre:
+ mitre_count += 1
+
+ lookup_command = mitre_lookup[ cvesource[ORM.CVESOURCE_DATASOURCE_ID] ].replace('%command%','--cve-detail=%s' % cve[ORM.CVE_NAME])
+ result_returncode,result_stdout,result_stderr = execute_process(lookup_command.split(' '))
+ if 0 != result_returncode:
+ print("ERROR_LOOKUP:%s" % lookup_command)
+ return(1)
+ description = ''
+ for line in result_stdout.decode("utf-8").splitlines():
+ try:
+ name = line[:line.index('=')]
+ value = line[line.index('=')+1:].replace("[EOL]","\n")
+ except:
+ continue
+ if name == 'description':
+ description = value
+ break
+ if description:
+ # print("%s='%s'" % (cve[ORM.CVE_NAME],description))
+ sql = ''' UPDATE orm_cve
+ SET description = ?
+ WHERE id = ?'''
+ cur_ds.execute(sql, (description,cve[ORM.CVE_ID],))
+ # conn.commit()
+ # return(0)
+
+ elif 0 == ds_count:
+ nosource_count += 1
+ else:
+ print('%05d: %-20s, SourceList=%s' % (i,cve[ORM.CVE_NAME],ds_list))
+
+ # Progress indicator support
+ if 19 == i % 100:
+ print('%05d: %-20s\r' % (i,cve[ORM.CVE_NAME]), end='')
+ pass
+ if (0 == i % 200):
+# conn.commit()
+ #print('')
+ pass
+ # Development/debug support
+ if cmd_skip and (i < cmd_skip): continue
+ if cmd_count and ((i - cmd_skip) > cmd_count): break
+
+ print("CVE RESERVED COUNT=%d of %d, mitre=%d, no_source=%d" % (reserved_count,fix_count,mitre_count,nosource_count))
+# conn.commit()
+
+
+#################################
# find_multiple_defects
#
@@ -1047,6 +1248,40 @@ def find_bad_links():
conn.close()
+#################################
+# find_empty_status
+#
+
+def find_empty_status():
+
+ conn = sqlite3.connect(srtDbName)
+ cur = conn.cursor()
+ cur_del = conn.cursor()
+
+ #
+ print('\n=== CVE Empty Status Check ===\n')
+ #
+
+ cur.execute('SELECT * FROM orm_cve')
+ empty_count = 0
+ date_count = 0
+ other_count = 0
+ total = 0
+ for i,cve in enumerate(cur):
+ total += 1
+ if not cve[ORM.CVE_STATUS]:
+ empty_count += 1
+ elif '-' in cve[ORM.CVE_STATUS]:
+ date_count += 1
+ else:
+ try:
+ value = int(cve[ORM.CVE_STATUS])
+ except:
+ other_count += 1
+
+
+ print("STATUS: Empty=%d, Date=%d, OtherBad=%d, total=%d of %d" % (empty_count,date_count,other_count,empty_count+date_count+other_count,total))
+
#################################
# main loop
@@ -1057,6 +1292,7 @@ def main(argv):
global cmd_skip
global cmd_count
global force
+ global srtDbName
# setup
parser = argparse.ArgumentParser(description='srtool.py: manage the SRTool database')
@@ -1074,6 +1310,9 @@ def main(argv):
parser.add_argument('--fix-missing-create-dates', action='store_const', const='fix_missing_create_dates', dest='command', help='Reset CVE srt_create dates to NIST release dates')
parser.add_argument('--fix-public-reserved', action='store_const', const='fix_public_reserved', dest='command', help='Reset CVE NEW_RESERVED if now public')
parser.add_argument('--fix-remove-bulk-cve-history', action='store_const', const='fix_remove_bulk_cve_history', dest='command', help='foo')
+ parser.add_argument('--fix-bad-mitre-init', action='store_const', const='fix_bad_mitre_init', dest='command', help='foo')
+
+ parser.add_argument('--find-empty-status', action='store_const', const='find_empty_status', dest='command', help='foo')
parser.add_argument('--find-multiple-defects', action='store_const', const='find_multiple_defects', dest='command', help='foo')
parser.add_argument('--find-duplicate-names', action='store_const', const='find_duplicate_names', dest='command', help='foo')
@@ -1081,6 +1320,8 @@ def main(argv):
parser.add_argument('--fix-defects-to-products', action='store_const', const='fix_defects_to_products', dest='command', help='foo')
parser.add_argument('--find-bad-links', action='store_const', const='find_bad_links', dest='command', help='Find bad links, e.g. "orm_cvesource" (with "-f" to fix)')
+ parser.add_argument('--database', '-D', dest='database', help='Selected database file')
+
parser.add_argument('--force', '-f', action='store_true', dest='force', help='Force the update')
parser.add_argument('--update-skip-history', '-H', action='store_true', dest='update_skip_history', help='Skip history updates')
parser.add_argument('--verbose', '-v', action='store_true', dest='verbose', help='Debugging: verbose output')
@@ -1098,6 +1339,10 @@ def main(argv):
cmd_count = int(args.count)
force = args.force
+ # Test for example the backup databases
+ if args.database:
+ srtDbName = args.database
+
if args.sources:
if args.sources.startswith('s'):
sources("set")
@@ -1137,7 +1382,8 @@ def main(argv):
fix_remove_bulk_cve_history()
elif 'fix_defects_to_products' == args.command:
fix_defects_to_products()
-
+ elif 'fix_bad_mitre_init' == args.command:
+ fix_bad_mitre_init()
elif 'find_multiple_defects' == args.command:
find_multiple_defects()
@@ -1146,6 +1392,8 @@ def main(argv):
elif 'find_bad_links' == args.command:
find_bad_links()
+ elif 'find_empty_status' == args.command:
+ find_empty_status()
else:
print("Command not found")
diff --git a/bin/mitre/srtool_mitre.py b/bin/mitre/srtool_mitre.py
index 3928e51e..75789b7a 100755
--- a/bin/mitre/srtool_mitre.py
+++ b/bin/mitre/srtool_mitre.py
@@ -56,6 +56,8 @@ mitre_cache_dir = 'data/cache/mitre'
# Debugging support
verbose = False
+cmd_skip = 0
+cmd_count = 0
# Development support
overrides = {}
@@ -88,8 +90,7 @@ def srt_error_log(msg):
f1.close()
-# Newly discovered or updated CVEs default to NEW for triage
-# Inited CVEs default to HISTORICAL, unless they are within the courtesy CVE_INIT_NEW_DELTA
+# Newly discovered CVEs default to NEW_RESERVED if reserved, else NEW for triage
init_new_date = None
def get_cve_default_status(is_init,publishedDate,description):
global init_new_date
@@ -110,19 +111,10 @@ def get_cve_default_status(is_init,publishedDate,description):
#print("\nPreset new data = %s" % init_new_date.strftime("%Y-%m-%d"))
init_new_date = init_new_date.strftime("%Y-%m-%d")
- if is_init:
- # Note: the NIST 'published date' is in the format "2017-05-11", so do a simple string compare
- #print("INIT status: %s versus %s" % (init_new_date,publishedDate))
-# if not publishedDate or (publishedDate > init_new_date):
-# # Is this reserved by Mitre? Is '** RESERVED **' within the first 20 char positions?
-# reserved_pos = description.find('** RESERVED **')
-# if (0 <= reserved_pos) and (20 > reserved_pos):
-# return ORM.STATUS_NEW_RESERVED
-# else:
- if True:
- return ORM.STATUS_NEW
-# else:
-# return ORM.STATUS_HISTORICAL
+ # Is this reserved by Mitre? Is '** RESERVED **' within the first 20 char positions?
+ reserved_pos = description.find('** RESERVED **')
+ if (0 <= reserved_pos) and (20 > reserved_pos):
+ return ORM.STATUS_NEW_RESERVED
else:
return ORM.STATUS_NEW
@@ -269,9 +261,6 @@ def append_cve_database(is_init,file_xml):
tree = ET.parse(file_xml)
root = tree.getroot()
- # Max count for development cycle
- cmd_count = 20 if get_override('SRTDBG_MINIMAL_DB') else 0
-
conn = sqlite3.connect(srtDbName)
cur = conn.cursor()
cur_write = conn.cursor()
@@ -319,11 +308,11 @@ def append_cve_database(is_init,file_xml):
# Get the default CVE status
status = get_cve_default_status(is_init,summary['Published'],summary['Description'])
- # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
+ # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
sql = ''' INSERT into orm_cve (name, name_sort, priority, status, comments, comments_private, tags, cve_data_type, cve_data_format, cve_data_version, public, publish_state, publish_date, acknowledge_date, description, publishedDate, lastModifiedDate, recommend, recommend_list, cvssV3_baseScore, cvssV3_baseSeverity, cvssV2_baseScore, cvssV2_severity, srt_updated, srt_created, packages)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'''
- cur.execute(sql, (cve_name, get_name_sort(cve_name), ORM.PRIORITY_UNDEFINED, status, '', '', '', 'CVE', 'MITRE', '', 1, ORM.PUBLISH_UNPUBLISHED, '', summary['Description'], summary['Published'], summary['Modified'],0, '', '', '', '', '', '', datetime.now(), datetime.now(),''))
- # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
+ cur.execute(sql, (cve_name, get_name_sort(cve_name), ORM.PRIORITY_UNDEFINED, status, '', '', '', 'CVE', 'MITRE', '', 1, ORM.PUBLISH_UNPUBLISHED, '', '', summary['Description'], summary['Published'], summary['Modified'],0, '', '', '', '', '', datetime.now(), datetime.now(),''))
+ # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
cve_id = cur.lastrowid
print("MITRE:ADDED %20s\r" % cve_name)
@@ -410,6 +399,8 @@ def dump(file_xml):
def main(argv):
global verbose
+ global cmd_skip
+ global cmd_count
# setup
@@ -425,6 +416,8 @@ def main(argv):
parser.add_argument('--force', '-f', action='store_true', dest='force_update', help='Force update')
parser.add_argument('--update-skip-history', '-H', action='store_true', dest='update_skip_history', help='Skip history updates')
parser.add_argument('--verbose', '-v', action='store_true', dest='is_verbose', help='Enable verbose debugging output')
+ parser.add_argument('--skip', dest='skip', help='Debugging: skip record count')
+ parser.add_argument('--count', dest='count', help='Debugging: short run record count')
parser.add_argument('--dump', '-D', action='store_const', const='dump', dest='command', help='test dump data')
parser.add_argument('--dump2', '-2', action='store_const', const='dump2', dest='command', help='test dump data')
@@ -432,6 +425,12 @@ def main(argv):
if args.is_verbose:
verbose = True
+ if None != args.skip:
+ cmd_skip = int(args.skip)
+ if None != args.count:
+ cmd_count = int(args.count)
+ elif get_override('SRTDBG_MINIMAL_DB'):
+ cmd_count = 20
if 'dump' == args.command:
dump(mitre_cvrf_xml)
diff --git a/bin/nist/datasource.json b/bin/nist/datasource.json
index de52a6b4..28633b61 100644
--- a/bin/nist/datasource.json
+++ b/bin/nist/datasource.json
@@ -23,8 +23,8 @@
"attributes" : "PREVIEW-SOURCE",
"cve_filter" : "",
"init" : "",
- "update" : "bin/nist/srtool_nist.py --update_nist_incremental --source='NIST Modified Data' --file=data/nvdcve-1.0-modified.json --url-file=nvdcve-1.0-modified.json.gz --url-meta=nvdcve-1.0-modified.meta",
- "lookup" : "bin/nist/srtool_nist.py --file=data/nvdcve-1.0-modified.json %command%",
+ "update" : "bin/nist/srtool_nist.py --update_nist_incremental --source='NIST Modified Data' --file=data/nvdcve-1.1-modified.json --url-file=nvdcve-1.1-modified.json.gz --url-meta=nvdcve-1.1-modified.meta",
+ "lookup" : "bin/nist/srtool_nist.py --file=data/nvdcve-1.1-modified.json %command%",
"update_frequency" : "2",
"_comment_" : "Update at 7:00 am",
"update_time" : "{\"hour\":\"7\"}"
diff --git a/lib/srtgui/tables.py b/lib/srtgui/tables.py
index e80ee71e..e0a6fb42 100644
--- a/lib/srtgui/tables.py
+++ b/lib/srtgui/tables.py
@@ -1739,6 +1739,13 @@ class SourcesTable(ToasterTable):
def setup_columns(self, *args, **kwargs):
+ self.add_column(title="ID",
+ hideable=True,
+ hidden=True,
+ orderable=True,
+ field_name="id",
+ )
+
self.add_column(title="Key",
hideable=False,
orderable=True,
diff --git a/lib/srtgui/templates/publish.html b/lib/srtgui/templates/publish.html
index cf0f2294..826e1953 100644
--- a/lib/srtgui/templates/publish.html
+++ b/lib/srtgui/templates/publish.html
@@ -4,324 +4,49 @@
{% load projecttags %}
{% load humanize %}
-{% block title %} Publish Requests {% endblock %}
-
+{% block title %} Publish Reports {% endblock %}
{% block pagecontent %}
-<div class="row">
- <!-- Breadcrumbs -->
- <div class="col-md-12">
- <ul class="breadcrumb" id="breadcrumb">
- <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
- <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
- <li>Publish Report Management</li>
- </ul>
- </div>
-</div>
-
-<h2>Publish Report Management</h2>
-<ul>
- <li>The SRTool supports exporting new and updated CVEs to external publishing tools</li>
-</ul>
-
-<hr>
-
-<h2>Publish Via Database Snapshots</h2>
-<h3> On Demand</h3>
-<ul>
- <li>This extracts the changes from a 'base' database backup snapshot to more recent 'top' snapshot</li>
- <li>The 'start' and 'stop' dates can extract a subset of those changes. Normally they are set to the 'base' and 'top' dates</li>
-</ul>
-
-<div style="padding-left:30px;">
- <div>
- <label> Start Snapshot: </label>
- <select id="snap_date_base">
- {% for snap in snapshot_list %}
- <option value="{{snap.date}}" {% if snap_start_index == snap.index %}selected{% endif %}>
- ({{snap.mode}}) {{snap.date}} {{snap.time}} | {{snap.day}}
- </option>
- {% endfor %}
- </select>
- </div>
- <div>
- <label> Stop Snapshot: </label>
- <select id="snap_date_top">
- {% for snap in snapshot_list %}
- <option value="{{snap.date}}" {% if snap_stop_index == snap.index %}selected{% endif %}>
- ({{snap.mode}}) {{snap.date}} {{snap.time}} | {{snap.day}}
- </option>
- {% endfor %}
- </select>
- </div>
- <div>
- Start Date: <input type="text" id="snap_date_start" value="{{snap_date_start}}">&nbsp;&nbsp;
- Stop Date: <input type="text" id="snap_date_stop" value="{{snap_date_stop}}">&nbsp;&nbsp;
- <I>(Format: yyyy-mm-dd)</I>
- </div>
-<br>
-</div>
-
-<div>
- <span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button">Generate</button></span>
- <!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
- <span id="export-snapshot-text">Generate the publish table on-demand (using snapshots)</span>
- <span id="generating-report" hidden style="color:red"><I>... Generating the report - this will take a few minutes ...</I></span>
-</div>
-<br>
-
-<form method="POST"> {% csrf_token %}
-<h3>Automatic (Under Development)</h3>
-<div style="padding-left: 25px;">
- <label> Frequency: </label>
- <select id="snap_frequency">
- {% for snap in snapshot_frequency_list %}
- <option value="{{snap}}" {% if snap == snap_frequency_select %}selected{% endif %}>
- {{snap}}
- </option>
- {% endfor %}
- </select>
- <span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button" disabled>Save</button></span>
- <!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
- Save the automatic publishing frequency
-</div>
-</form>
-
-<h3>Generated Reports</h3>
-<div style="padding-left: 25px;">
- <table class="table table-striped table-condensed" data-testid="vuln-hyperlinks-table">
- <thead>
- <tr>
- <th>Name</th>
- <th>Size</th>
- <th>Date</th>
- <th>Manage</th>
- </tr>
- </thead>
- {% if generated_report_list %}
- {% for report in generated_report_list %}
- <tr>
- <td>{{report.name}}</td>
- <td>{{report.size}}</td>
- <td>{{report.date}}</td>
- <td>
- <span id="attachment_entry_'+{{report.name}}+'" class="js-config-var-name"></span>
- <form id="downloadbanner-{{forloop.counter}}" enctype="multipart/form-data" method="post" >{% csrf_token %}
- <input type="hidden" id="action" name="action" value="download">
- <input type="hidden" id="report_id" name="report_name" value={{report.name}}>
- <span class="glyphicon glyphicon-download-alt submit-downloadreport" id="report_download_'+{{report.name}}+'" x-data="{{forloop.counter}}"></span>
- {% if request.user.is_creator %}
- <span class="glyphicon glyphicon-trash trash-report" id="report_trash_'+{{report.name}}+'" x-data="{{report.name}}"></span>
- {% endif %}
- </form>
- </td>
- </tr>
- {% endfor %}
- {% else %}
- <tr>
- <td>No report files found</td>
- </tr>
- {% endif %}
- </table>
- (last report = {{snap_last_calc}})
-</div>
-
-<hr>
-
-<form method="POST"> {% csrf_token %}
-<h2>Publish Via History Tables (Under development)</h2>
-<ul>
- <li>These tools can be used to (a) gather the candidate CVEs, (b) review and edit the list if needed, (c) generate the report when ready</li>
- <li>The user can explicitly include and exclude CVEs from the "New" list and the "Updated" list, in case the automatic caltulations need adjustment</li>
- <li>These mark-ups are inserted into the respective CVE's history at a mid-point date of the period, so they are both persistent and period-specific</li>
- <li>The user can clear the markups from the given period and start over, but this will not affect any other period</li>
-</ul>
-<h3> Publish Preparation</h3>
-<ul>
- <div>
- Start Date: <input type="text" name="date_start" value="{{date_start}}">&nbsp;&nbsp;
- Stop Date: <input type="text" name="date_stop" value="{{date_stop}}">
- </div>
- <br>
- <div>
- Product filter:
- <select name="product-filter" id="select-product-filter">
- <option value="0">WR Linux Suported Products</option>
- </select>
- </div>
- <br>
- <div>
- <button type="submit" name="action" value="recalculate">Recalculate publish table</button>
- Gather the items for this period to be published from SRTool, with user changes (last done {{last_calc}})
- </div>
- <br>
- <div>
- <button type="submit" name="action" value="reset">Reset user edits, Recalculate</button>
- Remove the user changes for this period, recalculate the table
- </div>
-</ul>
-<h3> Publish Preview and Modifications</h3>
-<ul>
- <div>
- <button type="submit" name="action" value="view">View the publish table</button>
- View the publish table, prune entries
- </div>
- <br>
- <div>
- <button type="submit" name="action" value="add-cve">Add via CVEs</button>
- Add recent CVEs to the table
- </div>
- <br>
- <div>
- <button type="submit" name="action" value="add-defect">Add via defects</button>
- Add CVEs of recent defects to the table
- </div>
- <br>
-</ul>
-<h3> Publish the Report</h3>
-<ul>
- <div>
- <button type="submit" name="action" value="export">Export</button>
- Export the publish table (using history)
- </div>
- <br>
-</ul>
-</form>
-
-<script>
- var selected_newcomment=false;
-
- $(document).ready(function() {
-
- function onCommitAjaxSuccess(data, textstatus) {
- document.getElementById("export-snapshot").disabled = false;
- /* document.getElementById("download-snapshot").disabled = false;*/
- document.getElementById("export-snapshot-text").innerText = "Generate the publish table on-demand (using snapshots)";
- document.getElementById("generating-report").style.display = "block";
- if (window.console && window.console.log) {
- console.log("XHR returned:", data, "(" + textstatus + ")");
- } else {
- alert("NO CONSOLE:\n");
- return;
- }
- if (data.error != "ok") {
- alert("error on request:\n" + data.error);
- return;
- }
- // reload the page with the updated tables
- location.reload(true);
- }
-
- function onCommitAjaxError(jqXHR, textstatus, error) {
- console.log("ERROR:"+error+"|"+textstatus);
- alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
- document.getElementById("export-snapshot").disabled = false;
- document.getElementById("export-snapshot-text").innerText = "Generate the publish table on-demand (using snapshots)";
- /* document.getElementById("download-snapshot").disabled = false; */
- document.getElementById("generating-report").style.display = "block";
- }
-
- /* ensure cookie exists {% csrf_token %} */
- function postCommitAjaxRequest(reqdata) {
- var ajax = $.ajax({
- type:"POST",
- data: reqdata,
- url:"{% url 'xhr_publish' %}",
- headers: { 'X-CSRFToken': $.cookie("csrftoken")},
- success: onCommitAjaxSuccess,
- error: onCommitAjaxError,
- });
- }
-
- $("#snap_date_base").change(function(){
- snap_date_base = $("#snap_date_base").val();
- snap_date_top = $("#snap_date_top").val();
- if (snap_date_base > snap_date_top) {
- $("#snap_date_base").val(snap_date_top);
- $("#snap_date_top").val(snap_date_base);
- $("#snap_date_start").val(snap_date_top);
- $("#snap_date_stop").val(snap_date_base);
- } else {
- snap_date_start = $("#snap_date_start").val();
- snap_date_stop = $("#snap_date_stop").val();
- $("#snap_date_start").val(snap_date_base);
- if (snap_date_stop < snap_date_base) {
- $("#snap_date_stop").val(snap_date_top);
- }
- }
- });
-
- $("#snap_date_top").change(function(){
- snap_date_base = $("#snap_date_base").val();
- snap_date_top = $("#snap_date_top").val();
- if (snap_date_base > snap_date_top) {
- $("#snap_date_base").val(snap_date_top);
- $("#snap_date_top").val(snap_date_base);
- $("#snap_date_start").val(snap_date_top);
- $("#snap_date_stop").val(snap_date_base);
- } else {
- snap_date_start = $("#snap_date_start").val();
- snap_date_stop = $("#snap_date_stop").val();
- if (snap_date_start > snap_date_top) {
- $("#snap_date_start").val(snap_date_base);
- }
- $("#snap_date_stop").val(snap_date_top);
- }
- });
-
- $('#export-snapshot').click(function(){
- snap_date_base = $("#snap_date_base").val();
- snap_date_top = $("#snap_date_top").val();
- snap_date_start = $("#snap_date_start").val();
- snap_date_stop = $("#snap_date_stop").val();
- if (snap_date_start > snap_date_stop) {
- alert("Error: the start date is after the stop date");
- return;
- }
- if (snap_date_start < snap_date_base) {
- alert("Error: the start date is before the snapshot base date");
- return;
- }
- if (snap_date_stop > snap_date_top) {
- alert("Error: the stop date is after the snapshot top date");
- return;
- }
- var result = confirm("Generate the report? This will take several minutes.");
- if (result){
- document.getElementById("export-snapshot").disabled = true;
- document.getElementById("export-snapshot-text").innerText = "... Generating the report - this will take a few minutes ...";
-
- /* document.getElementById("download-snapshot").disabled = true; */
- document.getElementById("generating-report").style.display = "none";
- postCommitAjaxRequest({
- "action" : 'export-snapshot',
- "snap_date_base" : snap_date_base,
- "snap_date_top" : snap_date_top,
- "snap_date_start" : snap_date_start,
- "snap_date_stop" : snap_date_stop
- });
- }
- });
-
-
- /* Manage report files */
-
- $('.submit-downloadreport').click(function() {
- $("#downloadbanner-"+this.getAttribute("x-data")).submit();
- });
-
- $('.trash-report').click(function() {
- var result = confirm("Are you sure?");
- if (result){
- postCommitAjaxRequest({
- "action" : 'submit-trashreport',
- "report_name" : $(this).attr('x-data'),
- });
- }
- });
-
-
-
- });
-</script>
+ <div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Management</h1>
+ </div>
+ </div>
+ <div class="row">
+ <div class="jumbotron well-transparent">
+
+ <div class="col-md-6">
+ <div>
+ <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Action</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'report' 'publish-summary' %}">Summary of CVEs</a></td>
+ <td>Summary across CVEs and Products</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'publish_diff_snapshot' %}">Difference Snapshots [UNDER DEVELOPMENT]</a></td>
+ <td>Difference Report via Snapshots</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'publish_diff_history' %}">Difference History [UNDER DEVELOPMENT]</a></td>
+ <td>Difference Report via History</td>
+ </tr>
+
+ </table>
+ </div>
+
+ </div>
+
+ </div>
+
+ </div>
+ </div>
{% endblock %}
diff --git a/lib/srtgui/templates/publish_diff_snapshot.html b/lib/srtgui/templates/publish_diff_snapshot.html
new file mode 100644
index 00000000..cf0f2294
--- /dev/null
+++ b/lib/srtgui/templates/publish_diff_snapshot.html
@@ -0,0 +1,327 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load projecttags %}
+{% load humanize %}
+
+{% block title %} Publish Requests {% endblock %}
+
+{% block pagecontent %}
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
+ <li>Publish Report Management</li>
+ </ul>
+ </div>
+</div>
+
+<h2>Publish Report Management</h2>
+<ul>
+ <li>The SRTool supports exporting new and updated CVEs to external publishing tools</li>
+</ul>
+
+<hr>
+
+<h2>Publish Via Database Snapshots</h2>
+<h3> On Demand</h3>
+<ul>
+ <li>This extracts the changes from a 'base' database backup snapshot to more recent 'top' snapshot</li>
+ <li>The 'start' and 'stop' dates can extract a subset of those changes. Normally they are set to the 'base' and 'top' dates</li>
+</ul>
+
+<div style="padding-left:30px;">
+ <div>
+ <label> Start Snapshot: </label>
+ <select id="snap_date_base">
+ {% for snap in snapshot_list %}
+ <option value="{{snap.date}}" {% if snap_start_index == snap.index %}selected{% endif %}>
+ ({{snap.mode}}) {{snap.date}} {{snap.time}} | {{snap.day}}
+ </option>
+ {% endfor %}
+ </select>
+ </div>
+ <div>
+ <label> Stop Snapshot: </label>
+ <select id="snap_date_top">
+ {% for snap in snapshot_list %}
+ <option value="{{snap.date}}" {% if snap_stop_index == snap.index %}selected{% endif %}>
+ ({{snap.mode}}) {{snap.date}} {{snap.time}} | {{snap.day}}
+ </option>
+ {% endfor %}
+ </select>
+ </div>
+ <div>
+ Start Date: <input type="text" id="snap_date_start" value="{{snap_date_start}}">&nbsp;&nbsp;
+ Stop Date: <input type="text" id="snap_date_stop" value="{{snap_date_stop}}">&nbsp;&nbsp;
+ <I>(Format: yyyy-mm-dd)</I>
+ </div>
+<br>
+</div>
+
+<div>
+ <span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button">Generate</button></span>
+ <!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
+ <span id="export-snapshot-text">Generate the publish table on-demand (using snapshots)</span>
+ <span id="generating-report" hidden style="color:red"><I>... Generating the report - this will take a few minutes ...</I></span>
+</div>
+<br>
+
+<form method="POST"> {% csrf_token %}
+<h3>Automatic (Under Development)</h3>
+<div style="padding-left: 25px;">
+ <label> Frequency: </label>
+ <select id="snap_frequency">
+ {% for snap in snapshot_frequency_list %}
+ <option value="{{snap}}" {% if snap == snap_frequency_select %}selected{% endif %}>
+ {{snap}}
+ </option>
+ {% endfor %}
+ </select>
+ <span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button" disabled>Save</button></span>
+ <!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
+ Save the automatic publishing frequency
+</div>
+</form>
+
+<h3>Generated Reports</h3>
+<div style="padding-left: 25px;">
+ <table class="table table-striped table-condensed" data-testid="vuln-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Size</th>
+ <th>Date</th>
+ <th>Manage</th>
+ </tr>
+ </thead>
+ {% if generated_report_list %}
+ {% for report in generated_report_list %}
+ <tr>
+ <td>{{report.name}}</td>
+ <td>{{report.size}}</td>
+ <td>{{report.date}}</td>
+ <td>
+ <span id="attachment_entry_'+{{report.name}}+'" class="js-config-var-name"></span>
+ <form id="downloadbanner-{{forloop.counter}}" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" id="action" name="action" value="download">
+ <input type="hidden" id="report_id" name="report_name" value={{report.name}}>
+ <span class="glyphicon glyphicon-download-alt submit-downloadreport" id="report_download_'+{{report.name}}+'" x-data="{{forloop.counter}}"></span>
+ {% if request.user.is_creator %}
+ <span class="glyphicon glyphicon-trash trash-report" id="report_trash_'+{{report.name}}+'" x-data="{{report.name}}"></span>
+ {% endif %}
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ {% else %}
+ <tr>
+ <td>No report files found</td>
+ </tr>
+ {% endif %}
+ </table>
+ (last report = {{snap_last_calc}})
+</div>
+
+<hr>
+
+<form method="POST"> {% csrf_token %}
+<h2>Publish Via History Tables (Under development)</h2>
+<ul>
+ <li>These tools can be used to (a) gather the candidate CVEs, (b) review and edit the list if needed, (c) generate the report when ready</li>
+ <li>The user can explicitly include and exclude CVEs from the "New" list and the "Updated" list, in case the automatic caltulations need adjustment</li>
+ <li>These mark-ups are inserted into the respective CVE's history at a mid-point date of the period, so they are both persistent and period-specific</li>
+ <li>The user can clear the markups from the given period and start over, but this will not affect any other period</li>
+</ul>
+<h3> Publish Preparation</h3>
+<ul>
+ <div>
+ Start Date: <input type="text" name="date_start" value="{{date_start}}">&nbsp;&nbsp;
+ Stop Date: <input type="text" name="date_stop" value="{{date_stop}}">
+ </div>
+ <br>
+ <div>
+ Product filter:
+ <select name="product-filter" id="select-product-filter">
+ <option value="0">WR Linux Suported Products</option>
+ </select>
+ </div>
+ <br>
+ <div>
+ <button type="submit" name="action" value="recalculate">Recalculate publish table</button>
+ Gather the items for this period to be published from SRTool, with user changes (last done {{last_calc}})
+ </div>
+ <br>
+ <div>
+ <button type="submit" name="action" value="reset">Reset user edits, Recalculate</button>
+ Remove the user changes for this period, recalculate the table
+ </div>
+</ul>
+<h3> Publish Preview and Modifications</h3>
+<ul>
+ <div>
+ <button type="submit" name="action" value="view">View the publish table</button>
+ View the publish table, prune entries
+ </div>
+ <br>
+ <div>
+ <button type="submit" name="action" value="add-cve">Add via CVEs</button>
+ Add recent CVEs to the table
+ </div>
+ <br>
+ <div>
+ <button type="submit" name="action" value="add-defect">Add via defects</button>
+ Add CVEs of recent defects to the table
+ </div>
+ <br>
+</ul>
+<h3> Publish the Report</h3>
+<ul>
+ <div>
+ <button type="submit" name="action" value="export">Export</button>
+ Export the publish table (using history)
+ </div>
+ <br>
+</ul>
+</form>
+
+<script>
+ var selected_newcomment=false;
+
+ $(document).ready(function() {
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ document.getElementById("export-snapshot").disabled = false;
+ /* document.getElementById("download-snapshot").disabled = false;*/
+ document.getElementById("export-snapshot-text").innerText = "Generate the publish table on-demand (using snapshots)";
+ document.getElementById("generating-report").style.display = "block";
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ document.getElementById("export-snapshot").disabled = false;
+ document.getElementById("export-snapshot-text").innerText = "Generate the publish table on-demand (using snapshots)";
+ /* document.getElementById("download-snapshot").disabled = false; */
+ document.getElementById("generating-report").style.display = "block";
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata) {
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:"{% url 'xhr_publish' %}",
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $("#snap_date_base").change(function(){
+ snap_date_base = $("#snap_date_base").val();
+ snap_date_top = $("#snap_date_top").val();
+ if (snap_date_base > snap_date_top) {
+ $("#snap_date_base").val(snap_date_top);
+ $("#snap_date_top").val(snap_date_base);
+ $("#snap_date_start").val(snap_date_top);
+ $("#snap_date_stop").val(snap_date_base);
+ } else {
+ snap_date_start = $("#snap_date_start").val();
+ snap_date_stop = $("#snap_date_stop").val();
+ $("#snap_date_start").val(snap_date_base);
+ if (snap_date_stop < snap_date_base) {
+ $("#snap_date_stop").val(snap_date_top);
+ }
+ }
+ });
+
+ $("#snap_date_top").change(function(){
+ snap_date_base = $("#snap_date_base").val();
+ snap_date_top = $("#snap_date_top").val();
+ if (snap_date_base > snap_date_top) {
+ $("#snap_date_base").val(snap_date_top);
+ $("#snap_date_top").val(snap_date_base);
+ $("#snap_date_start").val(snap_date_top);
+ $("#snap_date_stop").val(snap_date_base);
+ } else {
+ snap_date_start = $("#snap_date_start").val();
+ snap_date_stop = $("#snap_date_stop").val();
+ if (snap_date_start > snap_date_top) {
+ $("#snap_date_start").val(snap_date_base);
+ }
+ $("#snap_date_stop").val(snap_date_top);
+ }
+ });
+
+ $('#export-snapshot').click(function(){
+ snap_date_base = $("#snap_date_base").val();
+ snap_date_top = $("#snap_date_top").val();
+ snap_date_start = $("#snap_date_start").val();
+ snap_date_stop = $("#snap_date_stop").val();
+ if (snap_date_start > snap_date_stop) {
+ alert("Error: the start date is after the stop date");
+ return;
+ }
+ if (snap_date_start < snap_date_base) {
+ alert("Error: the start date is before the snapshot base date");
+ return;
+ }
+ if (snap_date_stop > snap_date_top) {
+ alert("Error: the stop date is after the snapshot top date");
+ return;
+ }
+ var result = confirm("Generate the report? This will take several minutes.");
+ if (result){
+ document.getElementById("export-snapshot").disabled = true;
+ document.getElementById("export-snapshot-text").innerText = "... Generating the report - this will take a few minutes ...";
+
+ /* document.getElementById("download-snapshot").disabled = true; */
+ document.getElementById("generating-report").style.display = "none";
+ postCommitAjaxRequest({
+ "action" : 'export-snapshot',
+ "snap_date_base" : snap_date_base,
+ "snap_date_top" : snap_date_top,
+ "snap_date_start" : snap_date_start,
+ "snap_date_stop" : snap_date_stop
+ });
+ }
+ });
+
+
+ /* Manage report files */
+
+ $('.submit-downloadreport').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ $('.trash-report').click(function() {
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trashreport',
+ "report_name" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+
+
+ });
+</script>
+
+{% endblock %}
diff --git a/lib/srtgui/urls.py b/lib/srtgui/urls.py
index a4947c51..c0df1c89 100644
--- a/lib/srtgui/urls.py
+++ b/lib/srtgui/urls.py
@@ -141,13 +141,17 @@ urlpatterns = [
name='manage_notifications'),
url(r'^triage_cves/$', views.triage_cves, name='triage_cves'),
url(r'^create_vulnerability/$', views.create_vulnerability, name='create_vulnerability'),
- url(r'^publish/$', views.publish, name='publish'),
url(r'^manage_report/$', views.manage_report, name='manage_report'),
url(r'^sources/$',
tables.SourcesTable.as_view(template_name="sources-toastertable.html"),
name='sources'),
url(r'^users/$', views.users, name='users'),
+ url(r'^publish/$', views.publish, name='publish'),
+ url(r'^publish_summary/$', views.publish_summary, name='publish_summary'),
+ url(r'^publish_diff_snapshot/$', views.publish_diff_snapshot, name='publish_diff_snapshot'),
+ url(r'^publish_diff_history/$', views.publish_diff_history, name='publish_diff_history'),
+
url(r'^maintenance/$', views.maintenance, name='maintenance'),
url(r'^history_cve/$',
tables.HistoryCveTable.as_view(template_name="history-cve-toastertable.html"),
diff --git a/lib/srtgui/views.py b/lib/srtgui/views.py
index 79bf7b17..7f819fd1 100644
--- a/lib/srtgui/views.py
+++ b/lib/srtgui/views.py
@@ -994,6 +994,26 @@ def publish(request):
# does this user have permission to see this record?
if not UserSafe.is_creator(request.user):
return redirect(landing)
+
+ context = {
+ }
+ return render(request, 'publish.html', context)
+
+def publish_summary(request):
+ # does this user have permission to see this record?
+ if not UserSafe.is_creator(request.user):
+ return redirect(landing)
+
+ context = {
+ }
+ return render(request, 'management.html', context)
+
+def publish_diff_snapshot(request):
+ # does this user have permission to see this record?
+ if not UserSafe.is_creator(request.user):
+ return redirect(landing)
+
+ main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
if request.method == "GET":
# Prepare available snapshots
@@ -1037,9 +1057,145 @@ def publish(request):
]
# List of available reports
generated_report_list = []
- for entry in os.scandir('data/wr'):
- if entry.name.startswith('cve-svns-srtool'):
- generated_report_list.append(ReportFile(entry.name,entry.stat().st_size,datetime.fromtimestamp(entry.stat().st_mtime)))
+ if os.path.isdir('data/publish'):
+ for entry in os.scandir('data/publish'):
+ if entry.name.startswith('cve-svns-srtool'):
+ generated_report_list.append(ReportFile(entry.name,entry.stat().st_size,datetime.fromtimestamp(entry.stat().st_mtime)))
+# generated_report_list.sort()
+ generated_report_list = sorted(generated_report_list,key=lambda x: x.name)
+
+ # Prepare History data
+ last_calc = SrtSetting.get_setting('publish_last_calc','06/08/2019')
+ date_start = SrtSetting.get_setting('publish_date_start','06/08/2019')
+ date_stop = SrtSetting.get_setting('publish_date_stop','06/21/2019')
+
+ context = {
+ 'date_start' : date_start,
+ 'date_stop' : date_stop,
+ 'last_calc' : last_calc,
+
+ 'snap_date_start' : snap_date_start,
+ 'snap_date_stop' : snap_date_stop,
+ 'snap_date_base' : snap_date_base,
+ 'snap_date_top' : snap_date_top,
+ 'snapshot_list' : snapshot_list,
+ 'snap_start_index' : '%02d' % snap_start_index,
+ 'snap_stop_index' : '%02d' % snap_stop_index,
+ 'snap_last_calc' : snap_last_calc,
+ 'generated_report_list' : generated_report_list,
+
+ 'snapshot_frequency_list' : snapshot_frequency_list,
+ 'snap_frequency_select' : snap_frequency_select,
+ }
+ return render(request, 'publish_diff_snapshot.html', context)
+ elif request.method == "POST":
+ action = request.POST['action']
+
+ if request.POST["action"] == "download":
+ report_name = request.POST['report_name']
+ file_path = 'data/publish/%s' % (report_name)
+ if file_path:
+ fsock = open(file_path, "rb")
+ content_type = MimeTypeFinder.get_mimetype(file_path)
+ response = HttpResponse(fsock, content_type = content_type)
+ disposition = 'attachment; filename="{}"'.format(file_path)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(file_path)
+ _log("EXPORT_POST_Q{%s} %s || %s " % (response, response['Content-Disposition'], disposition))
+ return response
+ else:
+ return render(request, "unavailable_artifact.html", context={})
+
+ # Dates (make as no timezone)
+ msg = ''
+ try:
+ msg = 'Start:%s' % request.POST.get('date_start', '')
+ date_start = datetime.strptime(request.POST.get('date_start', ''), '%m/%d/%Y')
+ msg = 'Stop:%s' % request.POST.get('date_stop', '')
+ date_stop = datetime.strptime(request.POST.get('date_stop', ''), '%m/%d/%Y')
+ if date_stop < date_start:
+# return 'Error:stop date is before start date'
+ _log('Error:stop date is before start date')
+ pass
+ except Exception as e:
+# return 'Error:bad format for dates (must be mm/dd/yyyy) (%s)(%s)' % (msg,e),''
+ _log('Error:bad format for dates (must be mm/dd/yyyy) (%s)(%s)' % (msg,e))
+ pass
+ SrtSetting.set_setting('publish_date_start',date_start.strftime('%m/%d/%Y'))
+ SrtSetting.set_setting('publish_date_stop',date_stop.strftime('%m/%d/%Y'))
+ if 'recalculate' == action:
+ # Calculate
+ publishCalculate(date_start,date_stop)
+ return redirect('publish')
+ if 'view' == action:
+ # Go to publish list page
+ return redirect('publish-list')
+ if 'add-cve' == action:
+ # Go to publish list page
+ return redirect('publish-cve')
+ if 'add-defect' == action:
+ # Go to publish list page
+ return redirect('publish-defect')
+ if 'reset' == action:
+ publishReset(date_start,date_stop)
+ publishCalculate(date_start,date_stop)
+ return redirect('publish')
+ if 'export' == action:
+ return redirect('/%s/report/publish' % main_app)
+ return redirect('publish')
+
+def publish_diff_history(request):
+ # does this user have permission to see this record?
+ if not UserSafe.is_creator(request.user):
+ return redirect(landing)
+
+ main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
+ if request.method == "GET":
+
+ # Prepare available snapshots
+ snapshot_list = []
+ snap_start_index = 0
+ snap_stop_index = 0
+ snap_date_base = SrtSetting.get_setting('publish_snap_date_base','2019-06-08')
+ snap_date_top = SrtSetting.get_setting('publish_snap_date_top','2019-06-16')
+ snap_date_start = SrtSetting.get_setting('publish_snap_date_start','2019-06-08')
+ snap_date_stop = SrtSetting.get_setting('publish_snap_date_stop','2019-06-16')
+ snap_last_calc = SrtSetting.get_setting('publish_snap_last_calc','')
+ backup_returncode,backup_stdout,backup_result = execute_process('bin/common/srtool_backup.py','--list-backups-db')
+ for i,line in enumerate(backup_stdout.decode("utf-8").splitlines()):
+ # Week|backup_2019_19|2019-05-18|12:51:51|Saturday, May 18 2019
+ backup_mode,backup_dir,backup_date,backup_time,backup_day = line.split('|')
+ if 'Now' != backup_mode:
+ snap = Snap(i,backup_mode,backup_dir,backup_date,backup_time,backup_day)
+ snapshot_list.append(snap)
+ if snap_date_base == snap.date:
+ snap_start_index = i
+ if snap_date_start < snap.date:
+ snap_date_start = snap.date
+ if snap_date_stop < snap.date:
+ snap_date_stop = snap.date
+ if snap_date_top == snap.date:
+ snap_stop_index = i
+ if snap_date_stop > snap.date:
+ snap_date_stop = snap.date
+ if not snap_stop_index:
+ snap_stop_index = i
+ if snap_date_stop < snap.date:
+ snap_date_stop = snap.date
+ # Report automation
+ snap_frequency_select = SrtSetting.get_setting('publish_snap_frequency','Off')
+ snapshot_frequency_list = [
+ 'Off',
+ 'Monthly',
+ 'Bi-monthly',
+ 'Weekly',
+ 'Daily',
+ ]
+ # List of available reports
+ generated_report_list = []
+ if os.path.isdir('data/publish'):
+ for entry in os.scandir('data/publish'):
+ if entry.name.startswith('cve-svns-srtool'):
+ generated_report_list.append(ReportFile(entry.name,entry.stat().st_size,datetime.fromtimestamp(entry.stat().st_mtime)))
# generated_report_list.sort()
generated_report_list = sorted(generated_report_list,key=lambda x: x.name)
@@ -1072,7 +1228,7 @@ def publish(request):
if request.POST["action"] == "download":
report_name = request.POST['report_name']
- file_path = 'data/wr/%s' % report_name
+ file_path = 'data/publish/%s' % (report_name)
if file_path:
fsock = open(file_path, "rb")
content_type = MimeTypeFinder.get_mimetype(file_path)
@@ -1119,10 +1275,12 @@ def publish(request):
publishCalculate(date_start,date_stop)
return redirect('publish')
if 'export' == action:
- return redirect('/wr/report/publish')
+ return redirect('/%s/report/publish' % main_app)
return redirect('publish')
+
+
def manage_report(request):
# does this user have permission to see this record?
if not UserSafe.is_creator(request.user):
@@ -2146,6 +2304,8 @@ def xhr_investigation_commit(request):
def xhr_publish(request):
_log("xhr_publish(%s)" % request.POST)
+ main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
+
def remove_mark(mark,line):
pos1 = line.find(mark)
if -1 == pos1:
@@ -2185,18 +2345,18 @@ def xhr_publish(request):
if (not top_dir) and (snap_date_top == backup_date) and ('Now' != backup_mode):
top_dir = 'backups/%s' % backup_dir
- _log('Publish:./bin/wr/srtool_publish.py --srt2update ' + base_dir)
- report_returncode,report_stdout,report_error = execute_process('./bin/wr/srtool_publish.py','--srt2update',base_dir)
+ _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (main_app,base_dir))
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,'--srt2update',base_dir)
if 0 != report_returncode:
return_data = {"error": "Error: base dir prep:%s:%s" % (report_error,report_stdout),}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
- _log('Publish:./bin/wr/srtool_publish.py --srt2update ' + top_dir)
- report_returncode,report_stdout,report_error = execute_process('./bin/wr/srtool_publish.py','--srt2update',top_dir)
+ _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (main_app,top_dir))
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,'--srt2update',top_dir)
if 0 != report_returncode:
return_data = {"error": "Error: top dir prep:%s:%s" % (report_error,report_stdout),}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
- _log('Publish:./bin/wr/srtool_publish.py --validate-update-svns --previous '+base_dir+' --current '+top_dir+' --start '+snap_date_start+' --stop '+snap_date_stop)
- report_returncode,report_stdout,report_error = execute_process('./bin/wr/srtool_publish.py',
+ _log('Publish:./bin/'+main_app+'/srtool_publish.py --validate-update-svns --previous '+base_dir+' --current '+top_dir+' --start '+snap_date_start+' --stop '+snap_date_stop)
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,
'--validate-update-svns','--previous',base_dir,'--current',top_dir,
'--start',snap_date_start,'--stop',snap_date_stop)
if 0 != report_returncode:
@@ -2212,7 +2372,7 @@ def xhr_publish(request):
_log('Publish:Done!')
elif 'submit-trashreport' == action:
report_name = request.POST['report_name']
- os.remove('data/wr/%s' % report_name)
+ os.remove('data/%s/%s' % (main_app,report_name))
else:
srtool_today_time = datetime.today()
srtool_today = datetime.today().strftime("%Y-%m-%d")
diff --git a/lib/yp/reports.py b/lib/yp/reports.py
new file mode 100755
index 00000000..dca99d10
--- /dev/null
+++ b/lib/yp/reports.py
@@ -0,0 +1,381 @@
+#
+# Security Response Tool Implementation
+#
+# Copyright (C) 2017-2020 Wind River Systems
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Please run flake8 on this file before sending patches
+
+import os
+import re
+import logging
+import json
+from collections import Counter
+from datetime import datetime, date
+import csv
+
+from orm.models import Cve, Vulnerability, Investigation, Defect, Product
+from orm.models import SRTool, PublishSet
+from srtgui.api import execute_process, readCveDetails, writeCveDetails, summaryCveDetails
+
+from srtgui.reports import Report, ReportManager, ProductsReport, ManagementReport, DefectsReport, PublishListReport
+
+
+from django.db.models import Q, F
+from django.db import Error
+from srtgui.templatetags.projecttags import filtered_filesizeformat
+
+logger = logging.getLogger("srt")
+
+SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_REPORT_DIR = '%s/reports' % SRT_BASE_DIR
+
+# quick development/debugging support
+from srtgui.api import _log
+
+def _log_args(msg, *args, **kwargs):
+ s = '%s:(' % msg
+ if args:
+ for a in args:
+ s += '%s,' % a
+ s += '),('
+ if kwargs:
+ for key, value in kwargs.items():
+ s += '(%s=%s),' % (key,value)
+ s += ')'
+ _log(s)
+
+###############################################################################
+#
+# YPPublishListReport: Yocto Project Management reports
+#
+
+class YPPublishListReport(PublishListReport):
+ """Report for the Publish Cve Page"""
+
+ def __init__(self, parent_page, *args, **kwargs):
+ _log_args("REPORT_YPPUBLISHLIST_INIT(%s)" % parent_page, *args, **kwargs)
+ super(YPPublishListReport, self).__init__(parent_page, *args, **kwargs)
+
+ def get_context_data(self, *args, **kwargs):
+ _log_args("REPORT_YPPUBLISHLIST_CONTEXT", *args, **kwargs)
+ context = super(YPPublishListReport, self).get_context_data(*args, **kwargs)
+
+ # Add a custom extension report type
+ context['report_type_list'] = '\
+ <option value="yp_summary">YP Summary Report</option> \
+ '
+
+ context['report_custom_list'] = ''
+ # Add scope
+ context['report_custom_list'] += '\
+ <input type="checkbox" id="new" name="new" checked>&nbsp;New CVEs</input> <br>\
+ <input type="checkbox" id="investigate" name="investigate" checked>&nbsp;Investigate CVEs</input> <br>\
+ <input type="checkbox" id="vulnerable" name="vulnerable" checked>&nbsp;Vulnerable CVEs</input> <br>\
+ <input type="checkbox" id="not-vulnerable" name="not-vulnerable" checked>&nbsp;Not Vulnerable CVEs</input> <br>\
+ <input type="checkbox" id="new-reserved" name="new-reserved" >&nbsp;New-Reserved CVEs</input> <br>\
+ <input type="checkbox" id="historical" name="historical" >&nbsp;Historical CVEs</input> <br>\
+ '
+ # Add extra
+ context['report_custom_list'] += '<br>'
+ context['report_custom_list'] += '\
+ <input type="checkbox" id="truncate" name="truncate" checked>&nbsp;Truncate fields (for simple text reports)</input> <BR>\
+ '
+
+ return context
+
+ def get_product_status_matrix(self,product_list,cve):
+ # Preset the default product status labels
+ status_table = {}
+ product_top_order = 99
+ product_top_defect = []
+ # Default all product status to the CVE's status
+ for product in product_list:
+ status_table[product.key] = SRTool.status_text(SRTool.NOT_VULNERABLE)
+ # Set the specific status for the child investigations
+ for cv in cve.cve_to_vulnerability.all():
+ #status_text = cv.vulnerability.get_status_text
+ for investigation in cv.vulnerability.vulnerability_investigation.all():
+# product_key = investigation.product.key
+ release_version_list = []
+ # Gather release versions, find the highest product's respective defect
+ for id in investigation.investigation_to_defect.all():
+ # Find defect(s) for higest ordered product
+ if product_top_order > investigation.product.order:
+ product_top_order = investigation.product.order
+ product_top_defect = []
+ if product_top_order == investigation.product.order:
+ product_top_defect.append(id.defect.name)
+ # Gather the status or release version
+ if id.defect.release_version:
+ release_version_list.append(id.defect.release_version)
+ release_version = '/'.join(release_version_list)
+ # Set investigation status, unless there are release versions
+ status_table[investigation.product.key] = investigation.get_status_text
+ if release_version:
+ status_table[investigation.product.key] = release_version
+ return status_table,product_top_defect
+
+ def exec_report(self, *args, **kwargs):
+ _log_args("REPORT_YPPUBLISHLIST_EXEC", *args, **kwargs)
+ super(YPPublishListReport, self).exec_report(*args, **kwargs)
+
+ request_POST = self.request.POST
+ format = request_POST.get('format', '')
+ report_type = request_POST.get('report_type', '')
+ csv_separator = request_POST.get('csv_separator', 'semi')
+ truncate = ('on' == request_POST.get('truncate', 'off'))
+ status_list = []
+ if ('on' == request_POST.get('new', 'off')): status_list.append(Cve.NEW)
+ if ('on' == request_POST.get('investigate', 'off')): status_list.append(Cve.INVESTIGATE)
+ if ('on' == request_POST.get('vulnerable', 'off')): status_list.append(Cve.VULNERABLE)
+ if ('on' == request_POST.get('not-vulnerable', 'off')): status_list.append(Cve.NOT_VULNERABLE)
+ if ('on' == request_POST.get('new-reserved', 'off')): status_list.append(Cve.NEW_RESERVED)
+ if ('on' == request_POST.get('historical', 'off')): status_list.append(Cve.HISTORICAL)
+
+ # Default to the regular report output if not our custom extension
+ if not report_type in ('yp_summary'):
+ return(super(YPPublishListReport, self).exec_report(*args, **kwargs))
+
+ if 'csv' == format:
+ separator = ';'
+ if csv_separator == 'comma': separator = ','
+ if csv_separator == 'tab': separator = '\t'
+ report_name = '%s/cve-svns-srtool-%s.csv' % (SRT_REPORT_DIR,datetime.today().strftime('%Y_%m_%d'))
+ else:
+ separator = ","
+ report_name = '%s/cve-svns-srtool-%s.txt' % (SRT_REPORT_DIR,datetime.today().strftime('%Y_%m_%d'))
+
+ # Get the desired product list
+ product_list = Product.objects.order_by('-order')
+
+ if 'yp_summary' == report_type:
+ with open(report_name, 'w', newline='') as csvfile:
+ writer = None
+
+ # Assemble the header
+ text_format = '%-18s,%16s,%-8s,%-8s,%-15s,%-15s,%-30s,%-25s,%15s,%15s,%20s,'
+ header = [
+ 'CVE Number',
+ 'Status',
+ 'CVSSv2_Severity',
+ 'CVSSv2_Score',
+ 'CVSSv3_Severity',
+ 'CVSSv3_Score',
+ 'CVE Description',
+ 'YP Comments',
+ 'Created Date',
+ 'Modified Date',
+ 'YP Acknowledged Date',
+ ]
+ # Assemble the product column namess
+ for product in product_list:
+ product_title = product.key
+ header.append(product_title)
+ min_len = max(16,len(product_title)+1)
+ str_format = "%s%ds," % ('%',min_len)
+ text_format += str_format
+# # Add Top Defect
+# header.append('Top Defect')
+# text_format += '%s'
+
+ # Print the header
+ if 'csv' == format:
+ writer = csv.writer(csvfile, delimiter=separator, quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer.writerow(header)
+ else:
+ writer = csvfile
+ print(text_format % tuple(header), file=csvfile)
+
+ for i,cve in enumerate(Cve.objects.filter(status__in=status_list).order_by('name_sort')):
+ # Compute the product columns
+ status_table,product_top_defect = self.get_product_status_matrix(product_list,cve)
+ # Assemble the row data
+ if cve.description:
+ if truncate:
+ description = cve.description[:26] + '...'
+ else:
+ description = cve.description
+ else:
+ description = ''
+
+ # Use publish date if acknowledge date not available
+ try:
+ acknowledge_date = cve.acknowledge_date
+ if not acknowledge_date:
+ acknowledge_date = datetime.strptime(cve.publishedDate, '%Y-%m-%d')
+ acknowledge_date = acknowledge_date.strftime('%m/%d/%Y')
+ except:
+ acknowledge_date = ''
+ _log("NO ACK:%s,%s" % (cve.acknowledge_date,cve.publishedDate))
+
+ row = [
+ cve.name,
+ cve.get_status_text,
+ cve.cvssV2_severity,
+ cve.cvssV2_baseScore,
+ cve.cvssV3_baseSeverity,
+ cve.cvssV3_baseScore,
+ description,
+ cve.get_public_comments[:20] if truncate else cve.get_public_comments,
+ cve.srt_created.strftime('%Y/%m/%d') if cve.srt_created else '',
+ cve.srt_updated.strftime('%Y/%m/%d') if cve.srt_updated else '',
+ acknowledge_date,
+ ]
+ # Append the product columns
+ for product in product_list:
+ # Show inactive status as normal status
+ row.append(status_table[product.key].replace('(','').replace(')',''))
+# row.append('/'.join(product_top_defect))
+ # Print the row
+ if 'csv' == format:
+ writer.writerow(row)
+ else:
+ print(text_format % tuple(row), file=writer)
+
+
+ return report_name,os.path.basename(report_name)
+
+###############################################################################
+#
+# EXAMPLE: simple custom extention to the Products report
+#
+class YPProductsReport(ProductsReport):
+ """Report for the Products Page"""
+
+ def __init__(self, parent_page, *args, **kwargs):
+ _log_args("YP_REPORT_PRODUCTS_INIT(%s)" % parent_page, *args, **kwargs)
+ super(YPProductsReport, self).__init__(parent_page, *args, **kwargs)
+
+ def get_context_data(self, *args, **kwargs):
+ _log_args("YP_REPORT_PRODUCTS_CONTEXT", *args, **kwargs)
+
+ # Fetch the default report context definition
+ context = super(YPProductsReport, self).get_context_data(*args, **kwargs)
+
+ # Add a custom extension report type
+ context['report_type_list'] += '\
+ <option value="wr_summary">YP Products Table</option> \
+ '
+
+ # Done!
+ return context
+
+ def exec_report(self, *args, **kwargs):
+ _log_args("YP_REPORT_PRODUCTS_EXEC", *args, **kwargs)
+
+ request_POST = self.request.POST
+
+ records = request_POST.get('records', '')
+ format = request_POST.get('format', '')
+ title = request_POST.get('title', '')
+ report_type = request_POST.get('report_type', '')
+ record_list = request_POST.get('record_list', '')
+
+ # Default to the regular report output if not our custom extension
+ if 'wr_summary' != report_type:
+ return(super(YPProductsReport, self).exec_report(*args, **kwargs))
+
+ # CUSTOM: prepend "wr" to the generated file name
+ report_name = '%s/wr_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:
+ separator = "\t"
+ else:
+ separator = ","
+
+ if ('wr_summary' == report_type):
+ if 'csv' == format:
+ # CUSTOM: prepend "YP" to the generated header
+ file.write("YP Name\tVersion\tProfile\tCPE\tSRT SPE\tInvestigations\tDefects\n")
+ if 'txt' == format:
+ # CUSTOM: prepend "YP" to the generated title
+ file.write("Report : YP Products Table\n")
+ file.write("\n")
+ # CUSTOM: prepend "YP" to the generated header
+ file.write("YP Name,Version,Profile,CPE,SRT SPE,Investigations,Defects\n")
+
+ for product in Product.objects.all():
+ # CUSTOM: prepend "YP" to the product name
+ file.write("YP %s%s" % (product.name,separator))
+ file.write("%s%s" % (product.version,separator))
+ file.write("%s%s" % (product.profile,separator))
+ file.write("%s%s" % (product.cpe,separator))
+ file.write("%s%s" % (product.defect_tags,separator))
+ file.write("%s%s" % (product.product_tags,separator))
+
+ for i,pi in enumerate(product.product_investigation.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pi.name))
+ file.write("%s" % separator)
+ for i,pd in enumerate(product.product_defect.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pd.name))
+ #file.write("%s" % separator)
+ file.write("\n")
+
+ return report_name,os.path.basename(report_name)
+
+###############################################################################
+#
+# Yocto Projects reports
+#
+# Available 'parent_page' values:
+# cve
+# vulnerability
+# investigation
+# defect
+# cves
+# select-cves
+# vulnerabilities
+# investigations
+# defects
+# products
+# select-publish
+# update-published
+# package-filters
+# cpes_srtool
+#
+
+class YPReportManager():
+ @staticmethod
+ def get_report_class(parent_page, *args, **kwargs):
+ _log("FOO:YPReportManager:'%s'" % parent_page)
+
+ if 'products' == parent_page:
+ # Extend the Products report
+ return YPProductsReport(parent_page, *args, **kwargs)
+
+ elif 'publish-summary' == parent_page:
+ return YPPublishListReport(parent_page, *args, **kwargs)
+
+ else:
+ # Return the default for all other reports
+ return ReportManager.get_report_class(parent_page, *args, **kwargs)
+
+ @staticmethod
+ def get_context_data(parent_page, *args, **kwargs):
+ _log_args("YP_REPORTMANAGER_CONTEXT", *args, **kwargs)
+ reporter = YPReportManager.get_report_class(parent_page, *args, **kwargs)
+ return reporter.get_context_data(*args, **kwargs)
+
+ @staticmethod
+ def exec_report(parent_page, *args, **kwargs):
+ _log_args("YP_REPORTMANAGER_EXEC", *args, **kwargs)
+ reporter = YPReportManager.get_report_class(parent_page, *args, **kwargs)
+ return reporter.exec_report(*args, **kwargs)
diff --git a/lib/yp/urls.py b/lib/yp/urls.py
index 586b87b6..494de9ae 100755
--- a/lib/yp/urls.py
+++ b/lib/yp/urls.py
@@ -5,4 +5,8 @@ urlpatterns = [
url(r'^hello/$', views.yp_hello, name='yp_hello'),
url(r'^$', views.yp_hello, name='yp_default'),
+
+ url(r'^report/(?P<page_name>\D+)$', views.report, name='report'),
+ url(r'^manage_report/$', views.manage_report, name='manage_report'),
+
]
diff --git a/lib/yp/views.py b/lib/yp/views.py
index 2d6d0043..3310e7e6 100755
--- a/lib/yp/views.py
+++ b/lib/yp/views.py
@@ -4,7 +4,7 @@
#
# Security Response Tool Implementation
#
-# Copyright (C) 2017-2018 Wind River Systems
+# Copyright (C) 2017-2020 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -21,9 +21,13 @@
#from django.urls import reverse_lazy
#from django.views import generic
-#from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
+from users.models import SrtUser, UserSafe
+from srtgui.views import MimeTypeFinder
+from yp.reports import YPReportManager
+
#from orm.models import SrtSetting
# quick development/debugging support
@@ -33,3 +37,47 @@ def yp_hello(request):
context = {}
_log("Note:yp_hello")
return render(request, 'yp_hello.html', context)
+
+def report(request,page_name):
+ if request.method == "GET":
+ context = YPReportManager.get_context_data(page_name,request=request)
+ record_list = request.GET.get('record_list', '')
+ _log("EXPORT_GET!:%s|%s|" % (request,record_list))
+ context['record_list'] = record_list
+ return render(request, 'report.html', context)
+ elif request.method == "POST":
+ _log("EXPORT_POST!:%s|%s" % (request,request.FILES))
+ parent_page = request.POST.get('parent_page', '')
+ file_name,response_file_name = YPReportManager.exec_report(parent_page,request=request)
+
+ if file_name.startswith("Error"):
+ # Refresh the page with the error message
+ context = YPReportManager.get_context_data(page_name,request=request)
+ context['error_message'] = file_name
+ record_list = request.GET.get('record_list', '')
+ _log("EXPORT_GET_WITH_ERROR!:%s|%s|" % (request,record_list))
+ context['record_list'] = record_list
+ return render(request, 'report.html', context)
+ elif file_name and response_file_name:
+ fsock = open(file_name, "rb")
+ content_type = MimeTypeFinder.get_mimetype(file_name)
+
+ response = HttpResponse(fsock, content_type = content_type)
+
+ disposition = "attachment; filename=" + response_file_name
+ response["Content-Disposition"] = disposition
+
+ _log("EXPORT_POST_Q{%s|" % (response))
+ return response
+ else:
+ return render(request, "unavailable_artifact.html", {})
+
+ return redirect('/')
+ raise Exception("Invalid HTTP method for this page")
+
+def manage_report(request):
+ # does this user have permission to see this record?
+ if not UserSafe.is_creator(request.user):
+ return redirect('/')
+
+ return redirect(report,'management')