Skip to content

feat: completed work on #155 #172

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
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
77 changes: 56 additions & 21 deletions cyclonedx/output/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,7 @@ def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str
del bom_json['metadata']['properties']

# Iterate Components
if 'components' in bom_json.keys():
for i in range(len(bom_json['components'])):
if self.component_version_optional() and bom_json['components'][i]['version'] == "":
del bom_json['components'][i]['version']

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_release_notes() and 'releaseNotes' in bom_json['components'][i].keys():
del bom_json['components'][i]['releaseNotes']
else:
bom_json['components'] = []
bom_json = self._recurse_specialise_component(bom_json=bom_json)

# Iterate Services
if 'services' in bom_json.keys():
Expand All @@ -126,11 +111,6 @@ def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str
and 'hashes' in bom_json['externalReferences'][i].keys():
del bom_json['externalReferences'][i]['hashes']

# Iterate Vulnerabilities
if 'vulnerabilities' in bom_json.keys():
for i in range(len(bom_json['vulnerabilities'])):
print("Checking " + str(bom_json['vulnerabilities'][i]))

return json.dumps(bom_json)

def output_as_string(self) -> str:
Expand All @@ -151,6 +131,61 @@ def _create_bom_element(self) -> Dict[str, Union[str, int]]:
def _get_schema_uri(self) -> Optional[str]:
pass

def _recurse_specialise_component(self, bom_json: Dict[Any, Any], base_key: str = 'components') -> Dict[Any, Any]:
if base_key in bom_json.keys():
for i in range(len(bom_json[base_key])):
if not self.component_supports_mime_type_attribute() \
and 'mime-type' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['mime-type']

if not self.component_supports_supplier() and 'supplier' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['supplier']

if not self.component_supports_author() and 'author' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['author']

if self.component_version_optional() and bom_json[base_key][i]['version'] == "":
del bom_json[base_key][i]['version']

if not self.component_supports_pedigree() and 'pedigree' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['pedigree']
elif 'pedigree' in bom_json[base_key][i].keys():
if 'ancestors' in bom_json[base_key][i]['pedigree'].keys():
# recurse into ancestors
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
bom_json=bom_json[base_key][i]['pedigree'], base_key='ancestors'
)
if 'descendants' in bom_json[base_key][i]['pedigree'].keys():
# recurse into descendants
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
bom_json=bom_json[base_key][i]['pedigree'], base_key='descendants'
)
if 'variants' in bom_json[base_key][i]['pedigree'].keys():
# recurse into variants
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
bom_json=bom_json[base_key][i]['pedigree'], base_key='variants'
)

if not self.external_references_supports_hashes() and 'externalReferences' \
in bom_json[base_key][i].keys():
for j in range(len(bom_json[base_key][i]['externalReferences'])):
del bom_json[base_key][i]['externalReferences'][j]['hashes']

if not self.component_supports_properties() and 'properties' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['properties']

# recurse
if 'components' in bom_json[base_key][i].keys():
bom_json[base_key][i] = self._recurse_specialise_component(bom_json=bom_json[base_key][i])

if not self.component_supports_evidence() and 'evidence' in bom_json[base_key][i].keys():
del bom_json[base_key][i]['evidence']

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

return bom_json


class JsonV1Dot0(Json, SchemaVersion1Dot0):

Expand Down
33 changes: 33 additions & 0 deletions cyclonedx/output/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def bom_supports_vulnerabilities_via_extension(self) -> bool:
def bom_requires_modified(self) -> bool:
return False

def component_supports_supplier(self) -> bool:
return True

def component_supports_author(self) -> bool:
return True

Expand Down Expand Up @@ -92,6 +95,12 @@ def pedigree_supports_patches(self) -> bool:
def component_supports_external_references(self) -> bool:
return True

def component_supports_properties(self) -> bool:
return True

def component_supports_evidence(self) -> bool:
return True

def component_supports_release_notes(self) -> bool:
return True

Expand Down Expand Up @@ -174,6 +183,12 @@ def bom_supports_vulnerabilities_via_extension(self) -> bool:
def component_supports_mime_type_attribute(self) -> bool:
return False

def component_supports_properties(self) -> bool:
return False

def component_supports_evidence(self) -> bool:
return False

def component_supports_release_notes(self) -> bool:
return False

Expand Down Expand Up @@ -220,12 +235,21 @@ def bom_supports_metadata(self) -> bool:
def component_supports_mime_type_attribute(self) -> bool:
return False

def component_supports_supplier(self) -> bool:
return False

def component_supports_author(self) -> bool:
return False

def component_supports_swid(self) -> bool:
return False

def component_supports_properties(self) -> bool:
return False

def component_supports_evidence(self) -> bool:
return False

def component_supports_release_notes(self) -> bool:
return False

Expand Down Expand Up @@ -281,6 +305,9 @@ def license_supports_expression(self) -> bool:
def component_supports_mime_type_attribute(self) -> bool:
return False

def component_supports_supplier(self) -> bool:
return False

def component_supports_swid(self) -> bool:
return False

