diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 925c2a90..ff0abc77 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -835,11 +835,13 @@ def __init__( url: XsUri, comment: Optional[str] = None, hashes: Optional[Iterable[HashType]] = None, + properties: Optional[Iterable['Property']] = None, ) -> None: self.url = url self.comment = comment self.type = type self.hashes = hashes or [] + self.properties = properties or [] @property @serializable.xml_sequence(1) @@ -909,10 +911,27 @@ def hashes(self) -> 'SortedSet[HashType]': def hashes(self, hashes: Iterable[HashType]) -> None: self._hashes = SortedSet(hashes) + @property + @serializable.view(SchemaVersion1Dot7) + @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property') + def properties(self) -> 'SortedSet[Property]': + """ + Provides the ability to document properties in a key/value store. This provides flexibility to include data not + officially supported in the standard without having to use additional namespaces or create extensions. + + Return: + Set of `Property` + """ + return self._properties + + @properties.setter + def properties(self, properties: Iterable['Property']) -> None: + self._properties = SortedSet(properties) + def __comparable_tuple(self) -> _ComparableTuple: return _ComparableTuple(( self._type, self._url, self._comment, - _ComparableTuple(self._hashes) + _ComparableTuple(self._hashes), _ComparableTuple(self.properties), )) def __eq__(self, other: object) -> bool: diff --git a/tests/_data/models.py b/tests/_data/models.py index 8d3a089d..4de64fcf 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -587,7 +587,7 @@ def get_bom_just_complete_metadata() -> Bom: def get_bom_with_external_references() -> Bom: bom = _make_bom(external_references=[ - get_external_reference_1(), get_external_reference_2() + get_external_reference_1(), get_external_reference_2(), get_external_reference_with_properties() ]) return bom @@ -895,6 +895,17 @@ def get_external_reference_2() -> ExternalReference: ) +def get_external_reference_with_properties() -> ExternalReference: + return ExternalReference( + type=ExternalReferenceType.VCS, + url=XsUri('https://cyclonedx.org'), + properties=[ + Property(name='property_1', value='value_1'), + Property(name='property_2', value='value_2') + ] + ) + + def get_issue_1() -> IssueType: return IssueType( type=IssueClassification.SECURITY, id='CVE-2021-44228', name='Apache Log3Shell', diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.1.xml.bin index 0a260e81..b7b31df0 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.1.xml.bin @@ -6,6 +6,9 @@ https://cyclonedx.org No comment + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.2.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.2.json.bin index b108f640..0e507a44 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.2.json.bin @@ -5,6 +5,10 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.2.xml.bin index 44a8e0a5..4fba839e 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.2.xml.bin @@ -8,6 +8,9 @@ https://cyclonedx.org No comment + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.3.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.3.json.bin index 19fcd07f..3e793c50 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.3.json.bin @@ -11,6 +11,10 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.3.xml.bin index 0ae18fba..13a9e149 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.3.xml.bin @@ -11,6 +11,9 @@ 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.4.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.4.json.bin index e90c3ea2..43fbfa6d 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.4.json.bin @@ -11,6 +11,10 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.4.xml.bin index f64b1c7a..7889bb9d 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.4.xml.bin @@ -11,6 +11,9 @@ 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.5.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.5.json.bin index 55238588..52a6c19d 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.5.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.5.json.bin @@ -11,6 +11,10 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.5.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.5.xml.bin index 411ab39a..1a6c87b1 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.5.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.5.xml.bin @@ -11,6 +11,9 @@ 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.6.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.6.json.bin index 82c9bc40..050afd04 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.6.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.6.json.bin @@ -11,6 +11,10 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.6.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.6.xml.bin index 7dee398e..6216a6d3 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.6.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.6.xml.bin @@ -11,6 +11,9 @@ 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + https://cyclonedx.org + https://cyclonedx.org diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.7.json.bin b/tests/_data/snapshots/get_bom_with_external_references-1.7.json.bin index 01744db6..ebeb8024 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.7.json.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.7.json.bin @@ -11,6 +11,20 @@ "type": "distribution", "url": "https://cyclonedx.org" }, + { + "properties": [ + { + "name": "property_1", + "value": "value_1" + }, + { + "name": "property_2", + "value": "value_2" + } + ], + "type": "vcs", + "url": "https://cyclonedx.org" + }, { "type": "website", "url": "https://cyclonedx.org" diff --git a/tests/_data/snapshots/get_bom_with_external_references-1.7.xml.bin b/tests/_data/snapshots/get_bom_with_external_references-1.7.xml.bin index 02af4c30..dcf64c05 100644 --- a/tests/_data/snapshots/get_bom_with_external_references-1.7.xml.bin +++ b/tests/_data/snapshots/get_bom_with_external_references-1.7.xml.bin @@ -11,6 +11,13 @@ 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + https://cyclonedx.org + + value_1 + value_2 + + https://cyclonedx.org diff --git a/tests/test_model_component.py b/tests/test_model_component.py index 9c47fe02..44f59a12 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -132,16 +132,24 @@ def test_multiple_basic_components(self) -> None: def test_external_references(self) -> None: c1 = Component(name='test-component') + properties = [ + Property(name='property_1', value='value_1'), + Property(name='property_2', value='value_2') + ] c1.external_references.add(ExternalReference( type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), - comment='No comment' + comment='No comment', + properties=properties )) self.assertEqual(c1.name, 'test-component') self.assertIsNone(c1.version) self.assertEqual(c1.type, ComponentType.LIBRARY) self.assertEqual(len(c1.external_references), 1) self.assertEqual(len(c1.hashes), 0) + self.assertIsNotNone(c1.external_references[0].properties) + self.assertIn(properties[0], c1.external_references[0].properties) + self.assertIn(properties[1], c1.external_references[0].properties) c2 = Component(name='test2-component') self.assertEqual(c2.name, 'test2-component') @@ -163,13 +171,15 @@ def test_component_equal_1(self) -> None: c1.external_references.add(ExternalReference( type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), - comment='No comment' + comment='No comment', + properties=[Property(name='property_1', value='value_1')] )) c2 = Component(name='test-component') c2.external_references.add(ExternalReference( type=ExternalReferenceType.OTHER, url=XsUri('https://cyclonedx.org'), - comment='No comment' + comment='No comment', + properties=[Property(name='property_1', value='value_1')] )) self.assertEqual(c1, c2)