Skip to content

Commit 9aef059

Browse files
authored
Add support for CycloneDX 1.6 outputs and inputs (#1165)
Signed-off-by: tdruez <tdruez@nexb.com>
1 parent 5b52ce9 commit 9aef059

File tree

10 files changed

+71
-10
lines changed

10 files changed

+71
-10
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Changelog
44
v34.4.0 (unreleased)
55
--------------------
66

7+
- Add support for CycloneDX 1.6 outputs and inputs.
8+
Also, the CycloneDX outputs can be downloaded as 1.6, 1.5, and 1.4 spec versions.
9+
710
v34.3.0 (2024-04-10)
811
--------------------
912

scanpipe/api/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ def results_download(self, request, *args, **kwargs):
139139
"""Return the results in the provided `output_format` as an attachment."""
140140
project = self.get_object()
141141
format = request.query_params.get("output_format", "json")
142+
version = request.query_params.get("version")
143+
output_kwargs = {}
142144

143145
if format == "json":
144146
return project_results_json_response(project, as_attachment=True)
@@ -147,7 +149,9 @@ def results_download(self, request, *args, **kwargs):
147149
elif format == "spdx":
148150
output_file = output.to_spdx(project)
149151
elif format == "cyclonedx":
150-
output_file = output.to_cyclonedx(project)
152+
if version:
153+
output_kwargs["version"] = version
154+
output_file = output.to_cyclonedx(project, **output_kwargs)
151155
elif format == "attribution":
152156
output_file = output.to_attribution(project)
153157
else:

scanpipe/pipes/output.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,12 +712,13 @@ def sort_bom_with_schema_ordering(bom_as_dict, schema_version):
712712
return json.dumps(ordered_dict, indent=2)
713713

714714

715-
def to_cyclonedx(project, schema_version=SchemaVersion.V1_5):
715+
def to_cyclonedx(project, version="1.6"):
716716
"""
717717
Generate output for the provided ``project`` in CycloneDX BOM format.
718718
The output file is created in the ``project`` "output/" directory.
719719
Return the path of the generated output file.
720720
"""
721+
schema_version = SchemaVersion.from_version(version)
721722
output_file = project.get_output_file_path("results", "cdx.json")
722723

723724
bom = get_cyclonedx_bom(project)

scanpipe/templates/scanpipe/dropdowns/project_download_dropdown.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
<a href="{% url 'project_results' project.slug 'spdx' %}" class="dropdown-item">
1919
<strong>SPDX</strong>
2020
</a>
21-
<a href="{% url 'project_results' project.slug 'cyclonedx' %}" class="dropdown-item">
21+
<div class="dropdown-item">
2222
<strong>CycloneDX</strong>
23-
</a>
23+
<div class="has-text-weight-semibold">
24+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.6' %}" class="tag is-link is-light mr-1">1.6</a>
25+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.5' %}" class="tag is-link is-light mr-1">1.5</a>
26+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.4' %}" class="tag is-link is-light">1.4</a>
27+
</div>
28+
</div>
2429
<a href="{% url 'project_results' project.slug 'attribution' %}" class="dropdown-item">
2530
<strong>Attribution</strong>
2631
</a>

scanpipe/templates/scanpipe/includes/project_downloads.html

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,29 @@
1010
<a class="tag is-success is-medium" href="{% url 'project_results' project.slug 'spdx' %}">
1111
SPDX <span class="icon ml-1"><i class="fa-solid fa-download"></i></span>
1212
</a>
13-
<a class="tag is-success is-medium" href="{% url 'project_results' project.slug 'cyclonedx' %}">
14-
CycloneDX <span class="icon ml-1"><i class="fa-solid fa-download"></i></span>
15-
</a>
13+
<div class="dropdown is-hoverable">
14+
<div class="dropdown-trigger">
15+
<button class="button tag is-success is-medium" aria-haspopup="true" aria-controls="dropdown-menu-cyclonedx">
16+
<span>CycloneDX</span>
17+
<span class="icon">
18+
<i class="fa-solid fa-download" aria-hidden="true"></i>
19+
</span>
20+
</button>
21+
</div>
22+
<div class="dropdown-menu" id="dropdown-menu-cyclonedx" role="menu">
23+
<div class="dropdown-content">
24+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.6' %}" class="dropdown-item has-text-weight-semibold">
25+
Spec 1.6 <span class="tag is-link is-light ml-1">Latest</span>
26+
</a>
27+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.5' %}" class="dropdown-item">
28+
Spec 1.5
29+
</a>
30+
<a href="{% url 'project_results' project.slug 'cyclonedx' '1.4' %}" class="dropdown-item">
31+
Spec 1.4
32+
</a>
33+
</div>
34+
</div>
35+
</div>
1636
<a class="tag is-success is-medium" href="{% url 'project_results' project.slug 'attribution' %}">
1737
Attribution <span class="icon ml-1"><i class="fa-solid fa-download"></i></span>
1838
</a>

scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
2+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
33
"bomFormat": "CycloneDX",
4-
"specVersion": "1.5",
4+
"specVersion": "1.6",
55
"serialNumber": "urn:uuid:b74fe5df-e965-415e-ba65-f38421a0695d",
66
"version": 1,
77
"metadata": {

scanpipe/tests/pipes/test_output.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ def test_scanpipe_pipes_outputs_to_cyclonedx(self, regen=FIXTURES_REGEN):
274274

275275
self.assertJSONEqual(results, expected_location.read_text())
276276

277+
output_file = output.to_cyclonedx(project=project, version="1.5")
278+
results_json = json.loads(output_file.read_text())
279+
self.assertEqual(
280+
"http://cyclonedx.org/schema/bom-1.5.schema.json", results_json["$schema"]
281+
)
282+
self.assertEqual("1.5", results_json["specVersion"])
283+
277284
def test_scanpipe_pipes_outputs_to_spdx(self):
278285
fixtures = self.data_path / "asgiref-3.3.0_fixtures.json"
279286
call_command("loaddata", fixtures, **{"verbosity": 0})

scanpipe/tests/test_api.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,18 @@ def test_scanpipe_api_project_action_results_download_output_formats(self):
540540
results = json.loads(response_value)
541541
self.assertIn("$schema", sorted(results.keys()))
542542

543+
data = {
544+
"output_format": "cyclonedx",
545+
"version": "1.5",
546+
}
547+
response = self.csrf_client.get(url, data=data)
548+
response_value = response.getvalue()
549+
results = json.loads(response_value)
550+
self.assertEqual(
551+
"http://cyclonedx.org/schema/bom-1.5.schema.json", results["$schema"]
552+
)
553+
self.assertEqual("1.5", results["specVersion"])
554+
543555
data = {"output_format": "xlsx"}
544556
response = self.csrf_client.get(url, data=data)
545557
expected = [

scanpipe/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@
126126
views.pipeline_help_view,
127127
name="pipeline_help",
128128
),
129+
path(
130+
"project/<slug:slug>/results/<str:format>/<str:version>/",
131+
views.ProjectResultsView.as_view(),
132+
name="project_results",
133+
),
129134
path(
130135
"project/<slug:slug>/results/<str:format>/",
131136
views.ProjectResultsView.as_view(),

scanpipe/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,8 @@ def get(self, request, *args, **kwargs):
12811281
self.object = self.get_object()
12821282
project = self.object
12831283
format = self.kwargs["format"]
1284+
version = self.kwargs.get("version")
1285+
output_kwargs = {}
12841286

12851287
if format == "json":
12861288
return project_results_json_response(project, as_attachment=True)
@@ -1289,7 +1291,9 @@ def get(self, request, *args, **kwargs):
12891291
elif format == "spdx":
12901292
output_file = output.to_spdx(project)
12911293
elif format == "cyclonedx":
1292-
output_file = output.to_cyclonedx(project)
1294+
if version:
1295+
output_kwargs["version"] = version
1296+
output_file = output.to_cyclonedx(project, **output_kwargs)
12931297
elif format == "attribution":
12941298
output_file = output.to_attribution(project)
12951299
else:

0 commit comments

Comments
 (0)