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

feat: add basic support for CDX 1.5 #488

Merged
merged 13 commits into from
Nov 27, 2023
Merged
27 changes: 26 additions & 1 deletion cyclonedx/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
NoPropertiesProvidedException,
UnknownHashTypeException,
)
from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4
from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4, SchemaVersion1Dot5

"""
Uniform set of models to represent objects within a CycloneDX software bill-of-materials.
Expand Down Expand Up @@ -395,22 +395,45 @@ class ExternalReferenceType(str, Enum):
See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_externalReferenceType
"""

ADVERSARY_MODEL = 'adversary-model' # Only supported in >= 1.5
ADVISORIES = 'advisories'
ATTESTATION = 'attestation' # Only supported in >= 1.5
BOM = 'bom'
BUILD_META = 'build-meta'
BUILD_SYSTEM = 'build-system'
CERTIFICATION_REPORT = 'certification-report' # Only supported in >= 1.5
CHAT = 'chat'
CODIFIED_INFRASTRUCTURE = 'codified-infrastructure' # Only supported in >= 1.5
COMPONENT_ANALYSIS_REPORT = 'component-analysis-report' # Only supported in >= 1.5
CONFIGURATION = 'configuration' # Only supported in >= 1.5
DISTRIBUTION = 'distribution'
DISTRIBUTION_INTAKE = 'distribution-intake' # Only supported in >= 1.5
DOCUMENTATION = 'documentation'
DYNAMIC_ANALYSIS_REPORT = 'dynamic-analysis-report' # Only supported in >= 1.5
EVIDENCE = 'evidence' # Only supported in >= 1.5
EXPLOITABILITY_STATEMENT = 'exploitability-statement' # Only supported in >= 1.5
FORMULATION = 'formulation' # Only supported in >= 1.5
ISSUE_TRACKER = 'issue-tracker'
LICENSE = 'license'
LOG = 'log' # Only supported in >= 1.5
MAILING_LIST = 'mailing-list'
MATURITY_REPORT = 'maturity-report' # Only supported in >= 1.5
MODEL_CARD = 'model-card' # Only supported in >= 1.5
OTHER = 'other'
PENTEST_REPORT = 'pentest-report' # Only supported in >= 1.5
POAM = 'poam' # Only supported in >= 1.5
QUALITY_METRICS = 'quality-metrics' # Only supported in >= 1.5
RELEASE_NOTES = 'release-notes' # Only supported in >= 1.4
RISK_ASSESSMENT = 'risk-assessment' # Only supported in >= 1.5
RUNTIME_ANALYSIS_REPORT = 'runtime-analysis-report' # Only supported in >= 1.5
SECURITY_CONTACT = 'security-contact' # Only supported in >= 1.5
STATIC_ANALYSIS_REPORT = 'static-analysis-report' # Only supported in >= 1.5
SOCIAL = 'social'
SCM = 'vcs'
SUPPORT = 'support'
THREAT_MODEL = 'threat-model' # Only supported in >= 1.5
VCS = 'vcs'
VULNERABILITY_ASSERTION = 'vulnerability-assertion' # Only supported in >= 1.5
WEBSITE = 'website'


Expand Down Expand Up @@ -541,6 +564,7 @@ def type(self, type: ExternalReferenceType) -> None:
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'hash')
def hashes(self) -> 'SortedSet[HashType]':
"""
Expand Down Expand Up @@ -1052,6 +1076,7 @@ def hashes(self, hashes: Iterable[HashType]) -> None:

@property
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
@serializable.xml_sequence(5)
def external_references(self) -> 'SortedSet[ExternalReference]':
Expand Down
11 changes: 10 additions & 1 deletion cyclonedx/model/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4,
SchemaVersion1Dot5,
)
from ..serialization import LicenseRepositoryHelper, UrnUuidHelper
from . import ExternalReference, OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool, get_now_utc
Expand All @@ -53,7 +54,7 @@ class BomMetaData:
This is our internal representation of the metadata complex type within the CycloneDX standard.

.. note::
See the CycloneDX Schema for Bom metadata: https://cyclonedx.org/docs/1.4/#type_metadata
See the CycloneDX Schema for Bom metadata: https://cyclonedx.org/docs/1.5/#type_metadata
"""

