Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add acknowledgement to LicenseExpression #582

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions cyclonedx/model/license.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
License related things
"""


from enum import Enum
from typing import TYPE_CHECKING, Any, Optional, Union
from warnings import warn

Expand All @@ -29,6 +29,7 @@

from .._internal.compare import ComparableTuple as _ComparableTuple
from ..exception.model import MutuallyExclusivePropertiesException
from ..schema.schema import SchemaVersion1Dot6
from . import AttachedText, XsUri


Expand Down Expand Up @@ -159,6 +160,24 @@ def __repr__(self) -> str:
return f'<License id={self._id!r}, name={self._name!r}>'


@serializable.serializable_enum
class LicenseExpressionAcknowledgement(str, Enum):
"""
This is our internal representation of the `type_licenseAcknowledgementEnumerationType` ENUM type
within the CycloneDX standard.

.. note::
Introduced in CycloneDX v1.6

.. note::
See the CycloneDX Schema for hashType:
https://cyclonedx.org/docs/1.6/#type_licenseAcknowledgementEnumerationType
"""

CONCLUDED = 'concluded'
DECLARED = 'declared'


@serializable.serializable_class(name='expression')
class LicenseExpression:
"""
Expand All @@ -170,8 +189,35 @@ class LicenseExpression:
https://cyclonedx.org/docs/1.4/json/#components_items_licenses_items_expression
"""

def __init__(self, value: str) -> None:
def __init__(self, value: str,
acknowledgement: Optional[LicenseExpressionAcknowledgement] = None) -> None:
self._value = value
self.acknowledgement = acknowledgement

@property
@serializable.view(SchemaVersion1Dot6)
@serializable.xml_attribute()
def acknowledgement(self) -> Optional[LicenseExpressionAcknowledgement]:
"""
Declared licenses and concluded licenses represent two different stages in the licensing process within
software development.

Declared licenses refer to the initial intention of the software authors regarding the
licensing terms under which their code is released. On the other hand, concluded licenses are the result of a
comprehensive analysis of the project's codebase to identify and confirm the actual licenses of the components
used, which may differ from the initially declared licenses. While declared licenses provide an upfront
indication of the licensing intentions, concluded licenses offer a more thorough understanding of the actual
licensing within a project, facilitating proper compliance and risk management. Observed licenses are defined
in evidence.licenses. Observed licenses form the evidence necessary to substantiate a concluded license.

Returns:
`LicenseExpressionAcknowledgement` or `None`
"""
return self._acknowledgement

@acknowledgement.setter
def acknowledgement(self, acknowledgement: Optional[LicenseExpressionAcknowledgement]) -> None:
self._acknowledgement = acknowledgement

@property
@serializable.xml_name('.')
Expand Down
8 changes: 5 additions & 3 deletions cyclonedx/serialization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""
Set of helper classes for use with ``serializable`` when conducting (de-)serialization.
"""

import json
from json import loads as json_loads
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type
from uuid import UUID
Expand Down Expand Up @@ -104,7 +104,7 @@ def json_normalize(cls, o: LicenseRepository, *,
# 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 [json.loads(expression.as_json(view_=view))] # type:ignore[attr-defined]
return [
{'license': json_loads(
li.as_json( # type:ignore[attr-defined]
Expand All @@ -123,7 +123,9 @@ def json_denormalize(cls, o: List[Dict[str, Any]],
repo.add(DisjunctiveLicense.from_json( # type:ignore[attr-defined]
li['license']))
elif 'expression' in li:
repo.add(LicenseExpression(li['expression']))
repo.add(LicenseExpression.from_json( # type:ignore[attr-defined]
li
))
else:
raise CycloneDxDeserializationException(f'unexpected: {li!r}')
return repo
Expand Down
8 changes: 5 additions & 3 deletions tests/_data/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
ImpactAnalysisState,
)
from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource
from cyclonedx.model.license import DisjunctiveLicense, License, LicenseExpression
from cyclonedx.model.license import DisjunctiveLicense, License, LicenseExpression, LicenseExpressionAcknowledgement
from cyclonedx.model.release_note import ReleaseNotes
from cyclonedx.model.service import Service
from cyclonedx.model.vulnerability import (
Expand Down Expand Up @@ -946,15 +946,17 @@ def get_bom_with_licenses() -> Bom:
),
components=[
Component(name='c-with-expression', type=ComponentType.LIBRARY, bom_ref='C1',
licenses=[LicenseExpression(value='Apache-2.0 OR MIT')]),
licenses=[LicenseExpression(value='Apache-2.0 OR MIT',
acknowledgement=LicenseExpressionAcknowledgement.CONCLUDED)]),
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')]),
licenses=[LicenseExpression(value='Apache-2.0 OR MIT',
acknowledgement=LicenseExpressionAcknowledgement.DECLARED)]),
Service(name='s-with-SPDX', bom_ref='S2',
licenses=[DisjunctiveLicense(id='Apache-2.0')]),
Service(name='s-with-name', bom_ref='S3',
Expand Down
2 changes: 2 additions & 0 deletions tests/_data/snapshots/get_bom_with_licenses-1.6.json.bin
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"bom-ref": "C1",
"licenses": [
{
"acknowledgement": "concluded",
"expression": "Apache-2.0 OR MIT"
}
],
Expand Down Expand Up @@ -138,6 +139,7 @@
"bom-ref": "S1",
"licenses": [
{
"acknowledgement": "declared",
"expression": "Apache-2.0 OR MIT"
}
],
Expand Down
4 changes: 2 additions & 2 deletions tests/_data/snapshots/get_bom_with_licenses-1.6.xml.bin
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<component type="library" bom-ref="C1">
<name>c-with-expression</name>
<licenses>
<expression>Apache-2.0 OR MIT</expression>
<expression acknowledgement="concluded">Apache-2.0 OR MIT</expression>
</licenses>
</component>
<component type="library" bom-ref="C3">
Expand All @@ -85,7 +85,7 @@
<service bom-ref="S1">
<name>s-with-expression</name>
<licenses>
<expression>Apache-2.0 OR MIT</expression>
<expression acknowledgement="declared">Apache-2.0 OR MIT</expression>
</licenses>
</service>
<service bom-ref="S3">
Expand Down
Loading