Skip to content

Add support for metadata component #118

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

Merged
merged 2 commits into from
Jan 13, 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
26 changes: 26 additions & 0 deletions cyclonedx/model/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def __init__(self, tools: Optional[List[Tool]] = None) -> None:
if not self.tools:
self.add_tool(ThisTool)

self.component: Optional[Component] = None

@property
def tools(self) -> List[Tool]:
"""
Expand Down Expand Up @@ -80,6 +82,30 @@ def timestamp(self) -> datetime:
def timestamp(self, timestamp: datetime) -> None:
self._timestamp = timestamp

@property
def component(self) -> Optional[Component]:
"""
The (optional) component that the BOM describes.

Returns:
`cyclonedx.model.component.Component` instance for this Bom Metadata.
"""
return self._component

@component.setter
def component(self, component: Component) -> None:
"""
The (optional) component that the BOM describes.

Args:
component
`cyclonedx.model.component.Component` instance to add to this Bom Metadata.

Returns:
None
"""
self._component = component


class Bom:
"""
Expand Down
25 changes: 18 additions & 7 deletions cyclonedx/output/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
from ..model.bom import Bom


ComponentDict = Dict[str, Union[
str,
List[Dict[str, str]],
List[Dict[str, Dict[str, str]]],
List[Dict[str, Union[str, List[Dict[str, str]]]]]]]


class Json(BaseOutput, BaseSchemaVersion):

def __init__(self, bom: Bom) -> None:
Expand Down Expand Up @@ -73,15 +80,19 @@ def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str
del bom_json['metadata']['tools'][i]['externalReferences']

# Iterate Components
for i in range(len(bom_json['components'])):
if not self.component_supports_author() and 'author' in bom_json['components'][i].keys():
del bom_json['components'][i]['author']
if 'components' in bom_json.keys():
for i in range(len(bom_json['components'])):
if not self.component_supports_author() and 'author' in bom_json['components'][i].keys():
del bom_json['components'][i]['author']

if not self.component_supports_mime_type_attribute() and 'mime-type' in bom_json['components'][i].keys():
del bom_json['components'][i]['mime-type']
if not self.component_supports_mime_type_attribute() \
and 'mime-type' in bom_json['components'][i].keys():
del bom_json['components'][i]['mime-type']

if not self.component_supports_release_notes() and 'releaseNotes' in bom_json['components'][i].keys():
del bom_json['components'][i]['releaseNotes']
if not self.component_supports_release_notes() and 'releaseNotes' in bom_json['components'][i].keys():
del bom_json['components'][i]['releaseNotes']
else:
bom_json['components'] = []

# Iterate Vulnerabilities
if 'vulnerabilities' in bom_json.keys():
Expand Down
3 changes: 3 additions & 0 deletions cyclonedx/output/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ def _add_metadata_element(self) -> None:
for tool in bom_metadata.tools:
self._add_tool(parent_element=tools_e, tool=tool)

if bom_metadata.component:
metadata_e.append(self._add_component_element(component=bom_metadata.component))

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:
Expand Down
23 changes: 23 additions & 0 deletions tests/fixtures/bom_v1.3_with_metadata_component.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.3.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2021-09-01T10:50:42.051979+00:00",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-python-lib",
"version": "VERSION"
}
],
"component": {
"type": "library",
"name": "cyclonedx-python-lib",
"version": "1.0.0"
}
},
"components": []
}
18 changes: 18 additions & 0 deletions tests/fixtures/bom_v1.3_with_metadata_component.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" version="1">
<metadata>
<timestamp>2021-09-01T10:50:42.051979+00:00</timestamp>
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cyclonedx-python-lib</name>
<version>VERSION</version>
</tool>
</tools>
<component type="library">
<name>cyclonedx-python-lib</name>
<version>1.0.0</version>
</component>
</metadata>
<components/>
</bom>
10 changes: 10 additions & 0 deletions tests/test_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from unittest import TestCase

from cyclonedx.model.bom import Bom, ThisTool, Tool
from cyclonedx.model.component import Component, ComponentType


