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: