From 67707864ac0f1b27bac166a8fd537ea38523fe6f Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 10 Oct 2023 19:43:36 +0200 Subject: [PATCH] fix: multiple licenses issue #365 (#466) breaking changes ------------------ * Reworked license related models and collections * API * Removed class `factory.license.LicenseChoiceFactory` The old functionality was integrated into `factory.license.LicenseFactory`. * Method `factory.license.LicenseFactory.make_from_string()`'s parameter `name_or_spdx` was renamed to `value` * Method `factory.license.LicenseFactory.make_from_string()`'s return value can also be a `LicenseExpression` The behavior imitates the old `factory.license.LicenseChoiceFactory.make_from_string()` * Renamed class `module.License` to `module.license.DisjunctliveLicense` * Removed class `module.LicenseChoice` Use dedicated classes `module.license.DisjunctliveLicense` and `module.license.LicenseExpression` instead * All occurrences of `models.LicenseChoice` were replaced by `models.licenses.License` * All occurrences of `SortedSet[LicenseChoice]` were specialized to `models.license.LicenseRepository` fixes ------------------ * serialization of multy-licenses #365 added ------------------ * API * Method `factory.license.LicenseFactory.make_with_expression()` * Class `model.license.DisjunctiveLicense` * Class `model.license.LicenseExpression` * Class `model.license.LicenseRepository` * Class `serialization.LicenseRepositoryHelper` tests ------------------ * added regression test for bug #365 misc ------------------ * raised dependency `py-serializable@^9.15` ---- fixes #365 ~~BLOCKED by a feature request to serializer: ~~ --------- Signed-off-by: Jan Kowalleck --- cyclonedx/factory/license.py | 91 +++---- cyclonedx/model/__init__.py | 184 +------------- cyclonedx/model/bom.py | 27 +-- cyclonedx/model/component.py | 26 +- cyclonedx/model/dependency.py | 4 +- cyclonedx/model/license.py | 224 ++++++++++++++++++ cyclonedx/model/service.py | 23 +- cyclonedx/schema/_res/__init__.py | 11 +- cyclonedx/serialization/__init__.py | 85 ++++++- docs/conf.py | 2 +- examples/complex.py | 4 +- pyproject.toml | 2 +- tests/_data/models.py | 90 +++++-- ...et_bom_just_complete_metadata-1.2.json.bin | 24 +- ...get_bom_just_complete_metadata-1.2.xml.bin | 24 +- ...et_bom_just_complete_metadata-1.3.json.bin | 24 +- ...get_bom_just_complete_metadata-1.3.xml.bin | 24 +- ...et_bom_just_complete_metadata-1.4.json.bin | 24 +- ...get_bom_just_complete_metadata-1.4.xml.bin | 24 +- ...ith_component_setuptools_basic-1.1.xml.bin | 4 +- ...th_component_setuptools_basic-1.2.json.bin | 4 +- ...ith_component_setuptools_basic-1.2.xml.bin | 4 +- ...th_component_setuptools_basic-1.3.json.bin | 4 +- ...ith_component_setuptools_basic-1.3.xml.bin | 4 +- ...th_component_setuptools_basic-1.4.json.bin | 4 +- ...ith_component_setuptools_basic-1.4.xml.bin | 4 +- ..._component_setuptools_complete-1.1.xml.bin | 24 +- ...component_setuptools_complete-1.2.json.bin | 24 +- ..._component_setuptools_complete-1.2.xml.bin | 24 +- ...component_setuptools_complete-1.3.json.bin | 24 +- ..._component_setuptools_complete-1.3.xml.bin | 24 +- ...component_setuptools_complete-1.4.json.bin | 24 +- ..._component_setuptools_complete-1.4.xml.bin | 24 +- ...etuptools_no_component_version-1.1.xml.bin | 4 +- ...tuptools_no_component_version-1.2.json.bin | 4 +- ...etuptools_no_component_version-1.2.xml.bin | 4 +- ...tuptools_no_component_version-1.3.json.bin | 4 +- ...etuptools_no_component_version-1.3.xml.bin | 4 +- ...tuptools_no_component_version-1.4.json.bin | 4 +- ...etuptools_no_component_version-1.4.xml.bin | 4 +- ..._component_setuptools_with_cpe-1.1.xml.bin | 4 +- ...component_setuptools_with_cpe-1.2.json.bin | 4 +- ..._component_setuptools_with_cpe-1.2.xml.bin | 4 +- ...component_setuptools_with_cpe-1.3.json.bin | 4 +- ..._component_setuptools_with_cpe-1.3.xml.bin | 4 +- ...component_setuptools_with_cpe-1.4.json.bin | 4 +- ..._component_setuptools_with_cpe-1.4.xml.bin | 4 +- ..._setuptools_with_release_notes-1.1.xml.bin | 4 +- ...setuptools_with_release_notes-1.2.json.bin | 4 +- ..._setuptools_with_release_notes-1.2.xml.bin | 4 +- ...setuptools_with_release_notes-1.3.json.bin | 4 +- ..._setuptools_with_release_notes-1.3.xml.bin | 4 +- ...setuptools_with_release_notes-1.4.json.bin | 4 +- ..._setuptools_with_release_notes-1.4.xml.bin | 4 +- ..._setuptools_with_vulnerability-1.1.xml.bin | 4 +- ...setuptools_with_vulnerability-1.2.json.bin | 4 +- ..._setuptools_with_vulnerability-1.2.xml.bin | 4 +- ...setuptools_with_vulnerability-1.3.json.bin | 4 +- ..._setuptools_with_vulnerability-1.3.xml.bin | 4 +- ...setuptools_with_vulnerability-1.4.json.bin | 4 +- ..._setuptools_with_vulnerability-1.4.xml.bin | 4 +- ..._bom_with_dependencies_hanging-1.1.xml.bin | 4 +- ...bom_with_dependencies_hanging-1.2.json.bin | 4 +- ..._bom_with_dependencies_hanging-1.2.xml.bin | 4 +- ...bom_with_dependencies_hanging-1.3.json.bin | 4 +- ..._bom_with_dependencies_hanging-1.3.xml.bin | 4 +- ...bom_with_dependencies_hanging-1.4.json.bin | 4 +- ..._bom_with_dependencies_hanging-1.4.xml.bin | 4 +- ...et_bom_with_dependencies_valid-1.1.xml.bin | 4 +- ...t_bom_with_dependencies_valid-1.2.json.bin | 4 +- ...et_bom_with_dependencies_valid-1.2.xml.bin | 4 +- ...t_bom_with_dependencies_valid-1.3.json.bin | 4 +- ...et_bom_with_dependencies_valid-1.3.xml.bin | 4 +- ...t_bom_with_dependencies_valid-1.4.json.bin | 4 +- ...et_bom_with_dependencies_valid-1.4.xml.bin | 4 +- .../get_bom_with_licenses-1.0.xml.bin | 20 ++ .../get_bom_with_licenses-1.1.xml.bin | 30 +++ .../get_bom_with_licenses-1.2.json.bin | 125 ++++++++++ .../get_bom_with_licenses-1.2.xml.bin | 82 +++++++ .../get_bom_with_licenses-1.3.json.bin | 132 +++++++++++ .../get_bom_with_licenses-1.3.xml.bin | 87 +++++++ .../get_bom_with_licenses-1.4.json.bin | 162 +++++++++++++ .../get_bom_with_licenses-1.4.xml.bin | 109 +++++++++ ...t_bom_with_licenses_expression-1.0.xml.bin | 4 + ...t_bom_with_licenses_expression-1.1.xml.bin | 4 + ..._bom_with_licenses_expression-1.2.json.bin | 17 ++ ...t_bom_with_licenses_expression-1.2.xml.bin | 13 + ..._bom_with_licenses_expression-1.3.json.bin | 22 ++ ...t_bom_with_licenses_expression-1.3.xml.bin | 16 ++ ..._bom_with_licenses_expression-1.4.json.bin | 56 +++++ ...t_bom_with_licenses_expression-1.4.xml.bin | 42 ++++ ...ta_component_and_dependencies-1.2.json.bin | 4 +- ...ata_component_and_dependencies-1.2.xml.bin | 4 +- ...ta_component_and_dependencies-1.3.json.bin | 4 +- ...ata_component_and_dependencies-1.3.xml.bin | 4 +- ...ta_component_and_dependencies-1.4.json.bin | 4 +- ...ata_component_and_dependencies-1.4.xml.bin | 4 +- ...get_bom_with_multiple_licenses-1.0.xml.bin | 10 + ...get_bom_with_multiple_licenses-1.1.xml.bin | 17 ++ ...et_bom_with_multiple_licenses-1.2.json.bin | 84 +++++++ ...get_bom_with_multiple_licenses-1.2.xml.bin | 57 +++++ ...et_bom_with_multiple_licenses-1.3.json.bin | 96 ++++++++ ...get_bom_with_multiple_licenses-1.3.xml.bin | 65 +++++ ...et_bom_with_multiple_licenses-1.4.json.bin | 128 ++++++++++ ...get_bom_with_multiple_licenses-1.4.xml.bin | 89 +++++++ .../get_bom_with_nested_services-1.2.json.bin | 4 +- .../get_bom_with_nested_services-1.2.xml.bin | 4 +- .../get_bom_with_nested_services-1.3.json.bin | 4 +- .../get_bom_with_nested_services-1.3.xml.bin | 4 +- .../get_bom_with_nested_services-1.4.json.bin | 4 +- .../get_bom_with_nested_services-1.4.xml.bin | 4 +- ...get_bom_with_services_complex-1.2.json.bin | 4 +- .../get_bom_with_services_complex-1.2.xml.bin | 4 +- ...get_bom_with_services_complex-1.3.json.bin | 4 +- .../get_bom_with_services_complex-1.3.xml.bin | 4 +- ...get_bom_with_services_complex-1.4.json.bin | 4 +- .../get_bom_with_services_complex-1.4.xml.bin | 4 +- tests/test_factory_license.py | 109 +++------ tests/test_model.py | 37 --- tests/test_model_bom.py | 15 +- tests/test_model_license.py | 117 +++++++++ 121 files changed, 2504 insertions(+), 594 deletions(-) create mode 100644 cyclonedx/model/license.py create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.0.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.1.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.2.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.2.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.3.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.3.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.4.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses-1.4.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.0.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.1.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.2.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.2.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.3.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.3.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.4.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_licenses_expression-1.4.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.0.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.1.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.xml.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.json.bin create mode 100644 tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.xml.bin create mode 100644 tests/test_model_license.py diff --git a/cyclonedx/factory/license.py b/cyclonedx/factory/license.py index 9974c046..d6446569 100644 --- a/cyclonedx/factory/license.py +++ b/cyclonedx/factory/license.py @@ -13,76 +13,59 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. -from typing import Optional +from typing import TYPE_CHECKING, Optional from ..exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException -from ..model import AttachedText, License, LicenseChoice, XsUri +from ..model.license import DisjunctiveLicense, LicenseExpression from ..spdx import fixup_id as spdx_fixup, is_compound_expression as is_spdx_compound_expression +if TYPE_CHECKING: # pragma: no cover + from ..model import AttachedText, XsUri + from ..model.license import License -class LicenseFactory: - """Factory for :class:`cyclonedx.model.License`.""" - - def make_from_string(self, name_or_spdx: str, *, - license_text: Optional[AttachedText] = None, - license_url: Optional[XsUri] = None) -> License: - """Make a :class:`cyclonedx.model.License` from a string.""" - try: - return self.make_with_id(name_or_spdx, text=license_text, url=license_url) - except InvalidSpdxLicenseException: - return self.make_with_name(name_or_spdx, text=license_text, url=license_url) - - def make_with_id(self, spdx_id: str, *, text: Optional[AttachedText] = None, - url: Optional[XsUri] = None) -> License: - """Make a :class:`cyclonedx.model.License` from an SPDX-ID. - - :raises InvalidSpdxLicenseException: if `spdx_id` was not known/supported SPDX-ID - """ - spdx_license_id = spdx_fixup(spdx_id) - if spdx_license_id is None: - raise InvalidSpdxLicenseException(spdx_id) - return License(id=spdx_license_id, text=text, url=url) - - def make_with_name(self, name: str, *, text: Optional[AttachedText] = None, url: Optional[XsUri] = None) -> License: - """Make a :class:`cyclonedx.model.License` with a name.""" - return License(name=name, text=text, url=url) +class LicenseFactory: + """Factory for :class:`cyclonedx.model.license.License`.""" -class LicenseChoiceFactory: - """Factory for :class:`cyclonedx.model.LicenseChoice`.""" - - def __init__(self, *, license_factory: LicenseFactory) -> None: - self.license_factory = license_factory - - def make_from_string(self, expression_or_name_or_spdx: str) -> LicenseChoice: - """Make a :class:`cyclonedx.model.LicenseChoice` from a string. - - Priority: SPDX license ID, SPDX license expression, named license - """ + def make_from_string(self, value: str, *, + license_text: Optional['AttachedText'] = None, + license_url: Optional['XsUri'] = None) -> 'License': + """Make a :class:`cyclonedx.model.license.License` from a string.""" try: - return LicenseChoice(license=self.license_factory.make_with_id(expression_or_name_or_spdx)) + return self.make_with_id(value, text=license_text, url=license_url) except InvalidSpdxLicenseException: pass try: - return self.make_with_compound_expression(expression_or_name_or_spdx) + return self.make_with_expression(value) except InvalidLicenseExpressionException: pass - return LicenseChoice(license=self.license_factory.make_with_name(expression_or_name_or_spdx)) + return self.make_with_name(value, text=license_text, url=license_url) - def make_with_compound_expression(self, compound_expression: str) -> LicenseChoice: - """Make a :class:`cyclonedx.model.LicenseChoice` with a compound expression. + def make_with_expression(self, expression: str) -> LicenseExpression: + """Make a :class:`cyclonedx.model.license.LicenseExpression` with a compound expression. Utilizes :func:`cyclonedx.spdx.is_compound_expression`. - :raises InvalidLicenseExpressionException: if `expression` is not known/supported license expression + :raises InvalidLicenseExpressionException: if param `value` is not known/supported license expression """ - if is_spdx_compound_expression(compound_expression): - return LicenseChoice(expression=compound_expression) - raise InvalidLicenseExpressionException(compound_expression) + if is_spdx_compound_expression(expression): + return LicenseExpression(expression) + raise InvalidLicenseExpressionException(expression) + + def make_with_id(self, spdx_id: str, *, + text: Optional['AttachedText'] = None, + url: Optional['XsUri'] = None) -> DisjunctiveLicense: + """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` from an SPDX-ID. + + :raises InvalidSpdxLicenseException: if param `spdx_id` was not known/supported SPDX-ID + """ + spdx_license_id = spdx_fixup(spdx_id) + if spdx_license_id is None: + raise InvalidSpdxLicenseException(spdx_id) + return DisjunctiveLicense(id=spdx_license_id, text=text, url=url) - def make_with_license(self, name_or_spdx: str, *, - license_text: Optional[AttachedText] = None, - license_url: Optional[XsUri] = None) -> LicenseChoice: - """Make a :class:`cyclonedx.model.LicenseChoice` with a license (name or SPDX-ID).""" - return LicenseChoice(license=self.license_factory.make_from_string( - name_or_spdx, license_text=license_text, license_url=license_url)) + def make_with_name(self, name: str, *, + text: Optional['AttachedText'] = None, + url: Optional['XsUri'] = None) -> DisjunctiveLicense: + """Make a :class:`cyclonedx.model.license.DisjunctiveLicense` with a name.""" + return DisjunctiveLicense(name=name, text=text, url=url) diff --git a/cyclonedx/model/__init__.py b/cyclonedx/model/__init__.py index 68ce6dc8..d30ad961 100644 --- a/cyclonedx/model/__init__.py +++ b/cyclonedx/model/__init__.py @@ -15,7 +15,6 @@ import hashlib import re -import warnings from datetime import datetime, timezone from enum import Enum from itertools import zip_longest @@ -28,7 +27,6 @@ from ..exception.model import ( InvalidLocaleTypeException, InvalidUriException, - MutuallyExclusivePropertiesException, NoPropertiesProvidedException, UnknownHashTypeException, ) @@ -444,20 +442,19 @@ def uri(self) -> str: return self._uri @classmethod - def serialize(cls, o: object) -> str: + def serialize(cls, o: Any) -> str: if isinstance(o, XsUri): return str(o) - raise ValueError(f'Attempt to serialize a non-XsUri: {o.__class__}') @classmethod - def deserialize(cls, o: object) -> 'XsUri': + def deserialize(cls, o: Any) -> 'XsUri': try: return XsUri(uri=str(o)) except ValueError: raise ValueError(f'XsUri string supplied ({o}) does not parse!') - def __eq__(self, other: object) -> bool: + def __eq__(self, other: Any) -> bool: if isinstance(other, XsUri): return hash(other) == hash(self) return False @@ -579,181 +576,6 @@ def __repr__(self) -> str: return f'' -@serializable.serializable_class -class License: - """ - This is our internal representation of `licenseType` complex type that can be used in multiple places within - a CycloneDX BOM document. - - .. note:: - See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseType - """ - - def __init__(self, *, id: Optional[str] = None, name: Optional[str] = None, - text: Optional[AttachedText] = None, url: Optional[XsUri] = None) -> None: - if not id and not name: - raise MutuallyExclusivePropertiesException('Either `id` or `name` MUST be supplied') - if id and name: - warnings.warn( - 'Both `id` and `name` have been supplied - `name` will be ignored!', - RuntimeWarning - ) - self.id = id - if not id: - self.name = name - else: - self.name = None - self.text = text - self.url = url - - @property - def id(self) -> Optional[str]: - """ - A valid SPDX license ID - - Returns: - `str` or `None` - """ - return self._id - - @id.setter - def id(self, id: Optional[str]) -> None: - self._id = id - - @property - def name(self) -> Optional[str]: - """ - If SPDX does not define the license used, this field may be used to provide the license name. - - Returns: - `str` or `None` - """ - return self._name - - @name.setter - def name(self, name: Optional[str]) -> None: - self._name = name - - @property - def text(self) -> Optional[AttachedText]: - """ - Specifies the optional full text of the attachment - - Returns: - `AttachedText` else `None` - """ - return self._text - - @text.setter - def text(self, text: Optional[AttachedText]) -> None: - self._text = text - - @property - def url(self) -> Optional[XsUri]: - """ - The URL to the attachment file. If the attachment is a license or BOM, an externalReference should also be - specified for completeness. - - Returns: - `XsUri` or `None` - """ - return self._url - - @url.setter - def url(self, url: Optional[XsUri]) -> None: - self._url = url - - def __eq__(self, other: object) -> bool: - if isinstance(other, License): - return hash(other) == hash(self) - return False - - def __lt__(self, other: Any) -> bool: - if isinstance(other, License): - return ComparableTuple((self.id, self.name)) < ComparableTuple((other.id, other.name)) - return NotImplemented - - def __hash__(self) -> int: - return hash((self.id, self.name, self.text, self.url)) - - def __repr__(self) -> str: - return f'' - - -@serializable.serializable_class -class LicenseChoice: - """ - This is our internal representation of `licenseChoiceType` complex type that can be used in multiple places within - a CycloneDX BOM document. - - .. note:: - See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseChoiceType - """ - - def __init__(self, *, license: Optional[License] = None, expression: Optional[str] = None) -> None: - if not license and not expression: - raise NoPropertiesProvidedException( - 'One of `license` or `expression` must be supplied - neither supplied' - ) - if license and expression: - warnings.warn( - 'Both `license` and `expression` have been supplied - `license` will take precedence', - RuntimeWarning - ) - self.license = license - if not license: - self.expression = expression - else: - self.expression = None - - @property - def license(self) -> Optional[License]: - """ - License definition - - Returns: - `License` or `None` - """ - return self._license - - @license.setter - def license(self, license: Optional[License]) -> None: - self._license = license - - @property - def expression(self) -> Optional[str]: - """ - A valid SPDX license expression (not enforced). - - Refer to https://spdx.org/specifications for syntax requirements. - - Returns: - `str` or `None` - """ - return self._expression - - @expression.setter - def expression(self, expression: Optional[str]) -> None: - self._expression = expression - - def __eq__(self, other: object) -> bool: - if isinstance(other, LicenseChoice): - return hash(other) == hash(self) - return False - - def __lt__(self, other: Any) -> bool: - if isinstance(other, LicenseChoice): - return ComparableTuple((self.license, self.expression)) < ComparableTuple( - (other.license, other.expression)) - return NotImplemented - - def __hash__(self) -> int: - return hash((self.license, self.expression)) - - def __repr__(self) -> str: - return f'' - - @serializable.serializable_class class Property: """ diff --git a/cyclonedx/model/bom.py b/cyclonedx/model/bom.py index f3c2a840..16c0d6c0 100644 --- a/cyclonedx/model/bom.py +++ b/cyclonedx/model/bom.py @@ -25,8 +25,6 @@ import serializable from sortedcontainers import SortedSet -from cyclonedx.serialization import UrnUuidHelper - from ..exception.model import LicenseExpressionAlongWithOthersException, UnknownComponentDependencyException from ..parser import BaseParser from ..schema.schema import ( @@ -36,19 +34,12 @@ SchemaVersion1Dot3, SchemaVersion1Dot4, ) -from . import ( - ExternalReference, - LicenseChoice, - OrganizationalContact, - OrganizationalEntity, - Property, - ThisTool, - Tool, - get_now_utc, -) +from ..serialization import LicenseRepositoryHelper, UrnUuidHelper +from . import ExternalReference, OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool, get_now_utc from .bom_ref import BomRef from .component import Component from .dependency import Dependable, Dependency +from .license import License, LicenseExpression, LicenseRepository from .service import Service from .vulnerability import Vulnerability @@ -69,7 +60,7 @@ def __init__(self, *, tools: Optional[Iterable[Tool]] = None, authors: Optional[Iterable[OrganizationalContact]] = None, component: Optional[Component] = None, manufacture: Optional[OrganizationalEntity] = None, supplier: Optional[OrganizationalEntity] = None, - licenses: Optional[Iterable[LicenseChoice]] = None, + licenses: Optional[Iterable[License]] = None, properties: Optional[Iterable[Property]] = None, timestamp: Optional[datetime] = None) -> None: self.timestamp = timestamp or get_now_utc() @@ -196,9 +187,9 @@ def supplier(self, supplier: Optional[OrganizationalEntity]) -> None: @property @serializable.view(SchemaVersion1Dot3) @serializable.view(SchemaVersion1Dot4) - @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.type_mapping(LicenseRepositoryHelper) @serializable.xml_sequence(7) - def licenses(self) -> "SortedSet[LicenseChoice]": + def licenses(self) -> LicenseRepository: """ A optional list of statements about how this BOM is licensed. @@ -208,8 +199,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": return self._licenses @licenses.setter - def licenses(self, licenses: Iterable[LicenseChoice]) -> None: - self._licenses = SortedSet(licenses) + def licenses(self, licenses: Iterable[License]) -> None: + self._licenses = LicenseRepository(licenses) @property @serializable.view(SchemaVersion1Dot3) @@ -585,7 +576,7 @@ def validate(self) -> bool: chain.from_iterable(c.get_all_nested_components(include_self=True) for c in self.components), self.services ): - if len(elem.licenses) > 1 and any(li.expression for li in elem.licenses): + if len(elem.licenses) > 1 and any(isinstance(li, LicenseExpression) for li in elem.licenses): raise LicenseExpressionAlongWithOthersException( f'Found LicenseExpression along with others licenses in: {elem!r}') diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 29e51eed..bf8501a9 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -33,7 +33,7 @@ SchemaVersion1Dot3, SchemaVersion1Dot4, ) -from ..serialization import BomRefHelper, PackageUrl +from ..serialization import BomRefHelper, LicenseRepositoryHelper, PackageUrl from . import ( AttachedText, ComparableTuple, @@ -42,7 +42,6 @@ HashAlgorithm, HashType, IdentifiableAction, - LicenseChoice, OrganizationalEntity, Property, XsUri, @@ -51,6 +50,7 @@ from .bom_ref import BomRef from .dependency import Dependable from .issue import IssueType +from .license import License, LicenseExpression, LicenseRepository from .release_note import ReleaseNotes @@ -182,7 +182,7 @@ class ComponentEvidence: See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_componentEvidenceType """ - def __init__(self, *, licenses: Optional[Iterable[LicenseChoice]] = None, + def __init__(self, *, licenses: Optional[Iterable[License]] = None, copyright: Optional[Iterable[Copyright]] = None) -> None: if not licenses and not copyright: raise NoPropertiesProvidedException( @@ -193,8 +193,8 @@ def __init__(self, *, licenses: Optional[Iterable[LicenseChoice]] = None, self.copyright = copyright or [] # type: ignore @property - @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'license') - def licenses(self) -> "SortedSet[LicenseChoice]": + @serializable.type_mapping(LicenseRepositoryHelper) + def licenses(self) -> LicenseRepository: """ Optional list of licenses obtained during analysis. @@ -204,8 +204,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": return self._licenses @licenses.setter - def licenses(self, licenses: Iterable[LicenseChoice]) -> None: - self._licenses = SortedSet(licenses) + def licenses(self, licenses: Iterable[License]) -> None: + self._licenses = LicenseRepository(licenses) @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'text') @@ -753,7 +753,7 @@ def __init__(self, *, name: str, type: ComponentType = ComponentType.LIBRARY, supplier: Optional[OrganizationalEntity] = None, author: Optional[str] = None, publisher: Optional[str] = None, group: Optional[str] = None, version: Optional[str] = None, description: Optional[str] = None, scope: Optional[ComponentScope] = None, - hashes: Optional[Iterable[HashType]] = None, licenses: Optional[Iterable[LicenseChoice]] = None, + hashes: Optional[Iterable[HashType]] = None, licenses: Optional[Iterable[License]] = None, copyright: Optional[str] = None, purl: Optional[PackageURL] = None, external_references: Optional[Iterable[ExternalReference]] = None, properties: Optional[Iterable[Property]] = None, release_notes: Optional[ReleaseNotes] = None, @@ -806,7 +806,7 @@ def __init__(self, *, name: str, type: ComponentType = ComponentType.LIBRARY, 'standard', DeprecationWarning ) if not licenses: - self.licenses = [LicenseChoice(expression=license_str)] # type: ignore + self.licenses = LicenseRepository([LicenseExpression(license_str)]) @property @serializable.xml_attribute() @@ -1027,9 +1027,9 @@ def hashes(self, hashes: Iterable[HashType]) -> None: @serializable.view(SchemaVersion1Dot2) @serializable.view(SchemaVersion1Dot3) @serializable.view(SchemaVersion1Dot4) - @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.type_mapping(LicenseRepositoryHelper) @serializable.xml_sequence(10) - def licenses(self) -> "SortedSet[LicenseChoice]": + def licenses(self) -> LicenseRepository: """ A optional list of statements about how this Component is licensed. @@ -1039,8 +1039,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": return self._licenses @licenses.setter - def licenses(self, licenses: Iterable[LicenseChoice]) -> None: - self._licenses = SortedSet(licenses) + def licenses(self, licenses: Iterable[License]) -> None: + self._licenses = LicenseRepository(licenses) @property @serializable.xml_sequence(11) diff --git a/cyclonedx/model/dependency.py b/cyclonedx/model/dependency.py index 317de161..ee40fa6d 100644 --- a/cyclonedx/model/dependency.py +++ b/cyclonedx/model/dependency.py @@ -30,14 +30,14 @@ class DependencyDependencies(serializable.BaseHelper): # type: ignore @classmethod - def serialize(cls, o: object) -> List[str]: + def serialize(cls, o: Any) -> List[str]: if isinstance(o, SortedSet): return list(map(lambda i: str(i.ref), o)) raise ValueError(f'Attempt to serialize a non-Dependency: {o.__class__}') @classmethod - def deserialize(cls, o: object) -> Set["Dependency"]: + def deserialize(cls, o: Any) -> Set["Dependency"]: dependencies: Set["Dependency"] = set() if isinstance(o, list): for v in o: diff --git a/cyclonedx/model/license.py b/cyclonedx/model/license.py new file mode 100644 index 00000000..70b76a8c --- /dev/null +++ b/cyclonedx/model/license.py @@ -0,0 +1,224 @@ +# encoding: utf-8 + +# 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 warnings +from typing import TYPE_CHECKING, Any, Optional, Union + +import serializable +from sortedcontainers import SortedSet + +from ..exception.model import MutuallyExclusivePropertiesException +from . import AttachedText, ComparableTuple, XsUri + +""" +License related things +""" + + +@serializable.serializable_class(name='license') +class DisjunctiveLicense: + """ + This is our internal representation of `licenseType` complex type that can be used in multiple places within + a CycloneDX BOM document. + + .. note:: + See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseType + """ + + def __init__(self, *, id: Optional[str] = None, name: Optional[str] = None, + text: Optional[AttachedText] = None, url: Optional[XsUri] = None) -> None: + if not id and not name: + raise MutuallyExclusivePropertiesException('Either `id` or `name` MUST be supplied') + if id and name: + warnings.warn( + 'Both `id` and `name` have been supplied - `name` will be ignored!', + RuntimeWarning + ) + self._id = id + self._name = name if not id else None + self._text = text + self._url = url + + @property + @serializable.xml_sequence(1) + def id(self) -> Optional[str]: + """ + A valid SPDX license ID + + Returns: + `str` or `None` + """ + return self._id + + @id.setter + def id(self, id: Optional[str]) -> None: + self._id = id + if id is not None: + self._name = None + + @property + @serializable.xml_sequence(1) + def name(self) -> Optional[str]: + """ + If SPDX does not define the license used, this field may be used to provide the license name. + + Returns: + `str` or `None` + """ + return self._name + + @name.setter + def name(self, name: Optional[str]) -> None: + self._name = name + if name is not None: + self._id = None + + @property + @serializable.xml_sequence(2) + def text(self) -> Optional[AttachedText]: + """ + Specifies the optional full text of the attachment + + Returns: + `AttachedText` else `None` + """ + return self._text + + @text.setter + def text(self, text: Optional[AttachedText]) -> None: + self._text = text + + @property + @serializable.xml_sequence(3) + def url(self) -> Optional[XsUri]: + """ + The URL to the attachment file. If the attachment is a license or BOM, an externalReference should also be + specified for completeness. + + Returns: + `XsUri` or `None` + """ + return self._url + + @url.setter + def url(self, url: Optional[XsUri]) -> None: + self._url = url + + def __eq__(self, other: object) -> bool: + if isinstance(other, DisjunctiveLicense): + return hash(other) == hash(self) + return False + + def __lt__(self, other: Any) -> bool: + if isinstance(other, DisjunctiveLicense): + return ComparableTuple((self._id, self._name)) < ComparableTuple((other._id, other._name)) + if isinstance(other, LicenseExpression): + return False # self after any LicenseExpression + return NotImplemented + + def __hash__(self) -> int: + return hash((self._id, self._name, self._text, self._url)) + + def __repr__(self) -> str: + return f'' + + +@serializable.serializable_class(name='expression') +class LicenseExpression: + """ + This is our internal representation of `licenseType`'s expression type that can be used in multiple places within + a CycloneDX BOM document. + + .. note:: + See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.4/xml/#type_licenseType + """ + + def __init__(self, value: str) -> None: + self._value = value + + @property + @serializable.xml_name('.') + @serializable.json_name('expression') + def value(self) -> str: + """ + Value of this LicenseExpression. + + Returns: + `str` + """ + return self._value + + @value.setter + def value(self, value: str) -> None: + self._value = value + + def __hash__(self) -> int: + return hash(self._value) + + def __eq__(self, other: object) -> bool: + if isinstance(other, LicenseExpression): + return self._value == other._value + return False + + def __lt__(self, other: Any) -> bool: + if isinstance(other, LicenseExpression): + return self._value < other._value + if isinstance(other, DisjunctiveLicense): + return True # self before any DisjunctiveLicense + return NotImplemented + + def __repr__(self) -> str: + return f'' + + +License = Union[LicenseExpression, DisjunctiveLicense] +"""TypeAlias for a union of supported license models. + +- :class:`LicenseExpression` +- :class:`DisjunctiveLicense` +""" + +if TYPE_CHECKING: # pragma: no cover + # workaround for https://github.com/python/mypy/issues/5264 + # this code path is taken when static code analysis or documentation tools runs through. + class LicenseRepository(SortedSet[License]): + """Collection of :class:`License`. + + This is a `set`, not a `list`. Order MUST NOT matter here. + If you wanted a certain order, then you should also express whether the items are concat by `AND` or `OR`. + If you wanted to do so, you should use :class:`LicenseExpression`. + + As a model, this MUST accept multiple :class:`LicenseExpression` along with + multiple :class:`DisjunctiveLicense`, as this was an accepted in CycloneDX JSON before v1.5. + So for modeling purposes, this is supported. + Denormalizers/deserializers will be thankful. + The normalization/serialization process SHOULD take care of these facts and do what is needed. + """ +else: + class LicenseRepository(SortedSet): + """Collection of :class:`License`. + + This is a `set`, not a `list`. Order MUST NOT matter here. + If you wanted a certain order, then you should also express whether the items are concat by `AND` or `OR`. + If you wanted to do so, you should use :class:`LicenseExpression`. + + As a model, this MUST accept multiple :class:`LicenseExpression` along with + multiple :class:`DisjunctiveLicense`, as this was an accepted in CycloneDX JSON before v1.5. + So for modeling purposes, this is supported. + Denormalizers/deserializers will be thankful. + The normalization/serialization process SHOULD take care of these facts and do what is needed. + """ diff --git a/cyclonedx/model/service.py b/cyclonedx/model/service.py index f681659a..b00b0714 100644 --- a/cyclonedx/model/service.py +++ b/cyclonedx/model/service.py @@ -18,20 +18,13 @@ import serializable from sortedcontainers import SortedSet -from cyclonedx.serialization import BomRefHelper +from cyclonedx.serialization import BomRefHelper, LicenseRepositoryHelper from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4 -from . import ( - ComparableTuple, - DataClassification, - ExternalReference, - LicenseChoice, - OrganizationalEntity, - Property, - XsUri, -) +from . import ComparableTuple, DataClassification, ExternalReference, OrganizationalEntity, Property, XsUri from .bom_ref import BomRef from .dependency import Dependable +from .license import License, LicenseRepository from .release_note import ReleaseNotes """ @@ -56,7 +49,7 @@ def __init__(self, *, name: str, bom_ref: Optional[Union[str, BomRef]] = None, group: Optional[str] = None, version: Optional[str] = None, description: Optional[str] = None, endpoints: Optional[Iterable[XsUri]] = None, authenticated: Optional[bool] = None, x_trust_boundary: Optional[bool] = None, data: Optional[Iterable[DataClassification]] = None, - licenses: Optional[Iterable[LicenseChoice]] = None, + licenses: Optional[Iterable[License]] = None, external_references: Optional[Iterable[ExternalReference]] = None, properties: Optional[Iterable[Property]] = None, services: Optional[Iterable['Service']] = None, @@ -245,9 +238,9 @@ def data(self, data: Iterable[DataClassification]) -> None: self._data = SortedSet(data) @property - @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses') + @serializable.type_mapping(LicenseRepositoryHelper) @serializable.xml_sequence(10) - def licenses(self) -> "SortedSet[LicenseChoice]": + def licenses(self) -> LicenseRepository: """ A optional list of statements about how this Service is licensed. @@ -257,8 +250,8 @@ def licenses(self) -> "SortedSet[LicenseChoice]": return self._licenses @licenses.setter - def licenses(self, licenses: Iterable[LicenseChoice]) -> None: - self._licenses = SortedSet(licenses) + def licenses(self, licenses: Iterable[License]) -> None: + self._licenses = LicenseRepository(licenses) @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') diff --git a/cyclonedx/schema/_res/__init__.py b/cyclonedx/schema/_res/__init__.py index d33cf695..2a672d03 100644 --- a/cyclonedx/schema/_res/__init__.py +++ b/cyclonedx/schema/_res/__init__.py @@ -14,16 +14,17 @@ # Copyright (c) OWASP Foundation. All Rights Reserved. -from os.path import dirname, join -from typing import Dict, Optional - -from .. import SchemaVersion - """ Content in here is internal, not for public use. Breaking changes without notice may happen. """ + +from os.path import dirname, join +from typing import Dict, Optional + +from .. import SchemaVersion + __DIR = dirname(__file__) BOM_XML: Dict[SchemaVersion, Optional[str]] = { diff --git a/cyclonedx/serialization/__init__.py b/cyclonedx/serialization/__init__.py index 094b57f8..7c547586 100644 --- a/cyclonedx/serialization/__init__.py +++ b/cyclonedx/serialization/__init__.py @@ -18,26 +18,33 @@ Set of helper classes for use with ``serializable`` when conducting (de-)serialization. """ +from json import loads as json_loads +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type from uuid import UUID +from xml.etree.ElementTree import Element # See https://github.com/package-url/packageurl-python/issues/65 from packageurl import PackageURL from serializable.helpers import BaseHelper from ..model.bom_ref import BomRef +from ..model.license import DisjunctiveLicense, LicenseExpression, LicenseRepository + +if TYPE_CHECKING: # pragma: no cover + from serializable import ViewType class BomRefHelper(BaseHelper): @classmethod - def serialize(cls, o: object) -> str: + def serialize(cls, o: Any) -> str: if isinstance(o, BomRef): return o.value raise ValueError(f'Attempt to serialize a non-BomRef: {o.__class__}') @classmethod - def deserialize(cls, o: object) -> BomRef: + def deserialize(cls, o: Any) -> BomRef: try: return BomRef(value=str(o)) except ValueError: @@ -47,14 +54,14 @@ def deserialize(cls, o: object) -> BomRef: class PackageUrl(BaseHelper): @classmethod - def serialize(cls, o: object) -> str: + def serialize(cls, o: Any, ) -> str: if isinstance(o, PackageURL): return str(o.to_string()) raise ValueError(f'Attempt to serialize a non-PackageURL: {o.__class__}') @classmethod - def deserialize(cls, o: object) -> PackageURL: + def deserialize(cls, o: Any) -> PackageURL: try: return PackageURL.from_string(purl=str(o)) except ValueError: @@ -64,15 +71,81 @@ def deserialize(cls, o: object) -> PackageURL: class UrnUuidHelper(BaseHelper): @classmethod - def serialize(cls, o: object) -> str: + def serialize(cls, o: Any) -> str: if isinstance(o, UUID): return o.urn raise ValueError(f'Attempt to serialize a non-UUID: {o.__class__}') @classmethod - def deserialize(cls, o: object) -> UUID: + def deserialize(cls, o: Any) -> UUID: try: return UUID(str(o)) except ValueError: raise ValueError(f'UUID string supplied ({o}) does not parse!') + + +class LicenseRepositoryHelper(BaseHelper): + @classmethod + def json_normalize(cls, o: LicenseRepository, *, + view: Optional[Type['ViewType']], + **__: Any) -> Any: + if len(o) == 0: + return None + expression = next((li for li in o if isinstance(li, LicenseExpression)), None) + if expression: + # mixed license expression and license? this is an invalid constellation according to schema! + # see https://github.com/CycloneDX/specification/pull/205 + # but models need to allow it for backwards compatibility with JSON CDX < 1.5 + return [{'expression': str(expression.value)}] + return [{'license': json_loads(li.as_json( # type:ignore[union-attr] + view_=view))} for li in o] + + @classmethod + def json_denormalize(cls, o: List[Dict[str, Any]], + **__: Any) -> LicenseRepository: + repo = LicenseRepository() + for li in o: + if 'license' in li: + repo.add(DisjunctiveLicense.from_json( # type:ignore[attr-defined] + li['license'])) + elif 'expression' in li: + repo.add(LicenseExpression(li['expression'])) + return repo + + @classmethod + def xml_normalize(cls, o: LicenseRepository, *, + element_name: str, + view: Optional[Type['ViewType']], + xmlns: Optional[str], + **__: Any) -> Optional[Element]: + if len(o) == 0: + return None + elem = Element(element_name) + expression = next((li for li in o if isinstance(li, LicenseExpression)), None) + if expression: + # mixed license expression and license? this is an invalid constellation according to schema! + # see https://github.com/CycloneDX/specification/pull/205 + # but models need to allow it for backwards compatibility with JSON CDX < 1.5 + elem.append(expression.as_xml( # type:ignore[attr-defined] + view, as_string=False, element_name='expression', xmlns=xmlns)) + else: + for li in o: + elem.append(li.as_xml( # type:ignore[union-attr] + view, as_string=False, element_name='license', xmlns=xmlns)) + return elem + + @classmethod + def xml_denormalize(cls, o: Element, + default_ns: Optional[str], + **__: Any) -> LicenseRepository: + repo = LicenseRepository() + for li in o: + tag = li.tag if default_ns is None else li.tag.replace(f'{{{default_ns}}}', '') + if tag == 'license': + repo.add(DisjunctiveLicense.from_xml( # type:ignore[attr-defined] + li, default_ns)) + elif tag == 'expression': + repo.add(LicenseExpression.from_xml( # type:ignore[attr-defined] + li, default_ns)) + return repo diff --git a/docs/conf.py b/docs/conf.py index f6672dbc..4225ce4e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.venv'] # -- Options for HTML output ------------------------------------------------- diff --git a/examples/complex.py b/examples/complex.py index 641c2ed2..e167d0af 100644 --- a/examples/complex.py +++ b/examples/complex.py @@ -20,7 +20,7 @@ from packageurl import PackageURL from cyclonedx.exception import MissingOptionalDependencyException -from cyclonedx.factory.license import LicenseChoiceFactory, LicenseFactory +from cyclonedx.factory.license import LicenseFactory from cyclonedx.model import OrganizationalEntity, XsUri from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component, ComponentType @@ -38,7 +38,7 @@ from cyclonedx.validation.xml import XmlValidator -lc_factory = LicenseChoiceFactory(license_factory=LicenseFactory()) +lc_factory = LicenseFactory() # region build the BOM diff --git a/pyproject.toml b/pyproject.toml index dbe4a874..e8045413 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ keywords = [ # ATTENTION: keep `deps.lowest.r` file in sync python = "^3.8" packageurl-python = ">= 0.11" -py-serializable = "^0.14.1" +py-serializable = "^0.15" sortedcontainers = "^2.4.0" license-expression = "^30" jsonschema = { version = "^4.18", extras=['format'], optional=true } diff --git a/tests/_data/models.py b/tests/_data/models.py index d1e6e2ba..6a1e178c 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -35,8 +35,6 @@ ExternalReference, ExternalReferenceType, HashType, - License, - LicenseChoice, Note, NoteText, OrganizationalContact, @@ -66,6 +64,7 @@ ImpactAnalysisState, ) from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource +from cyclonedx.model.license import DisjunctiveLicense, License, LicenseExpression from cyclonedx.model.release_note import ReleaseNotes from cyclonedx.model.service import Service from cyclonedx.model.vulnerability import ( @@ -104,7 +103,6 @@ 'a3f4096d-4211-4d68-9d2b-13973c86aca9', ) - BOM_SERIAL_NUMBER = UUID('1441d33a-e0fc-45b5-af3b-61ee52a88bac') BOM_TIMESTAMP = datetime.fromisoformat('2023-01-07 13:44:32.312678+00:00') @@ -269,11 +267,14 @@ def get_bom_just_complete_metadata() -> Bom: bom.metadata.component = get_component_setuptools_complete() bom.metadata.manufacture = get_org_entity_1() bom.metadata.supplier = get_org_entity_2() - bom.metadata.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') - ))] + bom.metadata.licenses = [DisjunctiveLicense( + id='Apache-2.0', + url=XsUri('https://www.apache.org/licenses/LICENSE-2.0.txt'), + text=AttachedText( + encoding=Encoding.BASE_64, + content='VGVzdCBjb250ZW50IC0gdGhpcyBpcyBub3QgdGhlIEFwYWNoZSAyLjAgbGljZW5zZSE=' + ) + )] bom.metadata.properties = get_properties_1() return bom @@ -309,9 +310,7 @@ def get_bom_with_services_complex() -> Bom: authenticated=False, x_trust_boundary=True, data=[ DataClassification(flow=DataFlow.OUTBOUND, classification='public') ], - licenses=[ - LicenseChoice(expression='Commercial') - ], + licenses=[DisjunctiveLicense(name='Commercial')], external_references=[ get_external_reference_1() ], @@ -339,9 +338,7 @@ def get_bom_with_nested_services() -> Bom: authenticated=False, x_trust_boundary=True, data=[ DataClassification(flow=DataFlow.OUTBOUND, classification='public') ], - licenses=[ - LicenseChoice(expression='Commercial') - ], + licenses=[DisjunctiveLicense(name='Commercial')], external_references=[ get_external_reference_1() ], @@ -480,7 +477,7 @@ def get_component_setuptools_simple( purl=PackageURL( type='pypi', name='setuptools', version='50.3.2', qualifiers='extension=tar.gz' ), - licenses=[LicenseChoice(expression='MIT License')], + licenses=[DisjunctiveLicense(id='MIT')], author='Test Author' ) @@ -491,7 +488,7 @@ def get_component_setuptools_simple_no_version(bom_ref: Optional[str] = None) -> purl=PackageURL( type='pypi', name='setuptools', qualifiers='extension=tar.gz' ), - licenses=[LicenseChoice(expression='MIT License')], + licenses=[DisjunctiveLicense(id='MIT')], author='Test Author' ) @@ -664,21 +661,43 @@ def get_vulnerability_source_owasp() -> VulnerabilitySource: return VulnerabilitySource(name='OWASP', url=XsUri('https://owasp.org')) +def get_bom_with_licenses() -> Bom: + return _makeBom( + metadata=BomMetaData( + licenses=[DisjunctiveLicense(id='CC-BY-1.0')], + component=Component(name='app', type=ComponentType.APPLICATION, bom_ref='my-app', + licenses=[DisjunctiveLicense(name='proprietary')]) + ), + components=[ + Component(name='c-with-expression', type=ComponentType.LIBRARY, bom_ref='C1', + licenses=[LicenseExpression(value='Apache-2.0 OR MIT')]), + Component(name='c-with-SPDX', type=ComponentType.LIBRARY, bom_ref='C2', + licenses=[DisjunctiveLicense(id='Apache-2.0')]), + Component(name='c-with-name', type=ComponentType.LIBRARY, bom_ref='C3', + licenses=[DisjunctiveLicense(name='(c) ACME Inc.')]), + ], + services=[ + Service(name='s-with-expression', bom_ref='S1', + licenses=[LicenseExpression(value='Apache-2.0 OR MIT')]), + Service(name='s-with-SPDX', bom_ref='S2', + licenses=[DisjunctiveLicense(id='Apache-2.0')]), + Service(name='s-with-name', bom_ref='S3', + licenses=[DisjunctiveLicense(name='(c) ACME Inc.')]), + ]) + + def get_bom_metadata_licenses_invalid() -> Bom: - return Bom(metadata=BomMetaData(licenses=[ - LicenseChoice(expression='Apache-2.0 OR MIT'), - LicenseChoice(license=License(id='MIT')), - ])) + return Bom(metadata=BomMetaData(licenses=get_invalid_license_repository())) -def get_invalid_license_repository() -> List[LicenseChoice]: +def get_invalid_license_repository() -> List[License]: """ license expression and a license -- this is an invalid constellation according to schema see https://github.com/CycloneDX/specification/pull/205 """ return [ - LicenseChoice(expression='Apache-2.0 OR MIT'), - LicenseChoice(license=License(id='GPL-2.0-only')), + LicenseExpression(value='Apache-2.0 OR MIT'), + DisjunctiveLicense(id='GPL-2.0-only'), ] @@ -717,6 +736,27 @@ def get_bom_service_licenses_invalid() -> Bom: ]) +def get_bom_with_multiple_licenses() -> Bom: + multi_licenses = ( + DisjunctiveLicense(id='MIT'), + DisjunctiveLicense(name='foo license'), + ) + return _makeBom( + metadata=BomMetaData( + licenses=multi_licenses, + component=Component(name='app', type=ComponentType.APPLICATION, bom_ref='my-app', + licenses=multi_licenses) + ), + components=[Component(name='comp', type=ComponentType.LIBRARY, bom_ref='my-compo', + licenses=multi_licenses)], + services=[Service(name='serv', bom_ref='my-serv', + licenses=multi_licenses)] + ) + + +# --- + + all_get_bom_funct_valid = tuple( (n, f) for n, f in getmembers(sys.modules[__name__], isfunction) if n.startswith('get_bom_') and not n.endswith('_invalid') @@ -728,6 +768,8 @@ def get_bom_service_licenses_invalid() -> Bom: ) all_get_bom_funct_with_incomplete_deps = { + # List of functions that return BOM with an incomplte dependency graph. + # It is expected that some process auto-fixes this before actual serialization takes place. get_bom_just_complete_metadata, get_bom_with_component_setuptools_basic, get_bom_with_component_setuptools_complete, @@ -741,4 +783,6 @@ def get_bom_service_licenses_invalid() -> Bom: get_bom_with_nested_services, get_bom_with_services_complex, get_bom_with_services_simple, + get_bom_with_licenses, + get_bom_with_multiple_licenses, } diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.json.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.json.bin index 7233c3c1..4d184212 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.json.bin @@ -25,7 +25,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -66,7 +68,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -77,7 +81,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -90,7 +96,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -111,7 +119,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -152,7 +162,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.xml.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.xml.bin index 75b6733e..9e4a71e0 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.2.xml.bin @@ -41,7 +41,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -56,7 +58,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -65,7 +69,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -76,7 +82,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -101,7 +109,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -143,7 +153,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.json.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.json.bin index 1a720cf2..4028367c 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.json.bin @@ -25,7 +25,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -88,7 +90,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -99,7 +103,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -112,7 +118,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -133,7 +141,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -180,7 +190,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.xml.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.xml.bin index 4b03243b..a657ab39 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.3.xml.bin @@ -41,7 +41,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -56,7 +58,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -65,7 +69,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -76,7 +82,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -104,7 +112,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -156,7 +166,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.json.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.json.bin index 60b9249a..1c5a5e40 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.json.bin @@ -25,7 +25,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -88,7 +90,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -99,7 +103,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -112,7 +118,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -132,7 +140,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -178,7 +188,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.xml.bin b/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.xml.bin index 98e51691..886b439a 100644 --- a/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_just_complete_metadata-1.4.xml.bin @@ -67,7 +67,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -82,7 +84,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -90,7 +94,9 @@ Test Author setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -100,7 +106,9 @@ Test Author setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -128,7 +136,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -180,7 +190,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.1.xml.bin index 0b33bec2..ba1ca960 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.json.bin index 9d0993c8..2f146446 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.xml.bin index ce6f3e6c..cb29e5ba 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.json.bin index 6e2f630e..a075deb2 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.xml.bin index 39cbf1d8..76ce40a0 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.json.bin index d3c70bb7..70616689 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.xml.bin index bef590f2..586abf6a 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_basic-1.4.xml.bin @@ -42,7 +42,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.1.xml.bin index cf2184b0..851c9d9b 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.1.xml.bin @@ -8,7 +8,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -19,7 +21,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -27,7 +31,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -37,7 +43,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -61,7 +69,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -99,7 +109,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.json.bin index 9cf4af5b..952801f9 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.json.bin @@ -9,7 +9,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -50,7 +52,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -61,7 +65,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -74,7 +80,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -95,7 +103,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -136,7 +146,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.xml.bin index 7c9f77b4..ab648c16 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.2.xml.bin @@ -32,7 +32,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -47,7 +49,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -56,7 +60,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -67,7 +73,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -92,7 +100,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -134,7 +144,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.json.bin index cf2dc3e1..d974b195 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.json.bin @@ -9,7 +9,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -72,7 +74,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -83,7 +87,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -96,7 +102,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -117,7 +125,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -164,7 +174,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.xml.bin index 316f5562..27861fd1 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.3.xml.bin @@ -32,7 +32,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -47,7 +49,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -56,7 +60,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -67,7 +73,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -95,7 +103,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -147,7 +157,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.json.bin index 525c924c..c19e9908 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.json.bin @@ -9,7 +9,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -72,7 +74,9 @@ ], "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -83,7 +87,9 @@ "bom-ref": "ccc8d7ee-4b9c-4750-aee0-a72585152291", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -96,7 +102,9 @@ "bom-ref": "8a3893b3-9923-4adb-a1d3-47456636ba0a", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -116,7 +124,9 @@ "bom-ref": "28b2d8ce-def0-446f-a221-58dee0b44acc", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", @@ -162,7 +172,9 @@ "bom-ref": "ded1d73e-1fca-4302-b520-f1bc53979958", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.xml.bin index 24b43c3f..02676140 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_complete-1.4.xml.bin @@ -58,7 +58,9 @@ This component is awesome required - MIT License + + MIT + Apache 2.0 baby! cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* @@ -73,7 +75,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -81,7 +85,9 @@ Test Author setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -91,7 +97,9 @@ Test Author setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz @@ -119,7 +127,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz @@ -171,7 +181,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.1.xml.bin index 4d6b504d..5bc27d5b 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.json.bin index 7bb0d1e1..556f871e 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.xml.bin index a993370c..39e16bf4 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.json.bin index 1fb453b4..0d3f3547 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.xml.bin index f12eaf36..b1198c98 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.json.bin index ac80eda9..a8f2ac87 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.xml.bin index a08ce14d..558c9be7 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_no_component_version-1.4.xml.bin @@ -41,7 +41,9 @@ Test Author setuptools - MIT License + + MIT + pkg:pypi/setuptools?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.1.xml.bin index 1ac5e156..21439ea9 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.json.bin index 2dd754f8..f9282117 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.json.bin @@ -6,7 +6,9 @@ "cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.xml.bin index 7a23d5d1..f8b4fd27 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.json.bin index 077f8b25..8ad7c766 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.json.bin @@ -6,7 +6,9 @@ "cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.xml.bin index a2d69905..457d800b 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.json.bin index 66f999c5..17799868 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.json.bin @@ -6,7 +6,9 @@ "cpe": "cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.xml.bin index a2c30d43..1cf91295 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_cpe-1.4.xml.bin @@ -42,7 +42,9 @@ setuptools 50.3.2 - MIT License + + MIT + cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:* pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.1.xml.bin index 0b33bec2..ba1ca960 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.json.bin index 9d0993c8..2f146446 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.xml.bin index ce6f3e6c..cb29e5ba 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.json.bin index 6e2f630e..a075deb2 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.xml.bin index 39cbf1d8..76ce40a0 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.json.bin index 6eed4f13..492ecc0b 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.xml.bin index 2d067b43..70852442 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_release_notes-1.4.xml.bin @@ -42,7 +42,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.1.xml.bin index 0b33bec2..ba1ca960 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.json.bin index 9d0993c8..2f146446 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.xml.bin index ce6f3e6c..cb29e5ba 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.json.bin index 6e2f630e..a075deb2 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.xml.bin index 39cbf1d8..76ce40a0 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.json.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.json.bin index 296520be..c1b42b96 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.xml.bin index 7ccc65eb..3326f3f9 100644 --- a/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_component_setuptools_with_vulnerability-1.4.xml.bin @@ -42,7 +42,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.1.xml.bin index ef886b52..2a2038c3 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.json.bin index c63b1b81..6dc68f3d 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "setuptools", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.xml.bin index 3d1ac1fb..39df1a3b 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.2.xml.bin @@ -20,7 +20,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.json.bin index 5ccacbb3..20919e4f 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "setuptools", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.xml.bin index 656e37d0..cb19113f 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.3.xml.bin @@ -20,7 +20,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.json.bin index abe58bd4..e860d1fb 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "setuptools", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.xml.bin index 5bea1c05..d13eaf18 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_hanging-1.4.xml.bin @@ -45,7 +45,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.1.xml.bin index 2f689777..9d446f67 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.1.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.1.xml.bin @@ -5,7 +5,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.json.bin index a9874292..44bde82b 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.xml.bin index 4fd57d86..45734322 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.2.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.json.bin index a44b69b8..945582d8 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.xml.bin index 6a5779c8..0ac9a56b 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.3.xml.bin @@ -16,7 +16,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.json.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.json.bin index 19e76581..4c0d4031 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.json.bin @@ -5,7 +5,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.xml.bin index 64970d15..10868bec 100644 --- a/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_dependencies_valid-1.4.xml.bin @@ -42,7 +42,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_licenses-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_licenses-1.0.xml.bin new file mode 100644 index 00000000..1b308eaf --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.0.xml.bin @@ -0,0 +1,20 @@ + + + + + c-with-SPDX + + false + + + c-with-expression + + false + + + c-with-name + + false + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_licenses-1.1.xml.bin new file mode 100644 index 00000000..97aa8eb3 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.1.xml.bin @@ -0,0 +1,30 @@ + + + + + c-with-SPDX + + + + Apache-2.0 + + + + + c-with-expression + + + Apache-2.0 OR MIT + + + + c-with-name + + + + (c) ACME Inc. + + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses-1.2.json.bin b/tests/_data/snapshots/get_bom_with_licenses-1.2.json.bin new file mode 100644 index 00000000..848073cd --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.2.json.bin @@ -0,0 +1,125 @@ +{ + "components": [ + { + "bom-ref": "C2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "c-with-SPDX", + "type": "library", + "version": "" + }, + { + "bom-ref": "C1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "c-with-expression", + "type": "library", + "version": "" + }, + { + "bom-ref": "C3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "c-with-name", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "C1" + }, + { + "ref": "C2" + }, + { + "ref": "C3" + }, + { + "ref": "S1" + }, + { + "ref": "S2" + }, + { + "ref": "S3" + }, + { + "ref": "my-app" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "name": "proprietary" + } + } + ], + "name": "app", + "type": "application", + "version": "" + }, + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "S2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "s-with-SPDX" + }, + { + "bom-ref": "S1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "s-with-expression" + }, + { + "bom-ref": "S3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "s-with-name" + } + ], + "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/_data/snapshots/get_bom_with_licenses-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_licenses-1.2.xml.bin new file mode 100644 index 00000000..3f67d83c --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.2.xml.bin @@ -0,0 +1,82 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + app + + + + proprietary + + + + + + + c-with-SPDX + + + + Apache-2.0 + + + + + c-with-expression + + + Apache-2.0 OR MIT + + + + c-with-name + + + + (c) ACME Inc. + + + + + + + s-with-SPDX + + + Apache-2.0 + + + + + s-with-expression + + Apache-2.0 OR MIT + + + + s-with-name + + + (c) ACME Inc. + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses-1.3.json.bin b/tests/_data/snapshots/get_bom_with_licenses-1.3.json.bin new file mode 100644 index 00000000..143c48ba --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.3.json.bin @@ -0,0 +1,132 @@ +{ + "components": [ + { + "bom-ref": "C2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "c-with-SPDX", + "type": "library", + "version": "" + }, + { + "bom-ref": "C1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "c-with-expression", + "type": "library", + "version": "" + }, + { + "bom-ref": "C3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "c-with-name", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "C1" + }, + { + "ref": "C2" + }, + { + "ref": "C3" + }, + { + "ref": "S1" + }, + { + "ref": "S2" + }, + { + "ref": "S3" + }, + { + "ref": "my-app" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "name": "proprietary" + } + } + ], + "name": "app", + "type": "application", + "version": "" + }, + "licenses": [ + { + "license": { + "id": "CC-BY-1.0" + } + } + ], + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "S2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "s-with-SPDX" + }, + { + "bom-ref": "S1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "s-with-expression" + }, + { + "bom-ref": "S3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "s-with-name" + } + ], + "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/_data/snapshots/get_bom_with_licenses-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_licenses-1.3.xml.bin new file mode 100644 index 00000000..89792211 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.3.xml.bin @@ -0,0 +1,87 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + app + + + + proprietary + + + + + + CC-BY-1.0 + + + + + + c-with-SPDX + + + + Apache-2.0 + + + + + c-with-expression + + + Apache-2.0 OR MIT + + + + c-with-name + + + + (c) ACME Inc. + + + + + + + s-with-SPDX + + + Apache-2.0 + + + + + s-with-expression + + Apache-2.0 OR MIT + + + + s-with-name + + + (c) ACME Inc. + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses-1.4.json.bin b/tests/_data/snapshots/get_bom_with_licenses-1.4.json.bin new file mode 100644 index 00000000..a4965e69 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.4.json.bin @@ -0,0 +1,162 @@ +{ + "components": [ + { + "bom-ref": "C2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "c-with-SPDX", + "type": "library" + }, + { + "bom-ref": "C1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "c-with-expression", + "type": "library" + }, + { + "bom-ref": "C3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "c-with-name", + "type": "library" + } + ], + "dependencies": [ + { + "ref": "C1" + }, + { + "ref": "C2" + }, + { + "ref": "C3" + }, + { + "ref": "S1" + }, + { + "ref": "S2" + }, + { + "ref": "S3" + }, + { + "ref": "my-app" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "name": "proprietary" + } + } + ], + "name": "app", + "type": "application" + }, + "licenses": [ + { + "license": { + "id": "CC-BY-1.0" + } + } + ], + "timestamp": "2023-01-07T13:44:32.312678+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": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "S2", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "s-with-SPDX" + }, + { + "bom-ref": "S1", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "name": "s-with-expression" + }, + { + "bom-ref": "S3", + "licenses": [ + { + "license": { + "name": "(c) ACME Inc." + } + } + ], + "name": "s-with-name" + } + ], + "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/_data/snapshots/get_bom_with_licenses-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_licenses-1.4.xml.bin new file mode 100644 index 00000000..2afc522a --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses-1.4.xml.bin @@ -0,0 +1,109 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + 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 + + + + + + app + + + proprietary + + + + + + CC-BY-1.0 + + + + + + c-with-SPDX + + + Apache-2.0 + + + + + c-with-expression + + Apache-2.0 OR MIT + + + + c-with-name + + + (c) ACME Inc. + + + + + + + s-with-SPDX + + + Apache-2.0 + + + + + s-with-expression + + Apache-2.0 OR MIT + + + + s-with-name + + + (c) ACME Inc. + + + + + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses_expression-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.0.xml.bin new file mode 100644 index 00000000..acb06612 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.0.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses_expression-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.1.xml.bin new file mode 100644 index 00000000..55ef5cda --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.1.xml.bin @@ -0,0 +1,4 @@ + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses_expression-1.2.json.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.2.json.bin new file mode 100644 index 00000000..1165e037 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.2.json.bin @@ -0,0 +1,17 @@ +{ + "metadata": { + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "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/_data/snapshots/get_bom_with_licenses_expression-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.2.xml.bin new file mode 100644 index 00000000..bc36ede0 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.2.xml.bin @@ -0,0 +1,13 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses_expression-1.3.json.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.3.json.bin new file mode 100644 index 00000000..1d765cdb --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.3.json.bin @@ -0,0 +1,22 @@ +{ + "metadata": { + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "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/_data/snapshots/get_bom_with_licenses_expression-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.3.xml.bin new file mode 100644 index 00000000..bb16ef4f --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.3.xml.bin @@ -0,0 +1,16 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + Apache-2.0 OR MIT + + + diff --git a/tests/_data/snapshots/get_bom_with_licenses_expression-1.4.json.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.4.json.bin new file mode 100644 index 00000000..d484276f --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.4.json.bin @@ -0,0 +1,56 @@ +{ + "metadata": { + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "timestamp": "2023-01-07T13:44:32.312678+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": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "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/_data/snapshots/get_bom_with_licenses_expression-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_licenses_expression-1.4.xml.bin new file mode 100644 index 00000000..0361c11f --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_licenses_expression-1.4.xml.bin @@ -0,0 +1,42 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + 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 + + + + + + Apache-2.0 OR MIT + + + diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.json.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.json.bin index 522592fa..8e1ad38b 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.json.bin @@ -38,7 +38,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.xml.bin index 0370a0e3..9f181dd3 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.2.xml.bin @@ -14,7 +14,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.json.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.json.bin index 2aae8fac..44d8a573 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.json.bin @@ -44,7 +44,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.xml.bin index 1b650130..6a0e37c3 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.3.xml.bin @@ -14,7 +14,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.json.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.json.bin index 531d42fc..6852fd3b 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.json.bin @@ -44,7 +44,9 @@ "bom-ref": "pkg:pypi/setuptools@50.3.2?extension=tar.gz", "licenses": [ { - "expression": "MIT License" + "license": { + "id": "MIT" + } } ], "name": "setuptools", diff --git a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.xml.bin index 1464249f..38c9e479 100644 --- a/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_metadata_component_and_dependencies-1.4.xml.bin @@ -40,7 +40,9 @@ setuptools 50.3.2 - MIT License + + MIT + pkg:pypi/setuptools@50.3.2?extension=tar.gz diff --git a/tests/_data/snapshots/get_bom_with_multiple_licenses-1.0.xml.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.0.xml.bin new file mode 100644 index 00000000..13a20245 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.0.xml.bin @@ -0,0 +1,10 @@ + + + + + comp + + false + + + diff --git a/tests/_data/snapshots/get_bom_with_multiple_licenses-1.1.xml.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.1.xml.bin new file mode 100644 index 00000000..27b2bce0 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.1.xml.bin @@ -0,0 +1,17 @@ + + + + + comp + + + + MIT + + + foo license + + + + + diff --git a/tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.json.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.json.bin new file mode 100644 index 00000000..19aadbf1 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.json.bin @@ -0,0 +1,84 @@ +{ + "components": [ + { + "bom-ref": "my-compo", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "comp", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "my-app" + }, + { + "ref": "my-compo" + }, + { + "ref": "my-serv" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "app", + "type": "application", + "version": "" + }, + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "my-serv", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "serv" + } + ], + "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/_data/snapshots/get_bom_with_multiple_licenses-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.xml.bin new file mode 100644 index 00000000..df26741d --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.2.xml.bin @@ -0,0 +1,57 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + app + + + + MIT + + + foo license + + + + + + + comp + + + + MIT + + + foo license + + + + + + + serv + + + MIT + + + foo license + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.json.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.json.bin new file mode 100644 index 00000000..1a6eba50 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.json.bin @@ -0,0 +1,96 @@ +{ + "components": [ + { + "bom-ref": "my-compo", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "comp", + "type": "library", + "version": "" + } + ], + "dependencies": [ + { + "ref": "my-app" + }, + { + "ref": "my-compo" + }, + { + "ref": "my-serv" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "app", + "type": "application", + "version": "" + }, + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "timestamp": "2023-01-07T13:44:32.312678+00:00", + "tools": [ + { + "name": "cyclonedx-python-lib", + "vendor": "CycloneDX", + "version": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "my-serv", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "serv" + } + ], + "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/_data/snapshots/get_bom_with_multiple_licenses-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.xml.bin new file mode 100644 index 00000000..1bb3e0ab --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.3.xml.bin @@ -0,0 +1,65 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + + app + + + + MIT + + + foo license + + + + + + MIT + + + foo license + + + + + + comp + + + + MIT + + + foo license + + + + + + + serv + + + MIT + + + foo license + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.json.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.json.bin new file mode 100644 index 00000000..b8751bb0 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.json.bin @@ -0,0 +1,128 @@ +{ + "components": [ + { + "bom-ref": "my-compo", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "comp", + "type": "library" + } + ], + "dependencies": [ + { + "ref": "my-app" + }, + { + "ref": "my-compo" + }, + { + "ref": "my-serv" + } + ], + "metadata": { + "component": { + "bom-ref": "my-app", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "app", + "type": "application" + }, + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "timestamp": "2023-01-07T13:44:32.312678+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": "TESTING" + } + ] + }, + "serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac", + "services": [ + { + "bom-ref": "my-serv", + "licenses": [ + { + "license": { + "id": "MIT" + } + }, + { + "license": { + "name": "foo license" + } + } + ], + "name": "serv" + } + ], + "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/_data/snapshots/get_bom_with_multiple_licenses-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.xml.bin new file mode 100644 index 00000000..7afbd2d7 --- /dev/null +++ b/tests/_data/snapshots/get_bom_with_multiple_licenses-1.4.xml.bin @@ -0,0 +1,89 @@ + + + + 2023-01-07T13:44:32.312678+00:00 + + + CycloneDX + cyclonedx-python-lib + TESTING + + + 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 + + + + + + app + + + MIT + + + foo license + + + + + + MIT + + + foo license + + + + + + comp + + + MIT + + + foo license + + + + + + + serv + + + MIT + + + foo license + + + + + + + + + + diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.2.json.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.2.json.bin index ae3197b7..e5ca79e9 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.2.json.bin @@ -52,7 +52,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.2.xml.bin index e01e6b3e..84fd1409 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.2.xml.bin @@ -43,7 +43,9 @@ public - Commercial + + Commercial + diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.3.json.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.3.json.bin index 9ab746db..9b89174d 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.3.json.bin @@ -58,7 +58,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.3.xml.bin index 00c1fd2b..29b5d27b 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.3.xml.bin @@ -43,7 +43,9 @@ public - Commercial + + Commercial + diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.4.json.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.4.json.bin index be23872d..f11014a5 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.4.json.bin @@ -92,7 +92,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_nested_services-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_nested_services-1.4.xml.bin index 986ed158..78c93eed 100644 --- a/tests/_data/snapshots/get_bom_with_nested_services-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_nested_services-1.4.xml.bin @@ -69,7 +69,9 @@ public - Commercial + + Commercial + diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.2.json.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.2.json.bin index 549dc40f..d66fe485 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.2.json.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.2.json.bin @@ -52,7 +52,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.2.xml.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.2.xml.bin index 7e6b4b9f..d03f661e 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.2.xml.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.2.xml.bin @@ -43,7 +43,9 @@ public - Commercial + + Commercial + diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.3.json.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.3.json.bin index 38ea8bb3..a4827614 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.3.json.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.3.json.bin @@ -58,7 +58,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.3.xml.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.3.xml.bin index 3725a725..136d59a1 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.3.xml.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.3.xml.bin @@ -43,7 +43,9 @@ public - Commercial + + Commercial + diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.4.json.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.4.json.bin index b4d58520..52ae938d 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.4.json.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.4.json.bin @@ -92,7 +92,9 @@ "group": "a-group", "licenses": [ { - "expression": "Commercial" + "license": { + "name": "Commercial" + } } ], "name": "my-first-service", diff --git a/tests/_data/snapshots/get_bom_with_services_complex-1.4.xml.bin b/tests/_data/snapshots/get_bom_with_services_complex-1.4.xml.bin index bb4f4a0f..3c9b6dbe 100644 --- a/tests/_data/snapshots/get_bom_with_services_complex-1.4.xml.bin +++ b/tests/_data/snapshots/get_bom_with_services_complex-1.4.xml.bin @@ -69,7 +69,9 @@ public - Commercial + + Commercial + diff --git a/tests/test_factory_license.py b/tests/test_factory_license.py index 022f6ac2..7d21a1d3 100644 --- a/tests/test_factory_license.py +++ b/tests/test_factory_license.py @@ -19,8 +19,9 @@ import unittest.mock from cyclonedx.exception.factory import InvalidLicenseExpressionException, InvalidSpdxLicenseException -from cyclonedx.factory.license import LicenseChoiceFactory, LicenseFactory -from cyclonedx.model import AttachedText, License, LicenseChoice, XsUri +from cyclonedx.factory.license import LicenseFactory +from cyclonedx.model import AttachedText, XsUri +from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression class TestFactoryLicense(unittest.TestCase): @@ -28,27 +29,38 @@ class TestFactoryLicense(unittest.TestCase): def test_make_from_string_with_id(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(id='bar', text=text, url=url) + expected = DisjunctiveLicense(id='bar', text=text, url=url) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'): - actual = LicenseFactory().make_from_string(name_or_spdx='foo', license_text=text, license_url=url) + with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'), \ + unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): + actual = LicenseFactory().make_from_string('foo', license_text=text, license_url=url) self.assertEqual(expected, actual) def test_make_from_string_with_name(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(name='foo', text=text, url=url) + expected = DisjunctiveLicense(name='foo', text=text, url=url) - with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None): - actual = LicenseFactory().make_from_string(name_or_spdx='foo', license_text=text, license_url=url) + with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None), \ + unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=False): + actual = LicenseFactory().make_from_string('foo', license_text=text, license_url=url) + + self.assertEqual(expected, actual) + + def test_make_from_string_with_expression(self) -> None: + expected = LicenseExpression('foo') + + with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value=None), \ + unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): + actual = LicenseFactory().make_from_string('foo') self.assertEqual(expected, actual) def test_make_with_id(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(id='bar', text=text, url=url) + expected = DisjunctiveLicense(id='bar', text=text, url=url) with unittest.mock.patch('cyclonedx.factory.license.spdx_fixup', return_value='bar'): actual = LicenseFactory().make_with_id(spdx_id='foo', text=text, url=url) @@ -63,80 +75,17 @@ def test_make_with_id_raises(self) -> None: def test_make_with_name(self) -> None: text = unittest.mock.NonCallableMock(spec=AttachedText) url = unittest.mock.NonCallableMock(spec=XsUri) - expected = License(name='foo', text=text, url=url) + expected = DisjunctiveLicense(name='foo', text=text, url=url) actual = LicenseFactory().make_with_name(name='foo', text=text, url=url) self.assertEqual(expected, actual) - -class TestFactoryLicenseChoice(unittest.TestCase): - - def test_make_from_string_with_license_id(self) -> None: - license_ = unittest.mock.NonCallableMock(spec=License) - expected = LicenseChoice(license=license_) - license_factory = unittest.mock.MagicMock(spec=LicenseFactory) - license_factory.make_with_id.return_value = license_ - factory = LicenseChoiceFactory(license_factory=license_factory) - - actual = factory.make_from_string('foo') - - self.assertEqual(expected, actual) - self.assertIs(license_, actual.license) - license_factory.make_with_id.assert_called_once_with('foo') - - def test_make_from_string_with_compound_expression(self) -> None: - expected = LicenseChoice(expression='foo') - license_factory = unittest.mock.MagicMock(spec=LicenseFactory) - license_factory.make_with_id.side_effect = InvalidSpdxLicenseException('foo') - factory = LicenseChoiceFactory(license_factory=license_factory) - - with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): - actual = factory.make_from_string('foo') - - self.assertEqual(expected, actual) - license_factory.make_with_id.assert_called_once_with('foo') - - def test_make_from_string_with_license_name(self) -> None: - license_ = unittest.mock.NonCallableMock(spec=License) - expected = LicenseChoice(license=license_) - license_factory = unittest.mock.MagicMock(spec=LicenseFactory) - license_factory.make_with_id.side_effect = InvalidSpdxLicenseException('foo') - license_factory.make_with_name.return_value = license_ - factory = LicenseChoiceFactory(license_factory=license_factory) - - with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=False): - actual = factory.make_from_string('foo') - - self.assertEqual(expected, actual) - self.assertIs(license_, actual.license) - license_factory.make_with_id.assert_called_once_with('foo') - license_factory.make_with_name.assert_called_once_with('foo') - - def test_make_with_compound_expression(self) -> None: - expected = LicenseChoice(expression='foo') - factory = LicenseChoiceFactory(license_factory=unittest.mock.MagicMock(spec=LicenseFactory)) - + def test_make_with_expression(self) -> None: + expected = LicenseExpression('foo') with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=True): - actual = factory.make_with_compound_expression('foo') - + actual = LicenseFactory().make_with_expression(expression='foo') self.assertEqual(expected, actual) - def test_make_with_compound_expression_raises(self) -> None: - factory = LicenseChoiceFactory(license_factory=unittest.mock.MagicMock(spec=LicenseFactory)) - with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=False): - with self.assertRaises(InvalidLicenseExpressionException, msg='foo'): - factory.make_with_compound_expression('foo') - - def test_make_with_license(self) -> None: - text = unittest.mock.NonCallableMock(spec=AttachedText) - url = unittest.mock.NonCallableMock(spec=XsUri) - license_ = unittest.mock.NonCallableMock(spec=License) - expected = LicenseChoice(license=license_) - license_factory = unittest.mock.MagicMock(spec=LicenseFactory) - license_factory.make_from_string.return_value = license_ - factory = LicenseChoiceFactory(license_factory=license_factory) - - actual = factory.make_with_license('foo', license_text=text, license_url=url) - - self.assertEqual(expected, actual) - self.assertIs(license_, actual.license) - license_factory.make_from_string.assert_called_once_with('foo', license_text=text, license_url=url) + def test_make_with_expression_raises(self) -> None: + with self.assertRaises(InvalidLicenseExpressionException, msg='foo'): + with unittest.mock.patch('cyclonedx.factory.license.is_spdx_compound_expression', return_value=False): + LicenseFactory().make_with_expression('foo') diff --git a/tests/test_model.py b/tests/test_model.py index af4e8ae8..97ffc4a9 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -35,8 +35,6 @@ HashAlgorithm, HashType, IdentifiableAction, - License, - LicenseChoice, Note, NoteText, OrganizationalContact, @@ -157,41 +155,6 @@ def test_compare_enum_then_none(self) -> None: self.assertNotEqual(tuple2, tuple1) -class TestModelLicense(TestCase): - - def test_sort(self) -> None: - # expected sort order: ([id], [name]) - expected_order = [1, 0, 3, 2] - licenses = [ - License(id='MIT'), - License(id='Apache-2.0'), - License(name='MIT'), - License(name='Apache-2.0'), - ] - sorted_licenses = sorted(licenses) - expected_licenses = reorder(licenses, expected_order) - self.assertListEqual(sorted_licenses, expected_licenses) - - -class TestModelLicenseChoice(TestCase): - - def test_sort(self) -> None: - license_a = License(id='Apache-2.0') - license_b = License(id='MIT') - - # expected sort order: ([license], [expression]) - expected_order = [1, 0, 3, 2] - licenses = [ - LicenseChoice(license=license_b), - LicenseChoice(license=license_a), - LicenseChoice(expression='MIT'), - LicenseChoice(expression='Apache-2.0'), - ] - sorted_licenses = sorted(licenses) - expected_licenses = reorder(licenses, expected_order) - self.assertListEqual(sorted_licenses, expected_licenses) - - class TestModelCopyright(TestCase): def test_same(self) -> None: diff --git a/tests/test_model_bom.py b/tests/test_model_bom.py index 0eb62d6c..8336f58e 100644 --- a/tests/test_model_bom.py +++ b/tests/test_model_bom.py @@ -23,18 +23,11 @@ from ddt import ddt, named_data from cyclonedx.exception.model import LicenseExpressionAlongWithOthersException -from cyclonedx.model import ( - License, - LicenseChoice, - OrganizationalContact, - OrganizationalEntity, - Property, - ThisTool, - Tool, -) +from cyclonedx.model import OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool from cyclonedx.model.bom import Bom, BomMetaData from cyclonedx.model.bom_ref import BomRef from cyclonedx.model.component import Component, ComponentType +from cyclonedx.model.license import DisjunctiveLicense from tests._data.models import ( get_bom_component_licenses_invalid, get_bom_component_nested_licenses_invalid, @@ -76,8 +69,8 @@ def test_basic_bom_metadata(self) -> None: manufacturer = OrganizationalEntity(name='test_manufacturer') supplier = OrganizationalEntity(name='test_supplier') licenses = [ - LicenseChoice(license=License(id='MIT')), - LicenseChoice(license=License(id='Apache-2.0')), + DisjunctiveLicense(id='MIT'), + DisjunctiveLicense(id='Apache-2.0'), ] properties = [ Property(name='property_1', value='value_1'), diff --git a/tests/test_model_license.py b/tests/test_model_license.py new file mode 100644 index 00000000..1ccfa534 --- /dev/null +++ b/tests/test_model_license.py @@ -0,0 +1,117 @@ +# 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. + + +from random import shuffle +from unittest import TestCase +from unittest.mock import MagicMock + +from cyclonedx.exception.model import MutuallyExclusivePropertiesException +from cyclonedx.model import AttachedText, XsUri +from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression +from tests import reorder + + +class TestModelDisjunctiveLicense(TestCase): + def test_create_complete_id(self) -> None: + text = MagicMock(spec=AttachedText) + url = MagicMock(spec=XsUri) + license = DisjunctiveLicense(id='foo', text=text, url=url) + self.assertEqual('foo', license.id) + self.assertIsNone(license.name) + self.assertIs(text, license.text) + self.assertIs(url, license.url) + + def test_update_id_name(self) -> None: + license = DisjunctiveLicense(id='foo') + self.assertEqual('foo', license.id) + self.assertIsNone(license.name) + license.name = 'bar' + self.assertIsNone(license.id) + self.assertEqual('bar', license.name) + + def test_create_complete_named(self) -> None: + text = MagicMock(spec=AttachedText) + url = MagicMock(spec=XsUri) + license = DisjunctiveLicense(name='foo', text=text, url=url) + self.assertIsNone(license.id) + self.assertEqual('foo', license.name) + self.assertIs(text, license.text) + self.assertIs(url, license.url) + + def test_update_name_id(self) -> None: + license = DisjunctiveLicense(name='foo') + self.assertEqual('foo', license.name) + self.assertIsNone(license.id) + license.id = 'bar' + self.assertIsNone(license.name) + self.assertEqual('bar', license.id) + + def test_throws_when_no_id_nor_name(self) -> None: + with self.assertRaises(MutuallyExclusivePropertiesException): + DisjunctiveLicense(id=None, name=None) + + def test_prefers_id_over_name(self) -> None: + with self.assertWarnsRegex( + RuntimeWarning, + 'Both `id` and `name` have been supplied - `name` will be ignored!'): + license = DisjunctiveLicense(id='foo', name='bar') + self.assertEqual('foo', license.id) + self.assertEqual(None, license.name) + + def test_equal(self) -> None: + a = DisjunctiveLicense(id='foo', name='bar') + b = DisjunctiveLicense(id='foo', name='bar') + c = DisjunctiveLicense(id='bar', name='foo') + self.assertEqual(a, b) + self.assertNotEqual(a, c) + self.assertNotEqual(a, 'foo') + + +class TestModelLicenseExpression(TestCase): + def test_create(self) -> None: + license = LicenseExpression('foo') + self.assertEqual('foo', license.value) + + def test_update(self) -> None: + license = LicenseExpression('foo') + self.assertEqual('foo', license.value) + license.value = 'bar' + self.assertEqual('bar', license.value) + + def test_equal(self) -> None: + a = LicenseExpression('foo') + b = LicenseExpression('foo') + c = LicenseExpression('bar') + self.assertEqual(a, b) + self.assertNotEqual(a, c) + self.assertNotEqual(a, 'foo') + + +class TestModelLicense(TestCase): + + def test_sort_mixed(self) -> None: + expected_order = [1, 2, 0] + licenses = [ + DisjunctiveLicense(name='my license'), + LicenseExpression(value='MIT or Apache-2.0'), + DisjunctiveLicense(id='MIT'), + ] + expected_licenses = reorder(licenses, expected_order) + shuffle(licenses) + sorted_licenses = sorted(licenses) + self.assertListEqual(sorted_licenses, expected_licenses)