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.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.plugin.MojoFailureException;
33  import org.apache.maven.plugins.annotations.LifecyclePhase;
34  import org.apache.maven.plugins.annotations.Mojo;
35  import org.apache.maven.plugins.annotations.ResolutionScope;
36  import org.apache.maven.project.MavenProject;
37  import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
38  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
39  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
40  import org.owasp.dependencycheck.dependency.Dependency;
41  import org.owasp.dependencycheck.utils.Settings;
42  
43  /**
44   * Maven Plugin that checks project dependencies and the dependencies of all child modules to see if they have any known
45   * published vulnerabilities.
46   *
47   * @author Jeremy Long <jeremy.long@owasp.org>
48   */
49  @Mojo(
50          name = "aggregate",
51          defaultPhase = LifecyclePhase.SITE,
52          aggregator = true,
53          threadSafe = true,
54          requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
55          requiresOnline = true
56  )
57  public class AggregateMojo extends BaseDependencyCheckMojo {
58  
59      /**
60       * Logger field reference.
61       */
62      private static final Logger LOGGER = Logger.getLogger(AggregateMojo.class.getName());
63  
64      /**
65       * Executes the aggregate dependency-check goal. This runs dependency-check and generates the subsequent reports.
66       *
67       * @throws MojoExecutionException thrown if there is ane exception running the mojo
68       * @throws MojoFailureException thrown if dependency-check is configured to fail the build
69       */
70      @Override
71      public void runCheck() throws MojoExecutionException, MojoFailureException {
72          final Engine engine = generateDataFile();
73  
74          if (getProject() == getReactorProjects().get(getReactorProjects().size() - 1)) {
75              final Map<MavenProject, Set<MavenProject>> children = buildAggregateInfo();
76              boolean hasOrchestration = false;
77              for (MavenProject current : getReactorProjects()) {
78                  final List<Dependency> dependencies = readDataFile(current);
79                  final List<MavenProject> childProjects = getAllChildren(current, children);
80                  //check for orchestration build - execution root with no children or dependencies
81                  if ((dependencies == null || dependencies.isEmpty()) && childProjects.isEmpty() && current.isExecutionRoot()) {
82                      hasOrchestration = true;
83                  }
84              }
85  
86              for (MavenProject current : getReactorProjects()) {
87                  List<Dependency> dependencies = readDataFile(current);
88                  final List<MavenProject> childProjects = getAllChildren(current, children);
89                  //check for orchestration build - execution root with no children or dependencies
90                  if ((dependencies == null || dependencies.isEmpty()) && childProjects.isEmpty() && current.isExecutionRoot()) {
91                      engine.resetFileTypeAnalyzers();
92                      for (MavenProject mod : getReactorProjects()) {
93                          scanArtifacts(mod, engine);
94                      }
95                      engine.analyzeDependencies();
96                  } else {
97                      if (dependencies == null) {
98                          dependencies = new ArrayList<Dependency>();
99                      }
100                     for (MavenProject reportOn : childProjects) {
101                         final List<Dependency> childDeps = readDataFile(reportOn);
102                         if (childDeps != null && !childDeps.isEmpty()) {
103                             dependencies.addAll(childDeps);
104                         }
105                     }
106                     engine.getDependencies().clear();
107                     engine.getDependencies().addAll(dependencies);
108                     final DependencyBundlingAnalyzer bundler = new DependencyBundlingAnalyzer();
109                     try {
110                         bundler.analyze(null, engine);
111                     } catch (AnalysisException ex) {
112                         LOGGER.log(Level.WARNING, "An error occured grouping the dependencies; duplicate entries may exist in the report", ex);
113                         LOGGER.log(Level.FINE, "Bundling Exception", ex);
114                     }
115                 }
116                 try {
117                     final File outputDir = getCorrectOutputDirectory(current);
118                     writeReports(engine, current, outputDir);
119                 } catch (MojoExecutionException ex) {
120                     if (!hasOrchestration) {
121                         throw ex;
122                     } // else ignore this
123                 }
124             }
125         }
126         engine.cleanup();
127         Settings.cleanup();
128     }
129 
130     /**
131      * Returns a list containing all the recursive, non-pom children of the given project, never <code>null</code>.
132      *
133      * @param project the parent project to collect the child project references
134      * @param childMap a map of the parent-child relationships
135      * @return a list of child projects
136      */
137     protected List<MavenProject> getAllChildren(MavenProject project, Map<MavenProject, Set<MavenProject>> childMap) {
138         final Set<MavenProject> children = childMap.get(project);
139         if (children == null) {
140             return Collections.emptyList();
141         }
142         final List<MavenProject> result = new ArrayList<MavenProject>();
143         for (MavenProject child : children) {
144             if (isMultiModule(child)) {
145                 result.addAll(getAllChildren(child, childMap));
146             } else {
147                 result.add(child);
148             }
149         }
150         return result;
151     }
152 
153     /**
154      * Test if the project has pom packaging
155      *
156      * @param mavenProject Project to test
157      * @return <code>true</code> if it has a pom packaging; otherwise <code>false</code>
158      */
159     protected boolean isMultiModule(MavenProject mavenProject) {
160         return "pom".equals(mavenProject.getPackaging());
161     }
162 
163     /**
164      * Builds the parent-child map.
165      *
166      * @return a map of the parent/child relationships
167      */
168     private Map<MavenProject, Set<MavenProject>> buildAggregateInfo() {
169         final Map<MavenProject, Set<MavenProject>> parentChildMap = new HashMap<MavenProject, Set<MavenProject>>();
170         for (MavenProject proj : getReactorProjects()) {
171             Set<MavenProject> depList = parentChildMap.get(proj.getParent());
172             if (depList == null) {
173                 depList = new HashSet<MavenProject>();
174                 parentChildMap.put(proj.getParent(), depList);
175             }
176             depList.add(proj);
177         }
178         return parentChildMap;
179     }
180 
181     /**
182      * Runs dependency-check's Engine and writes the serialized dependencies to disk.
183      *
184      * @return the Engine used to execute dependency-check
185      * @throws MojoExecutionException thrown if there is an exception running the mojo
186      * @throws MojoFailureException thrown if dependency-check is configured to fail the build if severe CVEs are
187      * identified.
188      */
189     protected Engine generateDataFile() throws MojoExecutionException, MojoFailureException {
190         final Engine engine;
191         try {
192             engine = initializeEngine();
193         } catch (DatabaseException ex) {
194             LOGGER.log(Level.FINE, "Database connection error", ex);
195             throw new MojoExecutionException("An exception occured connecting to the local database. Please see the log file for more details.", ex);
196         }
197         scanArtifacts(getProject(), engine);
198         engine.analyzeDependencies();
199         writeDataFile(engine.getDependencies());
200         showSummary(engine.getDependencies());
201         checkForFailure(engine.getDependencies());
202         return engine;
203     }
204 
205     @Override
206     public boolean canGenerateReport() {
207         return true; //aggregate always returns true for now - we can look at a more complicated/acurate solution later
208     }
209 
210     /**
211      * Returns the report name.
212      *
213      * @param locale the location
214      * @return the report name
215      */
216     public String getName(Locale locale) {
217         return "dependency-check:aggregate";
218     }
219 
220     /**
221      * Gets the description of the Dependency-Check report to be displayed in the Maven Generated Reports page.
222      *
223      * @param locale The Locale to get the description for
224      * @return the description
225      */
226     public String getDescription(Locale locale) {
227         return "Generates an aggregate report of all child Maven projects providing details on any "
228                 + "published vulnerabilities within project dependencies. This report is a best "
229                 + "effort and may contain false positives and false negatives.";
230     }
231 }