diff --git a/cdxev/auxiliary/sbomFunctions.py b/cdxev/auxiliary/sbomFunctions.py index c6764093..4d49e4cc 100644 --- a/cdxev/auxiliary/sbomFunctions.py +++ b/cdxev/auxiliary/sbomFunctions.py @@ -188,7 +188,8 @@ def get_bom_refs_from_dependencies(dependencies: Sequence[dict]) -> list[str]: def get_ref_from_components(list_of_components: Sequence[dict]) -> list[str]: """ - Function that returns a list of bom-refs from a list of components + Function that returns a list of bom-refs from a list of components. + This also includes nested components. Input: list_of_components: list with dicts of components @@ -196,13 +197,25 @@ def get_ref_from_components(list_of_components: Sequence[dict]) -> list[str]: Output: list_of_bom_refs: List of bom-refs from the components in the submitted list """ + list_of_all_components = extract_components(list_of_components) list_of_bom_refs = [] - for component in list_of_components: + for component in list_of_all_components: bom_ref = component.get("bom-ref", "") list_of_bom_refs.append(bom_ref) return list_of_bom_refs +def extract_components(list_of_components: Sequence[dict]) -> Sequence[dict]: + extracted_components = [] + for component in list_of_components: + if component.get("components", []) == []: + extracted_components.append(component) + else: + extracted_components.append(component) + extracted_components += extract_components(component.get("components", [])) + return extracted_components + + def compare_vulnerabilities( first_vulnerability: dict, second_vulnerability: dict ) -> bool: diff --git a/cdxev/build_public_bom.py b/cdxev/build_public_bom.py index cd4cb193..f30ab803 100644 --- a/cdxev/build_public_bom.py +++ b/cdxev/build_public_bom.py @@ -8,8 +8,6 @@ from jsonschema import Draft7Validator, FormatChecker -from cdxev.auxiliary.sbomFunctions import get_ref_from_components - def remove_internal_information_from_properties(component: dict) -> None: """ @@ -154,14 +152,17 @@ def build_public_bom(sbom: dict, path_to_schema: t.Union[Path, None]) -> dict: ) = remove_component_tagged_internal(components, path_to_schema) for bom_ref in list_of_removed_components: dependencies = merge_dependency_for_removed_component(bom_ref, dependencies) - new_compositions = get_ref_from_components(cleared_components) remove_internal_information_from_properties( sbom.get("metadata", {}).get("component", {}) ) sbom["components"] = cleared_components sbom["dependencies"] = dependencies - compositions = [{"aggregate": "incomplete", "assemblies": new_compositions}] - sbom["compositions"] = compositions + for composition in sbom.get("compositions", []): + new_assemblies = composition.get("assemblies").copy() + for bom_ref in composition.get("assemblies"): + if bom_ref in list_of_removed_components: + new_assemblies.remove(bom_ref) + composition["assemblies"] = new_assemblies return sbom diff --git a/tests/auxiliary/test_build_public_bom_sboms/Acme_Application_9.1.1_20220217T101458.cdx.json b/tests/auxiliary/test_build_public_bom_sboms/Acme_Application_9.1.1_20220217T101458.cdx.json index a605d99e..bb6d5103 100644 --- a/tests/auxiliary/test_build_public_bom_sboms/Acme_Application_9.1.1_20220217T101458.cdx.json +++ b/tests/auxiliary/test_build_public_bom_sboms/Acme_Application_9.1.1_20220217T101458.cdx.json @@ -49,7 +49,27 @@ "group": "org.acme", "name": "web-framework", "version": "1.0.0", - "purl": "pkg:maven/org.acme/web-framework@1.0.0" + "purl": "pkg:maven/org.acme/web-framework@1.0.0", + "components": [ + { + "type": "library", + "bom-ref": "sub_comp1", + "supplier": { + "name": "Acme, Inc." + }, + "licenses": [ + { + "license": { + "id": "Apache-1.0" + } + } + ], + "group": "org.acme", + "name": "sub_web-framework", + "version": "1.0.0", + "purl": "pkg:maven/org.acme/sub_web-framework@1.0.0" + } + ] }, { "type": "library", @@ -261,6 +281,7 @@ "aggregate": "incomplete", "assemblies": [ "comp1", + "sub_comp1", "comp2", "comp3", "comp4", diff --git a/tests/auxiliary/test_build_public_bom_sboms/internal_removed_sbom.json b/tests/auxiliary/test_build_public_bom_sboms/internal_removed_sbom.json index 6d0bb59c..c68813a0 100644 --- a/tests/auxiliary/test_build_public_bom_sboms/internal_removed_sbom.json +++ b/tests/auxiliary/test_build_public_bom_sboms/internal_removed_sbom.json @@ -45,7 +45,27 @@ "group": "org.acme", "name": "web-framework", "version": "1.0.0", - "purl": "pkg:maven/org.acme/web-framework@1.0.0" + "purl": "pkg:maven/org.acme/web-framework@1.0.0", + "components": [ + { + "type": "library", + "bom-ref": "sub_comp1", + "supplier": { + "name": "Acme, Inc." + }, + "licenses": [ + { + "license": { + "id": "Apache-1.0" + } + } + ], + "group": "org.acme", + "name": "sub_web-framework", + "version": "1.0.0", + "purl": "pkg:maven/org.acme/sub_web-framework@1.0.0" + } + ] }, { "type": "library", @@ -151,6 +171,7 @@ "aggregate": "incomplete", "assemblies": [ "comp1", + "sub_comp1", "comp2", "comp3", "comp4" diff --git a/tests/test_build_public_bom.py b/tests/test_build_public_bom.py index 309dc44d..e212f504 100644 --- a/tests/test_build_public_bom.py +++ b/tests/test_build_public_bom.py @@ -201,10 +201,8 @@ def test_build_public_from_documentation_4(self) -> None: self.assertDictEqual(external_bom, public_sbom) def test_build_public_no_schema(self) -> None: - sbom = get_test_sbom() sbom = get_test_sbom() public_sbom = get_test_sbom() - public_sbom["metadata"]["component"]["properties"].pop(1) public_sbom["components"][1]["properties"].pop(1) public_sbom["components"][1]["properties"].pop(2) @@ -213,17 +211,51 @@ def test_build_public_no_schema(self) -> None: public_sbom["components"][2]["properties"].pop(0) public_sbom["components"][5]["properties"].pop(0) external_bom = b_p_b.build_public_bom(sbom, None) + public_sbom["compositions"] = [ { "aggregate": "incomplete", "assemblies": [ "comp1", + "sub_comp1", "comp2", - "internalcomp2", "comp3", "comp4", + "internalcomp1", + "internalcomp2", "internalcomp3", + ], + } + ] + self.assertDictEqual(external_bom, public_sbom) + + def test_deletion_of_orphaned_bom_refs(self) -> None: + sbom = get_test_sbom() + public_sbom = get_test_sbom() + public_sbom["metadata"]["component"]["properties"].pop(1) + public_sbom["components"][1]["properties"].pop(1) + public_sbom["components"][1]["properties"].pop(2) + public_sbom["components"][6]["properties"].pop(0) + public_sbom["components"][3]["properties"].pop(0) + public_sbom["components"][2]["properties"].pop(0) + public_sbom["components"][5]["properties"].pop(0) + sbom["compositions"][0]["assemblies"].append("orphaned bom-ref 1") + sbom["compositions"][0]["assemblies"].append("orphaned bom-ref 2") + external_bom = b_p_b.build_public_bom(sbom, None) + public_sbom["compositions"] = [ + { + "aggregate": "incomplete", + "assemblies": [ + "comp1", + "sub_comp1", + "comp2", + "comp3", + "comp4", "internalcomp1", + "internalcomp2", + "internalcomp3", + "orphaned bom-ref 1", + "orphaned bom-ref 2", ], } ] diff --git a/tests/test_sbom_functions.py b/tests/test_sbom_functions.py new file mode 100644 index 00000000..7ced4784 --- /dev/null +++ b/tests/test_sbom_functions.py @@ -0,0 +1,82 @@ +import unittest +from typing import Sequence + +from cdxev.auxiliary import sbomFunctions as sbF + + +class TestComponentFunctions(unittest.TestCase): + def test_extract_components(self) -> None: + example_list: Sequence[dict] = [ + { + "bom-ref": "first_level_1", + "components": [ + { + "bom-ref": "second_level_1", + }, + { + "bom-ref": "second_level_2", + "components": [ + { + "bom-ref": "third_level_1", + }, + { + "bom-ref": "third_level_2", + }, + ], + }, + ], + }, + { + "bom-ref": "first_level_2", + "components": [ + { + "bom-ref": "second_level_3", + "components": [ + { + "bom-ref": "third_level_3", + "components": [ + { + "bom-ref": "fourth_level_1", + } + ], + } + ], + }, + { + "bom-ref": "second_level_4", + }, + ], + }, + { + "bom-ref": "first_level_3", + "components": [ + { + "bom-ref": "second_level_5", + } + ], + }, + ] + components = sbF.extract_components(example_list) + self.assertEqual( + set(sbF.get_ref_from_components(components)), + set( + [ + "first_level_1", + "first_level_2", + "first_level_3", + "second_level_1", + "second_level_2", + "second_level_3", + "second_level_4", + "second_level_5", + "third_level_1", + "third_level_2", + "third_level_3", + "fourth_level_1", + ] + ), + ) + + +if __name__ == "__main__": + unittest.main()