From fee263aef5ec59ab0d6642b80dabc6c6f0551c8d Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:26:53 +0200 Subject: [PATCH 1/2] Include bom-ref value within component hash calculation Fixes https://github.com/CycloneDX/cyclonedx-python-lib/issues/540 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com> --- cyclonedx/model/component.py | 2 +- tests/test_model_component.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 89e7020d..d3b27201 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -1783,7 +1783,7 @@ def __hash__(self) -> int: self.mime_type, self.supplier, self.author, self.publisher, self.description, self.scope, tuple(self.hashes), tuple(self.licenses), self.copyright, self.cpe, - self.purl, + self.purl, self.bom_ref.value, self.swid, self.pedigree, tuple(self.external_references), tuple(self.properties), tuple(self.components), self.evidence, self.release_notes, self.modified, diff --git a/tests/test_model_component.py b/tests/test_model_component.py index c25fdc91..6efa3bb5 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -219,6 +219,16 @@ def test_component_equal_3(self) -> None: self.assertNotEqual(c, c2) + def test_component_equal_4(self) -> None: + c = Component( + name='test-component', version='1.2.3', bom_ref='ref1' + ) + c2 = Component( + name='test-component', version='1.2.3', bom_ref='ref2' + ) + + self.assertNotEqual(c, c2) + def test_same_1(self) -> None: c1 = get_component_setuptools_simple() c2 = get_component_setuptools_simple() From efcca5375d3cc27cef14d6f1fdfadae36a28185e Mon Sep 17 00:00:00 2001 From: wkoot <3715211+wkoot@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:43:21 +0200 Subject: [PATCH 2/2] Add test case for duplicate components produced by cyclonedx-cli merge Addresses https://github.com/CycloneDX/cyclonedx-python-lib/issues/677 Signed-off-by: wkoot <3715211+wkoot@users.noreply.github.com> --- .../own/json/1.5/duplicate_components.json | 48 +++++++++++++++++++ tests/test_real_world_examples.py | 9 ++++ 2 files changed, 57 insertions(+) create mode 100644 tests/_data/own/json/1.5/duplicate_components.json diff --git a/tests/_data/own/json/1.5/duplicate_components.json b/tests/_data/own/json/1.5/duplicate_components.json new file mode 100644 index 00000000..93e5516c --- /dev/null +++ b/tests/_data/own/json/1.5/duplicate_components.json @@ -0,0 +1,48 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:66fa5692-2e9d-45c5-830a-ec8ccaf7dcc9", + "version": 1, + "metadata": { + "component": { + "type": "application", + "name": "test" + } + }, + "components": [ + { + "type": "operating-system", + "bom-ref": "test12", + "name": "alpine" + }, + { + "type": "container", + "bom-ref": "test11", + "name": "alpine" + }, + { + "type": "operating-system", + "bom-ref": "test22", + "name": "alpine" + }, + { + "type": "container", + "bom-ref": "test21", + "name": "alpine" + } + ], + "dependencies": [ + { + "ref": "test11", + "dependsOn": [ + "test12" + ] + }, + { + "ref": "test21", + "dependsOn": [ + "test22" + ] + } + ] +} diff --git a/tests/test_real_world_examples.py b/tests/test_real_world_examples.py index 757d33eb..da7a2feb 100644 --- a/tests/test_real_world_examples.py +++ b/tests/test_real_world_examples.py @@ -17,6 +17,7 @@ import unittest from datetime import datetime +from json import loads as json_loads from os.path import join from typing import Any from unittest.mock import patch @@ -36,3 +37,11 @@ def test_webgoat_6_1(self, *_: Any, **__: Any) -> None: def test_regression_issue_630(self, *_: Any, **__: Any) -> None: with open(join(OWN_DATA_DIRECTORY, 'xml', '1.6', 'regression_issue630.xml')) as input_xml: Bom.from_xml(input_xml) + + def test_merged_bom_duplicate_component(self, *_: Any, **__: Any) -> None: + with open(join(OWN_DATA_DIRECTORY, 'json', '1.5', 'duplicate_components.json')) as input_json: + json = json_loads(input_json.read()) + + bom = Bom.from_json(json) + self.assertEqual(4, len(bom.components)) # tests https://github.com/CycloneDX/cyclonedx-python-lib/issues/540 + bom.validate() # tests https://github.com/CycloneDX/cyclonedx-python-lib/issues/677