Skip to content

Commit a926b34

Browse files
authoredFeb 16, 2022
feat: completed work on #155 (#172)
fix: resolved #169 (part of #155) feat: as part of solving #155, #147 has been implemented Signed-off-by: Paul Horton <phorton@sonatype.com>
1 parent 5c954d1 commit a926b34

28 files changed

+623
-103
lines changed
 

‎cyclonedx/output/json.py

+56-21
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,7 @@ def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str
9393
del bom_json['metadata']['properties']
9494

9595
# Iterate Components
96-
if 'components' in bom_json.keys():
97-
for i in range(len(bom_json['components'])):
98-
if self.component_version_optional() and bom_json['components'][i]['version'] == "":
99-
del bom_json['components'][i]['version']
100-
101-
if not self.component_supports_author() and 'author' in bom_json['components'][i].keys():
102-
del bom_json['components'][i]['author']
103-
104-
if not self.component_supports_mime_type_attribute() \
105-
and 'mime-type' in bom_json['components'][i].keys():
106-
del bom_json['components'][i]['mime-type']
107-
108-
if not self.component_supports_release_notes() and 'releaseNotes' in bom_json['components'][i].keys():
109-
del bom_json['components'][i]['releaseNotes']
110-
else:
111-
bom_json['components'] = []
96+
bom_json = self._recurse_specialise_component(bom_json=bom_json)
11297

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

129-
# Iterate Vulnerabilities
130-
if 'vulnerabilities' in bom_json.keys():
131-
for i in range(len(bom_json['vulnerabilities'])):
132-
print("Checking " + str(bom_json['vulnerabilities'][i]))
133-
134114
return json.dumps(bom_json)
135115

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

134+
def _recurse_specialise_component(self, bom_json: Dict[Any, Any], base_key: str = 'components') -> Dict[Any, Any]:
135+
if base_key in bom_json.keys():
136+
for i in range(len(bom_json[base_key])):
137+
if not self.component_supports_mime_type_attribute() \
138+
and 'mime-type' in bom_json[base_key][i].keys():
139+
del bom_json[base_key][i]['mime-type']
140+
141+
if not self.component_supports_supplier() and 'supplier' in bom_json[base_key][i].keys():
142+
del bom_json[base_key][i]['supplier']
143+
144+
if not self.component_supports_author() and 'author' in bom_json[base_key][i].keys():
145+
del bom_json[base_key][i]['author']
146+
147+
if self.component_version_optional() and bom_json[base_key][i]['version'] == "":
148+
del bom_json[base_key][i]['version']
149+
150+
if not self.component_supports_pedigree() and 'pedigree' in bom_json[base_key][i].keys():
151+
del bom_json[base_key][i]['pedigree']
152+
elif 'pedigree' in bom_json[base_key][i].keys():
153+
if 'ancestors' in bom_json[base_key][i]['pedigree'].keys():
154+
# recurse into ancestors
155+
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
156+
bom_json=bom_json[base_key][i]['pedigree'], base_key='ancestors'
157+
)
158+
if 'descendants' in bom_json[base_key][i]['pedigree'].keys():
159+
# recurse into descendants
160+
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
161+
bom_json=bom_json[base_key][i]['pedigree'], base_key='descendants'
162+
)
163+
if 'variants' in bom_json[base_key][i]['pedigree'].keys():
164+
# recurse into variants
165+
bom_json[base_key][i]['pedigree'] = self._recurse_specialise_component(
166+
bom_json=bom_json[base_key][i]['pedigree'], base_key='variants'
167+
)
168+
169+
if not self.external_references_supports_hashes() and 'externalReferences' \
170+
in bom_json[base_key][i].keys():
171+
for j in range(len(bom_json[base_key][i]['externalReferences'])):
172+
del bom_json[base_key][i]['externalReferences'][j]['hashes']
173+
174+
if not self.component_supports_properties() and 'properties' in bom_json[base_key][i].keys():
175+
del bom_json[base_key][i]['properties']
176+
177+
# recurse
178+
if 'components' in bom_json[base_key][i].keys():
179+
bom_json[base_key][i] = self._recurse_specialise_component(bom_json=bom_json[base_key][i])
180+
181+
if not self.component_supports_evidence() and 'evidence' in bom_json[base_key][i].keys():
182+
del bom_json[base_key][i]['evidence']
183+
184+
if not self.component_supports_release_notes() and 'releaseNotes' in bom_json[base_key][i].keys():
185+
del bom_json[base_key][i]['releaseNotes']
186+
187+
return bom_json
188+
154189

