From 61dd1b2df264ec9bc676074780d7fa47874458fe Mon Sep 17 00:00:00 2001 From: Michael Schlenker Date: Thu, 20 Oct 2022 14:33:27 +0200 Subject: [PATCH] Allow serializing of patches with resolves nodes for xml Signed-off-by: Michael Schlenker --- cyclonedx/output/xml.py | 49 ++-- tests/data.py | 18 ++ .../1.4/bom_setuptools_complete_resolves.xml | 268 ++++++++++++++++++ tests/test_output_xml.py | 8 + 4 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/xml/1.4/bom_setuptools_complete_resolves.xml diff --git a/cyclonedx/output/xml.py b/cyclonedx/output/xml.py index d297b143..3fb0c01e 100644 --- a/cyclonedx/output/xml.py +++ b/cyclonedx/output/xml.py @@ -428,27 +428,7 @@ def _add_release_notes_element(release_notes: ReleaseNotes, parent_element: Elem for tag in release_notes.tags: ElementTree.SubElement(release_notes_tags_e, 'tag').text = tag if release_notes.resolves: - release_notes_resolves_e = ElementTree.SubElement(release_notes_e, 'resolves') - for issue in release_notes.resolves: - issue_e = ElementTree.SubElement( - release_notes_resolves_e, 'issue', {'type': issue.type.value} - ) - if issue.id: - ElementTree.SubElement(issue_e, 'id').text = issue.id - if issue.name: - ElementTree.SubElement(issue_e, 'name').text = issue.name - if issue.description: - ElementTree.SubElement(issue_e, 'description').text = issue.description - if issue.source: - issue_source_e = ElementTree.SubElement(issue_e, 'source') - if issue.source.name: - ElementTree.SubElement(issue_source_e, 'name').text = issue.source.name - if issue.source.url: - ElementTree.SubElement(issue_source_e, 'url').text = str(issue.source.url) - if issue.references: - issue_references_e = ElementTree.SubElement(issue_e, 'references') - for reference in issue.references: - ElementTree.SubElement(issue_references_e, 'url').text = str(reference) + Xml._add_resolves_element(release_notes.resolves, release_notes_e) if release_notes.notes: release_notes_notes_e = ElementTree.SubElement(release_notes_e, 'notes') for note in release_notes.notes: @@ -473,9 +453,34 @@ def add_patch_element(patch: Patch) -> ElementTree.Element: diff_element.append(Xml._add_attached_text(attached_text=patch.diff.text)) if patch.diff.url: ElementTree.SubElement(diff_element, 'url').text = str(patch.diff.url) - + if patch.resolves: + Xml._add_resolves_element(patch.resolves, patch_element) return patch_element + @staticmethod + def _add_resolves_element(resolves: "SortedSet[IssueType]", parent_element: ElementTree.Element) -> None: + resolves_e = ElementTree.SubElement(parent_element, 'resolves') + for issue in resolves: + issue_e = ElementTree.SubElement( + resolves_e, 'issue', {'type': issue.type.value} + ) + if issue.id: + ElementTree.SubElement(issue_e, 'id').text = issue.id + if issue.name: + ElementTree.SubElement(issue_e, 'name').text = issue.name + if issue.description: + ElementTree.SubElement(issue_e, 'description').text = issue.description + if issue.source: + issue_source_e = ElementTree.SubElement(issue_e, 'source') + if issue.source.name: + ElementTree.SubElement(issue_source_e, 'name').text = issue.source.name + if issue.source.url: + ElementTree.SubElement(issue_source_e, 'url').text = str(issue.source.url) + if issue.references: + issue_references_e = ElementTree.SubElement(issue_e, 'references') + for reference in issue.references: + ElementTree.SubElement(issue_references_e, 'url').text = str(reference) + @staticmethod def _add_properties_element(properties: "SortedSet[Property]", parent_element: ElementTree.Element) -> None: properties_e = ElementTree.SubElement(parent_element, 'properties') diff --git a/tests/data.py b/tests/data.py index 4fa73737..1c520197 100644 --- a/tests/data.py +++ b/tests/data.py @@ -141,6 +141,10 @@ def get_bom_with_component_setuptools_complete() -> Bom: return Bom(components=[get_component_setuptools_complete()]) +def get_bom_with_component_setuptools_complete_resolves() -> Bom: + return Bom(components=[get_component_setuptools_complete_resolves()]) + + def get_bom_with_component_setuptools_with_vulnerability() -> Bom: bom = Bom() component = get_component_setuptools_simple() @@ -375,6 +379,12 @@ def get_component_setuptools_complete(include_pedigree: bool = True) -> Componen return component +def get_component_setuptools_complete_resolves() -> Component: + component = get_component_setuptools_complete(False) + component.pedigree = get_pedigree_2() + return component + + def get_component_setuptools_simple( bom_ref: Optional[str] = 'pkg:pypi/setuptools@50.3.2?extension=tar.gz') -> Component: return Component( @@ -495,6 +505,14 @@ def get_pedigree_1() -> Pedigree: ) +def get_pedigree_2() -> Pedigree: + pedigree = get_pedigree_1() + pedigree.patches.add( + Patch(type_=PatchClassification.BACKPORT, resolves=[get_issue_1()]) + ) + return pedigree + + def get_properties_1() -> List[Property]: return [ Property(name='key1', value='val1'), diff --git a/tests/fixtures/xml/1.4/bom_setuptools_complete_resolves.xml b/tests/fixtures/xml/1.4/bom_setuptools_complete_resolves.xml new file mode 100644 index 00000000..666f3261 --- /dev/null +++ b/tests/fixtures/xml/1.4/bom_setuptools_complete_resolves.xml @@ -0,0 +1,268 @@ + + + + 2022-10-20T15:55:25.045777+00:00 + + + CycloneDX + cyclonedx-python-lib + 3.1.0 + + + https://github.com/CycloneDX/cyclonedx-python-lib/actions + + + https://pypi.org/project/cyclonedx-python-lib/ + + + https://cyclonedx.github.io/cyclonedx-python-lib/ + + + https://github.com/CycloneDX/cyclonedx-python-lib/issues + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE + + + https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md + + + https://github.com/CycloneDX/cyclonedx-python-lib + + + https://cyclonedx.org + + + + + + + + + CycloneDX + https://cyclonedx.org + + A N Other + someone@somewhere.tld + +44 (0)1234 567890 + + + Paul Horton + paul.horton@owasp.org + + + Test Author + CycloneDX + setuptools + 50.3.2 + This component is awesome + required + + MIT License + + Apache 2.0 baby! + cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+CjxTb2Z0d2FyZUlkZW50aXR5IHhtbDpsYW5nPSJFTiIgbmFtZT0iQWNtZSBBcHBsaWNhdGlvbiIgdmVyc2lvbj0iOS4xLjEiIAogdmVyc2lvblNjaGVtZT0ibXVsdGlwYXJ0bnVtZXJpYyIgCiB0YWdJZD0ic3dpZGdlbi1iNTk1MWFjOS00MmMwLWYzODItM2YxZS1iYzdhMmE0NDk3Y2JfOS4xLjEiIAogeG1sbnM9Imh0dHA6Ly9zdGFuZGFyZHMuaXNvLm9yZy9pc28vMTk3NzAvLTIvMjAxNS9zY2hlbWEueHNkIj4gCiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiAKIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3N0YW5kYXJkcy5pc28ub3JnL2lzby8xOTc3MC8tMi8yMDE1LWN1cnJlbnQvc2NoZW1hLnhzZCBzY2hlbWEueHNkIiA+CiAgPE1ldGEgZ2VuZXJhdG9yPSJTV0lEIFRhZyBPbmxpbmUgR2VuZXJhdG9yIHYwLjEiIC8+IAogIDxFbnRpdHkgbmFtZT0iQWNtZSwgSW5jLiIgcmVnaWQ9ImV4YW1wbGUuY29tIiByb2xlPSJ0YWdDcmVhdG9yIiAvPiAKPC9Tb2Z0d2FyZUlkZW50aXR5Pg== + + + + + Test Author + setuptools + 50.3.2 + + MIT License + + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + Test Author + setuptools + + MIT License + + pkg:pypi/setuptools?extension=tar.gz + + + + + Test Author + setuptools + + MIT License + + pkg:pypi/setuptools?extension=tar.gz + + + toml + 0.10.2 + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + pkg:pypi/toml@0.10.2?extension=tar.gz + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + + + Test Author + setuptools + 50.3.2 + + MIT License + + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + toml + 0.10.2 + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + pkg:pypi/toml@0.10.2?extension=tar.gz + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + + + a-random-uid + A commit message + + + + + + + + CVE-2021-44228 + Apache Log3Shell + Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features... + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-44228 + + + https://central.sonatype.org/news/20211213_log4shell_help + https://logging.apache.org/log4j/2.x/security.html + + + + + + Some notes here please + + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + val1 + val2 + + + + Test Author + setuptools + 50.3.2 + + MIT License + + pkg:pypi/setuptools@50.3.2?extension=tar.gz + + + toml + 0.10.2 + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + pkg:pypi/toml@0.10.2?extension=tar.gz + + + https://cyclonedx.org + No comment + + 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + + + + + + + + Commercial + Commercial 2 + + + + major + Release Notes Title + https://cyclonedx.org/theme/assets/images/CycloneDX-Twitter-Card.png + https://cyclonedx.org/cyclonedx-icon.png + This release is a test release + 2021-12-31T10:00:00+00:00 + + First Test Release + + + alpha + test + + + + CVE-2021-44228 + Apache Log3Shell + Apache Log4j2 2.0-beta9 through 2.12.1 and 2.13.0 through 2.15.0 JNDI features... + + NVD + https://nvd.nist.gov/vuln/detail/CVE-2021-44228 + + + https://central.sonatype.org/news/20211213_log4shell_help + https://logging.apache.org/log4j/2.x/security.html + + + + + + en-GB + U29tZSBzaW1wbGUgcGxhaW4gdGV4dA== + + + en-US + U29tZSBzaW1wbGUgcGxhaW4gdGV4dA== + + + + val1 + val2 + + + + + + + + diff --git a/tests/test_output_xml.py b/tests/test_output_xml.py index 487f15e7..dff080de 100644 --- a/tests/test_output_xml.py +++ b/tests/test_output_xml.py @@ -34,6 +34,7 @@ get_bom_just_complete_metadata, get_bom_with_component_setuptools_basic, get_bom_with_component_setuptools_complete, + get_bom_with_component_setuptools_complete_resolves, get_bom_with_component_setuptools_no_component_version, get_bom_with_component_setuptools_with_cpe, get_bom_with_component_setuptools_with_release_notes, @@ -181,6 +182,13 @@ def test_bom_v1_0_full_component(self) -> None: fixture='bom_setuptools_complete.xml' ) + @patch('cyclonedx.model.bom_ref.uuid4', return_value=MOCK_UUID_4) + def test_bom_v1_4_full_component_patch_resolves(self, mock_uuid: Mock) -> None: + self._validate_xml_bom( + bom=get_bom_with_component_setuptools_complete_resolves(), schema_version=SchemaVersion.V1_4, + fixture='bom_setuptools_complete_resolves.xml' + ) + def test_bom_v1_4_component_hashes_external_references(self) -> None: self._validate_xml_bom( bom=get_bom_with_component_toml_1(), schema_version=SchemaVersion.V1_4,