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)