| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- #
- # Copyright (c) 2017 Hewlett Packard Enterprise
- #
- # SPDX-License-Identifier: Apache-2.0
- """
- ================
- Custom Formatter
- ================
- This formatter outputs the issues in custom machine-readable format.
- default template: ``{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}``
- :Example:
- .. code-block:: none
- /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.
- .. versionadded:: 1.5.0
- .. versionchanged:: 1.7.3
- New field `CWE` added to output
- """
- 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, 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 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,
- "col": lambda issue: issue.col_offset,
- "end_col": lambda issue: issue.end_col_offset,
- "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,
- "cwe": lambda issue: issue.cwe,
- }
- # Create dictionary with tag sets to speed up search for similar tags
- tag_sim_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"
- )
- with fileobj:
- for defect in results:
- 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)
|