1
- import uuid
2
- from typing import Any , ClassVar
3
- import json
1
+ from typing import ClassVar
4
2
import hashlib
3
+ from functools import cached_property
5
4
6
- from pydantic import BaseModel , Field , PrivateAttr , computed_field
5
+ from pydantic import BaseModel , Field , computed_field
6
+
7
+ from contentctl .objects .detection_metadata import DetectionMetadata
7
8
8
9
9
10
class DetectionStanza (BaseModel ):
@@ -16,19 +17,17 @@ class DetectionStanza(BaseModel):
16
17
# The full name of the detection (e.g. "ESCU - My Detection - Rule")
17
18
name : str = Field (...)
18
19
19
- # The metadata extracted from the stanza
20
- _metadata : dict [str , Any ] = PrivateAttr (default = {})
21
-
22
20
# The key prefix indicating the metadata attribute
23
21
METADATA_LINE_PREFIX : ClassVar [str ] = "action.correlationsearch.metadata = "
24
22
25
- def model_post_init (self , __context : Any ) -> None :
26
- super ().model_post_init (__context )
27
- self ._parse_metadata ()
28
-
29
- def _parse_metadata (self ) -> None :
23
+ @computed_field
24
+ @cached_property
25
+ def metadata (self ) -> DetectionMetadata :
30
26
"""
31
- Using the provided lines, parse out the metadata
27
+ The metadata extracted from the stanza. Using the provided lines, parse out the metadata
28
+
29
+ :returns: the detection stanza's metadata
30
+ :rtype: :class:`contentctl.objects.detection_metadata.DetectionMetadata`
32
31
"""
33
32
# Set a variable to store the metadata line in
34
33
meta_line : str | None = None
@@ -47,56 +46,17 @@ def _parse_metadata(self) -> None:
47
46
if meta_line is None :
48
47
raise Exception (f"No metadata for detection '{ self .name } ' found in stanza." )
49
48
50
- # Try to load the metadata JSON into a dict
51
- try :
52
- self ._metadata : dict [str , Any ] = json .loads (meta_line [len (DetectionStanza .METADATA_LINE_PREFIX ):])
53
- except json .decoder .JSONDecodeError as e :
54
- raise Exception (
55
- f"Malformed metdata for detection '{ self .name } ': { e } "
56
- )
57
-
58
- @computed_field
59
- @property
60
- def deprecated (self ) -> int :
61
- """
62
- An int indicating whether the detection is deprecated
63
- :returns: int
64
- """
65
- return int (self ._metadata ["deprecated" ])
66
-
67
- @computed_field
68
- @property
69
- def detection_id (self ) -> uuid .UUID :
70
- """
71
- A UUID identifying the detection
72
- :returns: UUID
73
- """
74
- return uuid .UUID (self ._metadata ["detection_id" ])
75
-
76
- @computed_field
77
- @property
78
- def detection_version (self ) -> int :
79
- """
80
- The version of the detection
81
- :returns: int
82
- """
83
- return int (self ._metadata ["detection_version" ])
84
-
85
- @computed_field
86
- @property
87
- def publish_time (self ) -> float :
88
- """
89
- The time the detection was published
90
- :returns: float
91
- """
92
- return self ._metadata ["publish_time" ]
49
+ # Parse the metadata JSON into a model
50
+ return DetectionMetadata .model_validate_json (meta_line [len (DetectionStanza .METADATA_LINE_PREFIX ):])
93
51
94
52
@computed_field
95
- @property
53
+ @cached_property
96
54
def hash (self ) -> str :
97
55
"""
98
56
The SHA256 hash of the lines of the stanza, excluding the metadata line
99
- :returns: str (hexdigest)
57
+
58
+ :returns: hexdigest
59
+ :rtype: str
100
60
"""
101
61
hash = hashlib .sha256 ()
102
62
for line in self .lines :
@@ -109,7 +69,11 @@ def version_should_be_bumped(self, previous: "DetectionStanza") -> bool:
109
69
A helper method that compares this stanza against the same stanza from a previous build;
110
70
returns True if the version still needs to be bumped (e.g. the detection was changed but
111
71
the version was not), False otherwise.
72
+
112
73
:param previous: the previous build's DetectionStanza for comparison
74
+ :type previous: :class:`contentctl.objects.detection_stanza.DetectionStanza`
75
+
113
76
:returns: True if the version still needs to be bumped
77
+ :rtype: bool
114
78
"""
115
- return (self .hash != previous .hash ) and (self .detection_version <= previous .detection_version )
79
+ return (self .hash != previous .hash ) and (self .metadata . detection_version <= previous . metadata .detection_version )
0 commit comments