Skip to content

Commit 2eae03f

Browse files
authored
Merge pull request #372 from SigmaHQ/fix-mypy-issues
Fixed typing issues
2 parents d2d0d94 + 19b9fca commit 2eae03f

11 files changed

Lines changed: 103 additions & 84 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ authors = [
77
{ name = "Thomas Patzke", email = "thomas@patzke.org" },
88
]
99
readme = "README.md"
10-
repository = "https://github.com/SigmaHQ/pySigma"
1110
classifiers = [
1211
"Development Status :: 4 - Beta",
1312
"Intended Audience :: Developers",

sigma/collection.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import InitVar, dataclass, field
22
from functools import reduce
33
from pathlib import Path
4-
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, IO, TYPE_CHECKING
4+
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, IO, TYPE_CHECKING, cast
55
from uuid import UUID
66

77
import yaml
@@ -88,7 +88,9 @@ def resolve_rule_references(self) -> None:
8888
rule.resolve_rule_references(self)
8989

9090
# Extract all filters from the rules
91-
filters: List[SigmaFilter] = [rule for rule in self.rules if isinstance(rule, SigmaFilter)]
91+
filters: List[SigmaFilter] = [
92+
cast(SigmaFilter, rule) for rule in self.rules if isinstance(rule, SigmaFilter)
93+
]
9294
self.rules = [rule for rule in self.rules if not isinstance(rule, SigmaFilter)]
9395

9496
# Apply filters on each rule and replace the rule with the filtered rule
@@ -126,34 +128,33 @@ def from_dicts(
126128
if isinstance(
127129
rule, SigmaRule
128130
): # Included rules are already parsed, skip collection action processing
129-
parsed_rule = rule
130-
parsed_rules.append(parsed_rule)
131-
parsed_rule.source = source
131+
parsed_rules.append(rule)
132+
rule.source = source
132133
else:
133134
action = rule.get("action")
134135
if action is None: # no action defined
135136
if "correlation" in rule: # correlation rule - no global rule merge
136-
parsed_rule = SigmaCorrelationRule.from_dict(
137+
parsed_correlation_rule = SigmaCorrelationRule.from_dict(
137138
rule,
138139
collect_errors,
139140
source,
140141
)
141-
parsed_rules.append(parsed_rule)
142-
errors.extend(parsed_rule.errors) # Propagate errors from rule
142+
parsed_rules.append(parsed_correlation_rule)
143+
errors.extend(parsed_correlation_rule.errors) # Propagate errors from rule
143144
elif "filter" in rule: # correlation rule - no global rule merge
144-
parsed_rule = SigmaFilter.from_dict(
145+
parsed_filter_rule = SigmaFilter.from_dict(
145146
rule,
146147
collect_errors,
147148
source,
148149
)
149-
parsed_rules.append(parsed_rule)
150-
errors.extend(parsed_rule.errors) # Propagate errors from rule
150+
parsed_rules.append(parsed_filter_rule)
151+
errors.extend(parsed_filter_rule.errors) # Propagate errors from rule
151152
else: # merge with global rule and parse as simple rule
152-
parsed_rule = SigmaRule.from_dict(
153+
parsed_merged_rule = SigmaRule.from_dict(
153154
deep_dict_update(rule, global_rule), collect_errors, source
154155
)
155-
parsed_rules.append(parsed_rule)
156-
errors.extend(parsed_rule.errors) # Propagate errors from rule
156+
parsed_rules.append(parsed_merged_rule)
157+
errors.extend(parsed_merged_rule.errors) # Propagate errors from rule
157158
prev_rule = rule
158159
elif action == "global": # set global rule template
159160
del rule["action"]
@@ -245,6 +246,7 @@ def load_ruleset(
245246
:param recursion_pattern: Pattern used to recurse into directories, defaults to ``**/*.yml``.
246247
247248
:return: :class:`SigmaCollection` of all sigma rules contained in given paths.
249+
248250
"""
249251
if not isinstance(inputs, Iterable) or isinstance(inputs, str):
250252
raise TypeError(

sigma/conversion/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,7 +1984,7 @@ def convert_correlation_rule_from_template(
19841984
rule: SigmaCorrelationRule,
19851985
correlation_type: SigmaCorrelationTypeLiteral,
19861986
method: str,
1987-
) -> str:
1987+
) -> List[str]:
19881988
template = (
19891989
getattr(self, f"{correlation_type}_correlation_query") or self.default_correlation_query
19901990
)
@@ -2202,7 +2202,7 @@ def convert_correlation_aggregation_fields_from_template(
22022202
referenced_rules: List[SigmaRuleReference],
22032203
group_by: Optional[List[str]],
22042204
method: str,
2205-
):
2205+
) -> str:
22062206
if self.correlation_fields_expression is None:
22072207
return ""
22082208
else:
@@ -2215,9 +2215,13 @@ def convert_correlation_aggregation_fields_from_template(
22152215
# Include fields from the correlation rule
22162216
for fld in referenced_rules_fields + correlation_rule_fields:
22172217
# Exclude groupby fields and keep only unique fields (remove duplicates)
2218-
if fld not in group_by and fld not in all_fields:
2218+
if (group_by is None or fld not in group_by) and fld not in all_fields:
22192219
all_fields.append(fld)
2220-
if len(all_fields) == 0: # if no fields
2220+
if (
2221+
len(all_fields) == 0
2222+
or self.correlation_fields_field_expression is None
2223+
or self.correlation_fields_field_expression_joiner is None
2224+
):
22212225
return ""
22222226
return self.correlation_fields_expression[method].format(
22232227
fields=self.correlation_fields_field_expression_joiner[method].join(

sigma/correlations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,15 +423,17 @@ def resolve_rule_references(self, rule_collection: "SigmaCollection") -> None:
423423

424424
self.aliases.resolve_rule_references(rule_collection)
425425

426-
def flatten_rules(self, include_correlations: bool = True) -> List[SigmaRule]:
426+
def flatten_rules(
427+
self, include_correlations: bool = True
428+
) -> List[Union[SigmaRule, "SigmaCorrelationRule"]]:
427429
"""
428430
Flattens the rules in the correlation rule and returns a list of Sigma rules. If include_correlations
429431
is set to False, only the Sigma rules are returned, excluding nested correlation rules.
430432
431433
Returns:
432434
List of Sigma rules.
433435
"""
434-
rules = []
436+
rules: List[Union[SigmaRule, "SigmaCorrelationRule"]] = []
435437
for rule_ref in self.rules:
436438
rule = rule_ref.rule
437439
if isinstance(rule, SigmaCorrelationRule):

sigma/modifiers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ class SigmaNotEqualModifier(SigmaCompareModifier):
411411
op: ClassVar[CompareOperators] = CompareOperators.NEQ
412412

413413

414-
class SigmaFieldReferenceModifier(SigmaValueModifier):
414+
class SigmaFieldReferenceModifier(SigmaValueModifier[SigmaString, SigmaFieldReference]):
415415
"""Modifiers a plain string into the field reference type."""
416416

417417
def modify(self, val: SigmaString) -> SigmaFieldReference:
@@ -434,7 +434,11 @@ def modify(self, val: SigmaBool) -> SigmaExists:
434434
return SigmaExists(val.boolean)
435435

436436

437-
class SigmaExpandModifier(SigmaValueModifier[SigmaString, SigmaString]):
437+
class SigmaExpandModifier(
438+
SigmaValueModifier[
439+
Union[SigmaString, SigmaRegularExpression], Union[SigmaString, SigmaRegularExpression]
440+
]
441+
):
438442
"""
439443
Modifier for expansion of placeholders in values. It replaces placeholder strings (%something%)
440444
with stub objects that are later expanded to one or multiple strings or replaced with some SIEM

sigma/processing/resolver.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
)
88
from sigma.processing.pipeline import ProcessingPipeline
99
from typing import Dict, Iterable, List, Optional, Tuple, Union, cast, Callable
10+
from collections import namedtuple
1011

1112

1213
@dataclass
@@ -81,11 +82,13 @@ def resolve(
8182
compatibility check for the usage of the specified backend with the pipeline.
8283
"""
8384

84-
def resolve_path(spec):
85+
PipelineInfo = namedtuple("PipelineInfo", ["pipeline", "priority", "path"])
86+
87+
def resolve_path(spec: str) -> PipelineInfo:
8588
pipeline = self.resolve_pipeline(spec, target)
86-
return {"pipeline": pipeline, "priority": pipeline.priority, "path": spec}
89+
return PipelineInfo(pipeline=pipeline, priority=pipeline.priority, path=spec)
8790

88-
def resolve_spec(pipelines, spec):
91+
def resolve_spec(pipelines: List[PipelineInfo], spec: str) -> List[PipelineInfo]:
8992
spec_path = Path(spec.rstrip("/*"))
9093
if spec_path.is_dir():
9194
pipelines.extend([resolve_path(str(path)) for path in spec_path.glob("**/*.yml")])
@@ -94,11 +97,9 @@ def resolve_spec(pipelines, spec):
9497

9598
return pipelines
9699

97-
pipelines = reduce(resolve_spec, pipeline_specs, [])
100+
pipelines: List[PipelineInfo] = reduce(resolve_spec, pipeline_specs, [])
98101

99102
return (
100-
sum(
101-
[p["pipeline"] for p in sorted(pipelines, key=lambda p: (p["priority"], p["path"]))]
102-
)
103+
sum([p.pipeline for p in sorted(pipelines, key=lambda p: (p.priority, p.path))])
103104
or ProcessingPipeline()
104105
)

sigma/processing/transformations/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@
6969
}
7070

7171
__all__ = [
72-
# "Transformation",
73-
# "PreprocessingTransformation",
72+
"Transformation",
73+
"PreprocessingTransformation",
7474
"FieldMappingTransformation",
7575
"FieldPrefixMappingTransformation",
7676
"FieldFunctionTransformation",

sigma/processing/transformations/failure.py

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Set, Union
33
from sigma.correlations import SigmaCorrelationRule
4-
from sigma.exceptions import SigmaTransformationError, SigmaTypeError
4+
from sigma.exceptions import SigmaTransformationError
55
from sigma.processing.transformations.base import (
66
PreprocessingTransformation,
77
DetectionItemTransformation,
@@ -72,32 +72,35 @@ def _get_fields_from_detection(self, detection: SigmaDetection) -> Set[str]:
7272

7373
return field_names
7474

75-
def apply(self, rule: SigmaRule) -> None:
75+
def apply(self, rule: Union[SigmaRule, SigmaCorrelationRule]) -> None:
7676
super().apply(rule)
77-
78-
pipeline: "sigma.processing.pipeline.ProcessingPipeline" = self._pipeline
79-
field_mappings: "sigma.processing.tracking.FieldMappingTracking" = pipeline.field_mappings
80-
81-
# Get all field names used in the rule (after any transformations have been applied)
82-
all_fields = self._get_all_field_names(rule)
83-
84-
# Check which original fields from the rule were not explicitly mapped
85-
# We need to check the target_fields reverse mapping to see which original fields
86-
# are represented by the current field names
87-
unmapped_fields = []
88-
89-
for field in all_fields:
90-
# Check if this field is in the target_fields (meaning it was mapped from an original field)
91-
# or if it's in the field_mappings keys (meaning it was an original field that was mapped)
92-
is_mapped = field in field_mappings or field in field_mappings.target_fields
93-
if not is_mapped:
94-
unmapped_fields.append(field)
95-
96-
# Raise error if there are unmapped fields
97-
if unmapped_fields:
98-
unmapped_fields_str = ", ".join(
99-
unmapped_fields
100-
) # Create a comma-separated list of unmapped fields
101-
raise SigmaTransformationError(
102-
f"The following fields are not mapped: {unmapped_fields_str}", source=rule.source
103-
)
77+
if isinstance(rule, SigmaRule):
78+
pipeline = self._pipeline
79+
if pipeline is None:
80+
raise SigmaTransformationError("Pipeline is not set for the transformation.")
81+
field_mappings = pipeline.field_mappings
82+
83+
# Get all field names used in the rule (after any transformations have been applied)
84+
all_fields = self._get_all_field_names(rule)
85+
86+
# Check which original fields from the rule were not explicitly mapped
87+
# We need to check the target_fields reverse mapping to see which original fields
88+
# are represented by the current field names
89+
unmapped_fields = []
90+
91+
for field in all_fields:
92+
# Check if this field is in the target_fields (meaning it was mapped from an original field)
93+
# or if it's in the field_mappings keys (meaning it was an original field that was mapped)
94+
is_mapped = field in field_mappings or field in field_mappings.target_fields
95+
if not is_mapped:
96+
unmapped_fields.append(field)
97+
98+
# Raise error if there are unmapped fields
99+
if unmapped_fields:
100+
unmapped_fields_str = ", ".join(
101+
unmapped_fields
102+
) # Create a comma-separated list of unmapped fields
103+
raise SigmaTransformationError(
104+
f"The following fields are not mapped: {unmapped_fields_str}",
105+
source=rule.source,
106+
)

sigma/processing/transformations/placeholder.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,23 @@ def __post_init__(self) -> None:
5454
self.check_exclusivity()
5555
return super().__post_init__()
5656

57-
def apply_value(
58-
self, field: str, val: Union[SigmaString, SigmaRegularExpression]
59-
) -> Union[
60-
SigmaString, Iterable[SigmaString], SigmaRegularExpression, Iterable[SigmaRegularExpression]
57+
def apply_value(self, field: Optional[str], val: SigmaType) -> Union[
58+
None,
59+
SigmaString,
60+
Iterable[SigmaString],
61+
SigmaRegularExpression,
62+
Iterable[SigmaRegularExpression],
6163
]:
62-
if val.contains_placeholder(self.include, self.exclude):
64+
if isinstance(val, (SigmaString, SigmaRegularExpression)) and val.contains_placeholder(
65+
self.include, self.exclude
66+
):
6367
return val.replace_placeholders(self.placeholder_replacements_base)
6468
else:
6569
return None
6670

6771
def placeholder_replacements_base(
6872
self, p: Placeholder
69-
) -> Iterable[Union[str, SpecialChars, Placeholder, SigmaString]]:
73+
) -> Iterator[Union[str, SpecialChars, Placeholder, SigmaString]]:
7074
"""
7175
Base placeholder replacement callback. Calls real callback if placeholder is included or not excluded,
7276
else it passes the placeholder back to caller.

sigma/types.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def contains_placeholder(
478478
def replace_placeholders(
479479
self,
480480
callback: Callable[
481-
[Placeholder], Iterable[Union[str, SpecialChars, Placeholder, "SigmaString"]]
481+
[Placeholder], Iterator[Union[str, SpecialChars, Placeholder, "SigmaString"]]
482482
],
483483
) -> List["SigmaString"]:
484484
"""
@@ -690,15 +690,6 @@ def __init__(self, timestamp_part: TimestampPart, number: int):
690690
super().__init__(number)
691691

692692

693-
class SigmaTimestampPart(SigmaNumber):
694-
695-
timestamp_part: TimestampPart
696-
697-
def __init__(self, timestamp_part: TimestampPart, number: int):
698-
self.timestamp_part = timestamp_part
699-
super().__init__(number)
700-
701-
702693
@dataclass
703694
class SigmaBool(SigmaType):
704695
"""Boolean value type"""
@@ -831,7 +822,10 @@ def insert_placeholders(self) -> "SigmaRegularExpression":
831822
return self
832823

833824
def replace_placeholders(
834-
self, callback: Callable[[Placeholder], Iterator[Union[str, SpecialChars, Placeholder]]]
825+
self,
826+
callback: Callable[
827+
[Placeholder], Iterator[Union[str, SpecialChars, Placeholder, "SigmaString"]]
828+
],
835829
) -> List["SigmaRegularExpression"]:
836830
"""
837831
Replace all occurrences of string part matching regular expression with placeholder.

0 commit comments

Comments
 (0)