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 suppressed_issues: int = 0 78 79 _issues: ISSUES_TYPE = field(default_factory=list, init=False) 80 81 @property 82 def issues(self: "RuleReport") -> ISSUES_TYPE: 83 """List of issues found in the rule.""" 84 return self._issues 85
[docs] 86 def add_issue(self: "RuleReport", issue: Issue) -> None: 87 """Adds an issue to the report.""" 88 self._issues.append(issue)
89
[docs] 90 def add_issues(self: "RuleReport", issues: ISSUES_TYPE) -> None: 91 """Adds an issue to the report.""" 92 for issue in issues: 93 self._issues.append(issue)
94
[docs] 95 def to_dict(self: "RuleReport") -> dict[str, str]: 96 """Returns the RuleReport represented as a dictionary.""" 97 d = { 98 "rule": self.rule.raw, 99 "issues": [issue.to_dict() for issue in self.issues], 100 } 101 102 if self.summary is not None: 103 d["summary"] = self.summary 104 105 if self.line_begin is not None or self.line_end is not None: 106 d["lines"] = {} 107 108 if self.line_begin is not None: 109 d["lines"]["begin"] = self.line_begin 110 111 if self.line_begin is not None: 112 d["lines"]["end"] = self.line_end 113 114 return d
115
[docs] 116 def __repr__(self: "RuleReport") -> str: 117 """Returns the RuleReport represented as a string.""" 118 return json.dumps(self.to_dict())
119 120 121RULE_REPORTS_TYPE = MutableSequence[RuleReport] 122"""Type representing a sequence of multiple `RuleReport` instances.""" 123 124
[docs] 125@dataclass 126class OutputSummary: 127 """The `OutputSummary` dataclass represent a collection of summaries on the output of `suricata_check`.""" 128 129 overall_summary: SIMPLE_SUMMARY_TYPE 130 issues_by_group: SIMPLE_SUMMARY_TYPE 131 issues_by_type: EXTENSIVE_SUMMARY_TYPE
132 133
[docs] 134@dataclass 135class OutputReport: 136 """The `OutputSummary` dataclass represent the `suricata_check`, consisting of rule reports and summaries.""" 137 138 _rules: RULE_REPORTS_TYPE = field(default_factory=list, init=False) 139 summary: Optional[OutputSummary] = None 140 141 def __init__( 142 self: "OutputReport", 143 rules: RULE_REPORTS_TYPE = [], 144 summary: Optional[OutputSummary] = None, 145 ) -> None: 146 """Initialized the `OutputReport`, optionally with a list of rules and/or a summary.""" 147 self._rules = [] 148 for rule in rules: 149 self.add_rule(rule) 150 self.summary = summary 151 super().__init__() 152 153 @property 154 def rules(self: "OutputReport") -> RULE_REPORTS_TYPE: 155 """List of rules contained in the report.""" 156 return self._rules 157
[docs] 158 def add_rule(self: "OutputReport", rule_report: RuleReport) -> None: 159 """Adds an rule to the report.""" 160 self._rules.append(rule_report)