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