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.Collections;
23 import java.util.HashSet;
24 import java.util.Locale;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.Set;
28
29 import org.apache.maven.model.ConfigurationContainer;
30 import org.apache.maven.plugin.MojoExecutionException;
31 import org.apache.maven.plugins.annotations.LifecyclePhase;
32 import org.apache.maven.plugins.annotations.Mojo;
33 import org.apache.maven.plugins.annotations.Parameter;
34 import org.apache.maven.plugins.annotations.ResolutionScope;
35 import org.apache.maven.project.MavenProject;
36 import org.codehaus.plexus.util.xml.Xpp3Dom;
37 import org.owasp.dependencycheck.Engine;
38 import org.owasp.dependencycheck.exception.ExceptionCollection;
39
40
41
42
43
44
45
46 @Mojo(
47 name = "aggregate",
48 defaultPhase = LifecyclePhase.VERIFY,
49 aggregator = true,
50 threadSafe = true,
51 requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
52 requiresOnline = true
53 )
54 public class AggregateMojo extends BaseDependencyCheckMojo {
55
56
57
58
59 @SuppressWarnings("CanBeFinal")
60 @Parameter(property = "name", defaultValue = "dependency-check:aggregate", required = true)
61 private String name = "dependency-check:aggregate";
62
63
64
65
66
67
68
69
70 @Override
71 protected ExceptionCollection scanDependencies(final Engine engine) throws MojoExecutionException {
72 ExceptionCollection exCol = scanArtifacts(getProject(), engine, true);
73 for (MavenProject childProject : getDescendants(this.getProject())) {
74
75
76 final ExceptionCollection ex = scanArtifacts(childProject, engine, true);
77 if (ex != null) {
78 if (exCol == null) {
79 exCol = ex;
80 } else {
81 exCol.getExceptions().addAll(ex.getExceptions());
82 }
83 if (ex.isFatal()) {
84 exCol.setFatal(true);
85 final String msg = String.format("Fatal exception(s) analyzing %s", childProject.getName());
86 if (this.isFailOnError()) {
87 throw new MojoExecutionException(msg, exCol);
88 }
89 getLog().error(msg);
90 if (getLog().isDebugEnabled()) {
91 getLog().debug(exCol);
92 }
93 }
94 }
95 }
96 return exCol;
97 }
98
99
100
101
102
103
104
105
106
107
108 @Override
109 protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
110 ExceptionCollection exCol = scanPlugins(getProject(), engine, null);
111 for (MavenProject childProject : getDescendants(this.getProject())) {
112 exCol = scanPlugins(childProject, engine, exCol);
113 }
114 return exCol;
115 }
116
117
118
119
120
121
122
123
124 protected Set<MavenProject> getDescendants(MavenProject project) {
125 if (project == null) {
126 return Collections.emptySet();
127 }
128 final Set<MavenProject> descendants = new HashSet<>();
129 int size;
130 if (getLog().isDebugEnabled()) {
131 getLog().debug(String.format("Collecting descendants of %s", project.getName()));
132 }
133 for (String m : project.getModules()) {
134 for (MavenProject mod : getReactorProjects()) {
135 if (!isConfiguredToSkip(mod)) {
136 try {
137 File mpp = new File(project.getBasedir(), m);
138 mpp = mpp.getCanonicalFile();
139 if (mpp.compareTo(mod.getBasedir()) == 0 && descendants.add(mod)
140 && getLog().isDebugEnabled()) {
141 getLog().debug(String.format("Descendant module %s added", mod.getName()));
142
143 }
144 } catch (IOException ex) {
145 if (getLog().isDebugEnabled()) {
146 getLog().debug("Unable to determine module path", ex);
147 }
148 }
149 }
150 }
151 }
152 do {
153 size = descendants.size();
154 for (MavenProject p : getReactorProjects()) {
155 if (!isConfiguredToSkip(p)) {
156 if (project.equals(p.getParent()) || descendants.contains(p.getParent())) {
157 if (descendants.add(p) && getLog().isDebugEnabled()) {
158 getLog().debug(String.format("Descendant %s added", p.getName()));
159
160 }
161 for (MavenProject modTest : getReactorProjects()) {
162 if (!isConfiguredToSkip(modTest)) {
163 if (p.getModules() != null && p.getModules().contains(modTest.getName())
164 && descendants.add(modTest)
165 && getLog().isDebugEnabled()) {
166 getLog().debug(String.format("Descendant %s added", modTest.getName()));
167 }
168 }
169 }
170 }
171 final Set<MavenProject> addedDescendants = new HashSet<>();
172 for (MavenProject dec : descendants) {
173 if (!isConfiguredToSkip(dec)) {
174 for (String mod : dec.getModules()) {
175 try {
176 File mpp = new File(dec.getBasedir(), mod);
177 mpp = mpp.getCanonicalFile();
178 if (mpp.compareTo(p.getBasedir()) == 0) {
179 addedDescendants.add(p);
180 }
181 } catch (IOException ex) {
182 if (getLog().isDebugEnabled()) {
183 getLog().debug("Unable to determine module path", ex);
184 }
185 }
186 }
187 }
188 }
189 for (MavenProject addedDescendant : addedDescendants) {
190 if (!isConfiguredToSkip(addedDescendant)) {
191 if (descendants.add(addedDescendant) && getLog().isDebugEnabled()) {
192 getLog().debug(String.format("Descendant module %s added", addedDescendant.getName()));
193 }
194 }
195 }
196 }
197 }
198 } while (size != 0 && size != descendants.size());
199 if (getLog().isDebugEnabled()) {
200 getLog().debug(String.format("%s has %d children", project, descendants.size()));
201 }
202 return descendants;
203 }
204
205
206
207
208
209
210
211
212
213 protected boolean isConfiguredToSkip(MavenProject mavenProject) {
214 final Optional<String> value = mavenProject.getBuildPlugins().stream()
215 .filter(f -> "org.owasp:dependency-check-maven".equals(f.getKey()))
216 .map(ConfigurationContainer::getConfiguration)
217 .filter(c -> c != null && c instanceof Xpp3Dom)
218 .map(c -> (Xpp3Dom) c)
219 .map(c -> c.getChild("skip"))
220 .filter(Objects::nonNull)
221 .map(Xpp3Dom::getValue)
222 .findFirst();
223
224 final String property = mavenProject.getProperties().getProperty("dependency-check.skip");
225
226 final boolean skip = (value.isPresent() && "true".equalsIgnoreCase(value.get())) || "true".equalsIgnoreCase(property);
227 if (skip) {
228 getLog().debug("Aggregation skipping " + mavenProject.getId());
229 }
230 return skip;
231 }
232
233
234
235
236
237
238
239
240 protected boolean isMultiModule(MavenProject mavenProject) {
241 return "pom".equals(mavenProject.getPackaging());
242 }
243
244 @Override
245 public boolean canGenerateReport() {
246 return true;
247 }
248
249
250
251
252
253
254
255 @Override
256 public String getName(Locale locale) {
257 return name;
258 }
259
260
261
262
263
264
265
266
267 @Override
268 public String getDescription(Locale locale) {
269 return "Generates an aggregate report of all child Maven projects providing details on any "
270 + "published vulnerabilities within project dependencies. This report is a best "
271 + "effort and may contain false positives and false negatives.";
272 }
273 }