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