def __init__(self, *, tools: Optional[Iterable[Tool]] = None,
Expand Down Expand Up @@ -187,6 +188,7 @@ def supplier(self, supplier: Optional[OrganizationalEntity]) -> None:
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.type_mapping(LicenseRepositoryHelper)
@serializable.xml_sequence(7)
def licenses(self) -> LicenseRepository:
Expand All @@ -205,6 +207,7 @@ def licenses(self, licenses: Iterable[License]) -> None:
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(8)
def properties(self) -> 'SortedSet[Property]':
Expand Down Expand Up @@ -294,6 +297,7 @@ def __init__(self, *, components: Optional[Iterable[Component]] = None,
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_attribute()
def serial_number(self) -> UUID:
"""
Expand All @@ -313,6 +317,7 @@ def serial_number(self, serial_number: UUID) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(1)
def metadata(self) -> BomMetaData:
"""
Expand Down Expand Up @@ -392,6 +397,7 @@ def has_component(self, component: Component) -> bool:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'service')
@serializable.xml_sequence(3)
def services(self) -> 'SortedSet[Service]':
Expand All @@ -412,6 +418,7 @@ def services(self, services: Iterable[Service]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
@serializable.xml_sequence(4)
def external_references(self) -> 'SortedSet[ExternalReference]':
Expand Down Expand Up @@ -462,6 +469,7 @@ def has_vulnerabilities(self) -> bool:

@property
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'vulnerability')
@serializable.xml_sequence(8)
def vulnerabilities(self) -> 'SortedSet[Vulnerability]':
Expand Down Expand Up @@ -490,6 +498,7 @@ def version(self, version: int) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'dependency')
@serializable.xml_sequence(5)
def dependencies(self) -> 'SortedSet[Dependency]':
Expand Down
16 changes: 16 additions & 0 deletions cyclonedx/model/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4,
SchemaVersion1Dot5,
)
from ..serialization import BomRefHelper, LicenseRepositoryHelper, PackageUrl
from . import (
Expand Down Expand Up @@ -255,12 +256,16 @@ class ComponentType(str, Enum):
"""
APPLICATION = 'application'
CONTAINER = 'container'
DATA = 'data'
DEVICE = 'device'
DEVICE_DRIVER = 'device-driver'
FILE = 'file'
FIRMWARE = 'firmware'
FRAMEWORK = 'framework'
LIBRARY = 'library'
MACHINE_LEARNING_MODEL = 'machine-learning-model'
OPERATING_SYSTEM = 'operating-system'
PLATFORM = 'platform'


class Diff:
Expand Down Expand Up @@ -528,6 +533,7 @@ def commits(self, commits: Iterable[Commit]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'patch')
@serializable.xml_sequence(5)
def patches(self) -> 'SortedSet[Patch]':
Expand Down Expand Up @@ -849,6 +855,7 @@ def mime_type(self, mime_type: Optional[str]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_attribute()
@serializable.xml_name('bom-ref')
def bom_ref(self) -> BomRef:
Expand All @@ -867,6 +874,7 @@ def bom_ref(self) -> BomRef:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(1)
def supplier(self) -> Optional[OrganizationalEntity]:
"""
Expand All @@ -886,6 +894,7 @@ def supplier(self, supplier: Optional[OrganizationalEntity]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(2)
def author(self) -> Optional[str]:
"""
Expand Down Expand Up @@ -1028,6 +1037,7 @@ def hashes(self, hashes: Iterable[HashType]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.type_mapping(LicenseRepositoryHelper)
@serializable.xml_sequence(10)
def licenses(self) -> LicenseRepository:
Expand Down Expand Up @@ -1098,6 +1108,7 @@ def purl(self, purl: Optional[PackageURL]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(14)
def swid(self) -> Optional[Swid]:
"""
Expand Down Expand Up @@ -1127,6 +1138,7 @@ def modified(self, modified: bool) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(16)
def pedigree(self) -> Optional[Pedigree]:
"""
Expand All @@ -1147,6 +1159,7 @@ def pedigree(self, pedigree: Optional[Pedigree]) -> None:
@serializable.view(SchemaVersion1Dot2)
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
@serializable.xml_sequence(17)
def external_references(self) -> 'SortedSet[ExternalReference]':
Expand All @@ -1166,6 +1179,7 @@ def external_references(self, external_references: Iterable[ExternalReference])
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(18)
def properties(self) -> 'SortedSet[Property]':
Expand Down Expand Up @@ -1203,6 +1217,7 @@ def components(self, components: Iterable['Component']) -> None:
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(20)
def evidence(self) -> Optional[ComponentEvidence]:
"""
Expand All @@ -1219,6 +1234,7 @@ def evidence(self, evidence: Optional[ComponentEvidence]) -> None:

@property
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(21)
def release_notes(self) -> Optional[ReleaseNotes]:
"""
Expand Down
4 changes: 3 additions & 1 deletion cyclonedx/model/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from cyclonedx.serialization import BomRefHelper, LicenseRepositoryHelper

from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4
from ..schema.schema import SchemaVersion1Dot3, SchemaVersion1Dot4, SchemaVersion1Dot5
from . import ComparableTuple, DataClassification, ExternalReference, OrganizationalEntity, Property, XsUri
from .bom_ref import BomRef
from .dependency import Dependable
Expand Down Expand Up @@ -291,6 +291,7 @@ def services(self, services: Iterable['Service']) -> None:

@property
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_sequence(14)
def release_notes(self) -> Optional[ReleaseNotes]:
"""
Expand All @@ -308,6 +309,7 @@ def release_notes(self, release_notes: Optional[ReleaseNotes]) -> None:
@property
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.view(SchemaVersion1Dot5)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(12)
def properties(self) -> 'SortedSet[Property]':
Expand Down
2 changes: 1 addition & 1 deletion cyclonedx/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .json import Json as JsonOutputter
from .xml import Xml as XmlOutputter

LATEST_SUPPORTED_SCHEMA_VERSION = SchemaVersion.V1_4
LATEST_SUPPORTED_SCHEMA_VERSION = SchemaVersion.V1_5
jkowalleck marked this conversation as resolved.
Show resolved Hide resolved


class BaseOutput(ABC):
Expand Down
8 changes: 8 additions & 0 deletions cyclonedx/output/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4,
SchemaVersion1Dot5,
)
from . import BaseOutput, BomRefDiscriminator

Expand Down Expand Up @@ -117,7 +118,14 @@ def _get_schema_uri(self) -> str:
return 'http://cyclonedx.org/schema/bom-1.4.schema.json'


class JsonV1Dot5(Json, SchemaVersion1Dot5):

def _get_schema_uri(self) -> str:
return 'http://cyclonedx.org/schema/bom-1.5.schema.json'


BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Json]] = {
SchemaVersion.V1_5: JsonV1Dot5,
SchemaVersion.V1_4: JsonV1Dot4,
SchemaVersion.V1_3: JsonV1Dot3,
SchemaVersion.V1_2: JsonV1Dot2,
Expand Down
6 changes: 6 additions & 0 deletions cyclonedx/output/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4,
SchemaVersion1Dot5,
)
from . import BaseOutput, BomRefDiscriminator

