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.
Files changed (72) hide show
  1. docs/conf.py +65 -0
  2. pyoaev/__init__.py +26 -0
  3. pyoaev/_version.py +6 -0
  4. pyoaev/apis/__init__.py +20 -0
  5. pyoaev/apis/attack_pattern.py +28 -0
  6. pyoaev/apis/collector.py +29 -0
  7. pyoaev/apis/cve.py +18 -0
  8. pyoaev/apis/document.py +29 -0
  9. pyoaev/apis/endpoint.py +38 -0
  10. pyoaev/apis/inject.py +29 -0
  11. pyoaev/apis/inject_expectation/__init__.py +1 -0
  12. pyoaev/apis/inject_expectation/inject_expectation.py +118 -0
  13. pyoaev/apis/inject_expectation/model/__init__.py +7 -0
  14. pyoaev/apis/inject_expectation/model/expectation.py +173 -0
  15. pyoaev/apis/inject_expectation_trace.py +36 -0
  16. pyoaev/apis/injector.py +26 -0
  17. pyoaev/apis/injector_contract.py +56 -0
  18. pyoaev/apis/inputs/__init__.py +0 -0
  19. pyoaev/apis/inputs/search.py +72 -0
  20. pyoaev/apis/kill_chain_phase.py +22 -0
  21. pyoaev/apis/me.py +17 -0
  22. pyoaev/apis/organization.py +11 -0
  23. pyoaev/apis/payload.py +27 -0
  24. pyoaev/apis/security_platform.py +33 -0
  25. pyoaev/apis/tag.py +19 -0
  26. pyoaev/apis/team.py +25 -0
  27. pyoaev/apis/user.py +31 -0
  28. pyoaev/backends/__init__.py +14 -0
  29. pyoaev/backends/backend.py +136 -0
  30. pyoaev/backends/protocol.py +32 -0
  31. pyoaev/base.py +320 -0
  32. pyoaev/client.py +596 -0
  33. pyoaev/configuration/__init__.py +3 -0
  34. pyoaev/configuration/configuration.py +188 -0
  35. pyoaev/configuration/sources.py +44 -0
  36. pyoaev/contracts/__init__.py +5 -0
  37. pyoaev/contracts/contract_builder.py +44 -0
  38. pyoaev/contracts/contract_config.py +292 -0
  39. pyoaev/contracts/contract_utils.py +22 -0
  40. pyoaev/contracts/variable_helper.py +124 -0
  41. pyoaev/daemons/__init__.py +4 -0
  42. pyoaev/daemons/base_daemon.py +131 -0
  43. pyoaev/daemons/collector_daemon.py +91 -0
  44. pyoaev/exceptions.py +219 -0
  45. pyoaev/helpers.py +451 -0
  46. pyoaev/mixins.py +242 -0
  47. pyoaev/signatures/__init__.py +0 -0
  48. pyoaev/signatures/signature_match.py +12 -0
  49. pyoaev/signatures/signature_type.py +51 -0
  50. pyoaev/signatures/types.py +17 -0
  51. pyoaev/utils.py +211 -0
  52. pyoaev-1.18.20.dist-info/METADATA +134 -0
  53. pyoaev-1.18.20.dist-info/RECORD +72 -0
  54. pyoaev-1.18.20.dist-info/WHEEL +5 -0
  55. pyoaev-1.18.20.dist-info/licenses/LICENSE +201 -0
  56. pyoaev-1.18.20.dist-info/top_level.txt +4 -0
  57. scripts/release.py +127 -0
  58. test/__init__.py +0 -0
  59. test/apis/__init__.py +0 -0
  60. test/apis/expectation/__init__.py +0 -0
  61. test/apis/expectation/test_expectation.py +338 -0
  62. test/apis/injector_contract/__init__.py +0 -0
  63. test/apis/injector_contract/test_injector_contract.py +58 -0
  64. test/configuration/__init__.py +0 -0
  65. test/configuration/test_configuration.py +257 -0
  66. test/configuration/test_sources.py +69 -0
  67. test/daemons/__init__.py +0 -0
  68. test/daemons/test_base_daemon.py +109 -0
  69. test/daemons/test_collector_daemon.py +39 -0
  70. test/signatures/__init__.py +0 -0
  71. test/signatures/test_signature_match.py +25 -0
  72. 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