1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.maven;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Set;
28 import org.apache.maven.plugin.MojoExecutionException;
29 import org.apache.maven.plugin.MojoFailureException;
30 import org.apache.maven.plugins.annotations.LifecyclePhase;
31 import org.apache.maven.plugins.annotations.Mojo;
32 import org.apache.maven.plugins.annotations.Parameter;
33 import org.apache.maven.plugins.annotations.ResolutionScope;
34 import org.apache.maven.project.MavenProject;
35 import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
36 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
37 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
38 import org.owasp.dependencycheck.dependency.Dependency;
39 import org.owasp.dependencycheck.exception.ExceptionCollection;
40 import org.owasp.dependencycheck.exception.ReportException;
41 import org.owasp.dependencycheck.utils.Settings;
42
43
44
45
46
47
48
49 @Mojo(
50 name = "aggregate",
51 defaultPhase = LifecyclePhase.VERIFY,
52
53 threadSafe = false,
54 requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
55 requiresOnline = true
56 )
57 public class AggregateMojo extends BaseDependencyCheckMojo {
58
59
60
61
62 private static final String AGGREGATE_EXCEPTIONS = "AggregateExceptions";
63
64
65
66
67
68
69
70
71
72
73 @Override
74 public void runCheck() throws MojoExecutionException, MojoFailureException {
75 final MavenEngine engine = generateDataFile();
76 if (engine == null) {
77 return;
78 }
79
80 if (getProject() == getLastProject()) {
81
82 for (MavenProject current : getReactorProjects()) {
83 final File dataFile = getDataFile(current);
84 if (dataFile == null && !skipProject(current)) {
85 getLog().error(String.format("Module '%s' did not execute dependency-check; an attempt will be made to perform "
86 + "the check but dependencies may be missed resulting in false negatives.", current.getName()));
87 generateDataFile(engine, current);
88 }
89 }
90 for (MavenProject current : getReactorProjects()) {
91 List<Dependency> dependencies = readDataFile(current);
92 if (dependencies == null) {
93 dependencies = new ArrayList<Dependency>();
94 }
95 final Set<MavenProject> childProjects = getDescendants(current);
96 for (MavenProject reportOn : childProjects) {
97 final List<Dependency> childDeps = readDataFile(reportOn);
98 if (childDeps != null && !childDeps.isEmpty()) {
99 if (getLog().isDebugEnabled()) {
100 getLog().debug(String.format("Adding %d dependencies from %s", childDeps.size(), reportOn.getName()));
101 }
102 dependencies.addAll(childDeps);
103 } else if (getLog().isDebugEnabled()) {
104 getLog().debug(String.format("No dependencies read for %s", reportOn.getName()));
105 }
106 }
107 engine.getDependencies().clear();
108 engine.getDependencies().addAll(dependencies);
109 final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer();
110 try {
111 if (getLog().isDebugEnabled()) {
112 getLog().debug(String.format("Dependency count pre-bundler: %s", engine.getDependencies().size()));
113 }
114 bundler.analyze(null, engine);
115 if (getLog().isDebugEnabled()) {
116 getLog().debug(String.format("Dependency count post-bundler: %s", engine.getDependencies().size()));
117 }
118 } catch (AnalysisException ex) {
119 getLog().warn("An error occurred grouping the dependencies; duplicate entries may exist in the report", ex);
120 getLog().debug("Bundling Exception", ex);
121 }
122
123 File outputDir = getCorrectOutputDirectory(current);
124 if (outputDir == null) {
125
126
127 outputDir = new File(current.getBuild().getDirectory());
128 }
129 try {
130 writeReports(engine, current, outputDir);
131 } catch (ReportException ex) {
132 ExceptionCollection exCol = (ExceptionCollection) engine.getExecutionRoot().getContextValue(AGGREGATE_EXCEPTIONS);
133 if (exCol == null) {
134 exCol = new ExceptionCollection("Error writing aggregate report", ex);
135 } else {
136 exCol.addException(ex);
137 }
138 if (this.isFailOnError()) {
139 throw new MojoExecutionException("One or more exceptions occured during dependency-check analysis", exCol);
140 } else {
141 getLog().debug("One or more exceptions occured during dependency-check analysis", exCol);
142 }
143 }
144 }
145 }
146 engine.cleanup();
147 Settings.cleanup();
148 }
149
150
151
152
153
154
155
156 private MavenProject getLastProject() {
157 for (int x = getReactorProjects().size() - 1; x >= 0; x--) {
158 final MavenProject p = getReactorProjects().get(x);
159 if (!skipProject(p)) {
160 return p;
161 }
162 }
163 return null;
164 }
165
166
167
168
169
170
171
172 private boolean skipProject(MavenProject project) {
173 final String skip = (String) project.getProperties().get("maven.site.skip");
174 return "true".equalsIgnoreCase(skip) && isGeneratingSite();
175 }
176
177
178
179
180
181
182
183
184 protected Set<MavenProject> getDescendants(MavenProject project) {
185 if (project == null) {
186 return Collections.emptySet();
187 }
188 final Set<MavenProject> descendants = new HashSet<MavenProject>();
189 int size = 0;
190 if (getLog().isDebugEnabled()) {
191 getLog().debug(String.format("Collecting descendants of %s", project.getName()));
192 }
193 for (String m : project.getModules()) {
194 for (MavenProject mod : getReactorProjects()) {
195 try {
196 File mpp = new File(project.getBasedir(), m);
197 mpp = mpp.getCanonicalFile();
198 if (mpp.compareTo(mod.getBasedir()) == 0 && descendants.add(mod)
199 && getLog().isDebugEnabled()) {
200 getLog().debug(String.format("Decendent module %s added", mod.getName()));
201
202 }
203 } catch (IOException ex) {
204 if (getLog().isDebugEnabled()) {
205 getLog().debug("Unable to determine module path", ex);
206 }
207 }
208 }
209 }
210 do {
211 size = descendants.size();
212 for (MavenProject p : getReactorProjects()) {
213 if (project.equals(p.getParent()) || descendants.contains(p.getParent())) {
214 if (descendants.add(p) && getLog().isDebugEnabled()) {
215 getLog().debug(String.format("Decendent %s added", p.getName()));
216
217 }
218 for (MavenProject modTest : getReactorProjects()) {
219 if (p.getModules() != null && p.getModules().contains(modTest.getName())
220 && descendants.add(modTest)
221 && getLog().isDebugEnabled()) {
222 getLog().debug(String.format("Decendent %s added", modTest.getName()));
223 }
224 }
225 }
226 final Set<MavenProject> addedDescendants = new HashSet<MavenProject>();
227 for (MavenProject dec : descendants) {
228 for (String mod : dec.getModules()) {
229 try {
230 File mpp = new File(dec.getBasedir(), mod);
231 mpp = mpp.getCanonicalFile();
232 if (mpp.compareTo(p.getBasedir()) == 0) {
233 addedDescendants.add(p);
234 }
235 } catch (IOException ex) {
236 if (getLog().isDebugEnabled()) {
237 getLog().debug("Unable to determine module path", ex);
238 }
239 }
240 }
241 }
242 for (MavenProject addedDescendant : addedDescendants) {
243 if (descendants.add(addedDescendant) && getLog().isDebugEnabled()) {
244 getLog().debug(String.format("Decendent module %s added", addedDescendant.getName()));
245 }
246 }
247 }
248 } while (size != 0 && size != descendants.size());
249 if (getLog().isDebugEnabled()) {
250 getLog().debug(String.format("%s has %d children", project, descendants.size()));
251 }
252 return descendants;
253 }
254
255
256
257
258
259
260
261
262 protected boolean isMultiModule(MavenProject mavenProject) {
263 return "pom".equals(mavenProject.getPackaging());
264 }
265
266
267
268
269
270
271
272
273
274
275
276 protected MavenEngine generateDataFile() throws MojoExecutionException, MojoFailureException {
277 MavenEngine engine = null;
278 try {
279 engine = initializeEngine();
280 } catch (DatabaseException ex) {
281 if (getLog().isDebugEnabled()) {
282 getLog().debug("Database connection error", ex);
283 }
284 final String msg = "An exception occured connecting to the local database. Please see the log file for more details.";
285 if (this.isFailOnError()) {
286 throw new MojoExecutionException(msg, ex);
287 }
288 getLog().error(msg, ex);
289 return null;
290 }
291 return generateDataFile(engine, getProject());
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305
306 protected MavenEngine generateDataFile(MavenEngine engine, MavenProject project) throws MojoExecutionException, MojoFailureException {
307 if (getLog().isDebugEnabled()) {
308 getLog().debug(String.format("Begin Scanning: %s", project.getName()));
309 }
310 engine.getDependencies().clear();
311 engine.resetFileTypeAnalyzers();
312 scanArtifacts(project, engine);
313 try {
314 engine.analyzeDependencies();
315 } catch (ExceptionCollection ex) {
316 ExceptionCollection col = (ExceptionCollection) engine.getExecutionRoot().getContextValue(AGGREGATE_EXCEPTIONS);
317 if (col == null) {
318 col = ex;
319 } else if (ex.isFatal()) {
320 col.setFatal(true);
321 col.getExceptions().addAll(ex.getExceptions());
322 }
323 if (col.isFatal()) {
324 final String msg = String.format("Fatal exception(s) analyzing %s", project.getName());
325 if (this.isFailOnError()) {
326 throw new MojoExecutionException(msg, ex);
327 }
328 getLog().error(msg, col);
329 return null;
330 } else {
331 final String msg = String.format("Exception(s) analyzing %s", project.getName());
332 if (getLog().isDebugEnabled()) {
333 getLog().debug(msg, ex);
334 }
335 engine.getExecutionRoot().setContextValue(AGGREGATE_EXCEPTIONS, col);
336 }
337 }
338 final File target = new File(project.getBuild().getDirectory());
339 writeDataFile(project, target, engine.getDependencies());
340 showSummary(project, engine.getDependencies());
341 checkForFailure(engine.getDependencies());
342 return engine;
343 }
344
345 @Override
346 public boolean canGenerateReport() {
347 return true;
348 }
349
350
351
352
353 @SuppressWarnings("CanBeFinal")
354 @Parameter(property = "name", defaultValue = "dependency-check:aggregate", required = true)
355 private String name = "dependency-check:aggregate";
356
357
358
359
360
361
362
363 @Override
364 public String getName(Locale locale) {
365 return name;
366 }
367
368
369
370
371
372
373
374
375 @Override
376 public String getDescription(Locale locale) {
377 return "Generates an aggregate report of all child Maven projects providing details on any "
378 + "published vulnerabilities within project dependencies. This report is a best "
379 + "effort and may contain false positives and false negatives.";
380 }
381 }