From 087fd325199f753430d4333a5e24731bb74e8272 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 15 Jun 2023 13:24:37 +0200 Subject: [PATCH] fix Signed-off-by: Jan Kowalleck --- cyclonedx/serialization/__init__.py | 3 +- .../regression365_expression-preferred.json | 1 + .../1.2/regression365_multiple-licenses.json | 1 + .../regression365_expression-preferred.json | 1 + .../1.3/regression365_multiple-licenses.json | 1 + .../regression365_expression-preferred.json | 1 + .../1.4/regression365_multiple-licenses.json | 1 + .../regression365_expression-preferred.xml | 1 + .../1.0/regression365_multiple-licenses.xml | 1 + .../regression365_expression-preferred.xml | 1 + .../1.1/regression365_multiple-licenses.xml | 1 + .../regression365_expression-preferred.xml | 1 + .../1.2/regression365_multiple-licenses.xml | 1 + .../regression365_expression-preferred.xml | 1 + .../1.3/regression365_multiple-licenses.xml | 1 + .../regression365_expression-preferred.xml | 1 + .../1.4/regression365_multiple-licenses.xml | 1 + tests/test_regression365.py | 105 ++++++++++++++++++ 18 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/json/1.2/regression365_expression-preferred.json create mode 100644 tests/fixtures/json/1.2/regression365_multiple-licenses.json create mode 100644 tests/fixtures/json/1.3/regression365_expression-preferred.json create mode 100644 tests/fixtures/json/1.3/regression365_multiple-licenses.json create mode 100644 tests/fixtures/json/1.4/regression365_expression-preferred.json create mode 100644 tests/fixtures/json/1.4/regression365_multiple-licenses.json create mode 100644 tests/fixtures/xml/1.0/regression365_expression-preferred.xml create mode 100644 tests/fixtures/xml/1.0/regression365_multiple-licenses.xml create mode 100644 tests/fixtures/xml/1.1/regression365_expression-preferred.xml create mode 100644 tests/fixtures/xml/1.1/regression365_multiple-licenses.xml create mode 100644 tests/fixtures/xml/1.2/regression365_expression-preferred.xml create mode 100644 tests/fixtures/xml/1.2/regression365_multiple-licenses.xml create mode 100644 tests/fixtures/xml/1.3/regression365_expression-preferred.xml create mode 100644 tests/fixtures/xml/1.3/regression365_multiple-licenses.xml create mode 100644 tests/fixtures/xml/1.4/regression365_expression-preferred.xml create mode 100644 tests/fixtures/xml/1.4/regression365_multiple-licenses.xml create mode 100644 tests/test_regression365.py diff --git a/cyclonedx/serialization/__init__.py b/cyclonedx/serialization/__init__.py index 49eed8ff8..eb15b17a4 100644 --- a/cyclonedx/serialization/__init__.py +++ b/cyclonedx/serialization/__init__.py @@ -91,9 +91,10 @@ def deserialize(cls, o: object) -> SortedSet[LicenseChoice]: @classmethod def serialize(cls, o: object) -> List[LicenseChoice]: + # need to call `list(o)`, because `o` could be any iterable. licenses: List[LicenseChoice] = list(o) # type: ignore[call-overload] if len(licenses) > 1: - expression = next(license for license in licenses if license.expression) + expression = next((l for l in licenses if l.expression), None) if expression: warnings.warn( f'Licenses: found an expression {expression!r}, dropping the rest of: {licenses!r}', diff --git a/tests/fixtures/json/1.2/regression365_expression-preferred.json b/tests/fixtures/json/1.2/regression365_expression-preferred.json new file mode 100644 index 000000000..edcf9c054 --- /dev/null +++ b/tests/fixtures/json/1.2/regression365_expression-preferred.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"expression": "(Apache-2.0 OR MIT)"}], "name": "expression-preferred", "type": "library", "version": ""}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:09:38+00:00", "tools": [{"name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:66f6f3d4-0d24-4db3-b69c-bd547be9b0d3", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2"} \ No newline at end of file diff --git a/tests/fixtures/json/1.2/regression365_multiple-licenses.json b/tests/fixtures/json/1.2/regression365_multiple-licenses.json new file mode 100644 index 000000000..3c72b8260 --- /dev/null +++ b/tests/fixtures/json/1.2/regression365_multiple-licenses.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"license": {"id": "Apache-2.0", "text": {"content": "VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=", "contentType": "text/plain", "encoding": "base64"}, "url": "https://www.apache.org/licenses/LICENSE-2.0.txt"}}, {"license": {"name": "OSI_APACHE"}}], "name": "multiple-licenses", "type": "library", "version": ""}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:05:12+00:00", "tools": [{"name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:92f71d34-625a-4497-9891-3333c56a7af1", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.2"} \ No newline at end of file diff --git a/tests/fixtures/json/1.3/regression365_expression-preferred.json b/tests/fixtures/json/1.3/regression365_expression-preferred.json new file mode 100644 index 000000000..fb095a230 --- /dev/null +++ b/tests/fixtures/json/1.3/regression365_expression-preferred.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"expression": "(Apache-2.0 OR MIT)"}], "name": "expression-preferred", "type": "library", "version": ""}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:09:38+00:00", "tools": [{"name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:66f6f3d4-0d24-4db3-b69c-bd547be9b0d3", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3"} \ No newline at end of file diff --git a/tests/fixtures/json/1.3/regression365_multiple-licenses.json b/tests/fixtures/json/1.3/regression365_multiple-licenses.json new file mode 100644 index 000000000..fd11ced50 --- /dev/null +++ b/tests/fixtures/json/1.3/regression365_multiple-licenses.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"license": {"id": "Apache-2.0", "text": {"content": "VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=", "contentType": "text/plain", "encoding": "base64"}, "url": "https://www.apache.org/licenses/LICENSE-2.0.txt"}}, {"license": {"name": "OSI_APACHE"}}], "name": "multiple-licenses", "type": "library", "version": ""}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:05:12+00:00", "tools": [{"name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:92f71d34-625a-4497-9891-3333c56a7af1", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.3"} \ No newline at end of file diff --git a/tests/fixtures/json/1.4/regression365_expression-preferred.json b/tests/fixtures/json/1.4/regression365_expression-preferred.json new file mode 100644 index 000000000..dbe3ea3ff --- /dev/null +++ b/tests/fixtures/json/1.4/regression365_expression-preferred.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"expression": "(Apache-2.0 OR MIT)"}], "name": "expression-preferred", "type": "library"}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:09:38+00:00", "tools": [{"externalReferences": [{"type": "build-system", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"}, {"type": "distribution", "url": "https://pypi.org/project/cyclonedx-python-lib/"}, {"type": "documentation", "url": "https://cyclonedx.github.io/cyclonedx-python-lib/"}, {"type": "issue-tracker", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"}, {"type": "license", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"}, {"type": "release-notes", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"}, {"type": "vcs", "url": "https://github.com/CycloneDX/cyclonedx-python-lib"}, {"type": "website", "url": "https://cyclonedx.org"}], "name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:66f6f3d4-0d24-4db3-b69c-bd547be9b0d3", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4"} \ No newline at end of file diff --git a/tests/fixtures/json/1.4/regression365_multiple-licenses.json b/tests/fixtures/json/1.4/regression365_multiple-licenses.json new file mode 100644 index 000000000..8b2123e4f --- /dev/null +++ b/tests/fixtures/json/1.4/regression365_multiple-licenses.json @@ -0,0 +1 @@ +{"components": [{"bom-ref": "testing", "licenses": [{"license": {"id": "Apache-2.0", "text": {"content": "VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=", "contentType": "text/plain", "encoding": "base64"}, "url": "https://www.apache.org/licenses/LICENSE-2.0.txt"}}, {"license": {"name": "OSI_APACHE"}}], "name": "multiple-licenses", "type": "library"}], "dependencies": [{"ref": "testing"}], "metadata": {"timestamp": "2022-06-15T13:05:12+00:00", "tools": [{"externalReferences": [{"type": "build-system", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions"}, {"type": "distribution", "url": "https://pypi.org/project/cyclonedx-python-lib/"}, {"type": "documentation", "url": "https://cyclonedx.github.io/cyclonedx-python-lib/"}, {"type": "issue-tracker", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues"}, {"type": "license", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE"}, {"type": "release-notes", "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md"}, {"type": "vcs", "url": "https://github.com/CycloneDX/cyclonedx-python-lib"}, {"type": "website", "url": "https://cyclonedx.org"}], "name": "cyclonedx-python-lib", "vendor": "CycloneDX", "version": "4.0.0"}]}, "serialNumber": "urn:uuid:92f71d34-625a-4497-9891-3333c56a7af1", "version": 1, "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.4"} \ No newline at end of file diff --git a/tests/fixtures/xml/1.0/regression365_expression-preferred.xml b/tests/fixtures/xml/1.0/regression365_expression-preferred.xml new file mode 100644 index 000000000..057ae3fdf --- /dev/null +++ b/tests/fixtures/xml/1.0/regression365_expression-preferred.xml @@ -0,0 +1 @@ +expression-preferredfalse \ No newline at end of file diff --git a/tests/fixtures/xml/1.0/regression365_multiple-licenses.xml b/tests/fixtures/xml/1.0/regression365_multiple-licenses.xml new file mode 100644 index 000000000..860c56d74 --- /dev/null +++ b/tests/fixtures/xml/1.0/regression365_multiple-licenses.xml @@ -0,0 +1 @@ +multiple-licensesfalse \ No newline at end of file diff --git a/tests/fixtures/xml/1.1/regression365_expression-preferred.xml b/tests/fixtures/xml/1.1/regression365_expression-preferred.xml new file mode 100644 index 000000000..9ce805105 --- /dev/null +++ b/tests/fixtures/xml/1.1/regression365_expression-preferred.xml @@ -0,0 +1 @@ +expression-preferredApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE(Apache-2.0 OR MIT) \ No newline at end of file diff --git a/tests/fixtures/xml/1.1/regression365_multiple-licenses.xml b/tests/fixtures/xml/1.1/regression365_multiple-licenses.xml new file mode 100644 index 000000000..a429f11fc --- /dev/null +++ b/tests/fixtures/xml/1.1/regression365_multiple-licenses.xml @@ -0,0 +1 @@ +multiple-licensesApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE \ No newline at end of file diff --git a/tests/fixtures/xml/1.2/regression365_expression-preferred.xml b/tests/fixtures/xml/1.2/regression365_expression-preferred.xml new file mode 100644 index 000000000..c77bf6254 --- /dev/null +++ b/tests/fixtures/xml/1.2/regression365_expression-preferred.xml @@ -0,0 +1 @@ +2022-06-15T13:09:38+00:00CycloneDXcyclonedx-python-lib4.0.0expression-preferredApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE(Apache-2.0 OR MIT) \ No newline at end of file diff --git a/tests/fixtures/xml/1.2/regression365_multiple-licenses.xml b/tests/fixtures/xml/1.2/regression365_multiple-licenses.xml new file mode 100644 index 000000000..d3fb2f8fe --- /dev/null +++ b/tests/fixtures/xml/1.2/regression365_multiple-licenses.xml @@ -0,0 +1 @@ +2022-06-15T13:05:12+00:00CycloneDXcyclonedx-python-lib4.0.0multiple-licensesApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE \ No newline at end of file diff --git a/tests/fixtures/xml/1.3/regression365_expression-preferred.xml b/tests/fixtures/xml/1.3/regression365_expression-preferred.xml new file mode 100644 index 000000000..34e50421b --- /dev/null +++ b/tests/fixtures/xml/1.3/regression365_expression-preferred.xml @@ -0,0 +1 @@ +2022-06-15T13:09:38+00:00CycloneDXcyclonedx-python-lib4.0.0expression-preferredApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE(Apache-2.0 OR MIT) \ No newline at end of file diff --git a/tests/fixtures/xml/1.3/regression365_multiple-licenses.xml b/tests/fixtures/xml/1.3/regression365_multiple-licenses.xml new file mode 100644 index 000000000..78ffa14fe --- /dev/null +++ b/tests/fixtures/xml/1.3/regression365_multiple-licenses.xml @@ -0,0 +1 @@ +2022-06-15T13:05:12+00:00CycloneDXcyclonedx-python-lib4.0.0multiple-licensesApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE \ No newline at end of file diff --git a/tests/fixtures/xml/1.4/regression365_expression-preferred.xml b/tests/fixtures/xml/1.4/regression365_expression-preferred.xml new file mode 100644 index 000000000..2be73645b --- /dev/null +++ b/tests/fixtures/xml/1.4/regression365_expression-preferred.xml @@ -0,0 +1 @@ +2022-06-15T13:09:38+00:00CycloneDXcyclonedx-python-lib4.0.0https://github.com/CycloneDX/cyclonedx-python-lib/actionshttps://pypi.org/project/cyclonedx-python-lib/https://cyclonedx.github.io/cyclonedx-python-lib/https://github.com/CycloneDX/cyclonedx-python-lib/issueshttps://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSEhttps://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.mdhttps://github.com/CycloneDX/cyclonedx-python-libhttps://cyclonedx.orgexpression-preferredApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE(Apache-2.0 OR MIT) \ No newline at end of file diff --git a/tests/fixtures/xml/1.4/regression365_multiple-licenses.xml b/tests/fixtures/xml/1.4/regression365_multiple-licenses.xml new file mode 100644 index 000000000..0630eb215 --- /dev/null +++ b/tests/fixtures/xml/1.4/regression365_multiple-licenses.xml @@ -0,0 +1 @@ +2022-06-15T13:05:12+00:00CycloneDXcyclonedx-python-lib4.0.0https://github.com/CycloneDX/cyclonedx-python-lib/actionshttps://pypi.org/project/cyclonedx-python-lib/https://cyclonedx.github.io/cyclonedx-python-lib/https://github.com/CycloneDX/cyclonedx-python-lib/issueshttps://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSEhttps://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.mdhttps://github.com/CycloneDX/cyclonedx-python-libhttps://cyclonedx.orgmultiple-licensesApache-2.0VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=https://www.apache.org/licenses/LICENSE-2.0.txtOSI_APACHE \ No newline at end of file diff --git a/tests/test_regression365.py b/tests/test_regression365.py new file mode 100644 index 000000000..88edb2baa --- /dev/null +++ b/tests/test_regression365.py @@ -0,0 +1,105 @@ +# encoding: utf-8 +# This file is part of CycloneDX Python Lib +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) OWASP Foundation. All Rights Reserved. + +import re +from datetime import datetime, timezone +from unittest import TestCase +from os.path import dirname, join +from typing import Type, Union +from itertools import product, chain +from uuid import UUID + +from ddt import idata, ddt, unpack + +from cyclonedx.model import LicenseChoice, License, AttachedText, Encoding, XsUri +from cyclonedx.model.bom import Bom, Component +from cyclonedx.output.json import Json, JsonV1Dot4, JsonV1Dot3, JsonV1Dot2 +from cyclonedx.output.xml import Xml, XmlV1Dot4, XmlV1Dot3, XmlV1Dot2, XmlV1Dot1, XmlV1Dot0 + +_bom_multiple_licenses = Bom(serial_number=UUID(hex='92f71d34625a449798913333c56a7af1')) +_bom_multiple_licenses.metadata.timestamp = datetime(2022, 6, 15, 13, 5, 12, 0, timezone.utc) +_bom_multiple_licenses.components.add(Component( + name='multiple-licenses', + bom_ref='testing', + licenses=[ + LicenseChoice( + license=License( + id='Apache-2.0', + text=AttachedText( + content='VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=', + encoding=Encoding.BASE_64 + ), + url=XsUri('https://www.apache.org/licenses/LICENSE-2.0.txt') + ) + ), + LicenseChoice(license=License(name='OSI_APACHE')) + ])) + +_bom_expression_preferred = Bom(serial_number=UUID(hex='66f6f3d40d244db3b69cbd547be9b0d3')) +_bom_expression_preferred.metadata.timestamp = datetime(2022, 6, 15, 13, 9, 38, 0, timezone.utc) +_bom_expression_preferred.components.add(Component( + name='expression-preferred', + bom_ref='testing', + licenses=[ + LicenseChoice( + license=License( + id='Apache-2.0', + text=AttachedText( + content='VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=', + encoding=Encoding.BASE_64 + ), + url=XsUri('https://www.apache.org/licenses/LICENSE-2.0.txt') + ) + ), + LicenseChoice(expression='(Apache-2.0 OR MIT)'), + LicenseChoice(license=License(name='OSI_APACHE')) + ])) + + +@ddt +class Regression365(TestCase): + """ + This is a regression test against https://github.com/CycloneDX/cyclonedx-python-lib/issues/365 + + license list serialization must be like: + - if list contains any expressions: serialize the first expression only + - if list contains no expression: serialize all items + """ + + @idata(chain( + product( + [_bom_multiple_licenses, _bom_expression_preferred], + ['xml'], + [XmlV1Dot4, XmlV1Dot3, XmlV1Dot2, XmlV1Dot1, XmlV1Dot0] + ), + product( + [_bom_multiple_licenses, _bom_expression_preferred], + ['json'], + [JsonV1Dot4, JsonV1Dot3, JsonV1Dot2] + ), + )) + @unpack + def test_serialize(self, bom: Bom, target: str, schema_type: Union[Type[Json], Type[Xml]]) -> None: + serializer = schema_type(bom) + serialized = serializer.output_as_string() + + expected_file = join( + dirname(__file__), + f'fixtures/{target}/{serializer.get_schema_version()}/regression365_{bom.components[0].name}.{target}') + with open(expected_file) as expected_fh: + self.assertEqual(expected_fh.read(), serialized)