Skip to content

Commit

Permalink
Change: Make config file required, Separate plugin config from file c…
Browse files Browse the repository at this point in the history
…ontext
  • Loading branch information
NiklasHargarter committed Jun 13, 2024
1 parent fe8ab6b commit 0ba98e0
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 25 deletions.
49 changes: 43 additions & 6 deletions tests/plugins/test_http_links_in_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,50 @@

from pathlib import Path

from troubadix.plugin import LinterError
from troubadix.plugin import ConfigurationError, LinterError
from troubadix.plugins.http_links_in_tags import CheckHttpLinksInTags

from . import PluginTestCase

BASE_CONFIG = {CheckHttpLinksInTags.name: {"exclusions": []}}


class CheckHttpLinksInTagsTestCase(PluginTestCase):

def test_validate_config(self):
fake_context = self.create_file_plugin_context()

valid_plugin_config = {
"exclusions": ["Foo Bar. https://www.website.de/demo"]
}
valid_config = {"check_http_links_in_tags": valid_plugin_config}

# validate_and_extract_config is called by the init method
# when key config is given.
plugin = CheckHttpLinksInTags(fake_context, config=valid_config)

self.assertEqual(plugin.config, valid_plugin_config)

invalid_config_missing_plugin_key = {}
with self.assertRaises(ConfigurationError) as context:
plugin.validate_and_extract_plugin_config(
invalid_config_missing_plugin_key
)
self.assertEqual(
str(context.exception),
"Configuration for plugin 'check_http_links_in_tags' is missing.",
)
invalid_config_missing_required_key = {"check_http_links_in_tags": {}}
with self.assertRaises(ConfigurationError) as context:
plugin.validate_and_extract_plugin_config(
invalid_config_missing_required_key
)
self.assertEqual(
str(context.exception),
"Configuration for plugin 'check_http_links_in_tags' "
"is missing required key: 'exclusions'",
)

