Skip to content

Commit b45ff18

Browse files
DarthHatermadpahactions-user
authored
WIP on bom.services
* WIP but a lil hand up for @madpah Signed-off-by: Jeffry Hesse <[email protected]> * chore: added missing license header Signed-off-by: Paul Horton <[email protected]> * No default values for required fields * Add Services to BOM * Typo fix * aligned classes with standards, commented out Signature work for now, added first tests for Services Signed-off-by: Paul Horton <[email protected]> * addressed standards Signed-off-by: Paul Horton <[email protected]> * 1.2.0 Automatically generated by python-semantic-release Signed-off-by: Paul Horton <[email protected]> * feat: `bom-ref` for Component and Vulnerability default to a UUID (#142) * feat: `bom-ref` for Component and Vulnerability default to a UUID if not supplied ensuring they have a unique value #141 Signed-off-by: Paul Horton <[email protected]> * doc: updated documentation to reflect change Signed-off-by: Paul Horton <[email protected]> * patched other tests to support UUID for bom-ref Signed-off-by: Paul Horton <[email protected]> * better syntax Signed-off-by: Paul Horton <[email protected]> * 1.3.0 Automatically generated by python-semantic-release Signed-off-by: Paul Horton <[email protected]> * WIP but a lil hand up for @madpah Signed-off-by: Jeffry Hesse <[email protected]> Signed-off-by: Paul Horton <[email protected]> * chore: added missing license header Signed-off-by: Paul Horton <[email protected]> * aligned classes with standards, commented out Signature work for now, added first tests for Services Signed-off-by: Paul Horton <[email protected]> * removed signature from this branch Signed-off-by: Paul Horton <[email protected]> * Add Services to BOM * Typo fix * addressed standards Signed-off-by: Paul Horton <[email protected]> * resolved typing issues from merge Signed-off-by: Paul Horton <[email protected]> * added a bunch more tests for JSON output Signed-off-by: Paul Horton <[email protected]> Co-authored-by: Paul Horton <[email protected]> Co-authored-by: github-actions <[email protected]>
1 parent 4178181 commit b45ff18

21 files changed

+1374
-120
lines changed

cyclonedx/model/__init__.py

+70-1
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,78 @@ def sha1sum(filename: str) -> str:
5151
return h.hexdigest()
5252

5353

54+
class DataFlow(Enum):
55+
"""
56+
This is our internal representation of the dataFlowType simple type within the CycloneDX standard.
57+
58+
.. note::
59+
See the CycloneDX Schema: https://cyclonedx.org/docs/1.4/xml/#type_dataFlowType
60+
"""
61+
INBOUND = "inbound"
62+
OUTBOUND = "outbound"
63+
BI_DIRECTIONAL = "bi-directional"
64+
UNKNOWN = "unknown"
65+
66+
67+
class DataClassification:
68+
"""
69+
This is our internal representation of the `dataClassificationType` complex type within the CycloneDX standard.
70+
71+
.. note::
72+
See the CycloneDX Schema for dataClassificationType:
73+
https://cyclonedx.org/docs/1.4/xml/#type_dataClassificationType
74+
"""
75+
76+
def __init__(self, flow: DataFlow, classification: str) -> None:
77+
if not flow and not classification:
78+
raise NoPropertiesProvidedException(
79+
'One of `flow` or `classification` must be supplied - neither supplied'
80+
)
81+
82+
self.flow = flow
83+
self.classification = classification
84+
85+
@property
86+
def flow(self) -> DataFlow:
87+
"""
88+
Specifies the flow direction of the data.
89+
90+
Valid values are: inbound, outbound, bi-directional, and unknown.
91+
92+
Direction is relative to the service.
93+
94+
- Inbound flow states that data enters the service
95+
- Outbound flow states that data leaves the service
96+
- Bi-directional states that data flows both ways
97+
- Unknown states that the direction is not known
98+
99+
Returns:
100+
`DataFlow`
101+
"""
102+
return self._flow
103+
104+
@flow.setter
105+
def flow(self, flow: DataFlow) -> None:
106+
self._flow = flow
107+
108+
@property
109+
def classification(self) -> str:
110+
"""
111+
Data classification tags data according to its type, sensitivity, and value if altered, stolen, or destroyed.
112+
113+
Returns:
114+
`str`
115+
"""
116+
return self._classification
117+
118+
@classification.setter
119+
def classification(self, classification: str) -> None:
120+
self._classification = classification
121+
122+
54123
class Encoding(Enum):
55124
"""
56-
This is out internal representation of the encoding simple type within the CycloneDX standard.
125+
This is our internal representation of the encoding simple type within the CycloneDX standard.
57126
58127
.. note::
59128
See the CycloneDX Schema: https://cyclonedx.org/docs/1.4/#type_encoding

cyclonedx/model/bom.py

+96-15
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
# Copyright (c) OWASP Foundation. All Rights Reserved.
1919

2020
from datetime import datetime, timezone
21-
from typing import List, Optional
21+
from typing import cast, List, Optional
2222
from uuid import uuid4, UUID
2323

2424
from . import ThisTool, Tool
2525
from .component import Component
26+
from .service import Service
2627
from ..parser import BaseParser
2728

2829

@@ -133,7 +134,7 @@ def from_parser(parser: BaseParser) -> 'Bom':
133134
bom.add_components(parser.get_components())
134135
return bom
135136

136-
def __init__(self) -> None:
137+
def __init__(self, components: Optional[List[Component]] = None, services: Optional[List[Service]] = None) -> None:
137138
"""
138139
Create a new Bom that you can manually/programmatically add data to later.
139140
@@ -142,7 +143,8 @@ def __init__(self) -> None:
142143
"""
143144
self.uuid = uuid4()
144145
self.metadata = BomMetaData()
145-
self._components: List[Component] = []
146+
self.components = components
147+
self.services = services
146148

147149
@property
148150
def uuid(self) -> UUID:
@@ -176,17 +178,17 @@ def metadata(self, metadata: BomMetaData) -> None:
176178
self._metadata = metadata
177179

178180
@property
179-
def components(self) -> List[Component]:
181+
def components(self) -> Optional[List[Component]]:
180182
"""
181183
Get all the Components currently in this Bom.
182184
183185
Returns:
184-
List of all Components in this Bom.
186+
List of all Components in this Bom or `None`
185187
"""
186188
return self._components
187189

188190
@components.setter
189-
def components(self, components: List[Component]) -> None:
191+
def components(self, components: Optional[List[Component]]) -> None:
190192
self._components = components
191193

192194
def add_component(self, component: Component) -> None:
@@ -200,8 +202,10 @@ def add_component(self, component: Component) -> None:
200202
Returns:
201203
None
202204
"""
203-
if not self.has_component(component=component):
204-
self._components.append(component)
205+
if not self.components:
206+
self.components = [component]
207+
elif not self.has_component(component=component):
208+
self.components.append(component)
205209

206210
def add_components(self, components: List[Component]) -> None:
207211
"""
@@ -214,7 +218,7 @@ def add_components(self, components: List[Component]) -> None:
214218
Returns:
215219
None
216220
"""
217-
self.components = self._components + components
221+
self.components = (self._components or []) + components
218222

219223
def component_count(self) -> int:
220224
"""
@@ -223,7 +227,7 @@ def component_count(self) -> int:
223227
Returns:
224228
The number of Components in this Bom as `int`.
225229
"""
226-
return len(self._components)
230+
return len(self._components) if self._components else 0
227231

228232
def get_component_by_purl(self, purl: Optional[str]) -> Optional[Component]:
229233
"""
@@ -236,8 +240,11 @@ def get_component_by_purl(self, purl: Optional[str]) -> Optional[Component]:
236240
Returns:
237241
`Component` or `None`
238242
"""
243+
if not self._components:
244+
return None
245+
239246
if purl:
240-
found = list(filter(lambda x: x.purl == purl, self.components))
247+
found = list(filter(lambda x: x.purl == purl, cast(List[Component], self.components)))
241248
if len(found) == 1:
242249
return found[0]
243250

@@ -263,7 +270,80 @@ def has_component(self, component: Component) -> bool:
263270
Returns:
264271
`bool` - `True` if the supplied Component is part of this Bom, `False` otherwise.
265272
"""
266-
return component in self._components
273+
if not self.components:
274+
return False
275+
return component in self.components
276+
277+
@property
278+
def services(self) -> Optional[List[Service]]:
279+
"""
280+
Get all the Services currently in this Bom.
281+
282+
Returns:
283+
List of `Service` in this Bom or `None`
284+
"""
285+
return self._services
286+
287+
@services.setter
288+
def services(self, services: Optional[List[Service]]) -> None:
289+
self._services = services
290+
291+
def add_service(self, service: Service) -> None:
292+
"""
293+
Add a Service to this Bom instance.
294+
295+
Args:
296+
service:
297+
`cyclonedx.model.service.Service` instance to add to this Bom.
298+
299+
Returns:
300+
None
301+
"""
302+
if not self.services:
303+
self.services = [service]
304+
elif not self.has_service(service=service):
305+
self.services.append(service)
306+
307+
def add_services(self, services: List[Service]) -> None:
308+
"""
309+
Add multiple Services at once to this Bom instance.
310+
311+
Args:
312+
services:
313+
List of `cyclonedx.model.service.Service` instances to add to this Bom.
314+
315+
Returns:
316+
None
317+
"""
318+
self.services = (self.services or []) + services
319+
320+
def has_service(self, service: Service) -> bool:
321+
"""
322+
Check whether this Bom contains the provided Service.
323+
324+
Args:
325+
service:
326+
The instance of `cyclonedx.model.service.Service` to check if this Bom contains.
327+
328+
Returns:
329+
`bool` - `True` if the supplied Service is part of this Bom, `False` otherwise.
330+
"""
331+
if not self.services:
332+
return False
333+
334+
return service in self.services
335+
336+
def service_count(self) -> int:
337+
"""
338+
Returns the current count of Services within this Bom.
339+
340+
Returns:
341+
The number of Services in this Bom as `int`.
342+
"""
343+
if not self.services:
344+
return 0
345+
346+
return len(self.services)
267347

268348
def has_vulnerabilities(self) -> bool:
269349
"""
@@ -273,8 +353,9 @@ def has_vulnerabilities(self) -> bool:
273353
`bool` - `True` if at least one `cyclonedx.model.component.Component` has at least one Vulnerability,
274354
`False` otherwise.
275355
"""
276-
for c in self.components:
277-
if c.has_vulnerabilities():
278-
return True
356+
if self.components:
357+
for c in self.components:
358+
if c.has_vulnerabilities():
359+
return True
279360

280361
return False

cyclonedx/model/issue.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class IssueClassification(Enum):
2626
This is out internal representation of the enum `issueClassification`.
2727
2828
.. note::
29-
See the CycloneDX Schema definition: hhttps://cyclonedx.org/docs/1.4/xml/#type_issueClassification
29+
See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_issueClassification
3030
"""
3131
DEFECT = 'defect'
3232
ENHANCEMENT = 'enhancement'

0 commit comments

Comments
 (0)