Skip to content

Commit

Permalink
Merge "Custom formatter"
Browse files Browse the repository at this point in the history
  • Loading branch information
Zuul authored and openstack-gerrit committed Nov 24, 2017
2 parents aa0863f + d159335 commit 8f09d8b
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 13 deletions.
64 changes: 61 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ Usage::
$ bandit -h
usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
[-p PROFILE] [-t TESTS] [-s SKIPS] [-l] [-i]
[-f {csv,html,json,screen,txt,xml,yaml}] [-o [OUTPUT_FILE]] [-v]
[-d] [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
[-f {csv,custom,html,json,screen,txt,xml,yaml}]
[--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d]
[--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
[--ini INI_PATH] [--version]
targets [targets ...]

Expand Down Expand Up @@ -118,8 +119,12 @@ Usage::
(-l for LOW, -ll for MEDIUM, -lll for HIGH)
-i, --confidence report only issues of a given confidence level or
higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
-f {csv,html,json,screen,txt,xml,yaml}, --format {csv,html,json,screen,txt,xml,yaml}
-f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml}
specify output format
--msg-template MSG_TEMPLATE
specify output message template (only usable with
--format custom), see CUSTOM FORMAT section for list
of available values
-o [OUTPUT_FILE], --output [OUTPUT_FILE]
write report to filename
-v, --verbose output extra information like excluded and included
Expand All @@ -137,7 +142,33 @@ Usage::
arguments
--version show program's version number and exit

CUSTOM FORMATTING
-----------------

Available tags:

{abspath}, {relpath}, {line}, {test_id},
{severity}, {msg}, {confidence}, {range}

Example usage:

Default template:
bandit -r examples/ --format custom --msg-template \
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"

Provides same output as:
bandit -r examples/ --format custom

Tags can also be formatted in python string.format() style:
bandit -r examples/ --format custom --msg-template \
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"

See python documentation for more information about formatting style:
https://docs.python.org/3.4/library/string.html

The following tests were discovered and loaded:
-----------------------------------------------

B101 assert_used
B102 exec_used
B103 set_bad_file_permissions
Expand Down Expand Up @@ -340,6 +371,33 @@ To register your plugin, you have two options:
bandit.plugins =
mako = bandit_mako


Custom Formatting
-----------------

Available tags:

::
{abspath}, {relpath}, {line}, {test_id},
{severity}, {msg}, {confidence}, {range}

Example usage:

Default template::
bandit -r examples/ --format custom --msg-template \
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"

Provides same output as::
bandit -r examples/ --format custom

Tags can also be formatted in python string.format() style::
bandit -r examples/ --format custom --msg-template \
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"

See python documentation for more information about formatting style:
https://docs.python.org/3.4/library/string.html


Contributing
------------
Contributions to Bandit are always welcome! We can be found on
Expand Down
45 changes: 42 additions & 3 deletions bandit/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import logging
import os
import sys
import textwrap


import bandit
Expand Down Expand Up @@ -205,6 +206,13 @@ def main():
default=output_format, help='specify output format',
choices=sorted(extension_mgr.formatter_names)
)
parser.add_argument(
'--msg-template', action='store',
default=None, help='specify output message template'
' (only usable with --format custom),'
' see CUSTOM FORMAT section'
' for list of available values',
)
parser.add_argument(
'-o', '--output', dest='output_file', action='store', nargs='?',
type=argparse.FileType('w'), default=sys.stdout,
Expand Down Expand Up @@ -253,11 +261,41 @@ def main():
blacklist_info.append('%s\t%s' % (b['id'], b['name']))

plugin_list = '\n\t'.join(sorted(set(plugin_info + blacklist_info)))
parser.epilog = ('The following tests were discovered and'
' loaded:\n\t{0}\n'.format(plugin_list))
dedent_text = textwrap.dedent('''
CUSTOM FORMATTING
-----------------
Available tags:
{abspath}, {relpath}, {line}, {test_id},
{severity}, {msg}, {confidence}, {range}
Example usage:
Default template:
bandit -r examples/ --format custom --msg-template \\
"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"
Provides same output as:
bandit -r examples/ --format custom
Tags can also be formatted in python string.format() style:
bandit -r examples/ --format custom --msg-template \\
"{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"
See python documentation for more information about formatting style:
https://docs.python.org/3.4/library/string.html
The following tests were discovered and loaded:
-----------------------------------------------
''')
parser.epilog = dedent_text + "\t{0}".format(plugin_list)

# setup work - parse arguments, and initialize BanditManager
args = parser.parse_args()
# Check if `--msg-template` is not present without custom formatter
if args.output_format != 'custom' and args.msg_template is not None:
parser.error("--msg-template can only be used with --format=custom")

try:
b_conf = b_config.BanditConfig(config_file=args.config_file)
Expand Down Expand Up @@ -341,7 +379,8 @@ def main():
sev_level,
conf_level,
args.output_file,
args.output_format)
args.output_format,
args.msg_template)

