package com.denimgroup.threadfix.service.report; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JRField; import com.denimgroup.threadfix.data.dao.ScanDao; import com.denimgroup.threadfix.data.entities.Finding; import com.denimgroup.threadfix.data.entities.Scan; import com.denimgroup.threadfix.data.entities.ScanCloseVulnerabilityMap; import com.denimgroup.threadfix.data.entities.ScanReopenVulnerabilityMap; /** * The current strategy for this report is to keep Sets of the vulnerability IDs * representing the new / old / reopened vulns for each month. * All the collected scans are iterated through, following this process: * <br> * <br>Vulns are added to the new vuln set if the scan contains the first finding for the vuln. * <br>If a vuln is reopened by a scan, it is added to the reopened set. * <br>At the end of every month, all vulns are moved to the old vulns set for future months. * <br>If a vuln is closed by a scan, it is removed from all three sets. * <br> * <br>For each month, this method should yield counts for: * <br>All the new vulns that weren't also closed in the same month, * <br>The number of old vulnerabilities from previous months still open at the end of the month, * <br>And the number of vulnerabilities that resurfaced and were still open at the end of the month. * * @author mcollins * */ public class JasperMonthlyScanReport implements JRDataSource { private List<Scan> scanList = new ArrayList<Scan>(); private int index = 0; private Map<String, Object> resultsHash = new HashMap<String, Object>(); private List<Scan> normalizedScans = new ArrayList<Scan>(); Set<Integer> newVulns = new HashSet<Integer>(), oldVulns = new HashSet<Integer>(), reopenedVulns = new HashSet<Integer>(); public JasperMonthlyScanReport(List<Integer> applicationIdList, ScanDao scanDao) { if (scanDao != null && applicationIdList != null) this.scanList = scanDao .retrieveByApplicationIdList(applicationIdList); if (this.scanList != null && this.scanList.size() > 0) { Collections.sort(this.scanList, Scan.getTimeComparator()); normalizeForMonths(); } index = -1; } private void normalizeForMonths() { newVulns.clear(); oldVulns.clear(); reopenedVulns.clear(); int previousYear = -1, previousMonth = -1; Scan currentScan = null; for (Scan scan : this.scanList) { if (previousYear == -1) { // Start the process off with all new vulns from the first scan. previousYear = scan.getImportTime().get(Calendar.YEAR); previousMonth = scan.getImportTime().get(Calendar.MONTH); initializeSets(scan); currentScan = scan; } else { adjustSets(scan); if (scan.getImportTime().get(Calendar.YEAR) != previousYear || scan.getImportTime().get(Calendar.MONTH) != previousMonth) { addScanToReportList(currentScan); moveAllToOld(); // add a new current entry previousYear = scan.getImportTime().get(Calendar.YEAR); previousMonth = scan.getImportTime().get(Calendar.MONTH); currentScan = scan; } } } // include the last scan addScanToReportList(currentScan); insertEmptyScans(normalizedScans); } /** * Set initial set contents * @param scan */ private void initializeSets(Scan scan) { if (scan == null || scan.getFindings() == null) { return; } for (Finding finding : scan.getFindings()) { if (finding == null || finding.getVulnerability() == null) continue; newVulns.add(finding.getVulnerability().getId()); } } // At the end of each month, all existing vulns are old vulns private void moveAllToOld() { oldVulns.addAll(newVulns); oldVulns.addAll(reopenedVulns); newVulns.clear(); reopenedVulns.clear(); } // adjust the counts based on new Scan contents private void adjustSets(Scan scan) { if (scan != null) { // if the scan closes a vuln, remove it from all fields if (scan.getScanCloseVulnerabilityMaps() != null && !scan.getScanCloseVulnerabilityMaps().isEmpty()) { for (ScanCloseVulnerabilityMap map : scan.getScanCloseVulnerabilityMaps()) { newVulns.remove(map.getVulnerability().getId()); oldVulns.remove(map.getVulnerability().getId()); reopenedVulns.remove(map.getVulnerability().getId()); } } // if the scan reopens a vuln, add it to that count. if (scan.getScanReopenVulnerabilityMaps() != null && !scan.getScanReopenVulnerabilityMaps().isEmpty()) { for (ScanReopenVulnerabilityMap map : scan.getScanReopenVulnerabilityMaps()) { reopenedVulns.add(map.getVulnerability().getId()); } } // if there are any new vulns introduced by the scan, add those to the new vulns. if (scan.getFindings() != null) { for (Finding finding : scan.getFindings()) { if (finding.isFirstFindingForVuln()) { newVulns.add(finding.getVulnerability().getId()); } } } } } // adjust the scan numbers and add it to the list // the adjusted scan numbers are not and should not be saved private void addScanToReportList(Scan currentScan) { currentScan.setNumberOldVulnerabilities(oldVulns.size()); currentScan.setNumberNewVulnerabilities(newVulns.size()); currentScan.setNumberResurfacedVulnerabilities(reopenedVulns.size()); normalizedScans.add(currentScan); } // in order to get the bars to show up we need to add empty scans private void insertEmptyScans(List<Scan> scanList) { Scan previousScan = null; List<Scan> scansToInsert = new ArrayList<Scan>(); for (Scan scan : scanList) { if (previousScan == null) { previousScan = scan; continue; } if (scan.getImportTime().after(previousScan.getImportTime()) && (scan.getImportTime().get(Calendar.YEAR) != previousScan .getImportTime().get(Calendar.YEAR) || scan .getImportTime().get(Calendar.MONTH) != previousScan .getImportTime().get(Calendar.MONTH))) { scansToInsert.addAll(getScansBetween(previousScan, scan)); } previousScan = scan; } scanList.addAll(scansToInsert); Collections.sort(scanList, Scan.getTimeComparator()); } // skipping null checks for now private List<Scan> getScansBetween(Scan firstScan, Scan secondScan) { List<Scan> betweenScans = new ArrayList<Scan>(); Scan tempScan = firstScan; while (true) { if (secondScan.getImportTime().after(tempScan.getImportTime()) && (secondScan.getImportTime().get(Calendar.YEAR) != tempScan .getImportTime().get(Calendar.YEAR) || secondScan .getImportTime().get(Calendar.MONTH) != tempScan .getImportTime().get(Calendar.MONTH))) { Calendar newCalendar = Calendar.getInstance(); newCalendar.setTime(tempScan.getImportTime().getTime()); newCalendar.add(Calendar.MONTH, 1); if (secondScan.getImportTime().after(newCalendar) && (secondScan.getImportTime().get(Calendar.YEAR) != newCalendar .get(Calendar.YEAR) || secondScan .getImportTime().get(Calendar.MONTH) != newCalendar .get(Calendar.MONTH))) { Scan newScan = new Scan(); newScan.setNumberClosedVulnerabilities(0); newScan.setNumberNewVulnerabilities(0); newScan.setNumberResurfacedVulnerabilities(0); newScan.setNumberOldVulnerabilities(firstScan.getNumberOldVulnerabilities() + firstScan.getNumberNewVulnerabilities() + firstScan.getNumberResurfacedVulnerabilities()); newScan.setNumberOldVulnerabilitiesInitiallyFromThisChannel(0); newScan.setNumberTotalVulnerabilities(0); newScan.setImportTime(newCalendar); betweenScans.add(newScan); tempScan = newScan; } else { break; } } else { break; } } return betweenScans; } @Override public Object getFieldValue(JRField field) { if (field == null) return null; String name = field.getName(); if (name == null) return null; if (resultsHash.containsKey(name)) return resultsHash.get(name); else return null; } @Override public boolean next() { if (normalizedScans != null && index < normalizedScans.size() - 1) { if (index == -1) index = 0; else index++; buildHash(); return true; } else { return false; } } private void buildHash() { resultsHash.clear(); Scan scan = normalizedScans.get(index); resultsHash.put("newVulns", scan.getNumberNewVulnerabilities()); resultsHash.put("resurfacedVulns", scan.getNumberResurfacedVulnerabilities()); resultsHash.put("oldVulns", scan.getNumberOldVulnerabilities()); if (scan.getApplication() != null && scan.getApplication().getName() != null) resultsHash.put("name", scan.getApplication().getName()); if (scan.getImportTime() != null) resultsHash.put("importTime", scan.getImportTime()); } }