34
34
import io .swagger .v3 .oas .annotations .security .SecurityRequirement ;
35
35
import io .swagger .v3 .oas .annotations .security .SecurityRequirements ;
36
36
import io .swagger .v3 .oas .annotations .tags .Tag ;
37
- import jakarta .json .Json ;
38
- import jakarta .json .JsonArray ;
39
- import jakarta .json .JsonReader ;
40
- import jakarta .json .JsonString ;
41
- import jakarta .validation .Validator ;
42
- import jakarta .ws .rs .Consumes ;
43
- import jakarta .ws .rs .DefaultValue ;
44
- import jakarta .ws .rs .GET ;
45
- import jakarta .ws .rs .POST ;
46
- import jakarta .ws .rs .PUT ;
47
- import jakarta .ws .rs .Path ;
48
- import jakarta .ws .rs .PathParam ;
49
- import jakarta .ws .rs .Produces ;
50
- import jakarta .ws .rs .QueryParam ;
51
- import jakarta .ws .rs .WebApplicationException ;
52
- import jakarta .ws .rs .core .MediaType ;
53
- import jakarta .ws .rs .core .Response ;
54
37
import org .apache .commons .io .IOUtils ;
55
38
import org .apache .commons .io .input .BOMInputStream ;
56
39
import org .apache .commons .lang3 .StringUtils ;
72
55
import org .dependencytrack .parser .cyclonedx .CycloneDxValidator ;
73
56
import org .dependencytrack .parser .cyclonedx .InvalidBomException ;
74
57
import org .dependencytrack .persistence .QueryManager ;
58
+ import org .dependencytrack .plugin .PluginManager ;
59
+ import org .dependencytrack .proto .storage .v1alpha1 .FileMetadata ;
75
60
import org .dependencytrack .resources .v1 .problems .InvalidBomProblemDetails ;
76
61
import org .dependencytrack .resources .v1 .problems .ProblemDetails ;
77
62
import org .dependencytrack .resources .v1 .vo .BomSubmitRequest ;
78
63
import org .dependencytrack .resources .v1 .vo .BomUploadResponse ;
64
+ import org .dependencytrack .storage .FileStorage ;
79
65
import org .glassfish .jersey .media .multipart .BodyPartEntity ;
80
66
import org .glassfish .jersey .media .multipart .FormDataBodyPart ;
81
67
import org .glassfish .jersey .media .multipart .FormDataParam ;
82
68
69
+ import jakarta .json .Json ;
70
+ import jakarta .json .JsonArray ;
71
+ import jakarta .json .JsonReader ;
72
+ import jakarta .json .JsonString ;
73
+ import jakarta .validation .Validator ;
74
+ import jakarta .ws .rs .Consumes ;
75
+ import jakarta .ws .rs .DefaultValue ;
76
+ import jakarta .ws .rs .GET ;
77
+ import jakarta .ws .rs .POST ;
78
+ import jakarta .ws .rs .PUT ;
79
+ import jakarta .ws .rs .Path ;
80
+ import jakarta .ws .rs .PathParam ;
81
+ import jakarta .ws .rs .Produces ;
82
+ import jakarta .ws .rs .QueryParam ;
83
+ import jakarta .ws .rs .WebApplicationException ;
84
+ import jakarta .ws .rs .core .MediaType ;
85
+ import jakarta .ws .rs .core .Response ;
83
86
import java .io .ByteArrayInputStream ;
84
- import java .io .File ;
85
87
import java .io .IOException ;
86
88
import java .io .StringReader ;
87
89
import java .nio .charset .StandardCharsets ;
88
- import java .nio .file .Files ;
89
- import java .nio .file .StandardOpenOption ;
90
90
import java .security .Principal ;
91
+ import java .time .Instant ;
91
92
import java .util .Arrays ;
92
93
import java .util .Base64 ;
93
94
import java .util .List ;
@@ -330,7 +331,7 @@ public Response uploadBom(@Parameter(required = true) BomSubmitRequest request)
330
331
final String trimmedProjectName = StringUtils .trimToNull (request .getProjectName ());
331
332
if (request .isLatestProjectVersion ()) {
332
333
final Project oldLatest = qm .getLatestProjectVersion (trimmedProjectName );
333
- if (oldLatest != null && !qm .hasAccess (super .getPrincipal (), oldLatest )) {
334
+ if (oldLatest != null && !qm .hasAccess (super .getPrincipal (), oldLatest )) {
334
335
return Response .status (Response .Status .FORBIDDEN )
335
336
.entity ("Cannot create latest version for project with this name. Access to current latest " +
336
337
"version is forbidden!" )
@@ -436,7 +437,7 @@ public Response uploadBom(
436
437
}
437
438
if (isLatest ) {
438
439
final Project oldLatest = qm .getLatestProjectVersion (trimmedProjectName );
439
- if (oldLatest != null && !qm .hasAccess (super .getPrincipal (), oldLatest )) {
440
+ if (oldLatest != null && !qm .hasAccess (super .getPrincipal (), oldLatest )) {
440
441
return Response .status (Response .Status .FORBIDDEN )
441
442
.entity ("Cannot create latest version for project with this name. Access to current latest " +
442
443
"version is forbidden!" )
@@ -467,17 +468,17 @@ private Response process(QueryManager qm, Project project, String encodedBomData
467
468
return Response .status (Response .Status .FORBIDDEN ).entity ("Access to the specified project is forbidden" ).build ();
468
469
}
469
470
470
- final File bomFile ;
471
+ final FileMetadata bomFileMetadata ;
471
472
try (final var encodedInputStream = new ByteArrayInputStream (encodedBomData .getBytes (StandardCharsets .UTF_8 ));
472
473
final var decodedInputStream = Base64 .getDecoder ().wrap (encodedInputStream );
473
474
final var byteOrderMarkInputStream = new BOMInputStream (decodedInputStream )) {
474
- bomFile = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
475
+ bomFileMetadata = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
475
476
} catch (IOException e ) {
476
477
LOGGER .error ("An unexpected error occurred while validating or storing a BOM uploaded to project: " + project .getUuid (), e );
477
478
return Response .status (Response .Status .INTERNAL_SERVER_ERROR ).build ();
478
479
}
479
480
480
- final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFile );
481
+ final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFileMetadata );
481
482
qm .createWorkflowSteps (bomUploadEvent .getChainIdentifier ());
482
483
Event .dispatch (bomUploadEvent );
483
484
@@ -500,18 +501,18 @@ private Response process(QueryManager qm, Project project, List<FormDataBodyPart
500
501
return Response .status (Response .Status .FORBIDDEN ).entity ("Access to the specified project is forbidden" ).build ();
501
502
}
502
503
503
- final File bomFile ;
504
+ final FileMetadata bomFileMetadata ;
504
505
try (final var inputStream = bodyPartEntity .getInputStream ();
505
506
final var byteOrderMarkInputStream = new BOMInputStream (inputStream )) {
506
- bomFile = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
507
+ bomFileMetadata = validateAndStoreBom (IOUtils .toByteArray (byteOrderMarkInputStream ), project );
507
508
} catch (IOException e ) {
508
509
LOGGER .error ("An unexpected error occurred while validating or storing a BOM uploaded to project: " + project .getUuid (), e );
509
510
return Response .status (Response .Status .INTERNAL_SERVER_ERROR ).build ();
510
511
}
511
512
512
513
// todo: make option to combine all the bom data so components are reconciled in a single pass.
513
514
// todo: https://github.com/DependencyTrack/dependency-track/issues/130
514
- final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFile );
515
+ final BomUploadEvent bomUploadEvent = new BomUploadEvent (qm .detach (Project .class , project .getId ()), bomFileMetadata );
515
516
516
517
qm .createWorkflowSteps (bomUploadEvent .getChainIdentifier ());
517
518
Event .dispatch (bomUploadEvent );
@@ -526,21 +527,12 @@ private Response process(QueryManager qm, Project project, List<FormDataBodyPart
526
527
return Response .ok ().build ();
527
528
}
528
529
529
- private File validateAndStoreBom (final byte [] bomBytes , final Project project ) throws IOException {
530
+ private FileMetadata validateAndStoreBom (final byte [] bomBytes , final Project project ) throws IOException {
530
531
validate (bomBytes , project );
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
+ try (final var fileStorage = PluginManager .getInstance ().getExtension (FileStorage .class )) {
534
+ return fileStorage .store ("bom-upload/%s_%s" .formatted (Instant .now ().toEpochMilli (), project .getUuid ()), bomBytes );
541
535
}
542
-
543
- return tmpFile ;
544
536
}
545
537
546
538
static void validate (final byte [] bomBytes , final Project project ) {
@@ -634,7 +626,7 @@ private static boolean shouldValidate(final Project project) {
634
626
.map (org .dependencytrack .model .Tag ::getName )
635
627
.anyMatch (validationModeTags ::contains );
636
628
return (validationMode == BomValidationMode .ENABLED_FOR_TAGS && doTagsMatch )
637
- || (validationMode == BomValidationMode .DISABLED_FOR_TAGS && !doTagsMatch );
629
+ || (validationMode == BomValidationMode .DISABLED_FOR_TAGS && !doTagsMatch );
638
630
}
639
631
}
640
632
}
0 commit comments