Skip to content

Commit 068da83

Browse files
authored
Merge pull request #267 from CycloneDX/feat/bomlink-urn-helper-methods
feat: allow version of BOM to be defined feat: allow serial_number of BOM to be prescribed feat: add helper method to get URN for a BOM according to https://www.iana.org/assignments/urn-formal/cdx
2 parents b56c5c3 + 17341d6 commit 068da83

File tree

2 files changed

+35
-3
lines changed

2 files changed

+35
-3
lines changed

cyclonedx/model/bom.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,21 @@ def from_parser(parser: BaseParser) -> 'Bom':
233233

234234
def __init__(self, *, components: Optional[Iterable[Component]] = None,
235235
services: Optional[Iterable[Service]] = None,
236-
external_references: Optional[Iterable[ExternalReference]] = None) -> None:
236+
external_references: Optional[Iterable[ExternalReference]] = None,
237+
serial_number: Optional[UUID] = None, version: int = 1) -> None:
237238
"""
238239
Create a new Bom that you can manually/programmatically add data to later.
239240
240241
Returns:
241242
New, empty `cyclonedx.model.bom.Bom` instance.
242243
"""
243-
self.uuid = uuid4()
244+
self.uuid = serial_number or uuid4()
244245
self.metadata = BomMetaData()
245246
self.components = components or [] # type: ignore
246247
self.services = services or [] # type: ignore
247248
self.external_references = external_references or [] # type: ignore
248249
self.vulnerabilities = SortedSet()
250+
self.version = version
249251

250252
@property
251253
def uuid(self) -> UUID:
@@ -317,7 +319,7 @@ def get_urn_uuid(self) -> str:
317319
Returns:
318320
URN formatted UUID that uniquely identified this Bom instance.
319321
"""
320-
return 'urn:uuid:{}'.format(self.__uuid)
322+
return self.__uuid.urn
321323

322324
def has_component(self, component: Component) -> bool:
323325
"""
@@ -401,6 +403,17 @@ def vulnerabilities(self) -> "SortedSet[Vulnerability]":
401403
def vulnerabilities(self, vulnerabilities: Iterable[Vulnerability]) -> None:
402404
self._vulnerabilities = SortedSet(vulnerabilities)
403405

406+
@property
407+
def version(self) -> int:
408+
return self._version
409+
410+
@version.setter
411+
def version(self, version: int) -> None:
412+
self._version = version
413+
414+
def urn(self) -> str:
415+
return f'urn:cdx:{self.uuid}/{self.version}'
416+
404417
def validate(self) -> bool:
405418
"""
406419
Perform data-model level validations to make sure we have some known data integrity prior to attempting output

tests/test_model_bom.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Copyright (c) OWASP Foundation. All Rights Reserved.
1919

2020
from unittest import TestCase
21+
from uuid import uuid4
2122

2223
from data import get_bom_with_component_setuptools_with_vulnerability
2324

@@ -96,6 +97,7 @@ def test_bom_metadata_tool_multiple_tools(self) -> None:
9697
bom.metadata.tools.add(
9798
Tool(vendor='TestVendor', name='TestTool', version='0.0.0')
9899
)
100+
self.assertEqual(bom.version, 1)
99101
self.assertEqual(len(bom.metadata.tools), 2)
100102

101103
def test_metadata_component(self) -> None:
@@ -108,12 +110,29 @@ def test_metadata_component(self) -> None:
108110

109111
def test_empty_bom(self) -> None:
110112
bom = Bom()
113+
self.assertEqual(bom.version, 1)
111114
self.assertIsNotNone(bom.uuid)
112115
self.assertIsNotNone(bom.metadata)
113116
self.assertFalse(bom.components)
114117
self.assertFalse(bom.services)
115118
self.assertFalse(bom.external_references)
116119

120+
def test_empty_bom_defined_serial(self) -> None:
121+
serial_number = uuid4()
122+
bom = Bom(serial_number=serial_number)
123+
self.assertEqual(bom.uuid, serial_number)
124+
self.assertEqual(bom.get_urn_uuid(), serial_number.urn)
125+
self.assertEqual(bom.version, 1)
126+
self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/1')
127+
128+
def test_empty_bom_defined_serial_and_version(self) -> None:
129+
serial_number = uuid4()
130+
bom = Bom(serial_number=serial_number, version=2)
131+
self.assertEqual(bom.uuid, serial_number)
132+
self.assertEqual(bom.get_urn_uuid(), serial_number.urn)
133+
self.assertEqual(bom.version, 2)
134+
self.assertEqual(bom.urn(), f'urn:cdx:{serial_number}/2')
135+
117136
def test_bom_with_vulnerabilities(self) -> None:
118137
bom = get_bom_with_component_setuptools_with_vulnerability()
119138
self.assertTrue(bom.has_vulnerabilities())

0 commit comments

Comments
 (0)