20
20
* #L%
21
21
*/
22
22
23
+ import com .contrastsecurity .exceptions .UnauthorizedException ;
23
24
import com .contrastsecurity .maven .plugin .sdkx .ContrastScanSDK ;
25
+ import com .contrastsecurity .maven .plugin .sdkx .CreateProjectRequest ;
24
26
import com .contrastsecurity .maven .plugin .sdkx .DefaultContrastScanSDK ;
27
+ import com .contrastsecurity .maven .plugin .sdkx .Project ;
25
28
import com .contrastsecurity .maven .plugin .sdkx .ScanSummary ;
26
29
import com .contrastsecurity .maven .plugin .sdkx .scan .ArtifactScanner ;
27
30
import com .contrastsecurity .maven .plugin .sdkx .scan .ScanOperation ;
33
36
import java .nio .file .Files ;
34
37
import java .nio .file .Path ;
35
38
import java .time .Duration ;
39
+ import java .util .Collections ;
36
40
import java .util .concurrent .CompletableFuture ;
37
41
import java .util .concurrent .CompletionStage ;
38
42
import java .util .concurrent .ExecutionException ;
61
65
public final class ContrastScanMojo extends AbstractContrastMojo {
62
66
63
67
@ Parameter (defaultValue = "${project}" , readonly = true )
64
- private MavenProject project ;
68
+ private MavenProject mavenProject ;
65
69
66
70
/**
67
71
* Contrast Scan project unique ID to which the plugin runs new Scans. This will be replaced
68
72
* imminently with a project name
69
73
*/
70
- // TODO[JAVA-3297] replace this with "project"
71
- @ Parameter (required = true )
72
- private String projectId ;
74
+ @ Parameter (property = "project" , defaultValue = "${project.name}" )
75
+ private String projectName ;
73
76
74
77
/**
75
78
* File path of the Java artifact to upload for scanning. By default, uses the path to this
@@ -113,13 +116,13 @@ public final class ContrastScanMojo extends AbstractContrastMojo {
113
116
private ContrastSDK contrast ;
114
117
115
118
/** visible for testing */
116
- String getProjectId () {
117
- return projectId ;
119
+ String getProjectName () {
120
+ return projectName ;
118
121
}
119
122
120
123
/** visible for testing */
121
- void setProjectId (final String projectId ) {
122
- this .projectId = projectId ;
124
+ void setProjectName (final String projectName ) {
125
+ this .projectName = projectName ;
123
126
}
124
127
125
128
/** visible for testing */
@@ -136,21 +139,27 @@ void setConsoleOutput(final boolean consoleOutput) {
136
139
public void execute () throws MojoExecutionException , MojoFailureException {
137
140
// initialize plugin
138
141
initialize ();
142
+ final ContrastScanSDK contrastScan = new DefaultContrastScanSDK (contrast , getURL ());
139
143
140
144
// check that file exists
141
145
final Path file =
142
- artifactPath == null ? project .getArtifact ().getFile ().toPath () : artifactPath .toPath ();
146
+ artifactPath == null
147
+ ? mavenProject .getArtifact ().getFile ().toPath ()
148
+ : artifactPath .toPath ();
143
149
if (!Files .exists (file )) {
144
150
throw new MojoExecutionException (
145
151
file
146
152
+ " does not exist. Make sure to bind the scan goal to a phase that will execute after the artifact to scan has been built" );
147
153
}
148
154
149
- final ContrastScanSDK contrastScan = new DefaultContrastScanSDK (contrast , getURL ());
155
+ // get or create project
156
+ final Project project = findOrCreateProject (contrastScan );
157
+
158
+ // start scan
150
159
final ScheduledExecutorService executor = Executors .newSingleThreadScheduledExecutor ();
151
160
final ArtifactScanner scanner =
152
161
new ArtifactScanner (
153
- executor , contrastScan , getOrganizationId (), getProjectId (), POLL_SCAN_INTERVAL );
162
+ executor , contrastScan , getOrganizationId (), project . getId (), POLL_SCAN_INTERVAL );
154
163
try {
155
164
getLog ().info ("Uploading " + file .getFileName () + " to Contrast Scan" );
156
165
final ScanOperation operation = scanner .scanArtifact (file , label );
@@ -161,7 +170,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
161
170
}
162
171
163
172
// show link in build log
164
- final URL clickableScanURL = createClickableScanURL (operation .id ());
173
+ final URL clickableScanURL = createClickableScanURL (project . getId (), operation .id ());
165
174
getLog ().info ("Scan results will be available at " + clickableScanURL .toExternalForm ());
166
175
167
176
// optionally wait for results, output summary to console, output sarif to file system
@@ -174,13 +183,70 @@ public void execute() throws MojoExecutionException, MojoFailureException {
174
183
}
175
184
}
176
185
186
+ /**
187
+ * Finds a Scan project with the project name from the plugin configuration, or creates such a
188
+ * "Java" project if one does not exist.
189
+ *
190
+ * <p>Note: the Scan API does not expose an endpoint for doing this atomically, so it is possible
191
+ * that another process creates the project after having determined it to not-exist but before
192
+ * attempting to create it.
193
+ *
194
+ * @param contrastScan client with which to make the requests
195
+ * @return existing or new {@link Project}
196
+ * @throws MojoExecutionException when fails to make request to the Scan API
197
+ */
198
+ private Project findOrCreateProject (final ContrastScanSDK contrastScan )
199
+ throws MojoExecutionException {
200
+ final Project project ;
201
+ try {
202
+ project = contrastScan .findProjectByName (getOrganizationId (), projectName );
203
+ } catch (final IOException e ) {
204
+ throw new MojoExecutionException ("Failed to retrieve project " + projectName , e );
205
+ } catch (final UnauthorizedException e ) {
206
+ throw new MojoExecutionException (
207
+ "Authentication failure while retrieving project "
208
+ + projectName
209
+ + " - verify Contrast connection configuration" ,
210
+ e );
211
+ }
212
+ if (project != null ) {
213
+ getLog ().debug ("Found project with name " + projectName );
214
+ if (project .isArchived ()) {
215
+ // TODO the behavior of tools like this plugin has yet to be defined with respect to
216
+ // archived projects; however, the UI exposes no way to archive projects at the moment.
217
+ // For now, simply log a warning to help debug this in the future should we encounter this
218
+ // case
219
+ getLog ().warn ("Project " + projectName + " is archived" );
220
+ }
221
+ return project ;
222
+ }
223
+
224
+ // project does not exist, so create a new one
225
+ getLog ().debug ("No project exists with name " + projectName + " - creating one" );
226
+ final CreateProjectRequest request =
227
+ new CreateProjectRequest (
228
+ projectName , "JAVA" , Collections .emptyList (), Collections .emptyList ());
229
+ try {
230
+ return contrastScan .createProject (getOrganizationId (), request );
231
+ } catch (final IOException e ) {
232
+ throw new MojoExecutionException ("Failed to create project " + projectName , e );
233
+ } catch (final UnauthorizedException e ) {
234
+ throw new MojoExecutionException (
235
+ "Authentication failure while creating project "
236
+ + projectName
237
+ + " - verify Contrast connection configuration" ,
238
+ e );
239
+ }
240
+ }
241
+
177
242
/**
178
243
* visible for testing
179
244
*
180
245
* @return Contrast browser application URL for users to click-through and see their scan results
181
246
* @throws MojoExecutionException when the URL is malformed
182
247
*/
183
- URL createClickableScanURL (final String scanId ) throws MojoExecutionException {
248
+ URL createClickableScanURL (final String projectId , final String scanId )
249
+ throws MojoExecutionException {
184
250
final String path =
185
251
String .join (
186
252
"/" ,
0 commit comments