Skip to content

Commit

Permalink
Use the argparse parser to validate the ini config
Browse files Browse the repository at this point in the history
Closes PyCQA#938

Additionally this provides better error messages for
.bandit ini config files and ensures that the ini options
are parsed correctly by reusing the argparse parser in
`bandit.cli.main`
  • Loading branch information
ap-- committed Jul 28, 2022
1 parent 06aeadd commit 59f3ff5
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 6 deletions.
9 changes: 3 additions & 6 deletions bandit/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ def main():

# Handle .bandit files in projects to pass cmdline args from file
ini_options = _get_options_from_ini(args.ini_path, args.targets)
ini_options = utils.validate_ini_options(ini_options, parser)
if ini_options:
# prefer command line, then ini file
args.excluded_paths = _log_option_source(
Expand All @@ -482,14 +483,10 @@ def main():
"selected tests",
)

ini_targets = ini_options.get("targets")
if ini_targets:
ini_targets = ini_targets.split(",")

args.targets = _log_option_source(
parser.get_default("targets"),
args.targets,
ini_targets,
ini_options.get("targets"),
"selected targets",
)

Expand All @@ -512,7 +509,7 @@ def main():
args.context_lines = _log_option_source(
parser.get_default("context_lines"),
args.context_lines,
int(ini_options.get("number") or 0) or None,
ini_options.get("number"),
"max code lines output for issue",
)

Expand Down
97 changes: 97 additions & 0 deletions bandit/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os.path
import sys
from argparse import ArgumentError

try:
import configparser
Expand Down Expand Up @@ -360,6 +361,102 @@ def parse_ini_file(f_loc):
return None


def value_option(key, value):
if not value:
return []
return [f"--{key}", value]


def multi_option(key, value):
if not value.isdigit():
LOG.warning(f"INI config '{key}' is not a number: using default")
value = 0
return [f"--{key}"] * (int(value) - 1)


def flag_option(key, value):
try:
opt = {"false": False, "true": True}[value.lower()]
except KeyError:
LOG.warning(f"INI config '{key}' not 'True/False': using default")
opt = False
return [f"--{key}"] if opt else []


INI_KEY_TO_ARGS = {
"targets": lambda k, v: v.split(","),
"recursive": flag_option,
"aggregate": value_option,
"number": value_option,
"profile": value_option,
"tests": value_option,
"skips": lambda k, v: value_option("skip", v),
"level": multi_option,
"confidence": multi_option,
"format": value_option,
"msg-template": value_option,
"output": value_option,
"verbose": flag_option,
"debug": flag_option,
"quiet": flag_option,
"ignore-nosec": flag_option,
"exclude": value_option,
"baseline": value_option,
}
INI_KEY_RENAME = {
"aggregate": "agg_type",
"number": "context_lines",
"level": "severity",
"format": "output_format",
"msg-template": "msg_template",
"output": "output_file",
"ignore-nosec": "ignore_nosec",
"exclude": "excluded_paths",
}
ARGPARSE_KEY_RENAME = {v: k for k, v in INI_KEY_RENAME.items()}


def validate_ini_options(ini_config, parser):
"""Validate the ini config dict by reusing the argparse ArgumentParser"""
if ini_config is None:
return None

invalid_keys = set(ini_config) - set(INI_KEY_TO_ARGS)
# gracefully continue
for key in invalid_keys:
LOG.warning(
"INI config file contains invalid key %s in section [bandit]",
repr(key),
)
ini_config.pop(key)

ini_args = []
for key, value in ini_config.items():
key_args = INI_KEY_TO_ARGS[key](key, value)
ini_args.extend(key_args)

# nicer output on 3.9
if sys.version_info >= (3, 9):
parser.exit_on_error = False

try:
args = parser.parse_args(ini_args)
except SystemExit:
# python < 3.9 will have to catch SystemExit here.
LOG.error("INI config: parsing failed")
raise
except ArgumentError as err:
action, msg = err.args
ini_name = ARGPARSE_KEY_RENAME.get(action.dest, action.dest)
LOG.error(f"INI config '{ini_name}': {msg}")
sys.exit(2)

for key in ini_config:
ini_config[key] = getattr(args, INI_KEY_RENAME.get(key, key))

return ini_config


def check_ast_node(name):
"Check if the given name is that of a valid AST node."
try:
Expand Down

0 comments on commit 59f3ff5

Please sign in to comment.