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.utils.Settings;
40
41
42
43
44
45
46
47 @Mojo(
48 name = "aggregate",
49 defaultPhase = LifecyclePhase.VERIFY,
50
51 threadSafe = false,
52 requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
53 requiresOnline = true
54 )
55 public class AggregateMojo extends BaseDependencyCheckMojo {
56
57
58
59
60
61
62
63 @Override
64 public void runCheck() throws MojoExecutionException, MojoFailureException {
65 final Engine engine = generateDataFile();
66
67
68 if (getProject() == getLastProject()) {
69
70
71 for (MavenProject current : getReactorProjects()) {
72 final File dataFile = getDataFile(current);
73 if (dataFile == null && !skipProject(current)) {
74 getLog().error(String.format("Module '%s' did not execute dependency-check; an attempt will be made to perform "
75 + "the check but dependencies may be missed resulting in false negatives.", current.getName()));
76 generateDataFile(engine, current);
77 }
78 }
79
80 for (MavenProject current : getReactorProjects()) {
81 List<Dependency> dependencies = readDataFile(current);
82 if (dependencies == null) {
83 dependencies = new ArrayList<Dependency>();
84 }
85 final Set<MavenProject> childProjects = getDescendants(current);
86 for (MavenProject reportOn : childProjects) {
87 final List<Dependency> childDeps = readDataFile(reportOn);
88 if (childDeps != null && !childDeps.isEmpty()) {
89 if (getLog().isDebugEnabled()) {
90 getLog().debug(String.format("Adding %d dependencies from %s", childDeps.size(), reportOn.getName()));
91 }
92 dependencies.addAll(childDeps);
93 } else {
94 if (getLog().isDebugEnabled()) {
95 getLog().debug(String.format("No dependencies read for %s", reportOn.getName()));
96 }
97 }
98 }
99 engine.getDependencies().clear();
100 engine.getDependencies().addAll(dependencies);
101 final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer();
102 try {
103 if (getLog().isDebugEnabled()) {
104 getLog().debug(String.format("Dependency count pre-bundler: %s", engine.getDependencies().size()));
105 }
106 bundler.analyze(null, engine);
107 if (getLog().isDebugEnabled()) {
108 getLog().debug(String.format("Dependency count post-bundler: %s", engine.getDependencies().size()));
109 }
110 } catch (AnalysisException ex) {
111 getLog().warn("An error occurred grouping the dependencies; duplicate entries may exist in the report", ex);
112 getLog().debug("Bundling Exception", ex);
113 }
114
115 File outputDir = getCorrectOutputDirectory(current);
116 if (outputDir == null) {
117
118
119 outputDir = new File(current.getBuild().getDirectory());
120 }
121 writeReports(engine, current, outputDir);
122 }
123 }
124 engine.cleanup();
125 Settings.cleanup();
126 }
127
128
129
130
131
132
133 private MavenProject getLastProject() {
134 for (int x = getReactorProjects().size() - 1; x >= 0; x--) {
135 final MavenProject p = getReactorProjects().get(x);
136 if (!skipProject(p)) {
137 return p;
138 }
139
140 }
141 return null;
142 }
143
144
145
146
147
148
149
150 private boolean skipProject(MavenProject project) {
151 final String skip = (String) project.getProperties().get("maven.site.skip");
152 return "true".equalsIgnoreCase(skip) && isGeneratingSite();
153 }
154
155
156
157
158
159
160
161 protected Set<MavenProject> getDescendants(MavenProject project) {
162 if (project == null) {
163 return Collections.emptySet();
164 }
165 final Set<MavenProject> descendants = new HashSet<MavenProject>();
166 int size = 0;
167 if (getLog().isDebugEnabled()) {
168 getLog().debug(String.format("Collecting descendants of %s", project.getName()));
169 }
170 for (String m : project.getModules()) {
171 for (MavenProject mod : getReactorProjects()) {
172 try {
173 File mpp = new File(project.getBasedir(), m);
174 mpp = mpp.getCanonicalFile();
175 if (mpp.compareTo(mod.getBasedir()) == 0 && descendants.add(mod)
176 && getLog().isDebugEnabled()) {
177 getLog().debug(String.format("Decendent module %s added", mod.getName()));
178
179 }
180 } catch (IOException ex) {
181 if (getLog().isDebugEnabled()) {
182 getLog().debug("Unable to determine module path", ex);
183 }
184 }
185 }
186 }
187 do {
188 size = descendants.size();
189 for (MavenProject p : getReactorProjects()) {
190 if (project.equals(p.getParent()) || descendants.contains(p.getParent())) {
191 if (descendants.add(p) && getLog().isDebugEnabled()) {
192 getLog().debug(String.format("Decendent %s added", p.getName()));
193
194 }
195 for (MavenProject modTest : getReactorProjects()) {
196 if (p.getModules() != null && p.getModules().contains(modTest.getName())
197 && descendants.add(modTest)
198 && getLog().isDebugEnabled()) {
199 getLog().debug(String.format("Decendent %s added", modTest.getName()));
200 }
201 }
202 }
203 final Set<MavenProject> addedDescendants = new HashSet<MavenProject>();
204 for (MavenProject dec : descendants) {
205 for (String mod : dec.getModules()) {
206 try {
207 File mpp = new File(dec.getBasedir(), mod);
208 mpp = mpp.getCanonicalFile();
209 if (mpp.compareTo(p.getBasedir()) == 0) {
210 addedDescendants.add(p);
211 }
212 } catch (IOException ex) {
213 if (getLog().isDebugEnabled()) {
214 getLog().debug("Unable to determine module path", ex);
215 }
216 }
217 }
218 }
219 for (MavenProject addedDescendant : addedDescendants) {
220 if (descendants.add(addedDescendant) && getLog().isDebugEnabled()) {
221 getLog().debug(String.format("Decendent module %s added", addedDescendant.getName()));
222 }
223 }
224 }
225 } while (size != 0 && size != descendants.size());
226 if (getLog().isDebugEnabled()) {
227 getLog().debug(String.format("%s has %d children", project, descendants.size()));
228 }
229 return descendants;
230 }
231
232
233
234
235
236
237
238 protected boolean isMultiModule(MavenProject mavenProject) {
239 return "pom".equals(mavenProject.getPackaging());
240 }
241
242
243
244
245
246
247
248
249 protected Engine generateDataFile() throws MojoExecutionException, MojoFailureException {
250 final Engine engine;
251 try {
252 engine = initializeEngine();
253 } catch (DatabaseException ex) {
254 if (getLog().isDebugEnabled()) {
255 getLog().debug("Database connection error", ex);
256 }
257 throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex);
258 }
259 return generateDataFile(engine, getProject());
260 }
261
262
263
264
265
266
267
268
269
270
271 protected Engine generateDataFile(Engine engine, MavenProject project) throws MojoExecutionException, MojoFailureException {
272 if (getLog().isDebugEnabled()) {
273 getLog().debug(String.format("Begin Scanning: %s", project.getName()));
274 }
275 engine.getDependencies().clear();
276 engine.resetFileTypeAnalyzers();
277 scanArtifacts(project, engine);
278 engine.analyzeDependencies();
279 final File target = new File(project.getBuild().getDirectory());
280 writeDataFile(project, target, engine.getDependencies());
281 showSummary(project, engine.getDependencies());
282 checkForFailure(engine.getDependencies());
283 return engine;
284 }
285
286 @Override
287 public boolean canGenerateReport() {
288 return true;
289 }
290
291
292
293
294 @SuppressWarnings("CanBeFinal")
295 @Parameter(property = "name", defaultValue = "dependency-check:aggregate", required = true)
296 private String name = "dependency-check:aggregate";
297
298
299
300
301
302
303
304 @Override
305 public String getName(Locale locale) {
306 return name;
307 }
308
309
310
311
312
313
314
315 @Override
316 public String getDescription(Locale locale) {
317 return "Generates an aggregate report of all child Maven projects providing details on any "
318 + "published vulnerabilities within project dependencies. This report is a best "
319 + "effort and may contain false positives and false negatives.";
320 }
321 }