155190
class JsonV1Dot0(Json, SchemaVersion1Dot0):
156191

‎cyclonedx/output/schema.py

+33
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def bom_supports_vulnerabilities_via_extension(self) -> bool:
6565
def bom_requires_modified(self) -> bool:
6666
return False
6767

68+
def component_supports_supplier(self) -> bool:
69+
return True
70+
6871
def component_supports_author(self) -> bool:
6972
return True
7073

@@ -92,6 +95,12 @@ def pedigree_supports_patches(self) -> bool:
9295
def component_supports_external_references(self) -> bool:
9396
return True
9497

98+
def component_supports_properties(self) -> bool:
99+
return True
100+
101+
def component_supports_evidence(self) -> bool:
102+
return True
103+
95104
def component_supports_release_notes(self) -> bool:
96105
return True
97106

@@ -174,6 +183,12 @@ def bom_supports_vulnerabilities_via_extension(self) -> bool:
174183
def component_supports_mime_type_attribute(self) -> bool:
175184
return False
176185

186+
def component_supports_properties(self) -> bool:
187+
return False
188+
189+
def component_supports_evidence(self) -> bool:
190+
return False
191+
177192
def component_supports_release_notes(self) -> bool:
178193
return False
179194

@@ -220,12 +235,21 @@ def bom_supports_metadata(self) -> bool:
220235
def component_supports_mime_type_attribute(self) -> bool:
221236
return False
222237

238+
def component_supports_supplier(self) -> bool:
239+
return False
240+
223241
def component_supports_author(self) -> bool:
224242
return False
225243

226244
def component_supports_swid(self) -> bool:
227245
return False
228246

247+
def component_supports_properties(self) -> bool:
248+
return False
249+
250+
def component_supports_evidence(self) -> bool:
251+
return False
252+
229253
def component_supports_release_notes(self) -> bool:
230254
return False
231255

@@ -281,6 +305,9 @@ def license_supports_expression(self) -> bool:
281305
def component_supports_mime_type_attribute(self) -> bool:
282306
return False
283307

308+
def component_supports_supplier(self) -> bool:
309+
return False
310+
284311
def component_supports_swid(self) -> bool:
285312
return False
286313

@@ -290,6 +317,12 @@ def component_supports_pedigree(self) -> bool:
290317
def component_supports_external_references(self) -> bool:
291318
return False
292319

320+
def component_supports_properties(self) -> bool:
321+
return False
322+
323+
def component_supports_evidence(self) -> bool:
324+
return False
325+
293326
def component_supports_release_notes(self) -> bool:
294327
return False
295328

‎cyclonedx/output/xml.py

+46
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,20 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
180180

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

183+
# supplier
184+
if self.component_supports_supplier() and component.supplier:
185+
self._add_organizational_entity(
186+
parent_element=component_element, organization=component.supplier, tag_name='supplier'
187+
)
188+
189+
# author
183190
if self.component_supports_author() and component.author is not None:
184191
ElementTree.SubElement(component_element, 'author').text = component.author
185192

193+
# publisher
194+
if component.publisher:
195+
ElementTree.SubElement(component_element, 'publisher').text = component.publisher
196+
186197
# group
187198
if component.group:
188199
ElementTree.SubElement(component_element, 'group').text = component.group
@@ -201,6 +212,14 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
201212
else:
202213
ElementTree.SubElement(component_element, 'version').text = component.version
203214

215+
# description
216+
if component.description:
217+
ElementTree.SubElement(component_element, 'description').text = component.description
218+
219+
# scope
220+
if component.scope:
221+
ElementTree.SubElement(component_element, 'scope').text = component.scope.value
222+
204223
# hashes
205224
if component.hashes:
206225
Xml._add_hashes_to_element(hashes=component.hashes, element=component_element)
@@ -212,6 +231,10 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
212231
if not license_output:
213232
component_element.remove(licenses_e)
214233

234+
# copyright
235+
if component.copyright:
236+
ElementTree.SubElement(component_element, 'copyright').text = component.copyright
237+
215238
# cpe
216239
if component.cpe:
217240
ElementTree.SubElement(component_element, 'cpe').text = component.cpe
@@ -286,6 +309,29 @@ def _add_component_element(self, component: Component) -> ElementTree.Element:
286309
if self.component_supports_external_references() and len(component.external_references) > 0:
287310
self._add_external_references_to_element(ext_refs=component.external_references, element=component_element)
288311