# return an exit code of 1 if there are results, 0 otherwise
if b_mgr.results_count(sev_filter=sev_level, conf_filter=conf_level) > 0:
Expand Down
14 changes: 11 additions & 3 deletions bandit/core/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,17 @@ def results_count(self, sev_filter=b_constants.LOW,
return len(self.get_issue_list(sev_filter, conf_filter))

def output_results(self, lines, sev_level, conf_level, output_file,
output_format):
output_format, template=None):
'''Outputs results from the result store
:param lines: How many surrounding lines to show per result
:param sev_level: Which severity levels to show (LOW, MEDIUM, HIGH)
:param conf_level: Which confidence levels to show (LOW, MEDIUM, HIGH)
:param output_file: File to store results
:param output_format: output format plugin name
:param template: Output template with non-terminal tags <N>
(default: {abspath}:{line}:
{test_id}[bandit]: {severity}: {msg})
:return: -
'''
try:
Expand All @@ -153,8 +156,13 @@ def output_results(self, lines, sev_level, conf_level, output_file,

formatter = formatters_mgr[output_format]
report_func = formatter.plugin
report_func(self, fileobj=output_file, sev_level=sev_level,
conf_level=conf_level, lines=lines)
if output_format == 'custom':
report_func(self, fileobj=output_file, sev_level=sev_level,
conf_level=conf_level, lines=lines,
template=template)
else:
report_func(self, fileobj=output_file, sev_level=sev_level,
conf_level=conf_level, lines=lines)

except Exception as e:
raise RuntimeError("Unable to output report using '%s' formatter: "
Expand Down
163 changes: 163 additions & 0 deletions bandit/formatters/custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright (c) 2017 Hewlett Packard Enterprise
# -*- 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.

r"""
================
Custom Formatter
================
This formatter outputs the issues in custom machine-readable format.
default template: {abspath}:{line}: {test_id}[bandit]: {severity}: {msg}
:Example:
/usr/lib/python3.6/site-packages/openlp/core/utils/__init__.py: \
405: B310[bandit]: MEDIUM: Audit url open for permitted schemes. \
Allowing use of file:/ or custom schemes is often unexpected.
"""

import logging
import os
import re
import string
import sys

from bandit.core import test_properties


LOG = logging.getLogger(__name__)


class SafeMapper(dict):
"""Safe mapper to handle format key errors"""
@classmethod # To prevent PEP8 warnings in the test suite
def __missing__(cls, key):
return "{%s}" % key


@test_properties.accepts_baseline
def report(manager, fileobj, sev_level, conf_level, lines=-1, template=None):
"""Prints issues in custom format
:param manager: the bandit manager object
:param fileobj: The output file object, which may be sys.stdout
:param sev_level: Filtering severity level
:param conf_level: Filtering confidence level
:param lines: Number of lines to report, -1 for all
:param template: Output template with non-terminal tags <N>
(default: '{abspath}:{line}:
{test_id}[bandit]: {severity}: {msg}')
"""

machine_output = {'results': [], 'errors': []}
for (fname, reason) in manager.get_skipped():
machine_output['errors'].append({'filename': fname,
'reason': reason})

results = manager.get_issue_list(sev_level=sev_level,
conf_level=conf_level)

msg_template = template
if template is None:
msg_template = "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"

# Dictionary of non-terminal tags that will be expanded
tag_mapper = {
'abspath': lambda issue: os.path.abspath(issue.fname),
'relpath': lambda issue: os.path.relpath(issue.fname),
'line': lambda issue: issue.lineno,
'test_id': lambda issue: issue.test_id,
'severity': lambda issue: issue.severity,
'msg': lambda issue: issue.text,
'confidence': lambda issue: issue.confidence,
'range': lambda issue: issue.linerange
}

# Create dictionary with tag sets to speed up search for similar tags
tag_sim_dict = dict(
[(tag, set(tag)) for tag, _ in tag_mapper.items()]
)

# Parse the format_string template and check the validity of tags
try:
parsed_template_orig = list(string.Formatter().parse(msg_template))
# of type (literal_text, field_name, fmt_spec, conversion)

# Check the format validity only, ignore keys
string.Formatter().vformat(msg_template, (), SafeMapper(line=0))
except ValueError as e:
LOG.error("Template is not in valid format: %s", e.args[0])
sys.exit(2)

tag_set = {t[1] for t in parsed_template_orig if t[1] is not None}
if not tag_set:
LOG.error("No tags were found in the template. Are you missing '{}'?")
sys.exit(2)

def get_similar_tag(tag):
similarity_list = [(len(set(tag) & t_set), t)
for t, t_set in tag_sim_dict.items()]
return sorted(similarity_list)[-1][1]

tag_blacklist = []
for tag in tag_set:
# check if the tag is in dictionary
if tag not in tag_mapper:
similar_tag = get_similar_tag(tag)
LOG.warning(
"Tag '%s' was not recognized and will be skipped, "
"did you mean to use '%s'?", tag, similar_tag
)
tag_blacklist += [tag]

# Compose the message template back with the valid values only
msg_parsed_template_list = []
for literal_text, field_name, fmt_spec, conversion in parsed_template_orig:
if literal_text:
# if there is '{' or '}', double it to prevent expansion
literal_text = re.sub('{', '{{', literal_text)
literal_text = re.sub('}', '}}', literal_text)
msg_parsed_template_list.append(literal_text)

if field_name is not None:
if field_name in tag_blacklist:
msg_parsed_template_list.append(field_name)
continue
# Append the fmt_spec part
params = [field_name, fmt_spec, conversion]
markers = ['', ':', '!']
msg_parsed_template_list.append(
['{'] +
["%s" % (m + p) if p else ''
for m, p in zip(markers, params)] +
['}']
)

msg_parsed_template = "".join([item for lst in msg_parsed_template_list
for item in lst]) + "\n"
limit = lines if lines > 0 else None
with fileobj:
for defect in results[:limit]:
evaluated_tags = SafeMapper(
(k, v(defect)) for k, v in tag_mapper.items()
)
output = msg_parsed_template.format(**evaluated_tags)

fileobj.write(output)

if fileobj.name != sys.stdout.name:
LOG.info("Result written to file: %s", fileobj.name)
Loading

0 comments on commit 8f09d8b

Please sign in to comment.