Compare commits

..

3 Commits

Author SHA1 Message Date
Jeremy Long
3e4d012a69 Merge branch 'master' into reportGeneration 2017-05-23 21:02:49 -04:00
Jeremy Long
1b84095c0e Merge branch 'master' into reportGeneration 2017-05-23 21:01:34 -04:00
Jeremy Long
c96ef88222 Moved report generation into the engine, cleaned up code, etc. 2017-05-23 21:00:40 -04:00
16 changed files with 295 additions and 397 deletions

View File

@@ -940,16 +940,7 @@ public class Check extends Update {
throw new BuildException(ex); throw new BuildException(ex);
} }
} }
DatabaseProperties prop = null; engine.writeReports(getProjectName(),new File(reportOutputDirectory), reportFormat);
try (CveDB cve = CveDB.getInstance()) {
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
//TODO shouldn't this be a fatal exception
log("Unable to retrieve DB Properties", ex, Project.MSG_DEBUG);
}
final ReportGenerator reporter = new ReportGenerator(getProjectName(), engine.getDependencies(), engine.getAnalyzers(), prop);
reporter.generateReports(reportOutputDirectory, reportFormat);
if (this.failBuildOnCVSS <= 10) { if (this.failBuildOnCVSS <= 10) {
checkForFailure(engine.getDependencies()); checkForFailure(engine.getDependencies());

View File

@@ -281,18 +281,9 @@ public class App {
} }
exCol = ex; exCol = ex;
} }
final List<Dependency> dependencies = engine.getDependencies();
DatabaseProperties prop = null;
try (CveDB cve = CveDB.getInstance()) {
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
//TODO shouldn't this be a fatal exception
LOGGER.debug("Unable to retrieve DB Properties", ex);
}
final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
try { try {
report.generateReports(reportDirectory, outputFormat); engine.writeReports(applicationName, new File(reportDirectory), outputFormat);
} catch (ReportException ex) { } catch (ReportException ex) {
if (exCol != null) { if (exCol != null) {
exCol.addException(ex); exCol.addException(ex);
@@ -306,7 +297,7 @@ public class App {
} }
//Set the exit code based on whether we found a high enough vulnerability //Set the exit code based on whether we found a high enough vulnerability
for (Dependency dep : dependencies) { for (Dependency dep : engine.getDependencies()) {
if (!dep.getVulnerabilities().isEmpty()) { if (!dep.getVulnerabilities().isEmpty()) {
for (Vulnerability vuln : dep.getVulnerabilities()) { for (Vulnerability vuln : dep.getVulnerabilities()) {
LOGGER.debug("VULNERABILITY FOUND " + dep.getDisplayFileName()); LOGGER.debug("VULNERABILITY FOUND " + dep.getDisplayFileName());
@@ -316,7 +307,6 @@ public class App {
} }
} }
} }
return retCode; return retCode;
} finally { } finally {
if (engine != null) { if (engine != null) {

View File

@@ -54,6 +54,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.exception.ReportException;
import org.owasp.dependencycheck.reporting.ReportGenerator;
/** /**
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and * Scans files, directories, etc. for Dependencies. Analyzers are loaded and
@@ -796,4 +799,42 @@ public class Engine implements FileFilter {
exceptions.add(throwable); exceptions.add(throwable);
throw new ExceptionCollection(message, exceptions, true); throw new ExceptionCollection(message, exceptions, true);
} }
/**
* Writes the report to the given output directory.
*
* @param applicationName the name of the application/project
* @param groupId the Maven groupId
* @param artifactId the Maven artifactId
* @param version the Maven version
* @param outputDir the path to the output directory (can include the full
* file name if the format is not ALL)
* @param format the report format (ALL, HTML, CSV, JSON, etc.)
* @throws ReportException thrown if there is an error generating the report
*/
public void writeReports(String applicationName, String groupId, String artifactId,
String version, File outputDir, String format) throws ReportException {
DatabaseProperties prop = database.getDatabaseProperties();
final ReportGenerator r = new ReportGenerator(applicationName, groupId, artifactId, version, dependencies, getAnalyzers(), prop);
try {
r.write(outputDir.getAbsolutePath(), format);
} catch (ReportException ex) {
final String msg = String.format("Error generating the report for %s", applicationName);
throw new ReportException(msg, ex);
}
}
/**
* Writes the report to the given output directory.
*
* @param applicationName the name of the application/project
* @param outputDir the path to the output directory (can include the full
* file name if the format is not ALL)
* @param format the report format (ALL, HTML, CSV, JSON, etc.)
* @throws ReportException thrown if there is an error generating the report
*/
public void writeReports(String applicationName, File outputDir, String format) throws ReportException {
writeReports(applicationName, null, null, null, outputDir, format);
}
} }

View File

@@ -840,21 +840,15 @@ public class DependencyCheckScanAgent {
* *
* @param engine a dependency-check engine * @param engine a dependency-check engine
* @param outDirectory the directory to write the reports to * @param outDirectory the directory to write the reports to
* @throw ScanAgentException thrown if there is an error generating the
* report
*/ */
private void generateExternalReports(Engine engine, File outDirectory) { private void generateExternalReports(Engine engine, File outDirectory) throws ScanAgentException {
DatabaseProperties prop = null;
try (CveDB cve = CveDB.getInstance()) {
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
//TODO shouldn't this be a fatal exception
LOGGER.debug("Unable to retrieve DB Properties", ex);
}
final ReportGenerator r = new ReportGenerator(this.applicationName, engine.getDependencies(), engine.getAnalyzers(), prop);
try { try {
r.generateReports(outDirectory.getCanonicalPath(), this.reportFormat.name()); engine.writeReports(applicationName, outDirectory, this.reportFormat.name());
} catch (IOException | ReportException ex) { } catch (ReportException ex) {
LOGGER.error("Unexpected exception occurred during analysis; please see the verbose error log for more details."); LOGGER.debug("Unexpected exception occurred during analysis; please see the verbose error log for more details.", ex);
LOGGER.debug("", ex); throw new ScanAgentException("Error generating the report", ex);
} }
} }

View File

@@ -24,7 +24,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -37,7 +36,6 @@ import java.util.Properties;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import org.apache.commons.collections.map.ReferenceMap;
import org.owasp.dependencycheck.data.cwe.CweDB; import org.owasp.dependencycheck.data.cwe.CweDB;
import org.owasp.dependencycheck.dependency.Reference; import org.owasp.dependencycheck.dependency.Reference;
import org.owasp.dependencycheck.dependency.Vulnerability; import org.owasp.dependencycheck.dependency.Vulnerability;
@@ -50,8 +48,6 @@ import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static org.apache.commons.collections.map.AbstractReferenceMap.HARD;
import static org.apache.commons.collections.map.AbstractReferenceMap.SOFT;
import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*; import static org.owasp.dependencycheck.data.nvdcve.CveDB.PreparedStatementCveDb.*;
/** /**
@@ -95,9 +91,6 @@ public final class CveDB implements AutoCloseable {
*/ */
private final EnumMap<PreparedStatementCveDb, PreparedStatement> preparedStatements = new EnumMap<>(PreparedStatementCveDb.class); private final EnumMap<PreparedStatementCveDb, PreparedStatement> preparedStatements = new EnumMap<>(PreparedStatementCveDb.class);
@SuppressWarnings("unchecked")
private final Map<String, List<Vulnerability>> vulnerabilitiesForCpeCache = Collections.synchronizedMap(new ReferenceMap(HARD, SOFT));
/** /**
* The enum value names must match the keys of the statements in the * The enum value names must match the keys of the statements in the
* statement bundles "dbStatements*.properties". * statement bundles "dbStatements*.properties".
@@ -276,7 +269,6 @@ public final class CveDB implements AutoCloseable {
instance.usageCount -= 1; instance.usageCount -= 1;
if (instance.usageCount <= 0 && instance.isOpen()) { if (instance.usageCount <= 0 && instance.isOpen()) {
instance.usageCount = 0; instance.usageCount = 0;
clearCache();
instance.closeStatements(); instance.closeStatements();
try { try {
instance.connection.close(); instance.connection.close();
@@ -482,7 +474,6 @@ public final class CveDB implements AutoCloseable {
* @param value the property value * @param value the property value
*/ */
public synchronized void saveProperty(String key, String value) { public synchronized void saveProperty(String key, String value) {
clearCache();
try { try {
try { try {
final PreparedStatement mergeProperty = getPreparedStatement(MERGE_PROPERTY); final PreparedStatement mergeProperty = getPreparedStatement(MERGE_PROPERTY);
@@ -507,17 +498,6 @@ public final class CveDB implements AutoCloseable {
} }
} }
/**
* Clears cache. Should be called whenever something is modified. While this is not the optimal cache eviction
* strategy, this is good enough for typical usage (update DB and then only read) and it is easier to maintain
* the code.
*
* It should be also called when DB is closed.
*/
private void clearCache() {
vulnerabilitiesForCpeCache.clear();
}
/** /**
* Retrieves the vulnerabilities associated with the specified CPE. * Retrieves the vulnerabilities associated with the specified CPE.
* *
@@ -526,13 +506,6 @@ public final class CveDB implements AutoCloseable {
* @throws DatabaseException thrown if there is an exception retrieving data * @throws DatabaseException thrown if there is an exception retrieving data
*/ */
public synchronized List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException { public synchronized List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
final List<Vulnerability> cachedVulnerabilities = vulnerabilitiesForCpeCache.get(cpeStr);
if (cachedVulnerabilities != null) {
LOGGER.debug("Cache hit for {}", cpeStr);
return cachedVulnerabilities;
} else {
LOGGER.debug("Cache miss for {}", cpeStr);
}
final VulnerableSoftware cpe = new VulnerableSoftware(); final VulnerableSoftware cpe = new VulnerableSoftware();
try { try {
cpe.parseName(cpeStr); cpe.parseName(cpeStr);
@@ -581,7 +554,6 @@ public final class CveDB implements AutoCloseable {
} finally { } finally {
DBUtils.closeResultSet(rs); DBUtils.closeResultSet(rs);
} }
vulnerabilitiesForCpeCache.put(cpeStr, vulnerabilities);
return vulnerabilities; return vulnerabilities;
} }
@@ -661,7 +633,6 @@ public final class CveDB implements AutoCloseable {
* @throws DatabaseException is thrown if the database * @throws DatabaseException is thrown if the database
*/ */
public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException { public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException {
clearCache();
try { try {
int vulnerabilityId = 0; int vulnerabilityId = 0;
final PreparedStatement selectVulnerabilityId = getPreparedStatement(SELECT_VULNERABILITY_ID); final PreparedStatement selectVulnerabilityId = getPreparedStatement(SELECT_VULNERABILITY_ID);
@@ -828,7 +799,6 @@ public final class CveDB implements AutoCloseable {
* ensure orphan entries are removed. * ensure orphan entries are removed.
*/ */
public synchronized void cleanupDatabase() { public synchronized void cleanupDatabase() {
clearCache();
try { try {
final PreparedStatement ps = getPreparedStatement(CLEANUP_ORPHANS); final PreparedStatement ps = getPreparedStatement(CLEANUP_ORPHANS);
if (ps != null) { if (ps != null) {
@@ -964,7 +934,6 @@ public final class CveDB implements AutoCloseable {
* Deletes unused dictionary entries from the database. * Deletes unused dictionary entries from the database.
*/ */
public synchronized void deleteUnusedCpe() { public synchronized void deleteUnusedCpe() {
clearCache();
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
ps = connection.prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE")); ps = connection.prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE"));
@@ -987,7 +956,6 @@ public final class CveDB implements AutoCloseable {
* @param product the CPE product * @param product the CPE product
*/ */
public synchronized void addCpe(String cpe, String vendor, String product) { public synchronized void addCpe(String cpe, String vendor, String product) {
clearCache();
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
ps = connection.prepareStatement(statementBundle.getString("ADD_DICT_CPE")); ps = connection.prepareStatement(statementBundle.getString("ADD_DICT_CPE"));

View File

@@ -107,29 +107,8 @@ public class ReportGenerator {
*/ */
public ReportGenerator(String applicationName, List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) { public ReportGenerator(String applicationName, List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) {
velocityEngine = createVelocityEngine(); velocityEngine = createVelocityEngine();
context = createContext();
velocityEngine.init(); velocityEngine.init();
final EscapeTool enc = new EscapeTool(); context = createContext(applicationName, dependencies, analyzers, properties);
final DateTime dt = new DateTime();
final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("MMM d, yyyy 'at' HH:mm:ss z");
final DateTimeFormatter dateFormatXML = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
// final Date d = new Date();
// final DateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy 'at' HH:mm:ss z");
// final DateFormat dateFormatXML = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final String scanDate = dateFormat.print(dt);
final String scanDateXML = dateFormatXML.print(dt);
context.put("applicationName", applicationName);
context.put("dependencies", dependencies);
context.put("analyzers", analyzers);
context.put("properties", properties);
context.put("scanDate", scanDate);
context.put("scanDateXML", scanDateXML);
context.put("enc", enc);
context.put("version", Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown"));
} }
/** /**
@@ -148,9 +127,15 @@ public class ReportGenerator {
List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) { List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) {
this(applicationName, dependencies, analyzers, properties); this(applicationName, dependencies, analyzers, properties);
context.put("applicationVersion", version); if (version != null) {
context.put("artifactID", artifactID); context.put("applicationVersion", version);
context.put("groupID", groupID); }
if (artifactID != null) {
context.put("artifactID", artifactID);
}
if (groupID != null) {
context.put("groupID", groupID);
}
} }
/** /**
@@ -166,67 +151,235 @@ public class ReportGenerator {
} }
/** /**
* Creates a new Velocity Context. * Constructs the velocity context used to generate the dependency-check
* reports.
* *
* @return a Velocity Context * @param applicationName the application name being analyzed
* @param dependencies the list of dependencies
* @param analyzers the list of analyzers used
* @param properties the database properties (containing timestamps of the
* NVD CVE data)
* @return the velocity context
*/ */
private Context createContext() { private VelocityContext createContext(String applicationName, List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) {
return new VelocityContext(); final DateTime dt = new DateTime();
final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("MMM d, yyyy 'at' HH:mm:ss z");
final DateTimeFormatter dateFormatXML = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final String scanDate = dateFormat.print(dt);
final String scanDateXML = dateFormatXML.print(dt);
VelocityContext ctxt = new VelocityContext();
ctxt.put("applicationName", applicationName);
ctxt.put("dependencies", dependencies);
ctxt.put("analyzers", analyzers);
ctxt.put("properties", properties);
ctxt.put("scanDate", scanDate);
ctxt.put("scanDateXML", scanDateXML);
ctxt.put("enc", new EscapeTool());
ctxt.put("version", Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown"));
return ctxt;
} }
/** /**
* Generates the Dependency Reports for the identified dependencies. * Writes the dependency-check report to the given output location.
* *
* @param outputStream the OutputStream to send the generated report to * @param outputLocation the path where the reports should be written
* @param format the format the report should be written in * @param format the format the report should be written in (XML, HTML,
* @throws IOException is thrown when the template file does not exist * JSON, CSV, ALL) or even the path to a custom velocity template (either
* @throws Exception is thrown if there is an error writing out the reports * fully qualified or the template name on the class path).
*/ * @throws ReportException is thrown if there is an error creating out the
public void generateReports(OutputStream outputStream, Format format) throws IOException, Exception {
if (format == Format.XML || format == Format.ALL) {
generateReport("XmlReport", outputStream);
}
if (format == Format.HTML || format == Format.ALL) {
generateReport("HtmlReport", outputStream);
}
if (format == Format.VULN || format == Format.ALL) {
generateReport("VulnerabilityReport", outputStream);
}
if (format == Format.JSON || format == Format.ALL) {
generateReport("JsonReport", outputStream);
}
if (format == Format.CSV || format == Format.ALL) {
generateReport("CsvReport", outputStream);
}
}
/**
* Generates the Dependency Reports for the identified dependencies.
*
* @param outputDir the path where the reports should be written
* @param format the format the report should be written in
* @throws ReportException is thrown if there is an error writing out the
* reports * reports
*/ */
public void generateReports(String outputDir, Format format) throws ReportException { public void write(String outputLocation, String format) throws ReportException {
if (format == Format.XML || format == Format.ALL) { Format reportFormat = null;
generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml"); try {
reportFormat = Format.valueOf(format.toUpperCase());
} catch (IllegalArgumentException ex) {
LOGGER.trace("ignore this exception", ex);
} }
if (format == Format.JSON || format == Format.ALL) {
generateReport("JsonReport", outputDir + File.separator + "dependency-check-report.json"); if (reportFormat != null) {
pretifyJson(outputDir + File.separator + "dependency-check-report.json"); write(outputLocation, reportFormat);
} else {
File out = getReportFile(outputLocation, null);
if (out.isDirectory()) {
throw new ReportException("Unable to write non-standard VSL output to a directory, please specify a file name");
}
processTemplate(format, out);
} }
if (format == Format.CSV || format == Format.ALL) {
generateReport("CsvReport", outputDir + File.separator + "dependency-check-report.csv"); }
}
if (format == Format.HTML || format == Format.ALL) { /**
generateReport("HtmlReport", outputDir + File.separator + "dependency-check-report.html"); * Writes the dependency-check report(s).
} *
if (format == Format.VULN || format == Format.ALL) { * @param outputLocation the path where the reports should be written
generateReport("VulnerabilityReport", outputDir + File.separator + "dependency-check-vulnerability.html"); * @param format the format the report should be written in (XML, HTML, ALL)
* @throws ReportException is thrown if there is an error creating out the
* reports
*/
public void write(String outputLocation, Format format) throws ReportException {
if (format == Format.ALL) {
for (Format f : Format.values()) {
if (f != Format.ALL) {
write(outputLocation, f);
}
}
} else {
final File out = getReportFile(outputLocation, format);
final String templateName = format.toString().toLowerCase() + "Report";
processTemplate(templateName, out);
if (format == Format.JSON) {
pretifyJson(out.getPath());
}
} }
} }
// /**
// * Writes the dependency-check report(s).
// *
// * @param outputStream the OutputStream to send the generated report to
// * @param format the format the report should be written in
// * @throws ReportException thrown if the report format is ALL
// * @throws IOException is thrown when the template file does not exist
// * @throws Exception is thrown if there is an error writing out the reports
// */
// public void write(OutputStream outputStream, Format format) throws ReportException, IOException, Exception {
// if (format == Format.ALL) {
// throw new ReportException("Unable to write ALL reports to a single output stream, please check the API");
// }
// final String templateName = format.toString().toLowerCase() + "Report";
// processTemplate(templateName, outputStream);
// }
/**
* Determines the report file name based on the give output location and
* format. If the output location contains a full file name that has the
* correct extension for the given report type then the output location is
* returned. However, if the output location is a directory, this method
* will generate the correct name for the given output format.
*
* @param outputLocation the specified output location
* @param format the report format
* @return the report File
*/
protected File getReportFile(String outputLocation, Format format) {
File outFile = new File(outputLocation);
if (outFile.getParentFile() == null) {
outFile = new File(".", outputLocation);
}
final String pathToCheck = outputLocation.toLowerCase();
if (format == Format.XML && !pathToCheck.endsWith(".xml")) {
return new File(outFile, "dependency-check-report.xml");
}
if (format == Format.HTML && !pathToCheck.endsWith(".html") && !pathToCheck.endsWith(".htm")) {
return new File(outFile, "dependency-check-report.html");
}
if (format == Format.VULN && !pathToCheck.endsWith(".html") && !pathToCheck.endsWith(".htm")) {
return new File(outFile, "dependency-check-vulnerability.html");
}
if (format == Format.JSON && !pathToCheck.endsWith(".json")) {
return new File(outFile, "dependency-check-report.json");
}
if (format == Format.CSV && !pathToCheck.endsWith(".csv")) {
return new File(outFile, "dependency-check-report.csv");
}
return outFile;
}
/**
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param template the name of the template to load
* @param file the output file to write the report to
* @throws ReportException is thrown when the report cannot be generated
*/
protected void processTemplate(String template, File file) throws ReportException {
ensureParentDirectoryExists(file);
try (OutputStream output = new FileOutputStream(file)) {
processTemplate(template, output);
} catch (IOException ex) {
throw new ReportException(String.format("Unable to write to file: %s", file), ex);
}
}
/**
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param templateName the name of the template to load
* @param outputStream the OutputStream to write the report to
* @throws ReportException is thrown when an exception occurs
*/
protected void processTemplate(String templateName, OutputStream outputStream) throws ReportException {
InputStream input = null;
String logTag = null;
final File f = new File(templateName);
try {
if (f.isFile()) {
try {
logTag = templateName;
input = new FileInputStream(f);
} catch (FileNotFoundException ex) {
throw new ReportException("Unable to locate template file: " + templateName, ex);
}
} else {
logTag = "templates/" + templateName + ".vsl";
input = this.getClass().getClassLoader().getResourceAsStream(logTag);
}
if (input == null) {
logTag = templateName;
input = this.getClass().getClassLoader().getResourceAsStream(templateName);
}
if (input == null) {
throw new ReportException("Template file doesn't exist: " + logTag);
}
try (InputStreamReader reader = new InputStreamReader(input, "UTF-8");
OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8")) {
if (!velocityEngine.evaluate(context, writer, logTag, reader)) {
throw new ReportException("Failed to convert the template into html.");
}
writer.flush();
} catch (UnsupportedEncodingException ex) {
throw new ReportException("Unable to generate the report using UTF-8", ex);
} catch (IOException ex) {
throw new ReportException("Unable to write the report", ex);
}
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ex) {
LOGGER.trace("Error closing input", ex);
}
}
}
}
/**
* Validates that the given file's parent directory exists. If the directory
* does not exist an attempt to create the necessary path is made; if that
* fails a ReportException will be raised.
*
* @param file the file or directory directory
* @throws ReportException thrown if the parent directory does not exist and
* cannot be created
*/
private void ensureParentDirectoryExists(File file) throws ReportException {
if (!file.getParentFile().exists()) {
final boolean created = file.getParentFile().mkdirs();
if (!created) {
final String msg = String.format("Unable to create directory '%s'.", file.getParentFile().getAbsolutePath());
throw new ReportException(msg);
}
}
}
/** /**
* Reformats the given JSON file. * Reformats the given JSON file.
* *
@@ -311,139 +464,4 @@ public class ReportGenerator {
} }
} }
} }
/**
* Generates the Dependency Reports for the identified dependencies.
*
* @param outputDir the path where the reports should be written
* @param outputFormat the format the report should be written in (XML,
* HTML, ALL)
* @throws ReportException is thrown if there is an error creating out the
* reports
*/
public void generateReports(String outputDir, String outputFormat) throws ReportException {
final String format = outputFormat.toUpperCase();
final String pathToCheck = outputDir.toLowerCase();
if (format.matches("^(XML|HTML|VULN|JSON|ALL)$")) {
if ("XML".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".xml")) {
generateReport("XmlReport", outputDir);
} else {
generateReports(outputDir, Format.XML);
}
}
if ("HTML".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".html") || pathToCheck.endsWith(".htm")) {
generateReport("HtmlReport", outputDir);
} else {
generateReports(outputDir, Format.HTML);
}
}
if ("VULN".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".html") || pathToCheck.endsWith(".htm")) {
generateReport("VulnReport", outputDir);
} else {
generateReports(outputDir, Format.VULN);
}
}
if ("JSON".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".json")) {
generateReport("JsonReport", outputDir);
pretifyJson(outputDir);
} else {
generateReports(outputDir, Format.JSON);
}
}
if ("CSV".equalsIgnoreCase(format)) {
if (pathToCheck.endsWith(".csv")) {
generateReport("CsvReport", outputDir);
} else {
generateReports(outputDir, Format.JSON);
}
}
if ("ALL".equalsIgnoreCase(format)) {
generateReports(outputDir, Format.ALL);
}
}
}
/**
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param templateName the name of the template to load
* @param outputStream the OutputStream to write the report to
* @throws ReportException is thrown when an exception occurs
*/
protected void generateReport(String templateName, OutputStream outputStream) throws ReportException {
InputStream input = null;
String templatePath = null;
final File f = new File(templateName);
try {
if (f.exists() && f.isFile()) {
try {
templatePath = templateName;
input = new FileInputStream(f);
} catch (FileNotFoundException ex) {
throw new ReportException("Unable to locate template file: " + templateName, ex);
}
} else {
templatePath = "templates/" + templateName + ".vsl";
input = this.getClass().getClassLoader().getResourceAsStream(templatePath);
}
if (input == null) {
throw new ReportException("Template file doesn't exist: " + templatePath);
}
try (InputStreamReader reader = new InputStreamReader(input, "UTF-8");
OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8")) {
if (!velocityEngine.evaluate(context, writer, templatePath, reader)) {
throw new ReportException("Failed to convert the template into html.");
}
writer.flush();
} catch (UnsupportedEncodingException ex) {
throw new ReportException("Unable to generate the report using UTF-8", ex);
} catch (IOException ex) {
throw new ReportException("Unable to write the report", ex);
}
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ex) {
LOGGER.trace("Error closing input", ex);
}
}
}
}
/**
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param templateName the name of the template to load
* @param outFileName the filename and path to write the report to
* @throws ReportException is thrown when the report cannot be generated
*/
protected void generateReport(String templateName, String outFileName) throws ReportException {
File outFile = new File(outFileName);
if (outFile.getParentFile() == null) {
outFile = new File(".", outFileName);
}
if (!outFile.getParentFile().exists()) {
final boolean created = outFile.getParentFile().mkdirs();
if (!created) {
throw new ReportException("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'.");
}
}
try (OutputStream outputSteam = new FileOutputStream(outFile)) {
generateReport(templateName, outputSteam);
} catch (IOException ex) {
throw new ReportException("Unable to write to file: " + outFile, ex);
}
}
} }

View File

@@ -17,6 +17,7 @@
*/ */
package org.owasp.dependencycheck; package org.owasp.dependencycheck;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -72,12 +73,7 @@ public class EngineIT extends BaseDBTestCase {
throw ex; throw ex;
} }
} }
DatabaseProperties prop = null; instance.writeReports("dependency-check sample", new File("./target/"), "ALL");
try (CveDB cve = CveDB.getInstance()) {
prop = cve.getDatabaseProperties();
}
ReportGenerator rg = new ReportGenerator("DependencyCheck", instance.getDependencies(), instance.getAnalyzers(), prop);
rg.generateReports("./target/", "ALL");
instance.cleanup(); instance.cleanup();
} }
} }

View File

@@ -25,15 +25,12 @@ import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema; import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator; import javax.xml.validation.Validator;
import static org.junit.Assert.fail;
import org.junit.Test; import org.junit.Test;
import org.owasp.dependencycheck.BaseDBTestCase; import org.owasp.dependencycheck.BaseDBTestCase;
import org.owasp.dependencycheck.BaseTest; import org.owasp.dependencycheck.BaseTest;
import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ExceptionCollection;
import org.owasp.dependencycheck.exception.ReportException; import org.owasp.dependencycheck.exception.ReportException;
import org.owasp.dependencycheck.utils.InvalidSettingException; import org.owasp.dependencycheck.utils.InvalidSettingException;
@@ -47,67 +44,6 @@ import static org.junit.Assert.fail;
*/ */
public class ReportGeneratorIT extends BaseDBTestCase { public class ReportGeneratorIT extends BaseDBTestCase {
/**
* Test of generateReport method, of class ReportGenerator.
*
* @throws Exception is thrown when an exception occurs.
*/
@Test
public void testGenerateReport() throws Exception {
// String templateName = "HtmlReport";
// File f = new File("target/test-reports");
// if (!f.exists()) {
// f.mkdir();
// }
// String writeTo = "target/test-reports/Report.html";
// Map<String, Object> properties = new HashMap<String, Object>();
// Dependency d = new Dependency();
// d.setFileName("FileName.jar");
// d.setActualFilePath("lib/FileName.jar");
// d.addCPEentry("cpe://a:/some:cpe:1.0");
//
// List<Dependency> dependencies = new ArrayList<Dependency>();
// d.getProductEvidence().addEvidence("jar","filename","<test>test", Confidence.HIGH);
// d.getProductEvidence().addEvidence("manifest","vendor","<test>test", Confidence.HIGH);
//
// for (Evidence e : d.getProductEvidence().iterator(Confidence.HIGH)) {
// String t = e.getValue();
// }
// dependencies.add(d);
//
// Dependency d2 = new Dependency();
// d2.setFileName("Another.jar");
// d2.setActualFilePath("lib/Another.jar");
// d2.addCPEentry("cpe://a:/another:cpe:1.0");
// d2.addCPEentry("cpe://a:/another:cpe:1.1");
// d2.addCPEentry("cpe://a:/another:cpe:1.2");
// d2.getProductEvidence().addEvidence("jar","filename","another.jar", Confidence.HIGH);
// d2.getProductEvidence().addEvidence("manifest","vendor","Company A", Confidence.MEDIUM);
//
// for (Evidence e : d2.getProductEvidence().iterator(Confidence.HIGH)) {
// String t = e.getValue();
// }
//
// dependencies.add(d2);
//
// Dependency d3 = new Dependency();
// d3.setFileName("Third.jar");
// d3.setActualFilePath("lib/Third.jar");
// d3.getProductEvidence().addEvidence("jar","filename","third.jar", Confidence.HIGH);
//
// for (Evidence e : d3.getProductEvidence().iterator(Confidence.HIGH)) {
// String t = e.getValue();
// }
//
// dependencies.add(d3);
//
// properties.put("dependencies",dependencies);
//
// ReportGenerator instance = new ReportGenerator();
// instance.generateReport(templateName, writeTo, properties);
//assertTrue("need to add a real check here", false);
}
/** /**
* Generates an XML report containing known vulnerabilities and realistic * Generates an XML report containing known vulnerabilities and realistic
* data and validates the generated XML document against the XSD. * data and validates the generated XML document against the XSD.
@@ -115,7 +51,7 @@ public class ReportGeneratorIT extends BaseDBTestCase {
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testGenerateXMLReport() { public void testGenerateReport() {
try { try {
String templateName = "XmlReport"; String templateName = "XmlReport";
@@ -123,7 +59,7 @@ public class ReportGeneratorIT extends BaseDBTestCase {
if (!f.exists()) { if (!f.exists()) {
f.mkdir(); f.mkdir();
} }
String writeTo = "target/test-reports/Report.xml"; File writeTo = new File("target/test-reports/Report.xml");
File suppressionFile = BaseTest.getResourceAsFile(this, "incorrectSuppressions.xml"); File suppressionFile = BaseTest.getResourceAsFile(this, "incorrectSuppressions.xml");
Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile.getAbsolutePath()); Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile.getAbsolutePath());
@@ -135,29 +71,20 @@ public class ReportGeneratorIT extends BaseDBTestCase {
//File jetty = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath()); //File jetty = new File(this.getClass().getClassLoader().getResource("org.mortbay.jetty.jar").getPath());
File jetty = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar"); File jetty = BaseTest.getResourceAsFile(this, "org.mortbay.jetty.jar");
boolean autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false); Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
Engine engine = new Engine(); Engine engine = new Engine();
Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
engine.scan(struts); engine.scan(struts);
engine.scan(axis); engine.scan(axis);
engine.scan(jetty); engine.scan(jetty);
engine.analyzeDependencies(); engine.analyzeDependencies();
engine.writeReports("Test Report", "org.owasp", "dependency-check-core", "1.4.7", writeTo, "XML");
CveDB cveDB = CveDB.getInstance();
DatabaseProperties dbProp = cveDB.getDatabaseProperties();
ReportGenerator generator = new ReportGenerator("Test Report", "org.owasp", "dependency-check-core", "1.4.7",
engine.getDependencies(), engine.getAnalyzers(), dbProp);
generator.generateReport(templateName, writeTo);
cveDB.close();
engine.cleanup(); engine.cleanup();
InputStream xsdStream = ReportGenerator.class.getClassLoader().getResourceAsStream("schema/dependency-check.1.5.xsd"); InputStream xsdStream = ReportGenerator.class.getClassLoader().getResourceAsStream("schema/dependency-check.1.5.xsd");
StreamSource xsdSource = new StreamSource(xsdStream); StreamSource xsdSource = new StreamSource(xsdStream);
StreamSource xmlSource = new StreamSource(new File(writeTo)); StreamSource xmlSource = new StreamSource(writeTo);
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(xsdSource); Schema schema = sf.newSchema(xsdSource);
Validator validator = schema.newValidator(); Validator validator = schema.newValidator();

View File

@@ -123,7 +123,8 @@ public class AggregateMojo extends BaseDependencyCheckMojo {
outputDir = new File(this.getProject().getBuild().getDirectory()); outputDir = new File(this.getProject().getBuild().getDirectory());
} }
try { try {
writeReports(engine, this.getProject(), outputDir); final MavenProject p = this.getProject();
engine.writeReports(p.getName(), p.getGroupId(), p.getArtifactId(), p.getVersion(), outputDir, getFormat());
} catch (ReportException ex) { } catch (ReportException ex) {
if (exCol == null) { if (exCol == null) {
exCol = new ExceptionCollection("Error writing aggregate report", ex); exCol = new ExceptionCollection("Error writing aggregate report", ex);

View File

@@ -1069,35 +1069,6 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
return format; return format;
} }
/**
* Generates the reports for a given dependency-check engine.
*
* @param engine a dependency-check engine
* @param p the Maven project
* @param outputDir the directory path to write the report(s)
* @throws ReportException thrown if there is an error writing the report
*/
protected void writeReports(Engine engine, MavenProject p, File outputDir) throws ReportException {
DatabaseProperties prop = null;
try (CveDB cve = CveDB.getInstance()) {
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
//TODO shouldn't this throw an exception?
if (getLog().isDebugEnabled()) {
getLog().debug("Unable to retrieve DB Properties", ex);
}
}
final ReportGenerator r = new ReportGenerator(p.getName(), p.getGroupId(), p.getArtifactId(), p.getVersion(),
engine.getDependencies(), engine.getAnalyzers(), prop);
try {
r.generateReports(outputDir.getAbsolutePath(), format);
} catch (ReportException ex) {
final String msg = String.format("Error generating the report for %s", p.getName());
throw new ReportException(msg, ex);
}
}
//<editor-fold defaultstate="collapsed" desc="Methods to fail build or show summary"> //<editor-fold defaultstate="collapsed" desc="Methods to fail build or show summary">
/** /**
* Checks to see if a vulnerability has been identified with a CVSS score * Checks to see if a vulnerability has been identified with a CVSS score

View File

@@ -25,6 +25,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException; import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.exception.ExceptionCollection; import org.owasp.dependencycheck.exception.ExceptionCollection;
@@ -99,19 +100,19 @@ public class CheckMojo extends BaseDependencyCheckMojo {
ExceptionCollection exCol = scanArtifacts(getProject(), engine); ExceptionCollection exCol = scanArtifacts(getProject(), engine);
if (engine.getDependencies().isEmpty()) { if (engine.getDependencies().isEmpty()) {
getLog().info("No dependencies were identified that could be analyzed by dependency-check"); getLog().info("No dependencies were identified that could be analyzed by dependency-check");
} else { }
try { try {
engine.analyzeDependencies(); engine.analyzeDependencies();
} catch (ExceptionCollection ex) { } catch (ExceptionCollection ex) {
if (this.isFailOnError() && ex.isFatal()) { if (this.isFailOnError() && ex.isFatal()) {
throw new MojoExecutionException("One or more exceptions occurred during analysis", ex); throw new MojoExecutionException("One or more exceptions occurred during analysis", ex);
}
exCol = ex;
} }
exCol = ex;
} }
if (exCol == null || !exCol.isFatal()) { if (exCol == null || !exCol.isFatal()) {
try { try {
writeReports(engine, getProject(), getCorrectOutputDirectory()); final MavenProject p = this.getProject();
engine.writeReports(p.getName(), p.getGroupId(), p.getArtifactId(), p.getVersion(), getCorrectOutputDirectory(), getFormat());
} catch (ReportException ex) { } catch (ReportException ex) {
if (this.isFailOnError()) { if (this.isFailOnError()) {
if (exCol != null) { if (exCol != null) {