Skip to content
Open
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
347 changes: 336 additions & 11 deletions cyclonedx/model/license.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"""

from collections.abc import Iterable
from datetime import datetime
from enum import Enum
from json import loads as json_loads
from typing import TYPE_CHECKING, Any, Optional, Union
Expand All @@ -37,6 +38,7 @@
from ..schema.schema import SchemaVersion1Dot5, SchemaVersion1Dot6, SchemaVersion1Dot7
from . import AttachedText, Property, XsUri
from .bom_ref import BomRef
from .contact import OrganizationalContact, OrganizationalEntity


@serializable.serializable_enum
Expand Down Expand Up @@ -67,6 +69,319 @@ class LicenseAcknowledgement(str, Enum):
"""


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

.. note::
Introduced in CycloneDX v1.5

.. note::
See the CycloneDX Schema:
https://cyclonedx.org/docs/1.7/json/#metadata_tools_oneOf_i0_components_items_licenses_items_oneOf_i0_license_licensing_licenseTypes
"""

ACADEMIC = 'academic'
APPLIANCE = 'appliance'
CLIENT_ACCESS = 'client-access'
CONCURRENT_USER = 'concurrent-user'
CORE_POINTS = 'core-points'
CUSTOM_METRIC = 'custom-metric'
DEVICE = 'device'
EVALUATION = 'evaluation'
NAMED_USER = 'named-user'
NODE_LOCKED = 'node-locked'
OEM = 'oem'
PERPETUAL = 'perpetual'
PROCESSOR_POINTS = 'processor-points'
SUBSCRIPTION = 'subscription'
USER = 'user'
OTHER = 'other'


@serializable.serializable_class(ignore_unknown_during_deserialization=True)
class LicenseEntity:
"""
This is our internal representation of the licensor/licensee/purchaser type
within the CycloneDX standard.

Exactly one of ``organization`` or ``individual`` MUST be provided.

.. note::
Introduced in CycloneDX v1.5

.. note::
See the CycloneDX Schema definition:
https://cyclonedx.org/docs/1.7/json/#metadata_tools_oneOf_i0_components_items_licenses_items_oneOf_i0_license_licensing_licensor
"""

def __init__(
self, *,
organization: Optional[OrganizationalEntity] = None,
individual: Optional[OrganizationalContact] = None,
) -> None:
if not organization and not individual:
raise MutuallyExclusivePropertiesException(
'Either `organization` or `individual` MUST be supplied'
)
if organization and individual:
raise MutuallyExclusivePropertiesException(
'Only one of `organization` or `individual` MUST be supplied - not both'
)
self._organization = organization
self._individual = individual

@property
@serializable.xml_sequence(1)
def organization(self) -> Optional[OrganizationalEntity]:
"""
The organization.

Returns:
`OrganizationalEntity` or `None`
"""
return self._organization

@organization.setter
def organization(self, organization: Optional[OrganizationalEntity]) -> None:
self._organization = organization
if organization is not None:
self._individual = None

@property
@serializable.xml_sequence(2)
def individual(self) -> Optional[OrganizationalContact]:
"""
The individual.

Returns:
`OrganizationalContact` or `None`
"""
return self._individual

@individual.setter
def individual(self, individual: Optional[OrganizationalContact]) -> None:
self._individual = individual
if individual is not None:
self._organization = None

def __comparable_tuple(self) -> _ComparableTuple:
return _ComparableTuple((
self._organization, self._individual,
))

def __eq__(self, other: object) -> bool:
if isinstance(other, LicenseEntity):
return self.__comparable_tuple() == other.__comparable_tuple()
return False

def __lt__(self, other: Any) -> bool:
if isinstance(other, LicenseEntity):
return self.__comparable_tuple() < other.__comparable_tuple()
return NotImplemented

def __hash__(self) -> int:
return hash(self.__comparable_tuple())

def __repr__(self) -> str:
return f'<LicenseEntity organization={self._organization!r}, individual={self._individual!r}>'


@serializable.serializable_class(ignore_unknown_during_deserialization=True)
class Licensing:
"""
This is our internal representation of the `licensingType` complex type
within the CycloneDX standard.

