From fb18e17a7af3650f4cf0616a114c1742e0409136 Mon Sep 17 00:00:00 2001 From: Thibaut Le Page Date: Fri, 12 Apr 2019 17:49:15 +0200 Subject: [PATCH] Interpret wildcards in the file exclusion list (#450) This allows to specify wildcards in the excluded files list, which in turns makes it possible to use Bandit in projects where test files are not in a separate repository, but have just a name prefixed with test_ (as is common with Pytest). * bandit.core.manager: reuse _matches_glob_list for exclusion list * fix pep8: remove superfluous blank line * update documentation: -x accepts glob patterns * add failing test showing that exclude file globs are not supported --- README.rst | 8 ++++---- bandit/cli/main.py | 3 ++- bandit/core/manager.py | 13 +++++++------ doc/source/man/bandit.rst | 6 +++--- tests/unit/core/test_manager.py | 26 ++++++++++++++++++++++---- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index 57d29225c..7c8af9cce 100644 --- a/README.rst +++ b/README.rst @@ -143,9 +143,9 @@ Usage:: only show output in the case of an error --ignore-nosec do not skip lines with # nosec comments -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS - comma-separated list of paths to exclude from scan - (note that these are in addition to the excluded paths - provided in the config file) + comma-separated list of paths (glob patterns supported) + to exclude from scan (note that these are in addition + to the excluded paths provided in the config file) -b BASELINE, --baseline BASELINE path of a baseline report to compare against (only JSON-formatted files are accepted) @@ -289,7 +289,7 @@ Configuration An optional config file may be supplied and may include: - lists of tests which should or shouldn't be run - exclude_dirs - sections of the path, that if matched, will be excluded from - scanning + scanning (glob patterns supported) - overridden plugin settings - may provide different settings for some plugins diff --git a/bandit/cli/main.py b/bandit/cli/main.py index 90f033a85..1e74682b7 100644 --- a/bandit/cli/main.py +++ b/bandit/cli/main.py @@ -235,7 +235,8 @@ def main(): ) parser.add_argument( '-x', '--exclude', dest='excluded_paths', action='store', - default='', help='comma-separated list of paths to exclude from scan ' + default='', help='comma-separated list of paths (glob patterns ' + 'supported) to exclude from scan ' '(note that these are in addition to the excluded ' 'paths provided in the config file)' ) diff --git a/bandit/core/manager.py b/bandit/core/manager.py index 2406f0a3b..99d5e72ea 100644 --- a/bandit/core/manager.py +++ b/bandit/core/manager.py @@ -184,13 +184,13 @@ def discover_files(self, targets, recursive=False, excluded_paths=''): files_list = set() excluded_files = set() - excluded_path_strings = self.b_conf.get_option('exclude_dirs') or [] + excluded_path_globs = self.b_conf.get_option('exclude_dirs') or [] included_globs = self.b_conf.get_option('include') or ['*.py'] # if there are command line provided exclusions add them to the list if excluded_paths: for path in excluded_paths.split(','): - excluded_path_strings.append(path) + excluded_path_globs.append(path) # build list of files we will analyze for fname in targets: @@ -200,7 +200,7 @@ def discover_files(self, targets, recursive=False, excluded_paths=''): new_files, newly_excluded = _get_files_from_dir( fname, included_globs=included_globs, - excluded_path_strings=excluded_path_strings + excluded_path_strings=excluded_path_globs ) files_list.update(new_files) excluded_files.update(newly_excluded) @@ -213,7 +213,7 @@ def discover_files(self, targets, recursive=False, excluded_paths=''): # we'll scan it, regardless of whether it's in the included # file types list if _is_file_included(fname, included_globs, - excluded_path_strings, + excluded_path_globs, enforce_glob=False): files_list.add(fname) else: @@ -353,7 +353,8 @@ def _is_file_included(path, included_globs, excluded_path_strings, :param path: Full path of file to check :param parsed_extensions: List of parsed extensions - :param excluded_paths: List of paths from which we should not include files + :param excluded_paths: List of paths (globbing supported) from which we + should not include files :param enforce_glob: Can set to false to bypass extension check :return: Boolean indicating whether a file should be included ''' @@ -362,7 +363,7 @@ def _is_file_included(path, included_globs, excluded_path_strings, # if this is matches a glob of files we look at, and it isn't in an # excluded path if _matches_glob_list(path, included_globs) or not enforce_glob: - if not any(x in path for x in excluded_path_strings): + if not _matches_glob_list(path, excluded_path_strings): return_value = True return return_value diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst index 01cf56ff3..0fecb1e9a 100644 --- a/doc/source/man/bandit.rst +++ b/doc/source/man/bandit.rst @@ -59,9 +59,9 @@ OPTIONS only show output in the case of an error --ignore-nosec do not skip lines with # nosec comments -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS - comma-separated list of paths to exclude from scan - (note that these are in addition to the excluded paths - provided in the config file) + comma-separated list of paths (glob patterns supported) + to exclude from scan (note that these are in addition + to the excluded paths provided in the config file) -b BASELINE, --baseline BASELINE path of a baseline report to compare against (only JSON-formatted files are accepted) diff --git a/tests/unit/core/test_manager.py b/tests/unit/core/test_manager.py index 92ce77165..bcf1e92ef 100644 --- a/tests/unit/core/test_manager.py +++ b/tests/unit/core/test_manager.py @@ -70,24 +70,34 @@ def test_matches_globlist(self): def test_is_file_included(self): a = manager._is_file_included(path='a.py', included_globs=['*.py'], - excluded_path_strings='', + excluded_path_strings=[], enforce_glob=True) b = manager._is_file_included(path='a.dd', included_globs=['*.py'], - excluded_path_strings='', + excluded_path_strings=[], enforce_glob=False) c = manager._is_file_included(path='a.py', included_globs=['*.py'], - excluded_path_strings='a.py', + excluded_path_strings=['a.py'], enforce_glob=True) d = manager._is_file_included(path='a.dd', included_globs=['*.py'], - excluded_path_strings='', + excluded_path_strings=[], + enforce_glob=True) + + e = manager._is_file_included(path='x_a.py', included_globs=['*.py'], + excluded_path_strings=['x_*.py'], + enforce_glob=True) + + f = manager._is_file_included(path='x.py', included_globs=['*.py'], + excluded_path_strings=['x_*.py'], enforce_glob=True) self.assertTrue(a) self.assertTrue(b) self.assertFalse(c) self.assertFalse(d) + self.assertFalse(e) + self.assertTrue(f) @mock.patch('os.walk') def test_get_files_from_dir(self, os_walk): @@ -209,6 +219,14 @@ def test_discover_files_exclude_cmdline(self, isdir): m.assert_called_with('c', ['*.py', '*.pyw'], ['a', 'b'], enforce_glob=False) + @mock.patch('os.path.isdir') + def test_discover_files_exclude_glob(self, isdir): + isdir.return_value = False + self.manager.discover_files(['a.py', 'test_a.py', 'test.py'], True, + excluded_paths='test_*.py') + self.assertEqual(['a.py', 'test.py'], self.manager.files_list) + self.assertEqual(['test_a.py'], self.manager.excluded_files) + @mock.patch('os.path.isdir') def test_discover_files_include(self, isdir): isdir.return_value = False