Skip to content

Add basic json output #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions cve_bin_tool/OutputEngine.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import csv
import json
import os

from datetime import datetime
Expand All @@ -20,37 +21,56 @@ def generate_filename(self, extention=None):
self.filename = f"output.cve-bin-tool.{now}.{extention}"

def output_cves(self, outfile, output=None):

""" Output a list of CVEs
format is modules[checker_name][version] = dict{id: severity}
format self.modules[checker_name][version] = dict{id: severity}
to other formats like CSV or JSON
"""
if output == "json":
self.output_json(outfile)
else: # csv, console, or anything else that is unrecognised
self.output_csv(outfile)

def formatted_modules(self):
""" Returns self.modules converted into form
formatted_output[modulesname][version] = {
"cve": cve_number,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't decide if this should be cve_number (rather than cve) for consistency, since we use cve_number just about anywhere we refer to a single number (as opposed to a list of cves). I think rather than hold up a review for this I'm going to just merge, but we might have to revisit when we see what the dffml team decides they actually want out of the json output and whether the difference matters to them. @pdxjohnny if you have an opinion now, maybe we could open an issue to discuss what "ideal" json output will look like?

"severity": cve_severity,
}
"""
# if the output is csv we must open a file
if output == "csv":
outfile = open(f"{outfile}", "w")
formatted_output = {}
for modulename in self.modules:
formatted_output[modulename] = {}
for version, cves in self.modules[modulename].items():
formatted_output[modulename][version] = []
for cve_number, cve_severity in cves.items():
formatted_output[modulename][version].append(
{"cve": cve_number, "severity": cve_severity,}
)
return formatted_output

writer = csv.writer(outfile)
writer.writerow(["Module Name", "Version", "CVE Number", "Severity"])
def output_json(self, outfile):
""" Output a JSON of CVEs """
json.dump(self.formatted_modules(), outfile)

def output_csv(self, outfile):
""" Output a CSV of CVEs """
writer = csv.writer(outfile)
writer.writerow(["Module Name", "Version", "CVE Number", "Severity"])
for modulename, versions in self.modules.items():
for version, cve_list in versions.items():
for cve_number, cve_severity in cve_list.items():
row = [modulename, version, cve_number, cve_severity]
writer.writerow(row)

# We must also close the file
if output == "csv":
outfile.close()

def output_csv(self):
def output_file(self, output="csv"):

""" Generate a CSV file for list of CVE """
""" Generate a file for list of CVE """

# Check if we need to generate a filename
if self.filename == None:
self.generate_filename("csv")
self.generate_filename(output)
else:
self.filename = f"{self.filename}.csv"
self.filename = f"{self.filename}.{output}"
# check if the filename already exists
file_list = os.listdir(os.getcwd())
if self.filename in file_list:
Expand All @@ -60,7 +80,7 @@ def output_csv(self):
self.logger.info(
"Generating a new filename with Default Naming Convention"
)
self.generate_filename("csv")
self.generate_filename(output)

# try opening that file
try:
Expand All @@ -70,10 +90,11 @@ def output_csv(self):
except Exception as E:
self.logger.warning(E)
self.logger.info("Switching Back to Default Naming Convention")
self.generate_filename("csv")
self.generate_filename(output)

# Log the filename generated
self.logger.info(f"CSV output stored at {os.getcwd()}/{self.filename}")
self.logger.info(f"Output stored at {os.getcwd()}/{self.filename}")

# call to output_cves
self.output_cves(self.filename, "csv")
with open(self.filename, "w") as f:
self.output_cves(f, output)
7 changes: 3 additions & 4 deletions cve_bin_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,9 @@ def main(argv=None, outfile=sys.stdout):
and args.output == "console"
):
output.output_cves(outfile)

# If the args are passed for csv we will generate a CSV output
if args.output == "csv":
output.output_csv()
else:
# If the args are passed for csv/json we will generate a file output
output.output_file(args.output)

# Use the number of files with known cves as error code
# as requested by folk planning to automate use of this script.
Expand Down
44 changes: 44 additions & 0 deletions test/test_output_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
CVE-bin-tool OutputEngine tests
"""
import unittest
import io
import json

from cve_bin_tool.OutputEngine import OutputEngine


class TestOutputEngine(unittest.TestCase):
""" Test the OutputEngine class functions """

MOCK_MODULES = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hurrah for mock tests!

"modulename0": {
"1.0": {"CVE-1234-1234": "MEDIUM", "CVE-1234-9876": "LOW",},
"2.8.6": {"CVE-1234-1111": "LOW",},
},
"modulename1": {"3.2.1.0": {"CVE-1234-5678": "HIGH",}},
}
FORMATTED_MODULES = {
"modulename0": {
"1.0": [
{"cve": "CVE-1234-1234", "severity": "MEDIUM"},
{"cve": "CVE-1234-9876", "severity": "LOW"},
],
"2.8.6": [{"cve": "CVE-1234-1111", "severity": "LOW"},],
},
"modulename1": {"3.2.1.0": [{"cve": "CVE-1234-5678", "severity": "HIGH"},]},
}

def setUp(self):
self.output_engine = OutputEngine(modules=self.MOCK_MODULES)
self.mock_file = io.StringIO()

def test_formatted_modules(self):
""" Test reformatting modules """
self.assertEqual(self.output_engine.formatted_modules(), self.FORMATTED_MODULES)

def test_output_json(self):
""" Test formatting output as JSON """
self.output_engine.output_json(self.mock_file)
self.mock_file.seek(0) # reset file position
self.assertEqual(json.load(self.mock_file), self.FORMATTED_MODULES)