mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-23 05:10:40 +00:00
164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
# 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)
|