class TestBom(TestCase):
Expand All @@ -36,3 +37,12 @@ def test_bom_metadata_tool_multiple_tools(self) -> None:
Tool(vendor='TestVendor', name='TestTool', version='0.0.0')
)
self.assertEqual(len(bom.metadata.tools), 2)

def test_metadata_component(self) -> None:
metadata = Bom().metadata
self.assertTrue(metadata.component is None)
hextech = Component(name='Hextech', version='1.0.0',
component_type=ComponentType.LIBRARY)
metadata.component = hextech
self.assertFalse(metadata.component is None)
self.assertEquals(metadata.component, hextech)
2 changes: 2 additions & 0 deletions tests/test_e2e_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def setUpClass(cls) -> None:
def test_json_defaults(self) -> None:
outputter: Json = get_instance(bom=TestE2EEnvironment.bom, output_format=OutputFormat.JSON)
bom_json = json.loads(outputter.output_as_string())
self.assertTrue('metadata' in bom_json)
self.assertFalse('component' in bom_json['metadata'])
component_this_library = next(
(x for x in bom_json['components'] if
x['purl'] == 'pkg:pypi/{}@{}'.format(OUR_PACKAGE_NAME, OUR_PACKAGE_VERSION)), None
Expand Down
13 changes: 12 additions & 1 deletion tests/test_output_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from cyclonedx.model import Encoding, ExternalReference, ExternalReferenceType, HashType, LicenseChoice, Note, \
NoteText, OrganizationalContact, OrganizationalEntity, Property, Tool, XsUri
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component
from cyclonedx.model.component import Component, ComponentType
from cyclonedx.model.issue import IssueClassification, IssueType
from cyclonedx.model.release_note import ReleaseNotes
from cyclonedx.model.vulnerability import ImpactAnalysisState, ImpactAnalysisJustification, ImpactAnalysisResponse, \
Expand Down Expand Up @@ -327,3 +327,14 @@ def test_simple_bom_v1_4_with_vulnerabilities(self) -> None:
self.assertValidAgainstSchema(bom_json=outputter.output_as_string(), schema_version=SchemaVersion.V1_4)
self.assertEqualJsonBom(expected_json.read(), outputter.output_as_string())
expected_json.close()

def test_bom_v1_3_with_metadata_component(self) -> None:
bom = Bom()
bom.metadata.component = Component(
name='cyclonedx-python-lib', version='1.0.0', component_type=ComponentType.LIBRARY)
outputter = get_instance(bom=bom, output_format=OutputFormat.JSON)
self.assertIsInstance(outputter, JsonV1Dot3)
with open(join(dirname(__file__), 'fixtures/bom_v1.3_with_metadata_component.json')) as expected_json:
self.assertEqualJsonBom(outputter.output_as_string(), expected_json.read())

maxDiff = None
13 changes: 12 additions & 1 deletion tests/test_output_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from cyclonedx.model import Encoding, ExternalReference, ExternalReferenceType, HashType, Note, NoteText, \
OrganizationalContact, OrganizationalEntity, Property, Tool, XsUri
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component
from cyclonedx.model.component import Component, ComponentType
from cyclonedx.model.impact_analysis import ImpactAnalysisState, ImpactAnalysisJustification, ImpactAnalysisResponse, \
ImpactAnalysisAffectedStatus
from cyclonedx.model.issue import IssueClassification, IssueType
Expand Down Expand Up @@ -433,3 +433,14 @@ def test_with_component_release_notes_post_1_4(self) -> None:
self.assertEqualXmlBom(a=outputter.output_as_string(), b=expected_xml.read(),
namespace=outputter.get_target_namespace())
expected_xml.close()

def test_bom_v1_3_with_metadata_component(self) -> None:
bom = Bom()
bom.metadata.component = Component(
name='cyclonedx-python-lib', version='1.0.0', component_type=ComponentType.LIBRARY)
outputter: Xml = get_instance(bom=bom)
self.assertIsInstance(outputter, XmlV1Dot3)
with open(join(dirname(__file__), 'fixtures/bom_v1.3_with_metadata_component.xml')) as expected_xml:
self.assertEqualXmlBom(a=outputter.output_as_string(), b=expected_xml.read(),
namespace=outputter.get_target_namespace())
expected_xml.close()