From f8c22f8b641ae4736e4dbe048cb3d3a992855525 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Mon, 7 Sep 2015 19:42:49 -0700 Subject: [PATCH] Split each formatter into separate modules As we increase the number of formatters, it doesn't make sense to cram each into a single module. This patch splits them up by output format type. Any new formatter should also be implemented as a separate module. Change-Id: Ibbd0edb9af06a52bef28804a6619a07f323931e6 --- bandit/core/formatters.py | 274 ----------------------------- bandit/core/utils.py | 8 + bandit/formatters/__init__.py | 0 bandit/formatters/csv.py | 52 ++++++ bandit/formatters/json.py | 92 ++++++++++ bandit/formatters/text.py | 121 +++++++++++++ bandit/formatters/xml.py | 53 ++++++ setup.cfg | 8 +- tests/unit/core/test_formatters.py | 151 ---------------- tests/unit/formatters/__init__.py | 0 tests/unit/formatters/test_csv.py | 66 +++++++ tests/unit/formatters/test_json.py | 74 ++++++++ tests/unit/formatters/test_text.py | 70 ++++++++ tests/unit/formatters/test_xml.py | 83 +++++++++ 14 files changed, 623 insertions(+), 429 deletions(-) delete mode 100644 bandit/core/formatters.py create mode 100644 bandit/formatters/__init__.py create mode 100644 bandit/formatters/csv.py create mode 100644 bandit/formatters/json.py create mode 100644 bandit/formatters/text.py create mode 100644 bandit/formatters/xml.py delete mode 100644 tests/unit/core/test_formatters.py create mode 100644 tests/unit/formatters/__init__.py create mode 100644 tests/unit/formatters/test_csv.py create mode 100644 tests/unit/formatters/test_json.py create mode 100644 tests/unit/formatters/test_text.py create mode 100644 tests/unit/formatters/test_xml.py diff --git a/bandit/core/formatters.py b/bandit/core/formatters.py deleted file mode 100644 index 6d26a842..00000000 --- a/bandit/core/formatters.py +++ /dev/null @@ -1,274 +0,0 @@ -# -*- coding:utf-8 -*- -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import collections -import csv -import datetime -import json -import logging -from operator import itemgetter - -import six - -from bandit.core import constants - - -logger = logging.getLogger(__name__) - - -def _sum_scores(manager, sev): - summation = 0 - for scores in manager.scores: - summation += sum(scores['CONFIDENCE'][sev:]) - summation += sum(scores['SEVERITY'][sev:]) - return summation - - -def report_csv(manager, filename, sev_level, conf_level, lines=-1, - out_format='csv'): - '''Prints issues in CSV format - - :param manager: the bandit manager object - :param filename: The output file name, or None for stdout - :param sev_level: Filtering severity level - :param conf_level: Filtering confidence level - :param lines: Number of lines to report, -1 for all - :param out_format: The ouput format name - ''' - - results = manager.get_issue_list() - - if filename is None: - filename = 'bandit_results.csv' - - with open(filename, 'w') as fout: - fieldnames = ['filename', - 'test_name', - 'issue_severity', - 'issue_confidence', - 'issue_text', - 'line_number', - 'line_range'] - - writer = csv.DictWriter(fout, fieldnames=fieldnames, - extrasaction='ignore') - writer.writeheader() - for result in results: - if result.filter(sev_level, conf_level): - writer.writerow(result.as_dict(with_code=False)) - - print("CSV output written to file: %s" % filename) - - -def report_json(manager, filename, sev_level, conf_level, lines=-1, - out_format='json'): - '''''Prints issues in JSON format - - :param manager: the bandit manager object - :param filename: The output file name, or None for stdout - :param sev_level: Filtering severity level - :param conf_level: Filtering confidence level - :param lines: Number of lines to report, -1 for all - :param out_format: The ouput format name - ''' - - stats = dict(zip(manager.files_list, manager.scores)) - machine_output = dict({'results': [], 'errors': [], 'stats': []}) - for (fname, reason) in manager.skipped: - machine_output['errors'].append({'filename': fname, - 'reason': reason}) - - for filer, score in six.iteritems(stats): - totals = {} - rank = constants.RANKING - sev_idx = rank.index(sev_level) - for i in range(sev_idx, len(rank)): - severity = rank[i] - severity_value = constants.RANKING_VALUES[severity] - try: - sc = score['SEVERITY'][i] / severity_value - except ZeroDivisionError: - sc = 0 - totals[severity] = sc - - machine_output['stats'].append({ - 'filename': filer, - 'score': _sum_scores(manager, sev_idx), - 'issue totals': totals}) - - results = manager.get_issue_list() - collector = [] - for result in results: - if result.filter(sev_level, conf_level): - collector.append(result.as_dict()) - - if manager.agg_type == 'vuln': - machine_output['results'] = sorted(collector, - key=itemgetter('test_name')) - else: - machine_output['results'] = sorted(collector, - key=itemgetter('filename')) - - # timezone agnostic format - TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ" - - time_string = datetime.datetime.utcnow().strftime(TS_FORMAT) - machine_output['generated_at'] = time_string - - result = json.dumps(machine_output, sort_keys=True, - indent=2, separators=(',', ': ')) - - if filename: - with open(filename, 'w') as fout: - fout.write(result) - logger.info("JSON output written to file: %s" % filename) - else: - print(result) - - -def report_text(manager, filename, sev_level, conf_level, lines=-1, - out_format='txt'): - '''Prints issues in Text formt - - :param manager: the bandit manager object - :param filename: The output file name, or None for stdout - :param sev_level: Filtering severity level - :param conf_level: Filtering confidence level - :param lines: Number of lines to report, -1 for all - :param out_format: The ouput format name - ''' - - tmpstr_list = [] - - # use a defaultdict to default to an empty string - color = collections.defaultdict(str) - - if out_format == 'txt': - # get text colors from settings for TTY output - get_setting = manager.b_conf.get_setting - color = {'HEADER': get_setting('color_HEADER'), - 'DEFAULT': get_setting('color_DEFAULT'), - 'LOW': get_setting('color_LOW'), - 'MEDIUM': get_setting('color_MEDIUM'), - 'HIGH': get_setting('color_HIGH') - } - - # print header - tmpstr_list.append("%sRun started:%s\n\t%s\n" % ( - color['HEADER'], - color['DEFAULT'], - datetime.datetime.utcnow() - )) - - if manager.verbose: - # print which files were inspected - tmpstr_list.append("\n%sFiles in scope (%s):%s\n" % ( - color['HEADER'], len(manager.files_list), - color['DEFAULT'] - )) - - for item in zip(manager.files_list, map(_sum_scores, manager.scores)): - tmpstr_list.append("\t%s (score: %i)\n" % item) - - # print which files were excluded and why - tmpstr_list.append("\n%sFiles excluded (%s):%s\n" % - (color['HEADER'], len(manager.skipped), - color['DEFAULT'])) - for fname in manager.skipped: - tmpstr_list.append("\t%s\n" % fname) - - # print which files were skipped and why - tmpstr_list.append("\n%sFiles skipped (%s):%s\n" % ( - color['HEADER'], len(manager.skipped), - color['DEFAULT'] - )) - - for (fname, reason) in manager.skipped: - tmpstr_list.append("\t%s (%s)\n" % (fname, reason)) - - # print the results - tmpstr_list.append("\n%sTest results:%s\n" % ( - color['HEADER'], color['DEFAULT'] - )) - - issues = manager.get_issue_list() - if not len(issues): - tmpstr_list.append("\tNo issues identified.\n") - - for issue in issues: - # if the result isn't filtered out by severity - if issue.filter(sev_level, conf_level): - tmpstr_list.append("\n%s>> Issue: %s\n" % ( - color.get(issue.severity, color['DEFAULT']), - issue.text - )) - tmpstr_list.append(" Severity: %s Confidence: %s\n" % ( - issue.severity.capitalize(), - issue.confidence.capitalize() - )) - tmpstr_list.append(" Location: %s:%s\n" % ( - issue.fname, - issue.lineno - )) - tmpstr_list.append(color['DEFAULT']) - - tmpstr_list.append( - issue.get_code(lines, True)) - - result = ''.join(tmpstr_list) - - if filename: - with open(filename, 'w') as fout: - fout.write(result) - logger.info("Text output written to file: %s", filename) - else: - print(result) - - -def report_xml(manager, filename, sev_level, conf_level, lines=-1, - out_format='xml'): - '''Prints issues in XML formt - - :param manager: the bandit manager object - :param filename: The output file name, or None for stdout - :param sev_level: Filtering severity level - :param conf_level: Filtering confidence level - :param lines: Number of lines to report, -1 for all - :param out_format: The ouput format name - ''' - - import xml.etree.cElementTree as ET - - if filename is None: - filename = 'bandit_results.xml' - - issues = manager.get_issue_list() - root = ET.Element('testsuite', name='bandit', tests=str(len(issues))) - - for issue in issues: - test = issue.test - testcase = ET.SubElement(root, 'testcase', - classname=issue.fname, name=test) - if issue.filter(sev_level, conf_level): - text = 'Severity: %s Confidence: %s\n%s\nLocation %s:%s' - text = text % ( - issue.severity, issue.confidence, - issue.text, issue.fname, issue.lineno) - ET.SubElement(testcase, 'error', - type=issue.severity, - message=issue.text).text = text - - tree = ET.ElementTree(root) - tree.write(filename, encoding='utf-8', xml_declaration=True) - - print("XML output written to file: %s" % filename) diff --git a/bandit/core/utils.py b/bandit/core/utils.py index 2b84ed33..b8530ab5 100644 --- a/bandit/core/utils.py +++ b/bandit/core/utils.py @@ -386,3 +386,11 @@ def get_path_for_function(f): else: logger.warn("Cannot resolve file path for module %s", module_name) return None + + +def sum_scores(manager, sev): + summation = 0 + for scores in manager.scores: + summation += sum(scores['CONFIDENCE'][sev:]) + summation += sum(scores['SEVERITY'][sev:]) + return summation diff --git a/bandit/formatters/__init__.py b/bandit/formatters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bandit/formatters/csv.py b/bandit/formatters/csv.py new file mode 100644 index 00000000..6cf440bd --- /dev/null +++ b/bandit/formatters/csv.py @@ -0,0 +1,52 @@ +# -*- coding:utf-8 -*- +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import +import csv + + +def report(manager, filename, sev_level, conf_level, lines=-1, + out_format='csv'): + '''Prints issues in CSV format + + :param manager: the bandit manager object + :param filename: The output file name, or None for stdout + :param sev_level: Filtering severity level + :param conf_level: Filtering confidence level + :param lines: Number of lines to report, -1 for all + :param out_format: The ouput format name + ''' + + results = manager.get_issue_list() + + if filename is None: + filename = 'bandit_results.csv' + + with open(filename, 'w') as fout: + fieldnames = ['filename', + 'test_name', + 'issue_severity', + 'issue_confidence', + 'issue_text', + 'line_number', + 'line_range'] + + writer = csv.DictWriter(fout, fieldnames=fieldnames, + extrasaction='ignore') + writer.writeheader() + for result in results: + if result.filter(sev_level, conf_level): + writer.writerow(result.as_dict(with_code=False)) + + print("CSV output written to file: %s" % filename) diff --git a/bandit/formatters/json.py b/bandit/formatters/json.py new file mode 100644 index 00000000..247d78b1 --- /dev/null +++ b/bandit/formatters/json.py @@ -0,0 +1,92 @@ +# -*- coding:utf-8 -*- +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import +import datetime +import json +import logging +from operator import itemgetter + +import six + +from bandit.core import constants +from bandit.core import utils + +logger = logging.getLogger(__name__) + + +def report(manager, filename, sev_level, conf_level, lines=-1, + out_format='json'): + '''''Prints issues in JSON format + + :param manager: the bandit manager object + :param filename: The output file name, or None for stdout + :param sev_level: Filtering severity level + :param conf_level: Filtering confidence level + :param lines: Number of lines to report, -1 for all + :param out_format: The ouput format name + ''' + + stats = dict(zip(manager.files_list, manager.scores)) + machine_output = dict({'results': [], 'errors': [], 'stats': []}) + for (fname, reason) in manager.skipped: + machine_output['errors'].append({'filename': fname, + 'reason': reason}) + + for filer, score in six.iteritems(stats): + totals = {} + rank = constants.RANKING + sev_idx = rank.index(sev_level) + for i in range(sev_idx, len(rank)): + severity = rank[i] + severity_value = constants.RANKING_VALUES[severity] + try: + sc = score['SEVERITY'][i] / severity_value + except ZeroDivisionError: + sc = 0 + totals[severity] = sc + + machine_output['stats'].append({ + 'filename': filer, + 'score': utils.sum_scores(manager, sev_idx), + 'issue totals': totals}) + + results = manager.get_issue_list() + collector = [] + for result in results: + if result.filter(sev_level, conf_level): + collector.append(result.as_dict()) + + if manager.agg_type == 'vuln': + machine_output['results'] = sorted(collector, + key=itemgetter('test_name')) + else: + machine_output['results'] = sorted(collector, + key=itemgetter('filename')) + + # timezone agnostic format + TS_FORMAT = "%Y-%m-%dT%H:%M:%SZ" + + time_string = datetime.datetime.utcnow().strftime(TS_FORMAT) + machine_output['generated_at'] = time_string + + result = json.dumps(machine_output, sort_keys=True, + indent=2, separators=(',', ': ')) + + if filename: + with open(filename, 'w') as fout: + fout.write(result) + logger.info("JSON output written to file: %s" % filename) + else: + print(result) diff --git a/bandit/formatters/text.py b/bandit/formatters/text.py new file mode 100644 index 00000000..44924ad5 --- /dev/null +++ b/bandit/formatters/text.py @@ -0,0 +1,121 @@ +# -*- coding:utf-8 -*- +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +import datetime +import logging + +from bandit.core import utils + +logger = logging.getLogger(__name__) + + +def report(manager, filename, sev_level, conf_level, lines=-1, + out_format='txt'): + '''Prints issues in Text formt + + :param manager: the bandit manager object + :param filename: The output file name, or None for stdout + :param sev_level: Filtering severity level + :param conf_level: Filtering confidence level + :param lines: Number of lines to report, -1 for all + :param out_format: The ouput format name + ''' + + tmpstr_list = [] + + # use a defaultdict to default to an empty string + color = collections.defaultdict(str) + + if out_format == 'txt': + # get text colors from settings for TTY output + get_setting = manager.b_conf.get_setting + color = {'HEADER': get_setting('color_HEADER'), + 'DEFAULT': get_setting('color_DEFAULT'), + 'LOW': get_setting('color_LOW'), + 'MEDIUM': get_setting('color_MEDIUM'), + 'HIGH': get_setting('color_HIGH') + } + + # print header + tmpstr_list.append("%sRun started:%s\n\t%s\n" % ( + color['HEADER'], + color['DEFAULT'], + datetime.datetime.utcnow() + )) + + if manager.verbose: + # print which files were inspected + tmpstr_list.append("\n%sFiles in scope (%s):%s\n" % ( + color['HEADER'], len(manager.files_list), + color['DEFAULT'] + )) + + for item in zip(manager.files_list, map(utils.sum_scores, + manager.scores)): + tmpstr_list.append("\t%s (score: %i)\n" % item) + + # print which files were excluded and why + tmpstr_list.append("\n%sFiles excluded (%s):%s\n" % + (color['HEADER'], len(manager.skipped), + color['DEFAULT'])) + for fname in manager.skipped: + tmpstr_list.append("\t%s\n" % fname) + + # print which files were skipped and why + tmpstr_list.append("\n%sFiles skipped (%s):%s\n" % ( + color['HEADER'], len(manager.skipped), + color['DEFAULT'] + )) + + for (fname, reason) in manager.skipped: + tmpstr_list.append("\t%s (%s)\n" % (fname, reason)) + + # print the results + tmpstr_list.append("\n%sTest results:%s\n" % ( + color['HEADER'], color['DEFAULT'] + )) + + issues = manager.get_issue_list() + if not len(issues): + tmpstr_list.append("\tNo issues identified.\n") + + for issue in issues: + # if the result isn't filtered out by severity + if issue.filter(sev_level, conf_level): + tmpstr_list.append("\n%s>> Issue: %s\n" % ( + color.get(issue.severity, color['DEFAULT']), + issue.text + )) + tmpstr_list.append(" Severity: %s Confidence: %s\n" % ( + issue.severity.capitalize(), + issue.confidence.capitalize() + )) + tmpstr_list.append(" Location: %s:%s\n" % ( + issue.fname, + issue.lineno + )) + tmpstr_list.append(color['DEFAULT']) + + tmpstr_list.append( + issue.get_code(lines, True)) + + result = ''.join(tmpstr_list) + + if filename: + with open(filename, 'w') as fout: + fout.write(result) + logger.info("Text output written to file: %s", filename) + else: + print(result) diff --git a/bandit/formatters/xml.py b/bandit/formatters/xml.py new file mode 100644 index 00000000..e6bd5283 --- /dev/null +++ b/bandit/formatters/xml.py @@ -0,0 +1,53 @@ +# -*- coding:utf-8 -*- +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import +from xml.etree import cElementTree as ET + + +def report(manager, filename, sev_level, conf_level, lines=-1, + out_format='xml'): + '''Prints issues in XML formt + + :param manager: the bandit manager object + :param filename: The output file name, or None for stdout + :param sev_level: Filtering severity level + :param conf_level: Filtering confidence level + :param lines: Number of lines to report, -1 for all + :param out_format: The ouput format name + ''' + + if filename is None: + filename = 'bandit_results.xml' + + issues = manager.get_issue_list() + root = ET.Element('testsuite', name='bandit', tests=str(len(issues))) + + for issue in issues: + test = issue.test + testcase = ET.SubElement(root, 'testcase', + classname=issue.fname, name=test) + if issue.filter(sev_level, conf_level): + text = 'Severity: %s Confidence: %s\n%s\nLocation %s:%s' + text = text % ( + issue.severity, issue.confidence, + issue.text, issue.fname, issue.lineno) + ET.SubElement(testcase, 'error', + type=issue.severity, + message=issue.text).text = text + + tree = ET.ElementTree(root) + tree.write(filename, encoding='utf-8', xml_declaration=True) + + print("XML output written to file: %s" % filename) diff --git a/setup.cfg b/setup.cfg index db983abe..688b1d43 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,10 +25,10 @@ classifier = console_scripts = bandit = bandit.bandit:main bandit.formatters = - csv = bandit.core.formatters:report_csv - json = bandit.core.formatters:report_json - txt = bandit.core.formatters:report_text - xml = bandit.core.formatters:report_xml + csv = bandit.formatters.csv:report + json = bandit.formatters.json:report + txt = bandit.formatters.text:report + xml = bandit.formatters.xml:report bandit.plugins = # bandit/plugins/asserts.py assert_used = bandit.plugins.asserts:assert_used diff --git a/tests/unit/core/test_formatters.py b/tests/unit/core/test_formatters.py deleted file mode 100644 index c8acaa17..00000000 --- a/tests/unit/core/test_formatters.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from collections import defaultdict -import csv -import json -import os -import tempfile -from xml.etree import cElementTree as ET - -import six -import testtools - -import bandit -from bandit.core import constants -from bandit.core import config -from bandit.core import manager -from bandit.core import formatters -from bandit.core import issue - - -class FormattersTests(testtools.TestCase): - - def setUp(self): - super(FormattersTests, self).setUp() - cfg_file = os.path.join(os.getcwd(), 'bandit/config/bandit.yaml') - conf = config.BanditConfig(cfg_file) - self.manager = manager.BanditManager(conf, 'file') - (tmp_fd, self.tmp_fname) = tempfile.mkstemp() - self.context = {'filename': self.tmp_fname, - 'lineno': 4, - 'linerange': [4]} - self.check_name = 'hardcoded_bind_all_interfaces' - self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, - 'Possible binding to all interfaces.') - self.manager.out_file = self.tmp_fname - - self.issue.fname = self.context['filename'] - self.issue.lineno = self.context['lineno'] - self.issue.linerange = self.context['linerange'] - self.issue.test = self.check_name - - self.manager.results.append(self.issue) - - def test_report_csv(self): - formatters.report_csv(self.manager, self.tmp_fname, - self.issue.severity, self.issue.confidence) - - with open(self.tmp_fname) as f: - reader = csv.DictReader(f) - data = six.next(reader) - self.assertEqual(self.tmp_fname, data['filename']) - self.assertEqual(self.issue.severity, data['issue_severity']) - self.assertEqual(self.issue.confidence, data['issue_confidence']) - self.assertEqual(self.issue.text, data['issue_text']) - self.assertEqual(six.text_type(self.context['lineno']), - data['line_number']) - self.assertEqual(six.text_type(self.context['linerange']), - data['line_range']) - self.assertEqual(self.check_name, data['test_name']) - - def test_report_json(self): - self.manager.files_list = ['binding.py'] - self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING)}] - - formatters.report_json(self.manager, self.tmp_fname, - self.issue.severity, self.issue.confidence) - - with open(self.tmp_fname) as f: - data = json.loads(f.read()) - self.assertIsNotNone(data['generated_at']) - self.assertEqual(self.tmp_fname, data['results'][0]['filename']) - self.assertEqual(self.issue.severity, - data['results'][0]['issue_severity']) - self.assertEqual(self.issue.confidence, - data['results'][0]['issue_confidence']) - self.assertEqual(self.issue.text, data['results'][0]['issue_text']) - self.assertEqual(self.context['lineno'], - data['results'][0]['line_number']) - self.assertEqual(self.context['linerange'], - data['results'][0]['line_range']) - self.assertEqual(self.check_name, data['results'][0]['test_name']) - self.assertEqual('binding.py', data['stats'][0]['filename']) - self.assertEqual(0, data['stats'][0]['score']) - - def test_report_text(self): - self.manager.verbose = True - file_list = ['binding.py'] - scores = [{'SEVERITY': [0] * len(constants.RANKING), - 'CONFIDENCE': [0] * len(constants.RANKING)}] - exc_files = ['test_binding.py'] - - formatters.report_text(self.manager, self.tmp_fname, - self.issue.severity, self.issue.confidence) - - with open(self.tmp_fname) as f: - data = f.read() - expected = '>> Issue: %s' % self.issue.text - self.assertIn(expected, data) - expected = ' Severity: %s Confidence: %s' % ( - self.issue.severity.capitalize(), - self.issue.confidence.capitalize()) - self.assertIn(expected, data) - expected = ' Location: %s:%d' % (self.tmp_fname, - self.context['lineno']) - self.assertIn(expected, data) - - def _xml_to_dict(self, t): - d = {t.tag: {} if t.attrib else None} - children = list(t) - if children: - dd = defaultdict(list) - for dc in map(self._xml_to_dict, children): - for k, v in six.iteritems(dc): - dd[k].append(v) - d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in six.iteritems(dd)}} - if t.attrib: - d[t.tag].update(('@' + k, v) for k, v in six.iteritems(t.attrib)) - if t.text: - text = t.text.strip() - if children or t.attrib: - if text: - d[t.tag]['#text'] = text - else: - d[t.tag] = text - return d - - def test_report_xml(self): - formatters.report_xml(self.manager, self.tmp_fname, - self.issue.severity, self.issue.confidence) - - with open(self.tmp_fname) as f: - data = self._xml_to_dict(ET.XML(f.read())) - self.assertEqual(self.tmp_fname, - data['testsuite']['testcase']['@classname']) - self.assertEqual(self.issue.text, - data['testsuite']['testcase']['error']['@message']) - self.assertEqual(self.check_name, - data['testsuite']['testcase']['@name']) diff --git a/tests/unit/formatters/__init__.py b/tests/unit/formatters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/formatters/test_csv.py b/tests/unit/formatters/test_csv.py new file mode 100644 index 00000000..444b9a3f --- /dev/null +++ b/tests/unit/formatters/test_csv.py @@ -0,0 +1,66 @@ +# Copyright (c) 2015 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import csv +import os +import tempfile + +import six +import testtools + +import bandit +from bandit.core import config +from bandit.core import manager +from bandit.core import issue +from bandit.formatters import csv as b_csv + +class CsvFormatterTests(testtools.TestCase): + + def setUp(self): + super(CsvFormatterTests, self).setUp() + cfg_file = os.path.join(os.getcwd(), 'bandit/config/bandit.yaml') + conf = config.BanditConfig(cfg_file) + self.manager = manager.BanditManager(conf, 'file') + (tmp_fd, self.tmp_fname) = tempfile.mkstemp() + self.context = {'filename': self.tmp_fname, + 'lineno': 4, + 'linerange': [4]} + self.check_name = 'hardcoded_bind_all_interfaces' + self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, + 'Possible binding to all interfaces.') + self.manager.out_file = self.tmp_fname + + self.issue.fname = self.context['filename'] + self.issue.lineno = self.context['lineno'] + self.issue.linerange = self.context['linerange'] + self.issue.test = self.check_name + + self.manager.results.append(self.issue) + + def test_report(self): + b_csv.report(self.manager, self.tmp_fname, self.issue.severity, + self.issue.confidence) + + with open(self.tmp_fname) as f: + reader = csv.DictReader(f) + data = six.next(reader) + self.assertEqual(self.tmp_fname, data['filename']) + self.assertEqual(self.issue.severity, data['issue_severity']) + self.assertEqual(self.issue.confidence, data['issue_confidence']) + self.assertEqual(self.issue.text, data['issue_text']) + self.assertEqual(six.text_type(self.context['lineno']), + data['line_number']) + self.assertEqual(six.text_type(self.context['linerange']), + data['line_range']) + self.assertEqual(self.check_name, data['test_name']) diff --git a/tests/unit/formatters/test_json.py b/tests/unit/formatters/test_json.py new file mode 100644 index 00000000..6f504244 --- /dev/null +++ b/tests/unit/formatters/test_json.py @@ -0,0 +1,74 @@ +# Copyright (c) 2015 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import os +import tempfile + +import testtools + +import bandit +from bandit.core import constants +from bandit.core import config +from bandit.core import manager +from bandit.core import issue +from bandit.formatters import json as b_json + +class JsonFormatterTests(testtools.TestCase): + + def setUp(self): + super(JsonFormatterTests, self).setUp() + cfg_file = os.path.join(os.getcwd(), 'bandit/config/bandit.yaml') + conf = config.BanditConfig(cfg_file) + self.manager = manager.BanditManager(conf, 'file') + (tmp_fd, self.tmp_fname) = tempfile.mkstemp() + self.context = {'filename': self.tmp_fname, + 'lineno': 4, + 'linerange': [4]} + self.check_name = 'hardcoded_bind_all_interfaces' + self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, + 'Possible binding to all interfaces.') + self.manager.out_file = self.tmp_fname + + self.issue.fname = self.context['filename'] + self.issue.lineno = self.context['lineno'] + self.issue.linerange = self.context['linerange'] + self.issue.test = self.check_name + + self.manager.results.append(self.issue) + + def test_report(self): + self.manager.files_list = ['binding.py'] + self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING), + 'CONFIDENCE': [0] * len(constants.RANKING)}] + + b_json.report(self.manager, self.tmp_fname, self.issue.severity, + self.issue.confidence) + + with open(self.tmp_fname) as f: + data = json.loads(f.read()) + self.assertIsNotNone(data['generated_at']) + self.assertEqual(self.tmp_fname, data['results'][0]['filename']) + self.assertEqual(self.issue.severity, + data['results'][0]['issue_severity']) + self.assertEqual(self.issue.confidence, + data['results'][0]['issue_confidence']) + self.assertEqual(self.issue.text, data['results'][0]['issue_text']) + self.assertEqual(self.context['lineno'], + data['results'][0]['line_number']) + self.assertEqual(self.context['linerange'], + data['results'][0]['line_range']) + self.assertEqual(self.check_name, data['results'][0]['test_name']) + self.assertEqual('binding.py', data['stats'][0]['filename']) + self.assertEqual(0, data['stats'][0]['score']) diff --git a/tests/unit/formatters/test_text.py b/tests/unit/formatters/test_text.py new file mode 100644 index 00000000..07a3ab18 --- /dev/null +++ b/tests/unit/formatters/test_text.py @@ -0,0 +1,70 @@ +# Copyright (c) 2015 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import tempfile + +import testtools + +import bandit +from bandit.core import constants +from bandit.core import config +from bandit.core import manager +from bandit.core import issue +from bandit.formatters import text as b_text + +class TextFormatterTests(testtools.TestCase): + + def setUp(self): + super(TextFormatterTests, self).setUp() + cfg_file = os.path.join(os.getcwd(), 'bandit/config/bandit.yaml') + conf = config.BanditConfig(cfg_file) + self.manager = manager.BanditManager(conf, 'file') + (tmp_fd, self.tmp_fname) = tempfile.mkstemp() + self.context = {'filename': self.tmp_fname, + 'lineno': 4, + 'linerange': [4]} + self.check_name = 'hardcoded_bind_all_interfaces' + self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, + 'Possible binding to all interfaces.') + self.manager.out_file = self.tmp_fname + + self.issue.fname = self.context['filename'] + self.issue.lineno = self.context['lineno'] + self.issue.linerange = self.context['linerange'] + self.issue.test = self.check_name + + self.manager.results.append(self.issue) + + def test_report(self): + self.manager.verbose = True + file_list = ['binding.py'] + scores = [{'SEVERITY': [0] * len(constants.RANKING), + 'CONFIDENCE': [0] * len(constants.RANKING)}] + exc_files = ['test_binding.py'] + + b_text.report(self.manager, self.tmp_fname, self.issue.severity, + self.issue.confidence) + + with open(self.tmp_fname) as f: + data = f.read() + expected = '>> Issue: %s' % self.issue.text + self.assertIn(expected, data) + expected = ' Severity: %s Confidence: %s' % ( + self.issue.severity.capitalize(), + self.issue.confidence.capitalize()) + self.assertIn(expected, data) + expected = ' Location: %s:%d' % (self.tmp_fname, + self.context['lineno']) + self.assertIn(expected, data) diff --git a/tests/unit/formatters/test_xml.py b/tests/unit/formatters/test_xml.py new file mode 100644 index 00000000..688cdeff --- /dev/null +++ b/tests/unit/formatters/test_xml.py @@ -0,0 +1,83 @@ +# Copyright (c) 2015 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from collections import defaultdict +import os +import tempfile +from xml.etree import cElementTree as ET + +import six +import testtools + +import bandit +from bandit.core import config +from bandit.core import manager +from bandit.core import issue +from bandit.formatters import xml as b_xml + +class XmlFormatterTests(testtools.TestCase): + + def setUp(self): + super(XmlFormatterTests, self).setUp() + cfg_file = os.path.join(os.getcwd(), 'bandit/config/bandit.yaml') + conf = config.BanditConfig(cfg_file) + self.manager = manager.BanditManager(conf, 'file') + (tmp_fd, self.tmp_fname) = tempfile.mkstemp() + self.context = {'filename': self.tmp_fname, + 'lineno': 4, + 'linerange': [4]} + self.check_name = 'hardcoded_bind_all_interfaces' + self.issue = issue.Issue(bandit.MEDIUM, bandit.MEDIUM, + 'Possible binding to all interfaces.') + self.manager.out_file = self.tmp_fname + + self.issue.fname = self.context['filename'] + self.issue.lineno = self.context['lineno'] + self.issue.linerange = self.context['linerange'] + self.issue.test = self.check_name + + self.manager.results.append(self.issue) + + def _xml_to_dict(self, t): + d = {t.tag: {} if t.attrib else None} + children = list(t) + if children: + dd = defaultdict(list) + for dc in map(self._xml_to_dict, children): + for k, v in six.iteritems(dc): + dd[k].append(v) + d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in six.iteritems(dd)}} + if t.attrib: + d[t.tag].update(('@' + k, v) for k, v in six.iteritems(t.attrib)) + if t.text: + text = t.text.strip() + if children or t.attrib: + if text: + d[t.tag]['#text'] = text + else: + d[t.tag] = text + return d + + def test_report(self): + b_xml.report(self.manager, self.tmp_fname, self.issue.severity, + self.issue.confidence) + + with open(self.tmp_fname) as f: + data = self._xml_to_dict(ET.XML(f.read())) + self.assertEqual(self.tmp_fname, + data['testsuite']['testcase']['@classname']) + self.assertEqual(self.issue.text, + data['testsuite']['testcase']['error']['@message']) + self.assertEqual(self.check_name, + data['testsuite']['testcase']['@name'])