View Javadoc
1   /*
2    * This file is part of dependency-check-maven.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2013 Jeremy Long. All Rights Reserved.
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.ResolutionScope;
33  import org.apache.maven.project.MavenProject;
34  import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
35  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
36  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
37  import org.owasp.dependencycheck.dependency.Dependency;
38  import org.owasp.dependencycheck.utils.Settings;
39  
40  /**
41   * Maven Plugin that checks project dependencies and the dependencies of all child modules to see if they have any known published
42   * vulnerabilities.
43   *
44   * @author Jeremy Long
45   */
46  @Mojo(
47          name = "aggregate",
48          defaultPhase = LifecyclePhase.COMPILE,
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       * Executes the aggregate dependency-check goal. This runs dependency-check and generates the subsequent reports.
58       *
59       * @throws MojoExecutionException thrown if there is ane exception running the mojo
60       * @throws MojoFailureException thrown if dependency-check is configured to fail the build
61       */
62      @Override
63      public void runCheck() throws MojoExecutionException, MojoFailureException {
64          final Engine engine = generateDataFile();
65  
66          if (getProject() == getReactorProjects().get(getReactorProjects().size() - 1)) {
67  
68              //ensure that the .ser file was created for each.
69              for (MavenProject current : getReactorProjects()) {
70                  final File dataFile = getDataFile(current);
71                  if (dataFile == null) { //dc was never run on this project. write the ser to the target.
72                      if (getLog().isDebugEnabled()) {
73                          getLog().debug(String.format("Executing dependency-check on %s", current.getName()));
74                      }
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                     if (getLog().isDebugEnabled()) {
112                         getLog().debug("Bundling Exception", ex);
113                     }
114                 }
115 
116                 File outputDir = getCorrectOutputDirectory(current);
117                 if (outputDir == null) {
118                     //in some regards we shouldn't be writting this, but we are anyway.
119                     //we shouldn't write this because nothing is configured to generate this report.
120                     outputDir = new File(current.getBuild().getDirectory());
121                 }
122                 writeReports(engine, current, outputDir);
123             }
124         }
125         engine.cleanup();
126         Settings.cleanup();
127     }
128 
129     /**
130      * Returns a set containing all the descendant projects of the given project.
131      *
132      * @param project the project for which all descendants will be returned
133      * @return the set of descendant projects
134      */
135     protected Set<MavenProject> getDescendants(MavenProject project) {
136         if (project == null) {
137             return Collections.emptySet();
138         }
139         final Set<MavenProject> descendants = new HashSet<MavenProject>();
140         int size = 0;
141         if (getLog().isDebugEnabled()) {
142             getLog().debug(String.format("Collecting descendants of %s", project.getName()));
143         }
144         for (String m : project.getModules()) {
145             for (MavenProject mod : getReactorProjects()) {
146                 try {
147                     File mpp = new File(project.getBasedir(), m);
148                     mpp = mpp.getCanonicalFile();
149                     if (mpp.compareTo(mod.getBasedir()) == 0 && descendants.add(mod)) {
150                         if (getLog().isDebugEnabled()) {
151                             getLog().debug(String.format("Decendent module %s added", mod.getName()));
152                         }
153                     }
154                 } catch (IOException ex) {
155                     if (getLog().isDebugEnabled()) {
156                         getLog().debug("Unable to determine module path", ex);
157                     }
158                 }
159             }
160         }
161         do {
162             size = descendants.size();
163             for (MavenProject p : getReactorProjects()) {
164                 if (project.equals(p.getParent()) || descendants.contains(p.getParent())) {
165                     if (descendants.add(p)) {
166                         if (getLog().isDebugEnabled()) {
167                             getLog().debug(String.format("Decendent %s added", p.getName()));
168                         }
169                     }
170                     for (MavenProject modTest : getReactorProjects()) {
171                         if (p.getModules() != null && p.getModules().contains(modTest.getName())
172                                 && descendants.add(modTest)) {
173                             if (getLog().isDebugEnabled()) {
174                                 getLog().debug(String.format("Decendent %s added", modTest.getName()));
175                             }
176                         }
177                     }
178                 }
179                 for (MavenProject dec : descendants) {
180                     for (String mod : dec.getModules()) {
181                         try {
182                             File mpp = new File(dec.getBasedir(), mod);
183                             mpp = mpp.getCanonicalFile();
184                             if (mpp.compareTo(p.getBasedir()) == 0 && descendants.add(p)) {
185                                 if (getLog().isDebugEnabled()) {
186                                     getLog().debug(String.format("Decendent module %s added", p.getName()));
187                                 }
188                             }
189                         } catch (IOException ex) {
190                             if (getLog().isDebugEnabled()) {
191                                 getLog().debug("Unable to determine module path", ex);
192                             }
193                         }
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      * Test if the project has pom packaging
206      *
207      * @param mavenProject Project to test
208      * @return <code>true</code> if it has a pom packaging; otherwise <code>false</code>
209      */
210     protected boolean isMultiModule(MavenProject mavenProject) {
211         return "pom".equals(mavenProject.getPackaging());
212     }
213 
214     /**
215      * Initilizes the engine, runs a scan, and writes the serialized dependencies to disk.
216      *
217      * @return the Engine used to execute dependency-check
218      * @throws MojoExecutionException thrown if there is an exception running the mojo
219      * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified.
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      * Runs dependency-check's Engine and writes the serialized dependencies to disk.
236      *
237      * @param engine the Engine to use when scanning.
238      * @param project the project to scan and generate the data file for
239      * @return the Engine used to execute dependency-check
240      * @throws MojoExecutionException thrown if there is an exception running the mojo
241      * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are identified.
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; //aggregate always returns true for now - we can look at a more complicated/acurate solution later
261     }
262 
263     /**
264      * Returns the report name.
265      *
266      * @param locale the location
267      * @return the report name
268      */
269     public String getName(Locale locale) {
270         return "dependency-check:aggregate";
271     }
272 
273     /**
274      * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page.
275      *
276      * @param locale The Locale to get the description for
277      * @return the description
278      */
279     public String getDescription(Locale locale) {
280         return "Generates an aggregate report of all child Maven projects providing details on any "
281                 + "published vulnerabilities within project dependencies. This report is a best "
282                 + "effort and may contain false positives and false negatives.";
283     }
284 }