diff options
-rwxr-xr-x | bin/common/srtool_common.py | 22 | ||||
-rwxr-xr-x | bin/common/srtool_utils.py | 250 | ||||
-rwxr-xr-x | bin/mitre/srtool_mitre.py | 41 | ||||
-rw-r--r-- | bin/nist/datasource.json | 4 | ||||
-rw-r--r-- | lib/srtgui/tables.py | 7 | ||||
-rw-r--r-- | lib/srtgui/templates/publish.html | 361 | ||||
-rw-r--r-- | lib/srtgui/templates/publish_diff_snapshot.html | 327 | ||||
-rw-r--r-- | lib/srtgui/urls.py | 6 | ||||
-rw-r--r-- | lib/srtgui/views.py | 184 | ||||
-rwxr-xr-x | lib/yp/reports.py | 381 | ||||
-rwxr-xr-x | lib/yp/urls.py | 4 | ||||
-rwxr-xr-x | lib/yp/views.py | 52 |
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">→</span> - <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">→</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}}"> - Stop Date: <input type="text" id="snap_date_stop" value="{{snap_date_stop}}"> - <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}}"> - 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">→</span> + <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">→</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}}"> + Stop Date: <input type="text" id="snap_date_stop" value="{{snap_date_stop}}"> + <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}}"> + 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> New CVEs</input> <br>\ + <input type="checkbox" id="investigate" name="investigate" checked> Investigate CVEs</input> <br>\ + <input type="checkbox" id="vulnerable" name="vulnerable" checked> Vulnerable CVEs</input> <br>\ + <input type="checkbox" id="not-vulnerable" name="not-vulnerable" checked> Not Vulnerable CVEs</input> <br>\ + <input type="checkbox" id="new-reserved" name="new-reserved" > New-Reserved CVEs</input> <br>\ + <input type="checkbox" id="historical" name="historical" > Historical CVEs</input> <br>\ + ' + # Add extra + context['report_custom_list'] += '<br>' + context['report_custom_list'] += '\ + <input type="checkbox" id="truncate" name="truncate" checked> 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') |