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

BREAKING CHANGE: added new model BomRef unlocking logic later to en… #174

Merged
merged 1 commit into from
Feb 17, 2022
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
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madpah we should also implement __str__()
as i saw the string cast in the serializations.

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