54
54
import org .dependencytrack .parser .cyclonedx .CycloneDxValidator ;
55
55
import org .dependencytrack .parser .cyclonedx .InvalidBomException ;
56
56
import org .dependencytrack .persistence .QueryManager ;
57
+ import org .dependencytrack .plugin .PluginManager ;
58
+ import org .dependencytrack .proto .storage .v1alpha1 .FileMetadata ;
57
59
import org .dependencytrack .resources .v1 .problems .InvalidBomProblemDetails ;
58
60
import org .dependencytrack .resources .v1 .problems .ProblemDetails ;
59
61
import org .dependencytrack .resources .v1 .vo .BomSubmitRequest ;
60
62
import org .dependencytrack .resources .v1 .vo .BomUploadResponse ;
63
+ import org .dependencytrack .storage .FileStorage ;
61
64
import org .glassfish .jersey .media .multipart .BodyPartEntity ;
62
65
import org .glassfish .jersey .media .multipart .FormDataBodyPart ;
63
66
import org .glassfish .jersey .media .multipart .FormDataParam ;
80
83
import jakarta .ws .rs .core .MediaType ;
81
84
import jakarta .ws .rs .core .Response ;
82
85
import java .io .ByteArrayInputStream ;
83
- import java .io .File ;
84
86
import java .io .IOException ;
85
87
import java .io .StringReader ;
86
88
import java .nio .charset .StandardCharsets ;
87
- import java .nio .file .Files ;
88
- import java .nio .file .StandardOpenOption ;
89
89
import java .security .Principal ;
90
+ import java .time .Instant ;
90
91
import java .util .Arrays ;
91
92
import java .util .Base64 ;
92
93
import java .util .List ;
@@ -465,17 +466,17 @@ private Response process(QueryManager qm, Project project, String encodedBomData
465
466
if (project != null ) {
466
467
requireAccess (qm , project );
467
468
468
- final File bomFile ;
469
+ final FileMetadata bomFileMetadata ;
469
470
try (final var encodedInputStream = new ByteArrayInputStream (encodedBomData .getBytes (StandardCharsets .UTF_8 ));
470
471
final var decodedInputStream = Base64 .getDecoder ().wrap (encodedInputStream );
471
472
final var byteOrderMarkInputStream = new BOMInputStream (decodedInputStream )) {
472
- bomFile = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
473
+ bomFileMetadata = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
473
474
} catch (IOException e ) {
474
475
LOGGER .error ("An unexpected error occurred while validating or storing a BOM uploaded to project: " + project .getUuid (), e );
475
476
return Response .status (Response .Status .INTERNAL_SERVER_ERROR ).build ();
476
477
}
477
478
478
- final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFile );
479
+ final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFileMetadata );
479
480
qm .createWorkflowSteps (bomUploadEvent .getChainIdentifier ());
480
481
Event .dispatch (bomUploadEvent );
481
482
@@ -496,18 +497,18 @@ private Response process(QueryManager qm, Project project, List<FormDataBodyPart
496
497
if (project != null ) {
497
498
requireAccess (qm , project );
498
499
499
- final File bomFile ;
500
+ final FileMetadata bomFileMetadata ;
500
501
try (final var inputStream = bodyPartEntity .getInputStream ();
501
502
final var byteOrderMarkInputStream = new BOMInputStream (inputStream )) {
502
- bomFile = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project , artifactPart .getMediaType ());
503
+ bomFileMetadata = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project , artifactPart .getMediaType ());
503
504
} catch (IOException e ) {
504
505
LOGGER .error ("An unexpected error occurred while validating or storing a BOM uploaded to project: " + project .getUuid (), e );
505
506
return Response .status (Response .Status .INTERNAL_SERVER_ERROR ).build ();
506
507
}
507
508
508
509
// todo: make option to combine all the bom data so components are reconciled in a single pass.
509
510
// todo: https://github.com/DependencyTrack/dependency-track/issues/130
510
- final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFile );
511
+ final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFileMetadata );
511
512
512
513
qm .createWorkflowSteps (bomUploadEvent .getChainIdentifier ());
513
514
Event .dispatch (bomUploadEvent );
@@ -522,25 +523,22 @@ private Response process(QueryManager qm, Project project, List<FormDataBodyPart
522
523
return Response .ok ().build ();
523
524
}
524
525
525
- private File validateAndStoreBom (final byte [] bomBytes , final Project project ) throws IOException {
526
+ private FileMetadata validateAndStoreBom (final byte [] bomBytes , final Project project ) throws IOException {
526
527
return validateAndStoreBom (bomBytes , project , null );
527
528
}
528
529
529
- private File validateAndStoreBom (final byte [] bomBytes , final Project project , MediaType mediaType ) throws IOException {
530
+ private FileMetadata validateAndStoreBom (final byte [] bomBytes , final Project project , MediaType mediaType ) throws IOException {
530
531
validate (bomBytes , project , mediaType );
531
532
532
- // TODO: Store externally so other instances of the API server can pick it up.
533
- // https://github.com/CycloneDX/cyclonedx-bom-repo-server
534
- final java .nio .file .Path tmpPath = Files .createTempFile ("dtrack-bom-%s" .formatted (project .getUuid ()), null );
535
- final File tmpFile = tmpPath .toFile ();
536
- tmpFile .deleteOnExit ();
537
-
538
- LOGGER .debug ("Writing BOM for project %s to %s" .formatted (project .getUuid (), tmpPath ));
539
- try (final var tmpOutputStream = Files .newOutputStream (tmpPath , StandardOpenOption .WRITE )) {
540
- tmpOutputStream .write (bomBytes );
533
+ // TODO: Provide mediaType to FileStorage#store. Should be any of:
534
+ // * application/vnd.cyclonedx+json
535
+ // * application/vnd.cyclonedx+xml
536
+ // * application/x.vnd.cyclonedx+protobuf
537
+ // Consider also attaching the detected version, i.e. application/vnd.cyclonedx+xml; version=1.6
538
+ // See https://cyclonedx.org/specification/overview/ -> Media Types.
539
+ try (final var fileStorage = PluginManager .getInstance ().getExtension (FileStorage .class )) {
540
+ return fileStorage .store ("bom-upload/%s_%s" .formatted (Instant .now ().toEpochMilli (), project .getUuid ()), bomBytes );
541
541
}
542
-
543
- return tmpFile ;
544
542
}
545
543
546
544
static void validate (final byte [] bomBytes , final Project project ) {
@@ -636,7 +634,7 @@ private static boolean shouldValidate(final Project project) {
636
634
.map (org .dependencytrack .model .Tag ::getName )
637
635
.anyMatch (validationModeTags ::contains );
638
636
return (validationMode == BomValidationMode .ENABLED_FOR_TAGS && doTagsMatch )
639
- || (validationMode == BomValidationMode .DISABLED_FOR_TAGS && !doTagsMatch );
637
+ || (validationMode == BomValidationMode .DISABLED_FOR_TAGS && !doTagsMatch );
640
638
}
641
639
}
642
640
}
0 commit comments