Expand All @@ -290,6 +317,12 @@ def component_supports_pedigree(self) -> bool:
def component_supports_external_references(self) -> bool:
return False

def component_supports_properties(self) -> bool:
return False

def component_supports_evidence(self) -> bool:
return False

def component_supports_release_notes(self) -> bool:
return False

Expand Down
46 changes: 46 additions & 0 deletions cyclonedx/output/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,20 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:

component_element = ElementTree.Element('component', element_attributes)

# supplier
if self.component_supports_supplier() and component.supplier:
self._add_organizational_entity(
parent_element=component_element, organization=component.supplier, tag_name='supplier'
)

# author
if self.component_supports_author() and component.author is not None:
ElementTree.SubElement(component_element, 'author').text = component.author

# publisher
if component.publisher:
ElementTree.SubElement(component_element, 'publisher').text = component.publisher

# group
if component.group:
ElementTree.SubElement(component_element, 'group').text = component.group
Expand All @@ -201,6 +212,14 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
else:
ElementTree.SubElement(component_element, 'version').text = component.version

# description
if component.description:
ElementTree.SubElement(component_element, 'description').text = component.description

# scope
if component.scope:
ElementTree.SubElement(component_element, 'scope').text = component.scope.value

# hashes
if component.hashes:
Xml._add_hashes_to_element(hashes=component.hashes, element=component_element)
Expand All @@ -212,6 +231,10 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
if not license_output:
component_element.remove(licenses_e)

# copyright
if component.copyright:
ElementTree.SubElement(component_element, 'copyright').text = component.copyright

# cpe
if component.cpe:
ElementTree.SubElement(component_element, 'cpe').text = component.cpe
Expand Down Expand Up @@ -286,6 +309,29 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
if self.component_supports_external_references() and len(component.external_references) > 0:
self._add_external_references_to_element(ext_refs=component.external_references, element=component_element)

# properties
if self.component_supports_properties() and component.properties:
Xml._add_properties_element(properties=component.properties, parent_element=component_element)

# components
if component.components:
components_element = ElementTree.SubElement(component_element, 'components')
for nested_component in component.components:
components_element.append(self._add_component_element(component=nested_component))

# evidence
if self.component_supports_evidence() and component.evidence:
evidence_element = ElementTree.SubElement(component_element, 'evidence')
if component.evidence.licenses:
evidence_licenses_element = ElementTree.SubElement(evidence_element, 'licenses')
self._add_licenses_to_element(
licenses=component.evidence.licenses, parent_element=evidence_licenses_element
)
if component.evidence.copyright:
evidence_copyrights_element = ElementTree.SubElement(evidence_element, 'copyright')
for evidence_copyright in component.evidence.copyright:
ElementTree.SubElement(evidence_copyrights_element, 'text').text = evidence_copyright.text

# releaseNotes
if self.component_supports_release_notes() and component.release_notes:
Xml._add_release_notes_element(release_notes=component.release_notes, parent_element=component_element)
Expand Down
14 changes: 12 additions & 2 deletions tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
Property, Tool, XsUri
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Commit, Component, ComponentEvidence, ComponentType, Copyright, Patch, \
PatchClassification, Pedigree, Swid
PatchClassification, Pedigree, Swid, ComponentScope
from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource
from cyclonedx.model.release_note import ReleaseNotes
from cyclonedx.model.service import Service
Expand Down Expand Up @@ -67,15 +67,25 @@ def get_bom_with_component_setuptools_with_release_notes() -> Bom:


def get_bom_with_component_setuptools_complete() -> Bom:
component = get_component_setuptools_simple()
component = get_component_setuptools_simple(bom_ref=MOCK_UUID_6)
component.supplier = get_org_entity_1()
component.publisher = 'CycloneDX'
component.description = 'This component is awesome'
component.scope = ComponentScope.REQUIRED
component.copyright = 'Apache 2.0 baby!'
component.cpe = 'cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*'
component.swid = get_swid_1()
component.pedigree = get_pedigree_1()
component.external_references.add(
get_external_reference_1()
)
component.properties = get_properties_1()
component.components.update([
get_component_setuptools_simple(),
get_component_toml_with_hashes_with_references()
])
component.evidence = ComponentEvidence(copyright_=[Copyright(text='Commercial'), Copyright(text='Commercial 2')])
component.release_notes = get_release_notes()
return Bom(components=[component])


Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/json/1.2/bom_external_references.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
}
]
},
"components": [],
"externalReferences": [
{
"url": "https://cyclonedx.org",
Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/json/1.2/bom_services_complex.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"version": "1.0.0"
}
},
"components": [],
"services": [
{
"bom-ref": "my-specific-bom-ref-for-my-first-service",
Expand Down
2 changes: 0 additions & 2 deletions tests/fixtures/json/1.2/bom_services_nested.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"$schema": "http://cyclonedx.org/schema/bom-1.2a.schema.json",
"bomFormat": "CycloneDX",
"components": [
],
"metadata": {
"component": {
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/json/1.2/bom_services_simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"version": "1.0.0"
}
},
"components": [],
"services": [
{
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",
Expand Down
Loading