diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d27c8bf3e..14164ae8c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,9 @@ Changelog
v34.4.0 (unreleased)
--------------------
+- Add support for CycloneDX 1.6 outputs and inputs.
+ Also, the CycloneDX outputs can be downloaded as 1.6, 1.5, and 1.4 spec versions.
+
v34.3.0 (2024-04-10)
--------------------
diff --git a/scanpipe/api/views.py b/scanpipe/api/views.py
index f616dc669..41d862172 100644
--- a/scanpipe/api/views.py
+++ b/scanpipe/api/views.py
@@ -139,6 +139,8 @@ def results_download(self, request, *args, **kwargs):
"""Return the results in the provided `output_format` as an attachment."""
project = self.get_object()
format = request.query_params.get("output_format", "json")
+ version = request.query_params.get("version")
+ output_kwargs = {}
if format == "json":
return project_results_json_response(project, as_attachment=True)
@@ -147,7 +149,9 @@ def results_download(self, request, *args, **kwargs):
elif format == "spdx":
output_file = output.to_spdx(project)
elif format == "cyclonedx":
- output_file = output.to_cyclonedx(project)
+ if version:
+ output_kwargs["version"] = version
+ output_file = output.to_cyclonedx(project, **output_kwargs)
elif format == "attribution":
output_file = output.to_attribution(project)
else:
diff --git a/scanpipe/pipes/output.py b/scanpipe/pipes/output.py
index e612a0733..df093bdad 100644
--- a/scanpipe/pipes/output.py
+++ b/scanpipe/pipes/output.py
@@ -712,12 +712,13 @@ def sort_bom_with_schema_ordering(bom_as_dict, schema_version):
return json.dumps(ordered_dict, indent=2)
-def to_cyclonedx(project, schema_version=SchemaVersion.V1_5):
+def to_cyclonedx(project, version="1.6"):
"""
Generate output for the provided ``project`` in CycloneDX BOM format.
The output file is created in the ``project`` "output/" directory.
Return the path of the generated output file.
"""
+ schema_version = SchemaVersion.from_version(version)
output_file = project.get_output_file_path("results", "cdx.json")
bom = get_cyclonedx_bom(project)
diff --git a/scanpipe/templates/scanpipe/dropdowns/project_download_dropdown.html b/scanpipe/templates/scanpipe/dropdowns/project_download_dropdown.html
index 2729869c4..f07fd7cd8 100644
--- a/scanpipe/templates/scanpipe/dropdowns/project_download_dropdown.html
+++ b/scanpipe/templates/scanpipe/dropdowns/project_download_dropdown.html
@@ -18,9 +18,14 @@
SPDX
-
+
Attribution
diff --git a/scanpipe/templates/scanpipe/includes/project_downloads.html b/scanpipe/templates/scanpipe/includes/project_downloads.html
index b5da56267..6eb9eaa67 100644
--- a/scanpipe/templates/scanpipe/includes/project_downloads.html
+++ b/scanpipe/templates/scanpipe/includes/project_downloads.html
@@ -10,9 +10,29 @@
SPDX
-
- CycloneDX
-
+
+
+
+
+
+
Attribution
diff --git a/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json b/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
index 92b36fd8a..1aa023ccc 100644
--- a/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
+++ b/scanpipe/tests/data/cyclonedx/asgiref-3.3.0.cdx.json
@@ -1,7 +1,7 @@
{
- "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
+ "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
- "specVersion": "1.5",
+ "specVersion": "1.6",
"serialNumber": "urn:uuid:b74fe5df-e965-415e-ba65-f38421a0695d",
"version": 1,
"metadata": {
diff --git a/scanpipe/tests/pipes/test_output.py b/scanpipe/tests/pipes/test_output.py
index cb473c5c9..f634a8e9a 100644
--- a/scanpipe/tests/pipes/test_output.py
+++ b/scanpipe/tests/pipes/test_output.py
@@ -274,6 +274,13 @@ def test_scanpipe_pipes_outputs_to_cyclonedx(self, regen=FIXTURES_REGEN):
self.assertJSONEqual(results, expected_location.read_text())
+ output_file = output.to_cyclonedx(project=project, version="1.5")
+ results_json = json.loads(output_file.read_text())
+ self.assertEqual(
+ "http://cyclonedx.org/schema/bom-1.5.schema.json", results_json["$schema"]
+ )
+ self.assertEqual("1.5", results_json["specVersion"])
+
def test_scanpipe_pipes_outputs_to_spdx(self):
fixtures = self.data_path / "asgiref-3.3.0_fixtures.json"
call_command("loaddata", fixtures, **{"verbosity": 0})
diff --git a/scanpipe/tests/test_api.py b/scanpipe/tests/test_api.py
index f68b6aaa7..db9c48344 100644
--- a/scanpipe/tests/test_api.py
+++ b/scanpipe/tests/test_api.py
@@ -540,6 +540,18 @@ def test_scanpipe_api_project_action_results_download_output_formats(self):
results = json.loads(response_value)
self.assertIn("$schema", sorted(results.keys()))
+ data = {
+ "output_format": "cyclonedx",
+ "version": "1.5",
+ }
+ response = self.csrf_client.get(url, data=data)
+ response_value = response.getvalue()
+ results = json.loads(response_value)
+ self.assertEqual(
+ "http://cyclonedx.org/schema/bom-1.5.schema.json", results["$schema"]
+ )
+ self.assertEqual("1.5", results["specVersion"])
+
data = {"output_format": "xlsx"}
response = self.csrf_client.get(url, data=data)
expected = [
diff --git a/scanpipe/urls.py b/scanpipe/urls.py
index 131504708..efa90bf55 100644
--- a/scanpipe/urls.py
+++ b/scanpipe/urls.py
@@ -126,6 +126,11 @@
views.pipeline_help_view,
name="pipeline_help",
),
+ path(
+ "project//results///",
+ views.ProjectResultsView.as_view(),
+ name="project_results",
+ ),
path(
"project//results//",
views.ProjectResultsView.as_view(),
diff --git a/scanpipe/views.py b/scanpipe/views.py
index b2581a812..f32780604 100644
--- a/scanpipe/views.py
+++ b/scanpipe/views.py
@@ -1281,6 +1281,8 @@ def get(self, request, *args, **kwargs):
self.object = self.get_object()
project = self.object
format = self.kwargs["format"]
+ version = self.kwargs.get("version")
+ output_kwargs = {}
if format == "json":
return project_results_json_response(project, as_attachment=True)
@@ -1289,7 +1291,9 @@ def get(self, request, *args, **kwargs):
elif format == "spdx":
output_file = output.to_spdx(project)
elif format == "cyclonedx":
- output_file = output.to_cyclonedx(project)
+ if version:
+ output_kwargs["version"] = version
+ output_file = output.to_cyclonedx(project, **output_kwargs)
elif format == "attribution":
output_file = output.to_attribution(project)
else: