Skip to content

Commit

Permalink
BREAKING CHANGE: added new model BomRef unlocking logic later to en…
Browse files Browse the repository at this point in the history
…sure uniquness and dependency references (#174)

Signed-off-by: Paul Horton <[email protected]>
  • Loading branch information
madpah authored Feb 17, 2022
1 parent 020fcf0 commit d189f2c
Show file tree
Hide file tree
Showing 27 changed files with 192 additions and 185 deletions.
53 changes: 53 additions & 0 deletions cyclonedx/model/bom_ref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# encoding: utf-8

# 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 typing import Optional
from uuid import uuid4


class BomRef:
"""
An identifier that can be used to reference objects elsewhere in the BOM.
This copies a similar pattern used in the CycloneDX Python Library.
.. note::
See https://github.com/CycloneDX/cyclonedx-php-library/blob/master/docs/dev/decisions/BomDependencyDataModel.md
"""

def __init__(self, value: Optional[str] = None) -> None:
self.value = value or str(uuid4())

@property
def value(self) -> str:
return self._value

@value.setter
def value(self, value: str) -> None:
self._value = value

def __eq__(self, other: object) -> bool:
if isinstance(other, BomRef):
return hash(other) == hash(self)
return False

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

def __repr__(self) -> str:
return self.value
12 changes: 4 additions & 8 deletions cyclonedx/model/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
from enum import Enum
from os.path import exists
from typing import Iterable, Optional, Set
from uuid import uuid4

# See https://github.com/package-url/packageurl-python/issues/65
from packageurl import PackageURL # type: ignore

from . import AttachedText, Copyright, ExternalReference, HashAlgorithm, HashType, IdentifiableAction, LicenseChoice, \
OrganizationalEntity, Property, sha1sum, XsUri
from .bom_ref import BomRef
from .issue import IssueType
from .release_note import ReleaseNotes
from .vulnerability import Vulnerability
Expand Down Expand Up @@ -692,7 +692,7 @@ def __init__(self, *, name: str, component_type: ComponentType = ComponentType.L
) -> None:
self.type = component_type
self.mime_type = mime_type
self.bom_ref = bom_ref or str(uuid4())
self._bom_ref = BomRef(value=bom_ref)
self.supplier = supplier
self.author = author
self.publisher = publisher
Expand Down Expand Up @@ -766,22 +766,18 @@ def mime_type(self, mime_type: Optional[str]) -> None:
self._mime_type = mime_type

@property
def bom_ref(self) -> str:
def bom_ref(self) -> BomRef:
"""
An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be
unique within the BOM.
If a value was not provided in the constructor, a UUIDv4 will have been assigned.
Returns:
`str` as a unique identifiers for this Component
`BomRef`
"""
return self._bom_ref

@bom_ref.setter
def bom_ref(self, bom_ref: str) -> None:
self._bom_ref = bom_ref

@property
def supplier(self) -> Optional[OrganizationalEntity]:
"""
Expand Down
12 changes: 4 additions & 8 deletions cyclonedx/model/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) OWASP Foundation. All Rights Reserved.
from typing import Iterable, Optional, Set
from uuid import uuid4

from . import ExternalReference, DataClassification, LicenseChoice, OrganizationalEntity, Property, XsUri
from .bom_ref import BomRef
from .release_note import ReleaseNotes

"""
Expand Down Expand Up @@ -46,7 +46,7 @@ def __init__(self, *, name: str, bom_ref: Optional[str] = None, provider: Option
services: Optional[Iterable['Service']] = None,
release_notes: Optional[ReleaseNotes] = None,
) -> None:
self.bom_ref = bom_ref or str(uuid4())
self._bom_ref = BomRef(value=bom_ref)
self.provider = provider
self.group = group
self.name = name
Expand All @@ -63,22 +63,18 @@ def __init__(self, *, name: str, bom_ref: Optional[str] = None, provider: Option
self.properties = set(properties or [])

@property
def bom_ref(self) -> Optional[str]:
def bom_ref(self) -> BomRef:
"""
An optional identifier which can be used to reference the service elsewhere in the BOM. Uniqueness is enforced
within all elements and children of the root-level bom element.
If a value was not provided in the constructor, a UUIDv4 will have been assigned.
Returns:
`str` unique identifier for this Service
`BomRef` unique identifier for this Service
"""
return self._bom_ref

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

@property
def provider(self) -> Optional[OrganizationalEntity]:
"""
Expand Down
10 changes: 7 additions & 3 deletions cyclonedx/output/serializer/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) OWASP Foundation. All Rights Reserved.

from datetime import datetime
from decimal import Decimal
from enum import Enum
Expand All @@ -28,8 +27,9 @@
# See https://github.com/package-url/packageurl-python/issues/65
from packageurl import PackageURL # type: ignore

from cyclonedx.model import XsUri
from cyclonedx.model.component import Component
from ...model import XsUri
from ...model.bom_ref import BomRef
from ...model.component import Component

HYPHENATED_ATTRIBUTES = [
'bom_ref', 'mime_type', 'x_trust_boundary'
Expand All @@ -40,6 +40,10 @@
class CycloneDxJSONEncoder(JSONEncoder):

def default(self, o: Any) -> Any:
# BomRef
if isinstance(o, BomRef):
return str(o)

# datetime
if isinstance(o, datetime):
return o.isoformat()
Expand Down
9 changes: 5 additions & 4 deletions cyclonedx/output/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ..model import AttachedText, ExternalReference, HashType, IdentifiableAction, LicenseChoice, \
OrganizationalEntity, OrganizationalContact, Property, Tool
from ..model.bom import Bom
from ..model.bom_ref import BomRef
from ..model.component import Component, Patch
from ..model.release_note import ReleaseNotes
from ..model.service import Service
Expand Down Expand Up @@ -174,7 +175,7 @@ def _add_metadata_element(self) -> None:
def _add_component_element(self, component: Component) -> ElementTree.Element:
element_attributes = {'type': component.type.value}
if self.component_supports_bom_ref_attribute() and component.bom_ref:
element_attributes['bom-ref'] = component.bom_ref
element_attributes['bom-ref'] = str(component.bom_ref)
if self.component_supports_mime_type_attribute() and component.mime_type:
element_attributes['mime-type'] = component.mime_type

Expand Down Expand Up @@ -450,7 +451,7 @@ def _add_properties_element(properties: Set[Property], parent_element: ElementTr
def _add_service_element(self, service: Service) -> ElementTree.Element:
element_attributes = {}
if service.bom_ref:
element_attributes['bom-ref'] = service.bom_ref
element_attributes['bom-ref'] = str(service.bom_ref)

service_element = ElementTree.Element('service', element_attributes)

Expand Down Expand Up @@ -654,10 +655,10 @@ def _get_vulnerability_as_xml_element_post_1_4(self, vulnerability: Vulnerabilit
return vulnerability_element

@staticmethod
def _get_vulnerability_as_xml_element_pre_1_3(bom_ref: str,
def _get_vulnerability_as_xml_element_pre_1_3(bom_ref: BomRef,
vulnerability: Vulnerability) -> ElementTree.Element:
vulnerability_element = ElementTree.Element('v:vulnerability', {
'ref': bom_ref
'ref': str(bom_ref)
})

# id
Expand Down
4 changes: 4 additions & 0 deletions tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
MOCK_UUID_5 = 'bb5911d6-1a1d-41c9-b6e0-46e848d16655'
MOCK_UUID_6 = 'df70b5f1-8f53-47a4-be48-669ae78795e6'

TEST_UUIDS = [
MOCK_UUID_1, MOCK_UUID_2, MOCK_UUID_3, MOCK_UUID_4, MOCK_UUID_5, MOCK_UUID_6
]


def get_bom_with_component_setuptools_basic() -> Bom:
return Bom(components=[get_component_setuptools_simple()])
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/json/1.2/bom_services_complex.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
],
"component": {
"type": "library",
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
Expand Down Expand Up @@ -76,7 +76,7 @@
]
},
{
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "my-second-service"
}
]
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/json/1.2/bom_services_nested.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"bomFormat": "CycloneDX",
"metadata": {
"component": {
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"name": "cyclonedx-python-lib",
"type": "library",
"version": "1.0.0"
Expand Down Expand Up @@ -72,7 +72,7 @@
},
"services": [
{
"bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "first-nested-service"
},
{
Expand Down Expand Up @@ -105,11 +105,11 @@
"x-trust-boundary": true
},
{
"bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6",
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"name": "my-second-service",
"services": [
{
"bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"group": "what-group",
"name": "yet-another-nested-service",
"provider": {
Expand Down
6 changes: 3 additions & 3 deletions tests/fixtures/json/1.2/bom_services_simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
],
"component": {
"type": "library",
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
},
"services": [
{
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "my-first-service"
},
{
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"name": "my-second-service"
}
]
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/json/1.3/bom_services_complex.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
],
"component": {
"type": "library",
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
Expand Down Expand Up @@ -86,7 +86,7 @@
]
},
{
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "my-second-service"
}
]
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/json/1.3/bom_services_nested.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"bomFormat": "CycloneDX",
"metadata": {
"component": {
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"name": "cyclonedx-python-lib",
"type": "library",
"version": "1.0.0"
Expand Down Expand Up @@ -82,7 +82,7 @@
},
"services": [
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "first-nested-service"
},
{
Expand Down Expand Up @@ -115,11 +115,11 @@
"x-trust-boundary": true
},
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"name": "my-second-service",
"services": [
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"group": "what-group",
"name": "yet-another-nested-service",
"provider": {
Expand Down
6 changes: 3 additions & 3 deletions tests/fixtures/json/1.3/bom_services_simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
],
"component": {
"type": "library",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"bom-ref": "0b049d09-64c0-4490-a0f5-c84d9aacf857",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
},
"services": [
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "my-first-service"
},
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"name": "my-second-service"
}
]
Expand Down
4 changes: 2 additions & 2 deletions tests/fixtures/json/1.4/bom_services_complex.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
],
"component": {
"type": "library",
"bom-ref": "df70b5f1-8f53-47a4-be48-669ae78795e6",
"bom-ref": "17e3b199-dc0b-42ef-bfdd-1fa81a1e3eda",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
Expand Down Expand Up @@ -179,7 +179,7 @@
]
},
{
"bom-ref": "cd3e9c95-9d41-49e7-9924-8cf0465ae789",
"bom-ref": "be2c6502-7e9a-47db-9a66-e34f729810a3",
"name": "my-second-service"
}
]
Expand Down
Loading

0 comments on commit d189f2c

Please sign in to comment.