312+
# properties
313+
if self.component_supports_properties() and component.properties:
314+
Xml._add_properties_element(properties=component.properties, parent_element=component_element)
315+
316+
# components
317+
if component.components:
318+
components_element = ElementTree.SubElement(component_element, 'components')
319+
for nested_component in component.components:
320+
components_element.append(self._add_component_element(component=nested_component))
321+
322+
# evidence
323+
if self.component_supports_evidence() and component.evidence:
324+
evidence_element = ElementTree.SubElement(component_element, 'evidence')
325+
if component.evidence.licenses:
326+
evidence_licenses_element = ElementTree.SubElement(evidence_element, 'licenses')
327+
self._add_licenses_to_element(
328+
licenses=component.evidence.licenses, parent_element=evidence_licenses_element
329+
)
330+
if component.evidence.copyright:
331+
evidence_copyrights_element = ElementTree.SubElement(evidence_element, 'copyright')
332+
for evidence_copyright in component.evidence.copyright:
333+
ElementTree.SubElement(evidence_copyrights_element, 'text').text = evidence_copyright.text
334+
289335
# releaseNotes
290336
if self.component_supports_release_notes() and component.release_notes:
291337
Xml._add_release_notes_element(release_notes=component.release_notes, parent_element=component_element)

‎tests/data.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
Property, Tool, XsUri
2929
from cyclonedx.model.bom import Bom
3030
from cyclonedx.model.component import Commit, Component, ComponentEvidence, ComponentType, Copyright, Patch, \
31-
PatchClassification, Pedigree, Swid
31+
PatchClassification, Pedigree, Swid, ComponentScope
3232
from cyclonedx.model.issue import IssueClassification, IssueType, IssueTypeSource
3333
from cyclonedx.model.release_note import ReleaseNotes
3434
from cyclonedx.model.service import Service
@@ -67,15 +67,25 @@ def get_bom_with_component_setuptools_with_release_notes() -> Bom:
6767

6868

6969
def get_bom_with_component_setuptools_complete() -> Bom:
70-
component = get_component_setuptools_simple()
70+
component = get_component_setuptools_simple(bom_ref=MOCK_UUID_6)
71+
component.supplier = get_org_entity_1()
72+
component.publisher = 'CycloneDX'
73+
component.description = 'This component is awesome'
74+
component.scope = ComponentScope.REQUIRED
75+
component.copyright = 'Apache 2.0 baby!'
7176
component.cpe = 'cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*'
7277
component.swid = get_swid_1()
7378
component.pedigree = get_pedigree_1()
79+
component.external_references.add(
80+
get_external_reference_1()
81+
)
82+
component.properties = get_properties_1()
7483
component.components.update([
7584
get_component_setuptools_simple(),
7685
get_component_toml_with_hashes_with_references()
7786
])
7887
component.evidence = ComponentEvidence(copyright_=[Copyright(text='Commercial'), Copyright(text='Commercial 2')])
88+
component.release_notes = get_release_notes()
7989
return Bom(components=[component])
8090

8191

‎tests/fixtures/json/1.2/bom_external_references.json

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
}
1515
]
1616
},
17-
"components": [],
1817
"externalReferences": [
1918
{
2019
"url": "https://cyclonedx.org",

‎tests/fixtures/json/1.2/bom_services_complex.json

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"version": "1.0.0"
2121
}
2222
},
23-
"components": [],
2423
"services": [
2524
{
2625
"bom-ref": "my-specific-bom-ref-for-my-first-service",

‎tests/fixtures/json/1.2/bom_services_nested.json

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
22
"$schema": "http://cyclonedx.org/schema/bom-1.2a.schema.json",
33
"bomFormat": "CycloneDX",
4-
"components": [
5-
],
64
"metadata": {
75
"component": {
86
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",

‎tests/fixtures/json/1.2/bom_services_simple.json

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"version": "1.0.0"
2121
}
2222
},
23-
"components": [],
2423
"services": [
2524
{
2625
"bom-ref": "bb5911d6-1a1d-41c9-b6e0-46e848d16655",

0 commit comments

Comments
 (0)
Please sign in to comment.