Source code for suricata_check.utils.checker_typing

  1"""The `suricata_check.typing` module contains all types used by the `suricata-check` package."""
  2
  3import json
  4from collections.abc import MutableMapping, MutableSequence
  5from dataclasses import dataclass, field
  6from typing import (
  7    TYPE_CHECKING,
  8    Optional,
  9)
 10
 11if TYPE_CHECKING:
 12    from suricata_check.utils.rule import Rule as __Rule
 13
 14
[docs] 15class InvalidRuleError(RuntimeError): 16 """Raised when an invalid rule is detected. 17 18 Note that some rules may be invalid due to not following the Suricata rule syntax. 19 Rules following the syntax, but considered invalid by Suricata due to missing options need not raise this error. 20 Rules for which this error is not raised are not neccessarily syntactically correct but can be processed by suricata-check. 21 """ 22 23 def __init__(self: "InvalidRuleError", message: str) -> None: 24 """Initializes the `InvalidRuleError` with the raw rule as message.""" 25 super().__init__(message)
26 27
[docs] 28@dataclass 29class Issue: 30 """The `Issue` dataclass represents a single issue found in a rule.""" 31 32 code: str 33 message: str 34 severity: Optional[int] = None 35 checker: Optional[str] = None 36
[docs] 37 def to_dict(self: "Issue") -> dict[str, str]: 38 """Returns the Issue represented as a dictionary.""" 39 d = { 40 "code": self.code, 41 "message": self.message, 42 } 43 44 if self.checker is not None: 45 d["checker"] = self.checker 46 47 return d
48 49 @property 50 def hash(self: "Issue") -> int: 51 """Returns a unique hash that can be used as a fingerprint for the issue.""" 52 return hash(tuple(sorted(self.to_dict().items()))) 53
[docs] 54 def __repr__(self: "Issue") -> str: 55 """Returns the Issue represented as a string.""" 56 return json.dumps(self.to_dict())
57 58 59ISSUES_TYPE = MutableSequence[Issue] 60"""Type representing a sequence of multiple `Issue` instances.""" 61SIMPLE_SUMMARY_TYPE = MutableMapping[str, int] 62"""Type representing a dictionary-like object mapping a string to a number of issues.""" 63RULE_SUMMARY_TYPE = SIMPLE_SUMMARY_TYPE 64"""Type representing a dictionary-like object mapping a string to a number of issues.""" 65EXTENSIVE_SUMMARY_TYPE = MutableMapping[str, SIMPLE_SUMMARY_TYPE] 66"""Type representing a dictionary-like object mapping a string to a `SIMPLE_SUMMARY_TYPE`.""" 67 68
[docs] 69@dataclass 70class RuleReport: 71 """The `RuleReport` dataclass represents a rule, together with information on its location and detected issues.""" 72 73 rule: "__Rule" 74 summary: Optional[RULE_SUMMARY_TYPE] = None 75 line_begin: Optional[int] = None 76 line_end: Optional[int] = None 77 78 _issues: ISSUES_TYPE = field(default_factory=list, init=False) 79 80 @property 81 def issues(self: "RuleReport") -> ISSUES_TYPE: 82 """List of issues found in the rule.""" 83 return self._issues 84
[docs] 85 def add_issue(self: "RuleReport", issue: Issue) -> None: 86 """Adds an issue to the report.""" 87 self._issues.append(issue)
88
[docs] 89 def add_issues(self: "RuleReport", issues: ISSUES_TYPE) -> None: 90 """Adds an issue to the report.""" 91 for issue in issues: 92 self._issues.append(issue)
93
[docs] 94 def to_dict(self: "RuleReport") -> dict[str, str]: 95 """Returns the RuleReport represented as a dictionary.""" 96 d = { 97 "rule": self.rule.raw, 98 "issues": [issue.to_dict() for issue in self.issues], 99 } 100 101 if self.summary is not None: 102 d["summary"] = self.summary 103 104 if self.line_begin is not None or self.line_end is not None: 105 d["lines"] = {} 106 107 if self.line_begin is not None: 108 d["lines"]["begin"] = self.line_begin 109 110 if self.line_begin is not None: 111 d["lines"]["end"] = self.line_end 112 113 return d
114
[docs] 115 def __repr__(self: "RuleReport") -> str: 116 """Returns the RuleReport represented as a string.""" 117 return json.dumps(self.to_dict())
118 119 120RULE_REPORTS_TYPE = MutableSequence[RuleReport] 121"""Type representing a sequence of multiple `RuleReport` instances.""" 122 123
[docs] 124@dataclass 125class OutputSummary: 126 """The `OutputSummary` dataclass represent a collection of summaries on the output of `suricata_check`.""" 127 128 overall_summary: SIMPLE_SUMMARY_TYPE 129 issues_by_group: SIMPLE_SUMMARY_TYPE 130 issues_by_type: EXTENSIVE_SUMMARY_TYPE
131 132
[docs] 133@dataclass 134class OutputReport: 135 """The `OutputSummary` dataclass represent the `suricata_check`, consisting of rule reports and summaries.""" 136 137 _rules: RULE_REPORTS_TYPE = field(default_factory=list, init=False) 138 summary: Optional[OutputSummary] = None 139 140 def __init__( 141 self: "OutputReport", 142 rules: RULE_REPORTS_TYPE = [], 143 summary: Optional[OutputSummary] = None, 144 ) -> None: 145 """Initialized the `OutputReport`, optionally with a list of rules and/or a summary.""" 146 self._rules = [] 147 for rule in rules: 148 self.add_rule(rule) 149 self.summary = summary 150 super().__init__() 151 152 @property 153 def rules(self: "OutputReport") -> RULE_REPORTS_TYPE: 154 """List of rules contained in the report.""" 155 return self._rules 156
[docs] 157 def add_rule(self: "OutputReport", rule_report: RuleReport) -> None: 158 """Adds an rule to the report.""" 159 self._rules.append(rule_report)