Licensing details describing the licensor/licensee, license type, renewal
and expiration dates, and other important metadata.

.. note::
Introduced in CycloneDX v1.5

.. note::
See the CycloneDX Schema definition:
https://cyclonedx.org/docs/1.7/json/#metadata_tools_oneOf_i0_components_items_licenses_items_oneOf_i0_license_licensing
"""

def __init__(
self, *,
alt_ids: Optional[Iterable[str]] = None,
licensor: Optional[LicenseEntity] = None,
licensee: Optional[LicenseEntity] = None,
purchaser: Optional[LicenseEntity] = None,
purchase_order: Optional[str] = None,
license_types: Optional[Iterable[LicenseType]] = None,
last_renewal: Optional[datetime] = None,
expiration: Optional[datetime] = None,
) -> None:
self.alt_ids = alt_ids or []
self.licensor = licensor
self.licensee = licensee
self.purchaser = purchaser
self.purchase_order = purchase_order
self.license_types = license_types or []
self.last_renewal = last_renewal
self.expiration = expiration

@property
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'altId')
@serializable.xml_sequence(1)
def alt_ids(self) -> 'SortedSet[str]':
"""
License identifiers that may be used to manage licenses and their lifecycle.

Returns:
`SortedSet[str]`
"""
return self._alt_ids

@alt_ids.setter
def alt_ids(self, alt_ids: Iterable[str]) -> None:
self._alt_ids = SortedSet(alt_ids)

@property
@serializable.xml_sequence(2)
def licensor(self) -> Optional[LicenseEntity]:
"""
The individual or organization that grants a license to another individual or organization.

Returns:
`LicenseEntity` or `None`
"""
return self._licensor

@licensor.setter
def licensor(self, licensor: Optional[LicenseEntity]) -> None:
self._licensor = licensor

@property
@serializable.xml_sequence(3)
def licensee(self) -> Optional[LicenseEntity]:
"""
The individual or organization for which a license was granted to.

Returns:
`LicenseEntity` or `None`
"""
return self._licensee

@licensee.setter
def licensee(self, licensee: Optional[LicenseEntity]) -> None:
self._licensee = licensee

@property
@serializable.xml_sequence(4)
def purchaser(self) -> Optional[LicenseEntity]:
"""
The individual or organization that purchased the license.

Returns:
`LicenseEntity` or `None`
"""
return self._purchaser

@purchaser.setter
def purchaser(self, purchaser: Optional[LicenseEntity]) -> None:
self._purchaser = purchaser

@property
@serializable.xml_sequence(5)
def purchase_order(self) -> Optional[str]:
"""
The purchase order identifier the purchaser sent to a supplier or vendor to
authorize a purchase.

Returns:
`str` or `None`
"""
return self._purchase_order

@purchase_order.setter
def purchase_order(self, purchase_order: Optional[str]) -> None:
self._purchase_order = purchase_order

@property
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'licenseType')
@serializable.xml_sequence(6)
def license_types(self) -> 'SortedSet[LicenseType]':
"""
The type of license(s) that was granted to the licensee.

Returns:
`SortedSet[LicenseType]`
"""
return self._license_types

@license_types.setter
def license_types(self, license_types: Iterable[LicenseType]) -> None:
self._license_types = SortedSet(license_types)

@property
@serializable.type_mapping(serializable.helpers.XsdDateTime)
@serializable.xml_sequence(7)
def last_renewal(self) -> Optional[datetime]:
"""
The timestamp indicating when the license was last renewed. For new purchases, this is
often the purchase or acquisition date. For non-perpetual licenses or subscriptions, this
is the timestamp of when the license was last renewed.

