|
11 | 11 | import django_filters |
12 | 12 | from rest_framework import permissions |
13 | 13 | from rest_framework import serializers |
| 14 | +from rest_framework import status |
| 15 | +from rest_framework.decorators import action |
14 | 16 | from rest_framework.permissions import SAFE_METHODS |
| 17 | +from rest_framework.response import Response |
15 | 18 |
|
16 | 19 | from component_catalog.api import KeywordsField |
17 | 20 | from component_catalog.api import PackageEmbeddedSerializer |
|
31 | 34 | from dje.filters import NameVersionFilter |
32 | 35 | from dje.permissions import assign_all_object_permissions |
33 | 36 | from product_portfolio.filters import ComponentCompletenessAPIFilter |
| 37 | +from product_portfolio.forms import ImportFromScanForm |
| 38 | +from product_portfolio.forms import LoadSBOMsForm |
| 39 | +from product_portfolio.forms import PullProjectDataForm |
34 | 40 | from product_portfolio.models import CodebaseResource |
35 | 41 | from product_portfolio.models import Product |
36 | 42 | from product_portfolio.models import ProductComponent |
@@ -191,6 +197,57 @@ class Meta: |
191 | 197 | ) |
192 | 198 |
|
193 | 199 |
|
| 200 | +class LoadSBOMsFormSerializer(serializers.Serializer): |
| 201 | + """Serializer equivalent of LoadSBOMsForm, used for API documentation.""" |
| 202 | + |
| 203 | + input_file = serializers.FileField( |
| 204 | + required=True, |
| 205 | + help_text=LoadSBOMsForm.base_fields["input_file"].label, |
| 206 | + ) |
| 207 | + update_existing_packages = serializers.BooleanField( |
| 208 | + required=False, |
| 209 | + default=False, |
| 210 | + help_text=LoadSBOMsForm.base_fields["update_existing_packages"].help_text, |
| 211 | + ) |
| 212 | + scan_all_packages = serializers.BooleanField( |
| 213 | + required=False, |
| 214 | + default=False, |
| 215 | + help_text=LoadSBOMsForm.base_fields["scan_all_packages"].help_text, |
| 216 | + ) |
| 217 | + |
| 218 | + |
| 219 | +class ImportFromScanSerializer(serializers.Serializer): |
| 220 | + """Serializer equivalent of ImportFromScanForm, used for API documentation.""" |
| 221 | + |
| 222 | + upload_file = serializers.FileField( |
| 223 | + required=True, |
| 224 | + ) |
| 225 | + create_codebase_resources = serializers.BooleanField( |
| 226 | + required=False, |
| 227 | + default=False, |
| 228 | + help_text=ImportFromScanForm.base_fields["create_codebase_resources"].help_text, |
| 229 | + ) |
| 230 | + stop_on_error = serializers.BooleanField( |
| 231 | + required=False, |
| 232 | + default=False, |
| 233 | + help_text=ImportFromScanForm.base_fields["stop_on_error"].help_text, |
| 234 | + ) |
| 235 | + |
| 236 | + |
| 237 | +class PullProjectDataSerializer(serializers.Serializer): |
| 238 | + """Serializer equivalent of PullProjectDataForm, used for API documentation.""" |
| 239 | + |
| 240 | + project_name_or_uuid = serializers.CharField( |
| 241 | + required=True, |
| 242 | + help_text=PullProjectDataForm.base_fields["project_name_or_uuid"].label, |
| 243 | + ) |
| 244 | + update_existing_packages = serializers.BooleanField( |
| 245 | + required=False, |
| 246 | + default=False, |
| 247 | + help_text=PullProjectDataForm.base_fields["update_existing_packages"].help_text, |
| 248 | + ) |
| 249 | + |
| 250 | + |
194 | 251 | class ProductViewSet(CreateRetrieveUpdateListViewSet): |
195 | 252 | queryset = Product.objects.none() |
196 | 253 | serializer_class = ProductSerializer |
@@ -240,6 +297,72 @@ def perform_create(self, serializer): |
240 | 297 | super().perform_create(serializer) |
241 | 298 | assign_all_object_permissions(self.request.user, serializer.instance) |
242 | 299 |
|
| 300 | + @action(detail=True, methods=["post"], serializer_class=LoadSBOMsFormSerializer) |
| 301 | + def load_sboms(self, request, *args, **kwargs): |
| 302 | + """ |
| 303 | + Load Packages from SBOMs. |
| 304 | +
|
| 305 | + DejaCode supports the following SBOM formats: |
| 306 | + * CycloneDX BOM as JSON bom.json and .cdx.json, |
| 307 | + * SPDX document as JSON .spdx.json, |
| 308 | + * AboutCode .ABOUT files, |
| 309 | +
|
| 310 | + Multiple SBOMs: You can provide multiple SBOMs by packaging them into a zip |
| 311 | + archive. DejaCode will handle and process them accordingly. |
| 312 | + """ |
| 313 | + product = self.get_object() |
| 314 | + |
| 315 | + form = LoadSBOMsForm(data=request.POST, files=request.FILES) |
| 316 | + if not form.is_valid(): |
| 317 | + return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) |
| 318 | + |
| 319 | + form.submit(product=product, user=request.user) |
| 320 | + return Response({"status": "SBOM file submitted to ScanCode.io for inspection."}) |
| 321 | + |
| 322 | + @action(detail=True, methods=["post"], serializer_class=ImportFromScanSerializer) |
| 323 | + def import_from_scan(self, request, *args, **kwargs): |
| 324 | + """ |
| 325 | + Import the scan results in the Product. |
| 326 | +
|
| 327 | + Upload a ScanCode.io or ScanCode-toolkit JSON output file. |
| 328 | + """ |
| 329 | + product = self.get_object() |
| 330 | + |
| 331 | + form = ImportFromScanForm(user=request.user, data=request.POST, files=request.FILES) |
| 332 | + if not form.is_valid(): |
| 333 | + return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) |
| 334 | + |
| 335 | + try: |
| 336 | + warnings, created_counts = form.save(product=product) |
| 337 | + except ValidationError as error: |
| 338 | + return Response(error.messages, status=status.HTTP_400_BAD_REQUEST) |
| 339 | + |
| 340 | + if not created_counts: |
| 341 | + msg = "Nothing imported." |
| 342 | + else: |
| 343 | + msg = "Imported from Scan: " |
| 344 | + msg += ", ".join([f"{value} {key}" for key, value in created_counts.items()]) |
| 345 | + return Response({"status": msg}) |
| 346 | + |
| 347 | + @action(detail=True, methods=["post"], serializer_class=PullProjectDataSerializer) |
| 348 | + def pull_scancodeio_project_data(self, request, *args, **kwargs): |
| 349 | + """ |
| 350 | + Pull data from a ScanCode.io Project to import all its Discovered Packages. |
| 351 | + Imported Packages will be assigned to this Product. |
| 352 | + """ |
| 353 | + product = self.get_object() |
| 354 | + |
| 355 | + form = PullProjectDataForm(data=request.POST) |
| 356 | + if not form.is_valid(): |
| 357 | + return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) |
| 358 | + |
| 359 | + try: |
| 360 | + form.submit(product=product, user=request.user) |
| 361 | + except ValidationError as error: |
| 362 | + return Response(error.messages, status=status.HTTP_400_BAD_REQUEST) |
| 363 | + |
| 364 | + return Response({"status": "Packages import from ScanCode.io in progress..."}) |
| 365 | + |
243 | 366 |
|
244 | 367 | class BaseProductRelationSerializer(ValidateLicenseExpressionMixin, DataspacedSerializer): |
245 | 368 | product = NameVersionHyperlinkedRelatedField( |
|
0 commit comments