Skip to content

Commit ddd7847

Browse files
authored
add acknowledgement to LicenseExpression (#582)
Signed-off-by: Paul Horton <[email protected]>
1 parent 5c97c2d commit ddd7847

File tree

5 files changed

+62
-10
lines changed

5 files changed

+62
-10
lines changed

cyclonedx/model/license.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
License related things
2121
"""
2222

23-
23+
from enum import Enum
2424
from typing import TYPE_CHECKING, Any, Optional, Union
2525
from warnings import warn
2626

@@ -29,6 +29,7 @@
2929

3030
from .._internal.compare import ComparableTuple as _ComparableTuple
3131
from ..exception.model import MutuallyExclusivePropertiesException
32+
from ..schema.schema import SchemaVersion1Dot6
3233
from . import AttachedText, XsUri
3334

3435

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

161162

163+
@serializable.serializable_enum
164+
class LicenseExpressionAcknowledgement(str, Enum):
165+
"""
166+
This is our internal representation of the `type_licenseAcknowledgementEnumerationType` ENUM type
167+
within the CycloneDX standard.
168+
169+
.. note::
170+
Introduced in CycloneDX v1.6
171+
172+
.. note::
173+
See the CycloneDX Schema for hashType:
174+
https://cyclonedx.org/docs/1.6/#type_licenseAcknowledgementEnumerationType
175+
"""
176+
177+
CONCLUDED = 'concluded'
178+
DECLARED = 'declared'
179+
180+
162181
@serializable.serializable_class(name='expression')
163182
class LicenseExpression:
164183
"""
@@ -170,8 +189,35 @@ class LicenseExpression:
170189
https://cyclonedx.org/docs/1.4/json/#components_items_licenses_items_expression
171190
"""
172191

173-
def __init__(self, value: str) -> None:
192+
def __init__(self, value: str,
193+
acknowledgement: Optional[LicenseExpressionAcknowledgement] = None) -> None:
174194
self._value = value
195+
self.acknowledgement = acknowledgement
196+
197+
@property
198+
@serializable.view(SchemaVersion1Dot6)
199+
@serializable.xml_attribute()
200+
def acknowledgement(self) -> Optional[LicenseExpressionAcknowledgement]:
201+
"""
202+
Declared licenses and concluded licenses represent two different stages in the licensing process within
203+
software development.
204+
205+
Declared licenses refer to the initial intention of the software authors regarding the
206+
licensing terms under which their code is released. On the other hand, concluded licenses are the result of a
207+
comprehensive analysis of the project's codebase to identify and confirm the actual licenses of the components
208+
used, which may differ from the initially declared licenses. While declared licenses provide an upfront
209+
indication of the licensing intentions, concluded licenses offer a more thorough understanding of the actual
210+
licensing within a project, facilitating proper compliance and risk management. Observed licenses are defined
211+
in evidence.licenses. Observed licenses form the evidence necessary to substantiate a concluded license.
212+
213+
Returns:
214+
`LicenseExpressionAcknowledgement` or `None`
215+
"""
216+
return self._acknowledgement
217+
218+
@acknowledgement.setter
219+
def acknowledgement(self, acknowledgement: Optional[LicenseExpressionAcknowledgement]) -> None:
220+
self._acknowledgement = acknowledgement
175221

176222
@property
177223
@serializable.xml_name('.')

cyclonedx/serialization/__init__.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"""
1818
Set of helper classes for use with ``serializable`` when conducting (de-)serialization.
1919
"""
20-
20+
import json
2121
from json import loads as json_loads
2222
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type
2323
from uuid import UUID
@@ -104,7 +104,7 @@ def json_normalize(cls, o: LicenseRepository, *,
104104
# mixed license expression and license? this is an invalid constellation according to schema!
105105
# see https://github.com/CycloneDX/specification/pull/205
106106
# but models need to allow it for backwards compatibility with JSON CDX < 1.5
107-
return [{'expression': str(expression.value)}]
107+
return [json.loads(expression.as_json(view_=view))] # type:ignore[attr-defined]
108108
return [
109109
{'license': json_loads(
110110
li.as_json( # type:ignore[attr-defined]
@@ -123,7 +123,9 @@ def json_denormalize(cls, o: List[Dict[str, Any]],
123123
repo.add(DisjunctiveLicense.from_json( # type:ignore[attr-defined]
124124
li['license']))
125125
elif 'expression' in li:
126-
repo.add(LicenseExpression(li['expression']))
126+
repo.add(LicenseExpression.from_json( # type:ignore[attr-defined]
127+
li
128+
))
127129
else:
128130
raise CycloneDxDeserializationException(f'unexpected: {li!r}')
129131
return repo

tests/_data/models.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
ImpactAnalysisState,
8787
)
8888
from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource
89-
from cyclonedx.model.license import DisjunctiveLicense, License, LicenseExpression
89+
from cyclonedx.model.license import DisjunctiveLicense, License, LicenseExpression, LicenseExpressionAcknowledgement
9090
from cyclonedx.model.release_note import ReleaseNotes
9191
from cyclonedx.model.service import Service
9292
from cyclonedx.model.vulnerability import (
@@ -946,15 +946,17 @@ def get_bom_with_licenses() -> Bom:
946946
),
947947
components=[
948948
Component(name='c-with-expression', type=ComponentType.LIBRARY, bom_ref='C1',
949-
licenses=[LicenseExpression(value='Apache-2.0 OR MIT')]),
949+
licenses=[LicenseExpression(value='Apache-2.0 OR MIT',
950+
acknowledgement=LicenseExpressionAcknowledgement.CONCLUDED)]),
950951
Component(name='c-with-SPDX', type=ComponentType.LIBRARY, bom_ref='C2',
951952
licenses=[DisjunctiveLicense(id='Apache-2.0')]),
952953
Component(name='c-with-name', type=ComponentType.LIBRARY, bom_ref='C3',
953954
licenses=[DisjunctiveLicense(name='(c) ACME Inc.')]),
954955
],
955956
services=[
956957
Service(name='s-with-expression', bom_ref='S1',
957-
licenses=[LicenseExpression(value='Apache-2.0 OR MIT')]),
958+
licenses=[LicenseExpression(value='Apache-2.0 OR MIT',
959+
acknowledgement=LicenseExpressionAcknowledgement.DECLARED)]),
958960
Service(name='s-with-SPDX', bom_ref='S2',
959961
licenses=[DisjunctiveLicense(id='Apache-2.0')]),
960962
Service(name='s-with-name', bom_ref='S3',

tests/_data/snapshots/get_bom_with_licenses-1.6.json.bin

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"bom-ref": "C1",
1717
"licenses": [
1818
{
19+
"acknowledgement": "concluded",
1920
"expression": "Apache-2.0 OR MIT"
2021
}
2122
],
@@ -138,6 +139,7 @@
138139
"bom-ref": "S1",
139140
"licenses": [
140141
{
142+
"acknowledgement": "declared",
141143
"expression": "Apache-2.0 OR MIT"
142144
}
143145
],

tests/_data/snapshots/get_bom_with_licenses-1.6.xml.bin

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
<component type="library" bom-ref="C1">
6262
<name>c-with-expression</name>
6363
<licenses>
64-
<expression>Apache-2.0 OR MIT</expression>
64+
<expression acknowledgement="concluded">Apache-2.0 OR MIT</expression>
6565
</licenses>
6666
</component>
6767
<component type="library" bom-ref="C3">
@@ -85,7 +85,7 @@
8585
<service bom-ref="S1">
8686
<name>s-with-expression</name>
8787
<licenses>
88-
<expression>Apache-2.0 OR MIT</expression>
88+
<expression acknowledgement="declared">Apache-2.0 OR MIT</expression>
8989
</licenses>
9090
</service>
9191
<service bom-ref="S3">

0 commit comments

Comments
 (0)