1
+ /*
2
+ * This file is part of Dependency-Track.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * SPDX-License-Identifier: Apache-2.0
17
+ * Copyright (c) Steve Springett. All Rights Reserved.
18
+ */
19
+ package org .dependencytrack .resources .v1 ;
20
+
21
+ import alpine .server .filters .ApiFilter ;
22
+ import alpine .server .filters .AuthenticationFilter ;
23
+ import org .dependencytrack .ResourceTest ;
24
+ import org .dependencytrack .model .AnalysisResponse ;
25
+ import org .dependencytrack .model .AnalysisState ;
26
+ import org .dependencytrack .model .AnalyzerIdentity ;
27
+ import org .dependencytrack .model .Classifier ;
28
+ import org .dependencytrack .model .Component ;
29
+ import org .dependencytrack .model .Project ;
30
+ import org .dependencytrack .model .Severity ;
31
+ import org .dependencytrack .model .Vulnerability ;
32
+ import org .glassfish .jersey .media .multipart .MultiPartFeature ;
33
+ import org .glassfish .jersey .server .ResourceConfig ;
34
+ import org .glassfish .jersey .servlet .ServletContainer ;
35
+ import org .glassfish .jersey .test .DeploymentContext ;
36
+ import org .glassfish .jersey .test .ServletDeploymentContext ;
37
+ import org .junit .Test ;
38
+
39
+ import javax .ws .rs .core .Response ;
40
+
41
+ import static net .javacrumbs .jsonunit .assertj .JsonAssertions .assertThatJson ;
42
+ import static org .assertj .core .api .Assertions .assertThat ;
43
+ import static org .hamcrest .CoreMatchers .equalTo ;
44
+
45
+ public class VexResourceTest extends ResourceTest {
46
+
47
+ @ Override
48
+ protected DeploymentContext configureDeployment () {
49
+ return ServletDeploymentContext .forServlet (new ServletContainer (
50
+ new ResourceConfig (VexResource .class )
51
+ .register (ApiFilter .class )
52
+ .register (AuthenticationFilter .class )
53
+ .register (MultiPartFeature .class )))
54
+ .build ();
55
+ }
56
+
57
+ @ Test
58
+ public void exportProjectAsCycloneDxTest () {
59
+ var vulnA = new Vulnerability ();
60
+ vulnA .setVulnId ("INT-001" );
61
+ vulnA .setSource (Vulnerability .Source .INTERNAL );
62
+ vulnA .setSeverity (Severity .HIGH );
63
+ vulnA = qm .createVulnerability (vulnA , false );
64
+
65
+ var vulnB = new Vulnerability ();
66
+ vulnB .setVulnId ("INT-002" );
67
+ vulnB .setSource (Vulnerability .Source .INTERNAL );
68
+ vulnB .setSeverity (Severity .LOW );
69
+ vulnB = qm .createVulnerability (vulnB , false );
70
+
71
+ final var project = new Project ();
72
+ project .setName ("acme-app" );
73
+ project .setVersion ("1.0.0" );
74
+ project .setClassifier (Classifier .APPLICATION );
75
+ qm .persist (project );
76
+
77
+ var componentWithoutVuln = new Component ();
78
+ componentWithoutVuln .setProject (project );
79
+ componentWithoutVuln .setName ("acme-lib-a" );
80
+ componentWithoutVuln .setVersion ("1.0.0" );
81
+ componentWithoutVuln .setDirectDependencies ("[]" );
82
+ componentWithoutVuln = qm .createComponent (componentWithoutVuln , false );
83
+
84
+ var componentWithVuln = new Component ();
85
+ componentWithVuln .setProject (project );
86
+ componentWithVuln .setName ("acme-lib-b" );
87
+ componentWithVuln .setVersion ("1.0.0" );
88
+ componentWithVuln .setDirectDependencies ("[]" );
89
+ componentWithVuln = qm .createComponent (componentWithVuln , false );
90
+ qm .addVulnerability (vulnA , componentWithVuln , AnalyzerIdentity .INTERNAL_ANALYZER );
91
+
92
+ var componentWithVulnAndAnalysis = new Component ();
93
+ componentWithVulnAndAnalysis .setProject (project );
94
+ componentWithVulnAndAnalysis .setName ("acme-lib-c" );
95
+ componentWithVulnAndAnalysis .setVersion ("1.0.0" );
96
+ componentWithVulnAndAnalysis .setDirectDependencies ("[]" );
97
+ componentWithVulnAndAnalysis = qm .createComponent (componentWithVulnAndAnalysis , false );
98
+ qm .addVulnerability (vulnB , componentWithVulnAndAnalysis , AnalyzerIdentity .INTERNAL_ANALYZER );
99
+ qm .makeAnalysis (componentWithVulnAndAnalysis , vulnB , AnalysisState .RESOLVED , null , AnalysisResponse .UPDATE , null , true );
100
+
101
+ // Make componentWithoutVuln (acme-lib-a) depend on componentWithVuln (acme-lib-b)
102
+ componentWithoutVuln .setDirectDependencies ("""
103
+ [
104
+ {"uuid": "%s"}
105
+ ]
106
+ """ .formatted (componentWithVuln .getUuid ()));
107
+
108
+ // Make project depend on componentWithoutVuln (acme-lib-a)
109
+ // and componentWithVulnAndAnalysis (acme-lib-c)
110
+ project .setDirectDependencies ("""
111
+ [
112
+ {"uuid": "%s"},
113
+ {"uuid": "%s"}
114
+ ]
115
+ """
116
+ .formatted (
117
+ componentWithoutVuln .getUuid (),
118
+ componentWithVulnAndAnalysis .getUuid ()
119
+ ));
120
+ qm .persist (project );
121
+
122
+ final Response response = target ("%s/cyclonedx/project/%s" .formatted (V1_VEX , project .getUuid ()))
123
+ .request ()
124
+ .header (X_API_KEY , apiKey )
125
+ .get (Response .class );
126
+ assertThat (response .getStatus ()).isEqualTo (200 );
127
+ assertThatJson (getPlainTextBody (response ))
128
+ .withMatcher ("vulnAUuid" , equalTo (vulnA .getUuid ().toString ()))
129
+ .withMatcher ("vulnBUuid" , equalTo (vulnB .getUuid ().toString ()))
130
+ .withMatcher ("projectUuid" , equalTo (project .getUuid ().toString ()))
131
+ .isEqualTo ("""
132
+ {
133
+ "bomFormat": "CycloneDX",
134
+ "specVersion": "1.4",
135
+ "serialNumber": "${json-unit.any-string}",
136
+ "version": 1,
137
+ "metadata": {
138
+ "timestamp": "${json-unit.any-string}",
139
+ "component": {
140
+ "type": "application",
141
+ "bom-ref": "${json-unit.matches:projectUuid}",
142
+ "name": "acme-app",
143
+ "version": "1.0.0"
144
+ },
145
+ "tools": [
146
+ {
147
+ "vendor": "OWASP",
148
+ "name": "Dependency-Track",
149
+ "version": "${json-unit.any-string}"
150
+ }
151
+ ]
152
+ },
153
+ "vulnerabilities": [
154
+ {
155
+ "bom-ref": "${json-unit.matches:vulnAUuid}",
156
+ "id": "INT-001",
157
+ "source": {
158
+ "name": "INTERNAL"
159
+ },
160
+ "ratings": [
161
+ {
162
+ "source": {
163
+ "name": "INTERNAL"
164
+ },
165
+ "severity": "high",
166
+ "method": "other"
167
+ }
168
+ ],
169
+ "affects": [
170
+ {
171
+ "ref": "${json-unit.matches:projectUuid}"
172
+ }
173
+ ]
174
+ },
175
+ {
176
+ "bom-ref": "${json-unit.matches:vulnBUuid}",
177
+ "id": "INT-002",
178
+ "source": {
179
+ "name": "INTERNAL"
180
+ },
181
+ "ratings": [
182
+ {
183
+ "source": {
184
+ "name": "INTERNAL"
185
+ },
186
+ "severity": "low",
187
+ "method": "other"
188
+ }
189
+ ],
190
+ "analysis":{
191
+ "state": "resolved",
192
+ "response": [
193
+ "update"
194
+ ]
195
+ },
196
+ "affects": [
197
+ {
198
+ "ref": "${json-unit.matches:projectUuid}"
199
+ }
200
+ ]
201
+ }
202
+ ]
203
+ }
204
+ """ );
205
+ }
206
+
207
+ }
0 commit comments