Skip to content

Commit bf89719

Browse files
authored
Extend methods to generate Selection Schema for CVE and other projects (#1002)
2 parents 8ea1967 + c05fa15 commit bf89719

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

src/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ For usage in vulnerability management scenarios consider the following popular S
9797
from ssvc.decision_tables.helpers import ascii_tree
9898
print(ascii_tree(CISACoordinate))
9999

100+
#Creating an SSVC Selection for publish/export to external providers like CSAF or CVE
101+
from datetime import datetime, timezone
102+
from ssvc.decision_tables.cisa.cisa_coordinate_dt import LATEST as decision_table
103+
from ssvc import selection
104+
namespace = "ssvc"
105+
decision_points = ["Exploitation"]
106+
values = [["Public PoC"]]
107+
timestamp = datetime.now()
108+
selections = []
109+
110+
for dp in decision_table.decision_points.values():
111+
if dp.namespace == namespace and dp.name in decision_points:
112+
dp_index = decision_points.index(dp.name)
113+
selected = selection.Selection.from_decision_point(dp)
114+
selected.values = tuple(selection.MinimalDecisionPointValue(key=val.key,
115+
name=val.name) for val in dp.values if val.name in values[dp_index])
116+
selections.append(selected)
117+
118+
out = selection.SelectionList(selections=selections,timestamp=timestamp)
119+
print(out.model_dump_json(exclude_none=True, indent=4))
120+
121+
100122
Resources
101123
---------
102124

src/ssvc/selection.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,26 @@ def model_json_schema(cls, **kwargs):
312312
schema = strip_nullable_anyof(schema)
313313

314314
return order_schema(schema)
315+
def _post_process(self, data):
316+
"""
317+
Ensures all Selection.values are lists and removes empty array elements.
318+
"""
319+
for x in list(data.keys()):
320+
if not data[x]:
321+
print(x)
322+
del data[x]
323+
return data
324+
325+
def model_dump(self, *args, **kwargs):
326+
data = super().model_dump(*args, **kwargs)
327+
return self._post_process(data)
328+
329+
def model_dump_json(self, *args, **kwargs):
330+
import json
331+
jsontext = super().model_dump_json(*args, **kwargs)
332+
data = self._post_process(json.loads(jsontext))
333+
return json.dumps(data, **{k: v for k, v in kwargs.items() if k in json.dumps.__code__.co_varnames})
334+
315335

316336

317337
def main() -> None:

src/test/test_selections.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import unittest
2121
from datetime import datetime
2222
from unittest import expectedFailure
23+
import json
2324

2425
from ssvc import selection
2526
from ssvc.selection import MinimalDecisionPointValue, SelectionList
@@ -219,6 +220,32 @@ def test_reference_model(self):
219220
self.assertIn(uri, str(ref.uri))
220221
self.assertEqual(ref.summary, "Test description")
221222

223+
def test_model_dump_removes_empty_values(self):
224+
"""model_dump() should remove None or empty values."""
225+
result_clean = self.selections.model_dump(exclude_none=True)
226+
result_bloat = self.selections.model_dump()
227+
self.assertNotEqual(result_clean, result_bloat)
228+
self.assertIn("selections", result_clean)
229+
self.assertNotIn("metadata", result_clean)
230+
231+
def test_model_dump_json_respects_indent(self):
232+
"""model_dump_json() should apply JSON indentation and pruning."""
233+
json_text = self.selections.model_dump_json(indent=4)
234+
data = json.loads(json_text)
235+
self.assertIn("selections", data)
236+
self.assertNotIn("metadata", data)
237+
self.assertIn("\n \"selections\":", json_text)
238+
239+
def test_model_dump_json_excludes_none(self):
240+
"""exclude_none=True should work with post-processing."""
241+
json_text_clean = self.selections.model_dump_json(exclude_none=True)
242+
json_text_bloat = self.selections.model_dump_json()
243+
self.assertNotEqual(json_text_clean, json_text_bloat)
244+
data = json.loads(json_text_clean)
245+
self.assertIn("selections", data)
246+
self.assertNotIn("metadata", data)
247+
248+
222249
@expectedFailure
223250
def test_reference_model_without_summary(self):
224251
"""Test the Reference model."""
@@ -381,6 +408,17 @@ def test_selection_list_minimum_selections(self):
381408
timestamp=datetime.now(),
382409
)
383410

411+
def test_model_dump_removes_required_field(self):
412+
""" Test if a selections is dumped and breaks when items removed """
413+
s = SelectionList(
414+
selections=[self.s1],
415+
timestamp=datetime.now(),
416+
)
417+
dumped = s.model_dump()
418+
with self.assertRaises(Exception):
419+
del dumped['values']
420+
421+
384422

385423
if __name__ == "__main__":
386424
unittest.main()

0 commit comments

Comments
 (0)