pyoaev 1.18.20__py3-none-any.whl
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.
- docs/conf.py +65 -0
- pyoaev/__init__.py +26 -0
- pyoaev/_version.py +6 -0
- pyoaev/apis/__init__.py +20 -0
- pyoaev/apis/attack_pattern.py +28 -0
- pyoaev/apis/collector.py +29 -0
- pyoaev/apis/cve.py +18 -0
- pyoaev/apis/document.py +29 -0
- pyoaev/apis/endpoint.py +38 -0
- pyoaev/apis/inject.py +29 -0
- pyoaev/apis/inject_expectation/__init__.py +1 -0
- pyoaev/apis/inject_expectation/inject_expectation.py +118 -0
- pyoaev/apis/inject_expectation/model/__init__.py +7 -0
- pyoaev/apis/inject_expectation/model/expectation.py +173 -0
- pyoaev/apis/inject_expectation_trace.py +36 -0
- pyoaev/apis/injector.py +26 -0
- pyoaev/apis/injector_contract.py +56 -0
- pyoaev/apis/inputs/__init__.py +0 -0
- pyoaev/apis/inputs/search.py +72 -0
- pyoaev/apis/kill_chain_phase.py +22 -0
- pyoaev/apis/me.py +17 -0
- pyoaev/apis/organization.py +11 -0
- pyoaev/apis/payload.py +27 -0
- pyoaev/apis/security_platform.py +33 -0
- pyoaev/apis/tag.py +19 -0
- pyoaev/apis/team.py +25 -0
- pyoaev/apis/user.py +31 -0
- pyoaev/backends/__init__.py +14 -0
- pyoaev/backends/backend.py +136 -0
- pyoaev/backends/protocol.py +32 -0
- pyoaev/base.py +320 -0
- pyoaev/client.py +596 -0
- pyoaev/configuration/__init__.py +3 -0
- pyoaev/configuration/configuration.py +188 -0
- pyoaev/configuration/sources.py +44 -0
- pyoaev/contracts/__init__.py +5 -0
- pyoaev/contracts/contract_builder.py +44 -0
- pyoaev/contracts/contract_config.py +292 -0
- pyoaev/contracts/contract_utils.py +22 -0
- pyoaev/contracts/variable_helper.py +124 -0
- pyoaev/daemons/__init__.py +4 -0
- pyoaev/daemons/base_daemon.py +131 -0
- pyoaev/daemons/collector_daemon.py +91 -0
- pyoaev/exceptions.py +219 -0
- pyoaev/helpers.py +451 -0
- pyoaev/mixins.py +242 -0
- pyoaev/signatures/__init__.py +0 -0
- pyoaev/signatures/signature_match.py +12 -0
- pyoaev/signatures/signature_type.py +51 -0
- pyoaev/signatures/types.py +17 -0
- pyoaev/utils.py +211 -0
- pyoaev-1.18.20.dist-info/METADATA +134 -0
- pyoaev-1.18.20.dist-info/RECORD +72 -0
- pyoaev-1.18.20.dist-info/WHEEL +5 -0
- pyoaev-1.18.20.dist-info/licenses/LICENSE +201 -0
- pyoaev-1.18.20.dist-info/top_level.txt +4 -0
- scripts/release.py +127 -0
- test/__init__.py +0 -0
- test/apis/__init__.py +0 -0
- test/apis/expectation/__init__.py +0 -0
- test/apis/expectation/test_expectation.py +338 -0
- test/apis/injector_contract/__init__.py +0 -0
- test/apis/injector_contract/test_injector_contract.py +58 -0
- test/configuration/__init__.py +0 -0
- test/configuration/test_configuration.py +257 -0
- test/configuration/test_sources.py +69 -0
- test/daemons/__init__.py +0 -0
- test/daemons/test_base_daemon.py +109 -0
- test/daemons/test_collector_daemon.py +39 -0
- test/signatures/__init__.py +0 -0
- test/signatures/test_signature_match.py +25 -0
- test/signatures/test_signature_type.py +57 -0
pyoaev/base.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import importlib
|
|
3
|
+
import json
|
|
4
|
+
import pprint
|
|
5
|
+
import textwrap
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Type, Union
|
|
8
|
+
|
|
9
|
+
from pyoaev.exceptions import OpenAEVParsingError
|
|
10
|
+
|
|
11
|
+
from . import utils
|
|
12
|
+
from .client import OpenAEV, OpenAEVList
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"RESTObject",
|
|
16
|
+
"RESTObjectList",
|
|
17
|
+
"RESTManager",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RESTObject:
|
|
22
|
+
_id_attr: Optional[str] = "id"
|
|
23
|
+
_attrs: Dict[str, Any]
|
|
24
|
+
_created_from_list: bool # Indicates if object was created from a list() action
|
|
25
|
+
_module: ModuleType
|
|
26
|
+
_parent_attrs: Dict[str, Any]
|
|
27
|
+
_repr_attr: Optional[str] = None
|
|
28
|
+
_updated_attrs: Dict[str, Any]
|
|
29
|
+
manager: "RESTManager"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
manager: "RESTManager",
|
|
34
|
+
attrs: Dict[str, Any],
|
|
35
|
+
*,
|
|
36
|
+
created_from_list: bool = False,
|
|
37
|
+
) -> None:
|
|
38
|
+
if not isinstance(attrs, dict):
|
|
39
|
+
raise OpenAEVParsingError(
|
|
40
|
+
f"Attempted to initialize RESTObject with a non-dictionary value: "
|
|
41
|
+
f"{attrs!r}\nThis likely indicates an incorrect or malformed server "
|
|
42
|
+
f"response."
|
|
43
|
+
)
|
|
44
|
+
self.__dict__.update(
|
|
45
|
+
{
|
|
46
|
+
"manager": manager,
|
|
47
|
+
"_attrs": attrs,
|
|
48
|
+
"_updated_attrs": {},
|
|
49
|
+
"_module": importlib.import_module(self.__module__),
|
|
50
|
+
"_created_from_list": created_from_list,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
self.__dict__["_parent_attrs"] = self.manager.parent_attrs
|
|
54
|
+
self._create_managers()
|
|
55
|
+
|
|
56
|
+
def __getstate__(self) -> Dict[str, Any]:
|
|
57
|
+
state = self.__dict__.copy()
|
|
58
|
+
module = state.pop("_module")
|
|
59
|
+
state["_module_name"] = module.__name__
|
|
60
|
+
return state
|
|
61
|
+
|
|
62
|
+
def __setstate__(self, state: Dict[str, Any]) -> None:
|
|
63
|
+
module_name = state.pop("_module_name")
|
|
64
|
+
self.__dict__.update(state)
|
|
65
|
+
self.__dict__["_module"] = importlib.import_module(module_name)
|
|
66
|
+
|
|
67
|
+
def __getattr__(self, name: str) -> Any:
|
|
68
|
+
if name in self.__dict__["_updated_attrs"]:
|
|
69
|
+
return self.__dict__["_updated_attrs"][name]
|
|
70
|
+
|
|
71
|
+
if name in self.__dict__["_attrs"]:
|
|
72
|
+
value = self.__dict__["_attrs"][name]
|
|
73
|
+
if isinstance(value, list):
|
|
74
|
+
self.__dict__["_updated_attrs"][name] = value[:]
|
|
75
|
+
return self.__dict__["_updated_attrs"][name]
|
|
76
|
+
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
if name in self.__dict__["_parent_attrs"]:
|
|
80
|
+
return self.__dict__["_parent_attrs"][name]
|
|
81
|
+
|
|
82
|
+
message = f"{type(self).__name__!r} object has no attribute {name!r}"
|
|
83
|
+
if self._created_from_list:
|
|
84
|
+
message = f"{message}\n\n" + textwrap.fill(
|
|
85
|
+
f"{self.__class__!r} was created via a list() call and "
|
|
86
|
+
f"only a subset of the data may be present. To ensure "
|
|
87
|
+
f"all data is present get the object using a "
|
|
88
|
+
f"get(object.id) call. For more details, see:"
|
|
89
|
+
)
|
|
90
|
+
raise AttributeError(message)
|
|
91
|
+
|
|
92
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
93
|
+
self.__dict__["_updated_attrs"][name] = value
|
|
94
|
+
|
|
95
|
+
def asdict(self, *, with_parent_attrs: bool = False) -> Dict[str, Any]:
|
|
96
|
+
data = {}
|
|
97
|
+
if with_parent_attrs:
|
|
98
|
+
data.update(copy.deepcopy(self._parent_attrs))
|
|
99
|
+
data.update(copy.deepcopy(self._attrs))
|
|
100
|
+
data.update(copy.deepcopy(self._updated_attrs))
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def attributes(self) -> Dict[str, Any]:
|
|
105
|
+
return self.asdict(with_parent_attrs=True)
|
|
106
|
+
|
|
107
|
+
def to_json(self, *, with_parent_attrs: bool = False, **kwargs: Any) -> str:
|
|
108
|
+
return json.dumps(self.asdict(with_parent_attrs=with_parent_attrs), **kwargs)
|
|
109
|
+
|
|
110
|
+
def __str__(self) -> str:
|
|
111
|
+
return f"{type(self)} => {self.asdict()}"
|
|
112
|
+
|
|
113
|
+
def pformat(self) -> str:
|
|
114
|
+
return f"{type(self)} => \n{pprint.pformat(self.asdict())}"
|
|
115
|
+
|
|
116
|
+
def pprint(self) -> None:
|
|
117
|
+
print(self.pformat())
|
|
118
|
+
|
|
119
|
+
def __repr__(self) -> str:
|
|
120
|
+
name = self.__class__.__name__
|
|
121
|
+
|
|
122
|
+
if (self._id_attr and self._repr_value) and (self._id_attr != self._repr_attr):
|
|
123
|
+
return (
|
|
124
|
+
f"<{name} {self._id_attr}:{self.get_id()} "
|
|
125
|
+
f"{self._repr_attr}:{self._repr_value}>"
|
|
126
|
+
)
|
|
127
|
+
if self._id_attr:
|
|
128
|
+
return f"<{name} {self._id_attr}:{self.get_id()}>"
|
|
129
|
+
if self._repr_value:
|
|
130
|
+
return f"<{name} {self._repr_attr}:{self._repr_value}>"
|
|
131
|
+
|
|
132
|
+
return f"<{name}>"
|
|
133
|
+
|
|
134
|
+
def __eq__(self, other: object) -> bool:
|
|
135
|
+
if not isinstance(other, RESTObject):
|
|
136
|
+
return NotImplemented
|
|
137
|
+
if self.get_id() and other.get_id():
|
|
138
|
+
return self.get_id() == other.get_id()
|
|
139
|
+
return super() == other
|
|
140
|
+
|
|
141
|
+
def __ne__(self, other: object) -> bool:
|
|
142
|
+
if not isinstance(other, RESTObject):
|
|
143
|
+
return NotImplemented
|
|
144
|
+
if self.get_id() and other.get_id():
|
|
145
|
+
return self.get_id() != other.get_id()
|
|
146
|
+
return super() != other
|
|
147
|
+
|
|
148
|
+
def __dir__(self) -> Iterable[str]:
|
|
149
|
+
return set(self.attributes).union(super().__dir__())
|
|
150
|
+
|
|
151
|
+
def __hash__(self) -> int:
|
|
152
|
+
if not self.get_id():
|
|
153
|
+
return super().__hash__()
|
|
154
|
+
return hash(self.get_id())
|
|
155
|
+
|
|
156
|
+
def _create_managers(self) -> None:
|
|
157
|
+
# NOTE(jlvillal): We are creating our managers by looking at the class
|
|
158
|
+
# annotations. If an attribute is annotated as being a *Manager type
|
|
159
|
+
# then we create the manager and assign it to the attribute.
|
|
160
|
+
for attr, annotation in sorted(self.__annotations__.items()):
|
|
161
|
+
# We ignore creating a manager for the 'manager' attribute as that
|
|
162
|
+
# is done in the self.__init__() method
|
|
163
|
+
if attr in ("manager",):
|
|
164
|
+
continue
|
|
165
|
+
if not isinstance(annotation, (type, str)):
|
|
166
|
+
continue
|
|
167
|
+
if isinstance(annotation, type):
|
|
168
|
+
cls_name = annotation.__name__
|
|
169
|
+
else:
|
|
170
|
+
cls_name = annotation
|
|
171
|
+
# All *Manager classes are used except for the base "RESTManager" class
|
|
172
|
+
if cls_name == "RESTManager" or not cls_name.endswith("Manager"):
|
|
173
|
+
continue
|
|
174
|
+
cls = getattr(self._module, cls_name)
|
|
175
|
+
manager = cls(self.manager.openaev, parent=self)
|
|
176
|
+
# Since we have our own __setattr__ method, we can't use setattr()
|
|
177
|
+
self.__dict__[attr] = manager
|
|
178
|
+
|
|
179
|
+
def _update_attrs(self, new_attrs: Dict[str, Any]) -> None:
|
|
180
|
+
self.__dict__["_updated_attrs"] = {}
|
|
181
|
+
self.__dict__["_attrs"] = new_attrs
|
|
182
|
+
|
|
183
|
+
def get_id(self) -> Optional[Union[int, str]]:
|
|
184
|
+
"""Returns the id of the resource."""
|
|
185
|
+
if self._id_attr is None or not hasattr(self, self._id_attr):
|
|
186
|
+
return None
|
|
187
|
+
id_val = getattr(self, self._id_attr)
|
|
188
|
+
if TYPE_CHECKING:
|
|
189
|
+
assert id_val is None or isinstance(id_val, (int, str))
|
|
190
|
+
return id_val
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def _repr_value(self) -> Optional[str]:
|
|
194
|
+
"""Safely returns the human-readable resource name if present."""
|
|
195
|
+
if self._repr_attr is None or not hasattr(self, self._repr_attr):
|
|
196
|
+
return None
|
|
197
|
+
repr_val = getattr(self, self._repr_attr)
|
|
198
|
+
if TYPE_CHECKING:
|
|
199
|
+
assert isinstance(repr_val, str)
|
|
200
|
+
return repr_val
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def encoded_id(self) -> Optional[Union[int, str]]:
|
|
204
|
+
"""Ensure that the ID is url-encoded so that it can be safely used in a URL
|
|
205
|
+
path"""
|
|
206
|
+
obj_id = self.get_id()
|
|
207
|
+
if isinstance(obj_id, str):
|
|
208
|
+
obj_id = utils.EncodedId(obj_id)
|
|
209
|
+
return obj_id
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class RESTObjectList:
|
|
213
|
+
def __init__(
|
|
214
|
+
self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: OpenAEVList
|
|
215
|
+
) -> None:
|
|
216
|
+
self.manager = manager
|
|
217
|
+
self._obj_cls = obj_cls
|
|
218
|
+
self._list = _list
|
|
219
|
+
|
|
220
|
+
def __iter__(self) -> "RESTObjectList":
|
|
221
|
+
return self
|
|
222
|
+
|
|
223
|
+
def __len__(self) -> int:
|
|
224
|
+
return len(self._list)
|
|
225
|
+
|
|
226
|
+
def __next__(self) -> RESTObject:
|
|
227
|
+
return self.next()
|
|
228
|
+
|
|
229
|
+
def next(self) -> RESTObject:
|
|
230
|
+
data = self._list.next()
|
|
231
|
+
return self._obj_cls(self.manager, data, created_from_list=True)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def current_page(self) -> int:
|
|
235
|
+
"""The current page number."""
|
|
236
|
+
return self._list.current_page
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def prev_page(self) -> Optional[int]:
|
|
240
|
+
"""The previous page number.
|
|
241
|
+
|
|
242
|
+
If None, the current page is the first.
|
|
243
|
+
"""
|
|
244
|
+
return self._list.prev_page
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def next_page(self) -> Optional[int]:
|
|
248
|
+
"""The next page number.
|
|
249
|
+
|
|
250
|
+
If None, the current page is the last.
|
|
251
|
+
"""
|
|
252
|
+
return self._list.next_page
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def per_page(self) -> Optional[int]:
|
|
256
|
+
"""The number of items per page."""
|
|
257
|
+
return self._list.per_page
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def total_pages(self) -> Optional[int]:
|
|
261
|
+
"""The total number of pages."""
|
|
262
|
+
return self._list.total_pages
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def total(self) -> Optional[int]:
|
|
266
|
+
"""The total number of items."""
|
|
267
|
+
return self._list.total
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class RESTManager:
|
|
271
|
+
"""Base class for CRUD operations on objects.
|
|
272
|
+
|
|
273
|
+
Derived class must define ``_path`` and ``_obj_cls``.
|
|
274
|
+
|
|
275
|
+
``_path``: Base URL path on which requests will be sent (e.g. '/projects')
|
|
276
|
+
``_obj_cls``: The class of objects that will be created
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
_create_attrs: Any
|
|
280
|
+
_update_attrs: Any
|
|
281
|
+
_path: Optional[str] = None
|
|
282
|
+
_obj_cls: Optional[Type[RESTObject]] = None
|
|
283
|
+
_from_parent_attrs: Dict[str, Any] = {}
|
|
284
|
+
_types: Dict[str, Any] = {}
|
|
285
|
+
|
|
286
|
+
_computed_path: Optional[str]
|
|
287
|
+
_parent: Optional[RESTObject]
|
|
288
|
+
_parent_attrs: Dict[str, Any]
|
|
289
|
+
openaev: OpenAEV
|
|
290
|
+
|
|
291
|
+
def __init__(self, openaev: OpenAEV, parent: Optional[RESTObject] = None) -> None:
|
|
292
|
+
self.openaev = openaev
|
|
293
|
+
self._parent = parent # for nested managers
|
|
294
|
+
self._computed_path = self._compute_path()
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def parent_attrs(self) -> Optional[Dict[str, Any]]:
|
|
298
|
+
return self._parent_attrs
|
|
299
|
+
|
|
300
|
+
def _compute_path(self, path: Optional[str] = None) -> Optional[str]:
|
|
301
|
+
self._parent_attrs = {}
|
|
302
|
+
if path is None:
|
|
303
|
+
path = self._path
|
|
304
|
+
if path is None:
|
|
305
|
+
return None
|
|
306
|
+
if self._parent is None or not self._from_parent_attrs:
|
|
307
|
+
return path
|
|
308
|
+
|
|
309
|
+
data: Dict[str, Optional[utils.EncodedId]] = {}
|
|
310
|
+
for self_attr, parent_attr in self._from_parent_attrs.items():
|
|
311
|
+
if not hasattr(self._parent, parent_attr):
|
|
312
|
+
data[self_attr] = None
|
|
313
|
+
continue
|
|
314
|
+
data[self_attr] = utils.EncodedId(getattr(self._parent, parent_attr))
|
|
315
|
+
self._parent_attrs = data
|
|
316
|
+
return path.format(**data)
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def path(self) -> Optional[str]:
|
|
320
|
+
return self._computed_path
|