Skip to content

Commit 564b3e6

Browse files
committed
wip
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent da433b6 commit 564b3e6

4 files changed

Lines changed: 132 additions & 16 deletions

File tree

cyclonedx/model/component_evidence.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818

19-
from collections.abc import Iterable, Generator
19+
from collections.abc import Iterable
2020
from decimal import Decimal
2121
from enum import Enum
22+
from json import loads as json_loads
2223
from typing import Any, Optional, Union
24+
from warnings import warn
2325
from xml.etree.ElementTree import Element as XmlElement
2426

2527
# See https://github.com/package-url/packageurl-python/issues/65
2628
import py_serializable as serializable
2729
from sortedcontainers import SortedSet
2830

29-
from ..exception.serialization import SerializationOfUnexpectedValueException
3031
from .._internal.bom_ref import bom_ref_from_str as _bom_ref_from_str
3132
from .._internal.compare import ComparableTuple as _ComparableTuple
3233
from ..exception.model import InvalidConfidenceException, InvalidValueException
@@ -151,12 +152,12 @@ class _IdentityToolRepositorySerializationHelper(serializable.helpers.BaseHelper
151152
""" THIS CLASS IS NON-PUBLIC API """
152153

153154
@classmethod
154-
def json_serialize(cls, o: Iterable['BomRef']) -> tuple[str]:
155-
return tuple(i.value for i in o)
155+
def json_serialize(cls, o: Iterable['BomRef']) -> tuple[str, ...]:
156+
return tuple(t.value for t in o if t.value)
156157

157158
@classmethod
158-
def json_deserialize(cls, o: Iterable[str]) -> tuple[BomRef]:
159-
return tuple(BomRef(value=i) for i in o)
159+
def json_deserialize(cls, o: Iterable[str]) -> tuple[BomRef, ...]:
160+
return tuple(BomRef(value=t) for t in o)
160161

161162
@classmethod
162163
def xml_normalize(cls, o: Iterable[BomRef], *,
@@ -166,17 +167,17 @@ def xml_normalize(cls, o: Iterable[BomRef], *,
166167
if len(o) == 0:
167168
return None
168169
elem_s = XmlElement(f'{{{xmlns}}}tools' if xmlns else 'tools')
170+
tool_name = f'{{{xmlns}}}tool' if xmlns else 'tool'
171+
ref_name = f'{{{xmlns}}}ref' if xmlns else 'ref'
169172
elem_s.extend(
170-
XmlElement(
171-
f'{{{xmlns}}}tool' if xmlns else 'tool',
172-
{'ref': t.value}
173-
) for t in o if t)
173+
XmlElement(tool_name, {ref_name: t.value}) \
174+
for t in o if t.value)
174175
return elem_s
175176

176177
@classmethod
177178
def xml_denormalize(cls, o: 'XmlElement', *,
178179
default_ns: Optional[str],
179-
**__: Any) -> tuple[BomRef]:
180+
**__: Any) -> tuple[BomRef, ...]:
180181
return tuple(BomRef(value=t.get('ref')) for t in o)
181182

182183

@@ -287,6 +288,58 @@ def __repr__(self) -> str:
287288
f' methods={self.methods}, tools={self.tools}>'
288289

289290

291+
class _IdentityRepositorySerializationHelper(serializable.helpers.BaseHelper):
292+
""" THIS CLASS IS NON-PUBLIC API """
293+
294+
@classmethod
295+
def json_normalize(cls, o: Iterable[Identity], *,
296+
view: Optional[type['serializable.ViewType']],
297+
**__: Any) -> Optional[Any]:
298+
o = tuple(o)
299+
if l := len(o) == 0:
300+
return None
301+
if view is SchemaVersion1Dot5:
302+
if l >= 1:
303+
warn(f'serialization omitted some identity evidences due to unsupported amount: {o!r}',
304+
category=UserWarning, stacklevel=0)
305+
return json_loads(o[0].as_json(view)) # type:ignore[attr-defined]
306+
return tuple(json_loads(i.as_json(view)) for i in o) # type:ignore[attr-defined]
307+
308+
@classmethod
309+
def json_deserialize(cls, o: Any) -> tuple[Identity]:
310+
if isinstance(o, list):
311+
return tuple(Identity.from_json(i) for i in o) # type:ignore[attr-defined]
312+
return (Identity.from_json(o),) # type:ignore[attr-defined]
313+
314+
@classmethod
315+
def xml_normalize(cls, o: Iterable[Identity], *,
316+
element_name: str,
317+
view: Optional[type['serializable.ViewType']],
318+
xmlns: Optional[str],
319+
**__: Any) -> Optional[XmlElement]:
320+
o = tuple(o)
321+
if l := len(o) == 0:
322+
return None
323+
if view is SchemaVersion1Dot5:
324+
if l >= 1:
325+
warn(f'serialization omitted some identity evidences due to unsupported amount: {o!r}',
326+
category=UserWarning, stacklevel=0)
327+
o = (o[0],)
328+
elem_s = XmlElement(f'{{{xmlns}}}' if xmlns else '')
329+
elem_s.extend(i.as_xml( # type:ignore[attr-defined]
330+
view,
331+
as_string=False,
332+
element_name=element_name, xmlns=xmlns
333+
) for i in o)
334+
return elem_s
335+
336+
@classmethod
337+
def xml_denormalize(cls, o: 'XmlElement', *,
338+
default_ns: Optional[str],
339+
**__: Any) -> Identity:
340+
return Identity.from_xml(o, default_ns) # type:ignore[attr-defined,no-any-return]
341+
342+
290343
@serializable.serializable_class
291344
class Occurrence:
292345
"""
@@ -586,7 +639,7 @@ def __init__(
586639

587640
@property
588641
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'frame')
589-
def frames(self) -> 'List[CallStackFrame]':
642+
def frames(self) -> 'list[CallStackFrame]':
590643
"""
591644
Array of stack frames
592645
"""
@@ -650,7 +703,7 @@ def __init__(
650703
@property
651704
@serializable.view(SchemaVersion1Dot5)
652705
@serializable.view(SchemaVersion1Dot6)
653-
@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity')
706+
@serializable.type_mapping(_IdentityRepositorySerializationHelper)
654707
@serializable.xml_sequence(1)
655708
def identity(self) -> 'SortedSet[Identity]':
656709
"""
@@ -662,9 +715,9 @@ def identity(self) -> 'SortedSet[Identity]':
662715
@identity.setter
663716
def identity(self, identity: Union[Iterable[Identity], Identity]) -> None:
664717
self._identity = SortedSet(
665-
(Identity,) # convert to iterable
718+
(identity,)
666719
if isinstance(identity, Identity)
667-
else identity # is iterable already
720+
else identity
668721
)
669722

670723
@property

tests/_data/models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,20 @@ def get_component_evidence_basic(tools: Iterable[Component]) -> ComponentEvidenc
794794
),
795795
],
796796
tools=(tool.bom_ref for tool in tools)
797-
)
797+
),
798+
Identity(
799+
field=IdentityField.HASH,
800+
confidence=Decimal('0.1'),
801+
concluded_value='example-hash',
802+
methods=[
803+
Method(
804+
technique=AnalysisTechnique.ATTESTATION,
805+
confidence=Decimal('0.1'),
806+
value='analysis-tool'
807+
),
808+
],
809+
tools=(tool.bom_ref for tool in tools)
810+
),
798811
],
799812
occurrences=[
800813
Occurrence(

tests/_data/snapshots/get_bom_with_component_evidence-1.5.json.bin

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@
44
"author": "Test Author",
55
"bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz",
66
"evidence": {
7+
"callstack": {
8+
"frames": [
9+
{
10+
"column": 5,
11+
"fullFilename": "path/to/file",
12+
"function": "example_function",
13+
"line": 10,
14+
"module": "example.module",
15+
"package": "example.package",
16+
"parameters": [
17+
"param1",
18+
"param2"
19+
]
20+
}
21+
]
22+
},
723
"copyright": [
824
{
925
"text": "Commercial"
@@ -12,12 +28,31 @@
1228
"text": "Commercial 2"
1329
}
1430
],
31+
"identity": {
32+
"confidence": 0.1,
33+
"field": "hash",
34+
"methods": [
35+
{
36+
"confidence": 0.1,
37+
"technique": "attestation",
38+
"value": "analysis-tool"
39+
}
40+
],
41+
"tools": [
42+
"cbom:generator"
43+
]
44+
},
1545
"licenses": [
1646
{
1747
"license": {
1848
"id": "MIT"
1949
}
2050
}
51+
],
52+
"occurrences": [
53+
{
54+
"location": "path/to/file"
55+
}
2156
]
2257
},
2358
"licenses": [

tests/_data/snapshots/get_bom_with_component_evidence-1.6.json.bin

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@
2929
}
3030
],
3131
"identity": [
32+
{
33+
"concludedValue": "example-hash",
34+
"confidence": 0.1,
35+
"field": "hash",
36+
"methods": [
37+
{
38+
"confidence": 0.1,
39+
"technique": "attestation",
40+
"value": "analysis-tool"
41+
}
42+
],
43+
"tools": [
44+
"cbom:generator"
45+
]
46+
},
3247
{
3348
"concludedValue": "example-component",
3449
"confidence": 0.9,

0 commit comments

Comments
 (0)