def test_ok(self):
path = Path("some/file.nasl")
content = (
Expand All @@ -42,9 +79,9 @@ def test_ok(self):
fake_context = self.create_file_plugin_context(
nasl_file=path,
file_content=content,
plugin_config=fake_plugin_config,
)
plugin = CheckHttpLinksInTags(fake_context)
plugin = CheckHttpLinksInTags(fake_context, config=BASE_CONFIG)
plugin.config = fake_plugin_config

results = list(plugin.run())

Expand All @@ -53,7 +90,7 @@ def test_ok(self):
def test_exclude_inc_file(self):
path = Path("some/file.inc")
fake_context = self.create_file_plugin_context(nasl_file=path)
plugin = CheckHttpLinksInTags(fake_context)
plugin = CheckHttpLinksInTags(fake_context, config=BASE_CONFIG)

results = list(plugin.run())

Expand All @@ -71,7 +108,7 @@ def test_not_ok(self):
fake_context = self.create_file_plugin_context(
nasl_file=path, file_content=content
)
plugin = CheckHttpLinksInTags(fake_context)
plugin = CheckHttpLinksInTags(fake_context, config=BASE_CONFIG)

results = list(plugin.run())

Expand Down Expand Up @@ -99,7 +136,7 @@ def test_not_ok2(self):
fake_context = self.create_file_plugin_context(
nasl_file=path, file_content=content
)
plugin = CheckHttpLinksInTags(fake_context)
plugin = CheckHttpLinksInTags(fake_context, config=BASE_CONFIG)

results = list(plugin.run())

Expand Down
70 changes: 64 additions & 6 deletions tests/test_argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def test_parse_files(self):
"--files",
"tests/plugins/test.nasl",
"tests/plugins/fail2.nasl",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

Expand All @@ -52,6 +54,8 @@ def test_parse_include_tests(self):
[
"--include-tests",
"CheckBadwords",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)
self.assertEqual(parsed_args.included_plugins, ["CheckBadwords"])
Expand All @@ -63,14 +67,23 @@ def test_parse_exclude_tests(self):
[
"--exclude-tests",
"CheckBadwords",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)
self.assertEqual(parsed_args.excluded_plugins, ["CheckBadwords"])
self.assertIsNone(parsed_args.included_plugins)

def test_parse_include_patterns(self):
parsed_args = parse_args(
self.terminal, ["-f", "--include-patterns", "troubadix/*"]
self.terminal,
[
"-f",
"--include-patterns",
"troubadix/*",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

self.assertTrue(parsed_args.full)
Expand All @@ -89,12 +102,21 @@ def test_parse_files_non_recursive_fail(self):
"tests/plugins/test.nasl",
"tests/plugins/fail2.nasl",
"--non-recursive",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

def test_parse_exclude_patterns(self):
parsed_args = parse_args(
self.terminal, ["-f", "--exclude-patterns", "troubadix/*"]
self.terminal,
[
"-f",
"--exclude-patterns",
"troubadix/*",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

self.assertTrue(parsed_args.full)
Expand All @@ -109,6 +131,8 @@ def test_parse_max_cpu(self):
"troubadix/*",
"-j",
"1337",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

Expand All @@ -126,6 +150,8 @@ def test_parse_min_cpu(self):
"troubadix/*",
"-j",
"-1337",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

Expand All @@ -136,23 +162,55 @@ def test_parse_min_cpu(self):

def test_parse_root(self):
with tempfile.TemporaryDirectory() as tmpdir:
parsed_args = parse_args(self.terminal, ["--root", tmpdir])
parsed_args = parse_args(
self.terminal,
[
"--root",
tmpdir,
"--plugins-config-file",
"some/fake/path/config.toml",
],
)
self.assertEqual(parsed_args.root, Path(tmpdir))

def test_parse_fix(self):
parsed_args = parse_args(self.terminal, ["--fix"])
parsed_args = parse_args(
self.terminal,
[
"--fix",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

self.assertTrue(parsed_args.fix)

def test_parse_ignore_warnings(self):
parsed_args = parse_args(self.terminal, ["--ignore-warnings"])
parsed_args = parse_args(
self.terminal,
[
"--ignore-warnings",
"--plugins-config-file",
"some/fake/path/config.toml",
],
)

self.assertTrue(parsed_args.ignore_warnings)

def test_parse_log_file_statistic(self):
with tempfile.NamedTemporaryFile() as tmpfile:
print(tmpfile.name)
parsed_args = parse_args(
self.terminal, ["--log-file-statistic", str(tmpfile.name)]
self.terminal,
[
"--log-file-statistic",
str(tmpfile.name),
"--plugins-config-file",
"some/fake/path/config.toml",
],
)
self.assertEqual(parsed_args.log_file_statistic, Path(tmpfile.name))

def test_parse_config_missing(self):
with self.assertRaises(SystemExit):
parse_args(self.terminal, [])
7 changes: 7 additions & 0 deletions troubadix/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,11 @@ def parse_args(
)
sys.exit(1)

if not parsed_args.plugins_config_file:
terminal.warning(

Check warning on line 277 in troubadix/argparser.py

View check run for this annotation

Codecov / codecov/patch

troubadix/argparser.py#L277

Added line #L277 was not covered by tests
"A plugin_config file needs to be specified with "
" '--plugins_config_file'"
)
sys.exit(1)

Check warning on line 281 in troubadix/argparser.py

View check run for this annotation

Codecov / codecov/patch

troubadix/argparser.py#L281

Added line #L281 was not covered by tests

return parsed_args
46 changes: 43 additions & 3 deletions troubadix/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def __init__(

self._file_content = None
self._lines = None
self.plugin_config = {}

@property
def file_content(self) -> str:
Expand All @@ -80,12 +79,48 @@ def __init__(self, *, root: Path, nasl_files: Iterable[Path]) -> None:
self.nasl_files = nasl_files


class ConfigurationError(Exception):
"""Custom exception for plugin_configurion errors."""


class Plugin(ABC):
"""A linter plugin"""

name: str = None
description: str = None

# Value to indicate that a plugin depends on an external configuration
require_external_config = False

def __init__(self, config: dict) -> None:
if self.require_external_config:
self.config = self.validate_and_extract_plugin_config(config)

Check warning

Code scanning / CodeQL

`__init__` method calls overridden method Warning

Call to self.
validate_and_extract_plugin_config
in __init__ method, which is overridden by
method CheckHttpLinksInTags.validate_and_extract_plugin_config
.

def validate_and_extract_plugin_config(self, config: dict) -> dict:
"""
Validates and extracts the configuration for a specific plugin
from the entire configuration.
Not @abstract due to only being necessary
if require_external_config is true
Args:
config (dict): The entire configuration dictionary.
Returns:
dict: The configuration dictionary for the specific plugin.
Raises:
ConfigurationError: If the plugin configuration is not present
or missing required keys.
"""
raise RuntimeError(

Check warning on line 117 in troubadix/plugin.py

View check run for this annotation

Codecov / codecov/patch

troubadix/plugin.py#L117

Added line #L117 was not covered by tests
f"{self.__class__.__name__} has not implemented method"
" 'validate_and_extract_plugin_config'."
" This method should be overridden in subclasses,"
" if they require external config"
)

@abstractmethod
def run(self) -> Iterator[LinterResult]:
pass
Expand All @@ -97,14 +132,19 @@ def fix(self) -> Iterator[LinterResult]:
class FilesPlugin(Plugin):
"""A plugin that does checks over all files"""

def __init__(self, context: FilesPluginContext) -> None:
def __init__(self, context: FilesPluginContext, **kwargs) -> None:
if "config" in kwargs:
super().__init__(kwargs["config"])

Check warning on line 137 in troubadix/plugin.py

View check run for this annotation

Codecov / codecov/patch

troubadix/plugin.py#L137

Added line #L137 was not covered by tests
self.context = context


class FilePlugin(Plugin):
"""A plugin that does checks on single files"""

def __init__(self, context: FilePluginContext) -> None:
def __init__(self, context: FilePluginContext, **kwargs) -> None:
if "config" in kwargs:
super().__init__(kwargs["config"])

self.context = context


Expand Down
35 changes: 33 additions & 2 deletions troubadix/plugins/http_links_in_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,37 @@

from troubadix.helper import SpecialScriptTag, get_common_tag_patterns
from troubadix.helper.patterns import get_special_script_tag_pattern
from troubadix.plugin import FilePlugin, LinterError, LinterResult
from troubadix.plugin import (
ConfigurationError,
FilePlugin,
LinterError,
LinterResult,
)


class CheckHttpLinksInTags(FilePlugin):
name = "check_http_links_in_tags"
mandatory_config_keys = ["exclusions"]
require_external_config = True

def validate_and_extract_plugin_config(self, config: dict) -> dict:
# check for this plugin in the whole config
if self.name not in config:
raise ConfigurationError(
f"Configuration for plugin '{self.name}' is missing."
)

plugin_config = config[self.name]

# Check the plugin-specific configuration
# for keys required by this plugin
if "exclusions" not in plugin_config:
raise ConfigurationError(
f"Configuration for plugin '{self.name}' is missing "
"required key: 'exclusions'"
)

return plugin_config

def run(self) -> Iterator[LinterResult]:
if self.context.nasl_file.suffix == ".inc":
Expand All @@ -49,6 +75,11 @@ def contains_http_link_in_tag(self) -> Iterator[LinterResult]:
nasl_file: The VT that is going to be checked
file_content: The content of the file that is going to be
checked
config: The plugin configuration provided
by the plugin_configuration.toml file.
config must include keys:
exclusions: A list of Strings that should be ignored
due to containing a valid use of a url in a tag
"""

file_content = self.context.file_content
Expand Down Expand Up @@ -119,7 +150,7 @@ def contains_nvd_mitre_link_in_xref(self) -> Iterator[LinterResult]:
)

def check_to_continue(self, http_link_match_group: str) -> bool:
exclusions = self.context.plugin_config.get("exclusions", [])
exclusions = self.config["exclusions"]
return any(
exclusion in http_link_match_group for exclusion in exclusions
)
Loading

0 comments on commit 0ba98e0

Please sign in to comment.