stormlibpp 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/PKG-INFO +1 -1
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/pyproject.toml +1 -1
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp/__init__.py +2 -1
- stormlibpp-0.3.0/src/stormlibpp/node.py +197 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp.egg-info/PKG-INFO +1 -1
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp.egg-info/SOURCES.txt +1 -0
- stormlibpp-0.3.0/tests/test_node.py +43 -0
- stormlibpp-0.2.0/src/stormlibpp/node.py +0 -13
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/LICENSE +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/README.md +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/setup.cfg +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp/errors.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp/stormpkg.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp/telepath.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp/utils.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp.egg-info/dependency_links.txt +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp.egg-info/requires.txt +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/src/stormlibpp.egg-info/top_level.txt +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/tests/test_stormpkg.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/tests/test_telepath.py +0 -0
- {stormlibpp-0.2.0 → stormlibpp-0.3.0}/tests/test_utils.py +0 -0
|
@@ -29,9 +29,10 @@ are briefly described below:
|
|
|
29
29
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
__version__ = "0.
|
|
32
|
+
__version__ = "0.3.0"
|
|
33
33
|
|
|
34
34
|
from . import errors
|
|
35
35
|
from . import utils
|
|
36
|
+
from .node import StormNode
|
|
36
37
|
from .stormpkg import StormPkg
|
|
37
38
|
from .telepath import (genDefaultTelepathRetn, TelepathRetn)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Work with storm:node objects in Python."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from typing import Any, Self, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
NodeForm = str
|
|
9
|
+
"""A valid syn:form value."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NodeVals(TypedDict):
|
|
13
|
+
"""The items a Node can have in the second part of packed NodeTuple."""
|
|
14
|
+
|
|
15
|
+
iden: str
|
|
16
|
+
nodedata: dict[str, Any]
|
|
17
|
+
props: dict[str, Any]
|
|
18
|
+
tags: dict[str, Any]
|
|
19
|
+
tagprops: dict[str, Any]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
NodeDef = tuple[NodeForm, str]
|
|
23
|
+
"""A nodes definition, the node's form and primary value."""
|
|
24
|
+
|
|
25
|
+
NodeTuple = tuple[NodeDef, NodeVals]
|
|
26
|
+
"""A packed node, this is what get's sent from Storm to a Telepath endpoint.
|
|
27
|
+
|
|
28
|
+
Or what get's returned from a Cortex's Python APIs.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ItemContainer:
|
|
33
|
+
"""Hold key/value items in a SimpleNamespace.
|
|
34
|
+
|
|
35
|
+
Keys must be strings.
|
|
36
|
+
|
|
37
|
+
Supports ``obj['item']`` (and ``obj['item'] = 'val'``) notation.
|
|
38
|
+
|
|
39
|
+
When "getting" an item with ``__getitem__``, that doesn't exist in the
|
|
40
|
+
container, ``None`` is returned instead of raising an exception.
|
|
41
|
+
|
|
42
|
+
Calling ``dir`` on this object will only return names for items this object
|
|
43
|
+
"holds" instead of all parts of this object.
|
|
44
|
+
|
|
45
|
+
Supports checking if an item is "held" via the ``in`` operator.
|
|
46
|
+
|
|
47
|
+
Supports iteration of items in the SimpleNamespace. Each iteration returns
|
|
48
|
+
a tuple of the item name followed by the item value.
|
|
49
|
+
|
|
50
|
+
Implements only magic methods, and all of them operate on the underlying
|
|
51
|
+
SimpleNamespace. To convert this object to a ``dict`` of the items it holds,
|
|
52
|
+
call ``vars`` on an instance.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, /, **kwargs) -> None:
|
|
56
|
+
self.__ns = SimpleNamespace(**kwargs)
|
|
57
|
+
|
|
58
|
+
def __contains__(self, __name: str) -> bool:
|
|
59
|
+
return __name in dir(self)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def __dict__(self):
|
|
63
|
+
return self.__ns.__dict__
|
|
64
|
+
|
|
65
|
+
def __dir__(self) -> list[str]:
|
|
66
|
+
return [val for val in dir(self.__ns) if not val.startswith("_")]
|
|
67
|
+
|
|
68
|
+
def __iter__(self) -> Any:
|
|
69
|
+
for item in dir(self):
|
|
70
|
+
yield (item, self[item])
|
|
71
|
+
|
|
72
|
+
def __getitem__(self, __name: str) -> Any:
|
|
73
|
+
return getattr(self.__ns, __name, None)
|
|
74
|
+
|
|
75
|
+
def __repr__(self) -> str:
|
|
76
|
+
return repr(self.__ns).replace("namespace", self.__class__.__name__)
|
|
77
|
+
|
|
78
|
+
def __setitem__(self, __name: str, __value: str) -> None:
|
|
79
|
+
setattr(self.__ns, __name, __value)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Props(ItemContainer):
|
|
83
|
+
"""Properties of a StormNode."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Tags(ItemContainer):
|
|
87
|
+
"""Tags of a StormNode."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class StormNode:
|
|
92
|
+
"""A storm:node Storm object that is in a Python runtime.
|
|
93
|
+
|
|
94
|
+
It supports "unpacking" the node into this object, edits to the object, and
|
|
95
|
+
repacking of the node to be sent back to Storm.
|
|
96
|
+
|
|
97
|
+
This node can be created purely from Python code rather than a packed
|
|
98
|
+
``storm:node`` object. Use the ``__init__`` directly instead of ``unpack``.
|
|
99
|
+
This allows the creation of a new node in Python that can be passed back to
|
|
100
|
+
Storm.
|
|
101
|
+
|
|
102
|
+
The intent is to represent an object that can be read from and written back
|
|
103
|
+
to a Cortex. It isn't meant to provide the same API that a ``storm:node``
|
|
104
|
+
has in Storm.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
form: NodeForm,
|
|
110
|
+
value: Any,
|
|
111
|
+
/,
|
|
112
|
+
iden: str = "",
|
|
113
|
+
tags: dict = {},
|
|
114
|
+
props: dict = {},
|
|
115
|
+
nodedata: dict = {},
|
|
116
|
+
tagprops: dict = {},
|
|
117
|
+
**kwargs,
|
|
118
|
+
) -> None:
|
|
119
|
+
|
|
120
|
+
self._form = form
|
|
121
|
+
self._value = value
|
|
122
|
+
|
|
123
|
+
self._tags = Props(**tags)
|
|
124
|
+
self._props = Tags(**props)
|
|
125
|
+
|
|
126
|
+
self.iden = iden
|
|
127
|
+
self.nodedata = nodedata
|
|
128
|
+
self.tagprops = tagprops
|
|
129
|
+
|
|
130
|
+
# Not really sure if this is needed, but saving it for now.
|
|
131
|
+
self._xtras = kwargs
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def form(self) -> str:
|
|
135
|
+
"""The form of this Node."""
|
|
136
|
+
|
|
137
|
+
return self._form
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def value(self) -> str:
|
|
141
|
+
"""The value of this Node."""
|
|
142
|
+
|
|
143
|
+
return self._value
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def tags(self):
|
|
147
|
+
"""The tags of this Node."""
|
|
148
|
+
|
|
149
|
+
return self._tags
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def props(self):
|
|
153
|
+
"""The properties of this Node."""
|
|
154
|
+
|
|
155
|
+
return self._props
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def nodevals(self):
|
|
159
|
+
"""The NodeVals dict this Node would have in a NodeTuple."""
|
|
160
|
+
|
|
161
|
+
return NodeVals(
|
|
162
|
+
iden=self.iden,
|
|
163
|
+
tagprops=self.tagprops,
|
|
164
|
+
nodedata=self.nodedata,
|
|
165
|
+
props=vars(self.props),
|
|
166
|
+
tags=vars(self.tags),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def unpack(cls, packedtup: NodeTuple) -> Self:
|
|
171
|
+
form, name = packedtup[0]
|
|
172
|
+
vals = NodeVals(**packedtup[1])
|
|
173
|
+
return cls(
|
|
174
|
+
form,
|
|
175
|
+
name,
|
|
176
|
+
**vals
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def pack(self) -> NodeTuple:
|
|
180
|
+
return ((self.form, self.value), self.nodevals)
|
|
181
|
+
|
|
182
|
+
def addTag(self, tag, timestamps: tuple = (None, None)) -> None:
|
|
183
|
+
if tag in self._tags and timestamps != (None, None) and self._tags[tag] != timestamps:
|
|
184
|
+
self._tags[tag] = timestamps
|
|
185
|
+
elif tag not in self._tags:
|
|
186
|
+
self._tags[tag] = timestamps
|
|
187
|
+
|
|
188
|
+
def addTags(self, tags, timestamps: tuple = (None, None)) -> None:
|
|
189
|
+
for tag in tags:
|
|
190
|
+
self.addTag(tag, timestamps=timestamps)
|
|
191
|
+
|
|
192
|
+
def addProp(self, propname: str, propval: Any) -> None:
|
|
193
|
+
self._props[propname] = propval
|
|
194
|
+
|
|
195
|
+
def addProps(self, props: dict[str, Any]) -> None:
|
|
196
|
+
for propname, propval in props.items():
|
|
197
|
+
self._props[propname] = propval
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Tests for stormlibpp.node."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import stormlibpp
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PACKED_NODE = (('it:app:yara:rule', '5d0292d99a7da59a3a119e193f140143'), {'iden': '57ce731580ed6976b608e87dfc7f2ece81790f2cb8cdaf958013c738b28cd56e', 'tags': {'test': (None, None), 'test.tag': (None, None)}, 'props': {'.created': 1687720565352, 'text': 'rule dummy { condition: true }', 'name': 'dummy', 'enabled': 1}, 'tagprops': {}, 'nodedata': {}})
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestNode:
|
|
11
|
+
|
|
12
|
+
def test_packing(self):
|
|
13
|
+
node = stormlibpp.StormNode.unpack(PACKED_NODE)
|
|
14
|
+
assert node.pack() == PACKED_NODE
|
|
15
|
+
|
|
16
|
+
def test_add_tags(self):
|
|
17
|
+
tags = ["#test.othertag", "#atag"]
|
|
18
|
+
node = stormlibpp.StormNode.unpack(PACKED_NODE)
|
|
19
|
+
node.addTags(tags)
|
|
20
|
+
|
|
21
|
+
for tag in tags:
|
|
22
|
+
assert tag in node.tags
|
|
23
|
+
|
|
24
|
+
assert dir(node.tags) == [item[0] for item in iter(node.tags)]
|
|
25
|
+
|
|
26
|
+
def test_add_props(self):
|
|
27
|
+
props = {'prop': 'test', 'other': 'value'}
|
|
28
|
+
node = stormlibpp.StormNode.unpack(PACKED_NODE)
|
|
29
|
+
node.addProps(props)
|
|
30
|
+
|
|
31
|
+
for prop in props:
|
|
32
|
+
assert prop in node.props
|
|
33
|
+
|
|
34
|
+
for propname, propval in node.props:
|
|
35
|
+
if propname in props:
|
|
36
|
+
assert props[propname] == propval
|
|
37
|
+
|
|
38
|
+
def test_values(self):
|
|
39
|
+
node = stormlibpp.StormNode.unpack(PACKED_NODE)
|
|
40
|
+
|
|
41
|
+
assert node.form == PACKED_NODE[0][0]
|
|
42
|
+
assert node.value == PACKED_NODE[0][1]
|
|
43
|
+
assert node.nodevals == PACKED_NODE[1]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
class PackedNode:
|
|
2
|
-
"""A "packed" storm:node Storm object that is in a Python runtime.
|
|
3
|
-
|
|
4
|
-
It supports "unpacking" the node into this object, edits to the object, and
|
|
5
|
-
repacking of the node to be sent back to Storm.
|
|
6
|
-
|
|
7
|
-
This node can be created purely from Python code rather than a packed
|
|
8
|
-
``storm:node`` object. Use the ``__init__`` directly instead of ``unpack``.
|
|
9
|
-
This allows the creation of a new node in Python that can be passed back to
|
|
10
|
-
Storm.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
raise NotImplementedError
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|