Expand Down Expand Up @@ -114,7 +115,12 @@ class XmlV1Dot4(Xml, SchemaVersion1Dot4):
pass


class XmlV1Dot5(Xml, SchemaVersion1Dot5):
pass


BY_SCHEMA_VERSION: Dict[SchemaVersion, Type[Xml]] = {
SchemaVersion.V1_5: XmlV1Dot5,
SchemaVersion.V1_4: XmlV1Dot4,
SchemaVersion.V1_3: XmlV1Dot3,
SchemaVersion.V1_2: XmlV1Dot2,
Expand Down
1 change: 1 addition & 0 deletions cyclonedx/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SchemaVersion(Enum):
my_sv = SchemaVersion.V1_3
"""

V1_5 = (1, 5)
V1_4 = (1, 4)
V1_3 = (1, 3)
V1_2 = (1, 2)
Expand Down
2 changes: 2 additions & 0 deletions cyclonedx/schema/_res/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ Currently using version
| [`bom-1.2.SNAPSHOT.xsd`](bom-1.2.SNAPSHOT.xsd) | applied changes: 1 |
| [`bom-1.3.SNAPSHOT.xsd`](bom-1.3.SNAPSHOT.xsd) | applied changes: 1 |
| [`bom-1.4.SNAPSHOT.xsd`](bom-1.4.SNAPSHOT.xsd) | applied changes: 1 |
| [`bom-1.5.SNAPSHOT.xsd`](bom-1.5.SNAPSHOT.xsd) | applied changes: 1 |
| [`bom-1.2.SNAPSHOT.schema.json`](bom-1.2.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`bom-1.3.SNAPSHOT.schema.json`](bom-1.3.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`bom-1.4.SNAPSHOT.schema.json`](bom-1.4.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`bom-1.5.SNAPSHOT.schema.json`](bom-1.5.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`bom-1.2-strict.SNAPSHOT.schema.json`](bom-1.2-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`bom-1.3-strict.SNAPSHOT.schema.json`](bom-1.3-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 |
| [`spdx.SNAPSHOT.xsd`](spdx.SNAPSHOT.xsd) | |
Expand Down
3 changes: 3 additions & 0 deletions cyclonedx/schema/_res/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
__DIR = dirname(__file__)

BOM_XML: Dict[SchemaVersion, Optional[str]] = {
SchemaVersion.V1_5: join(__DIR, 'bom-1.5.SNAPSHOT.xsd'),
SchemaVersion.V1_4: join(__DIR, 'bom-1.4.SNAPSHOT.xsd'),
SchemaVersion.V1_3: join(__DIR, 'bom-1.3.SNAPSHOT.xsd'),
SchemaVersion.V1_2: join(__DIR, 'bom-1.2.SNAPSHOT.xsd'),
Expand All @@ -36,6 +37,7 @@
}

BOM_JSON: Dict[SchemaVersion, Optional[str]] = {
SchemaVersion.V1_5: join(__DIR, 'bom-1.5.SNAPSHOT.schema.json'),
SchemaVersion.V1_4: join(__DIR, 'bom-1.4.SNAPSHOT.schema.json'),
SchemaVersion.V1_3: join(__DIR, 'bom-1.3.SNAPSHOT.schema.json'),
SchemaVersion.V1_2: join(__DIR, 'bom-1.2.SNAPSHOT.schema.json'),
Expand All @@ -46,6 +48,7 @@

BOM_JSON_STRICT: Dict[SchemaVersion, Optional[str]] = {
# >= v1.4 is already strict - no special file here
SchemaVersion.V1_5: join(__DIR, 'bom-1.5.SNAPSHOT.schema.json'),
SchemaVersion.V1_4: join(__DIR, 'bom-1.4.SNAPSHOT.schema.json'),
# <= 1.3 need special files
SchemaVersion.V1_3: join(__DIR, 'bom-1.3-strict.SNAPSHOT.schema.json'),
Expand Down
Loading