18
18
*/
19
19
package org .dependencytrack .resources .v1 ;
20
20
21
+ import jakarta .json .Json ;
22
+ import jakarta .json .JsonArray ;
23
+ import jakarta .json .JsonReader ;
24
+ import jakarta .json .JsonString ;
25
+ import jakarta .validation .Validator ;
26
+ import jakarta .ws .rs .Consumes ;
27
+ import jakarta .ws .rs .DefaultValue ;
28
+ import jakarta .ws .rs .GET ;
29
+ import jakarta .ws .rs .POST ;
30
+ import jakarta .ws .rs .PUT ;
31
+ import jakarta .ws .rs .Path ;
32
+ import jakarta .ws .rs .PathParam ;
33
+ import jakarta .ws .rs .Produces ;
34
+ import jakarta .ws .rs .QueryParam ;
35
+ import jakarta .ws .rs .WebApplicationException ;
36
+ import jakarta .ws .rs .core .MediaType ;
37
+ import jakarta .ws .rs .core .Response ;
38
+ import java .io .ByteArrayInputStream ;
39
+ import java .io .IOException ;
40
+ import java .io .StringReader ;
41
+ import java .nio .charset .StandardCharsets ;
42
+ import java .security .Principal ;
43
+ import java .time .Instant ;
44
+ import java .util .Arrays ;
45
+ import java .util .Base64 ;
46
+ import java .util .List ;
47
+ import java .util .Set ;
48
+
49
+ import static java .util .function .Predicate .not ;
50
+ import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_MODE ;
51
+ import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_TAGS_EXCLUSIVE ;
52
+ import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_TAGS_INCLUSIVE ;
53
+
21
54
import alpine .common .logging .Logger ;
22
55
import alpine .event .framework .Event ;
23
56
import alpine .model .ConfigProperty ;
54
87
import org .dependencytrack .parser .cyclonedx .CycloneDxValidator ;
55
88
import org .dependencytrack .parser .cyclonedx .InvalidBomException ;
56
89
import org .dependencytrack .persistence .QueryManager ;
90
+ import org .dependencytrack .plugin .PluginManager ;
91
+ import org .dependencytrack .proto .storage .v1alpha1 .FileMetadata ;
57
92
import org .dependencytrack .resources .v1 .problems .InvalidBomProblemDetails ;
58
93
import org .dependencytrack .resources .v1 .problems .ProblemDetails ;
59
94
import org .dependencytrack .resources .v1 .vo .BomSubmitRequest ;
60
95
import org .dependencytrack .resources .v1 .vo .BomUploadResponse ;
96
+ import org .dependencytrack .storage .FileStorage ;
61
97
import org .glassfish .jersey .media .multipart .BodyPartEntity ;
62
98
import org .glassfish .jersey .media .multipart .FormDataBodyPart ;
63
99
import org .glassfish .jersey .media .multipart .FormDataParam ;
64
100
65
- import jakarta .json .Json ;
66
- import jakarta .json .JsonArray ;
67
- import jakarta .json .JsonReader ;
68
- import jakarta .json .JsonString ;
69
- import jakarta .validation .Validator ;
70
- import jakarta .ws .rs .Consumes ;
71
- import jakarta .ws .rs .DefaultValue ;
72
- import jakarta .ws .rs .GET ;
73
- import jakarta .ws .rs .POST ;
74
- import jakarta .ws .rs .PUT ;
75
- import jakarta .ws .rs .Path ;
76
- import jakarta .ws .rs .PathParam ;
77
- import jakarta .ws .rs .Produces ;
78
- import jakarta .ws .rs .QueryParam ;
79
- import jakarta .ws .rs .WebApplicationException ;
80
- import jakarta .ws .rs .core .MediaType ;
81
- import jakarta .ws .rs .core .Response ;
82
- import java .io .ByteArrayInputStream ;
83
- import java .io .File ;
84
- import java .io .IOException ;
85
- import java .io .StringReader ;
86
- import java .nio .charset .StandardCharsets ;
87
- import java .nio .file .Files ;
88
- import java .nio .file .StandardOpenOption ;
89
- import java .security .Principal ;
90
- import java .util .Arrays ;
91
- import java .util .Base64 ;
92
- import java .util .List ;
93
- import java .util .Set ;
94
-
95
- import static java .util .function .Predicate .not ;
96
- import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_MODE ;
97
- import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_TAGS_EXCLUSIVE ;
98
- import static org .dependencytrack .model .ConfigPropertyConstants .BOM_VALIDATION_TAGS_INCLUSIVE ;
99
-
100
101
/**
101
102
* JAX-RS resources for processing bill-of-material (bom) documents.
102
103
*
@@ -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