Returns:
`datetime` or `None`
"""
return self._last_renewal

@last_renewal.setter
def last_renewal(self, last_renewal: Optional[datetime]) -> None:
self._last_renewal = last_renewal

@property
@serializable.type_mapping(serializable.helpers.XsdDateTime)
@serializable.xml_sequence(8)
def expiration(self) -> Optional[datetime]:
"""
The timestamp indicating when the current license expires (if applicable).

Returns:
`datetime` or `None`
"""
return self._expiration

@expiration.setter
def expiration(self, expiration: Optional[datetime]) -> None:
self._expiration = expiration

def __comparable_tuple(self) -> _ComparableTuple:
return _ComparableTuple((
_ComparableTuple(self._alt_ids),
self._licensor, self._licensee, self._purchaser,
self._purchase_order,
_ComparableTuple(self._license_types),
self._last_renewal, self._expiration,
))

def __eq__(self, other: object) -> bool:
if isinstance(other, Licensing):
return self.__comparable_tuple() == other.__comparable_tuple()
return False

def __lt__(self, other: Any) -> bool:
if isinstance(other, Licensing):
return self.__comparable_tuple() < other.__comparable_tuple()
return NotImplemented

def __hash__(self) -> int:
return hash(self.__comparable_tuple())

def __repr__(self) -> str:
return f'<Licensing alt_ids={self._alt_ids!r}, licensor={self._licensor!r}, licensee={self._licensee!r}, ' \
f'purchaser={self._purchaser!r}, purchase_order={self._purchase_order!r}, ' \
f'license_types={self._license_types!r}, last_renewal={self._last_renewal!r}, ' \
f'expiration={self._expiration!r}>'


@serializable.serializable_class(
name='license',
ignore_unknown_during_deserialization=True
Expand All @@ -85,6 +400,7 @@ def __init__(
bom_ref: Optional[Union[str, BomRef]] = None,
id: Optional[str] = None, name: Optional[str] = None,
text: Optional[AttachedText] = None, url: Optional[XsUri] = None,
licensing: Optional[Licensing] = None,
acknowledgement: Optional[LicenseAcknowledgement] = None,
properties: Optional[Iterable[Property]] = None,
) -> None:
Expand All @@ -100,6 +416,7 @@ def __init__(
self._name = name if not id else None
self._text = text
self._url = url
self._licensing = licensing
self._acknowledgement = acknowledgement
self._properties = SortedSet(properties or [])

Expand Down Expand Up @@ -191,17 +508,24 @@ def url(self) -> Optional[XsUri]:
def url(self, url: Optional[XsUri]) -> None:
self._url = url

# @property
# ...
# @serializable.view(SchemaVersion1Dot5)
# @serializable.view(SchemaVersion1Dot6)
# @serializable.xml_sequence(5)
# def licensing(self) -> ...:
# ... # TODO since CDX1.5
#
# @licensing.setter
# def licensing(self, ...) -> None:
# ... # TODO since CDX1.5
@property
@serializable.view(SchemaVersion1Dot5)
@serializable.view(SchemaVersion1Dot6)
@serializable.view(SchemaVersion1Dot7)
@serializable.xml_sequence(5)
def licensing(self) -> Optional[Licensing]:
"""
Licensing details describing the licensor/licensee, license type, renewal and expiration
dates, and other important metadata.

Returns:
`Licensing` or `None`
"""
return self._licensing

@licensing.setter
def licensing(self, licensing: Optional[Licensing]) -> None:
self._licensing = licensing

@property
@serializable.view(SchemaVersion1Dot5)
Expand Down Expand Up @@ -255,6 +579,7 @@ def __comparable_tuple(self) -> _ComparableTuple:
self._id, self._name,
self._url,
self._text,
self._licensing,
self._bom_ref.value,
_ComparableTuple(self._properties),
))
Expand Down
Loading