//////////////////////////////////////////////////////////////////////// // // Copyright (c) 2009-2013 Denim Group, Ltd. // // The contents of this file are subject to the Mozilla Public License // Version 2.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://www.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is ThreadFix. // // The Initial Developer of the Original Code is Denim Group, Ltd. // Portions created by Denim Group, Ltd. are Copyright (C) // Denim Group, Ltd. All Rights Reserved. // // Contributor(s): Denim Group, Ltd. // //////////////////////////////////////////////////////////////////////// package com.denimgroup.threadfix.data.dao.hibernate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.Criteria; import org.hibernate.SessionFactory; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.denimgroup.threadfix.data.dao.ScanDao; import com.denimgroup.threadfix.data.entities.DataFlowElement; import com.denimgroup.threadfix.data.entities.DeletedCloseMap; import com.denimgroup.threadfix.data.entities.DeletedDataFlowElement; import com.denimgroup.threadfix.data.entities.DeletedFinding; import com.denimgroup.threadfix.data.entities.DeletedReopenMap; import com.denimgroup.threadfix.data.entities.DeletedRepeatFindingMap; import com.denimgroup.threadfix.data.entities.DeletedScan; import com.denimgroup.threadfix.data.entities.DeletedSurfaceLocation; 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; import com.denimgroup.threadfix.data.entities.ScanRepeatFindingMap; import com.denimgroup.threadfix.data.entities.SurfaceLocation; /** * Hibernate Scan DAO implementation. Most basic methods are implemented in the * AbstractGenericDao * * @author mcollins * @see AbstractGenericDao */ @Repository public class HibernateScanDao implements ScanDao { private String selectStart = "(select count(*) from Vulnerability vulnerability where vulnerability.genericSeverity.intValue = "; private String idStart = "scan.id as id, "; private String vulnIds = " and vulnerability in (select finding.vulnerability.id from Finding finding where finding.scan = scan))"; private String mapVulnIds = " and vulnerability in (select map.finding.vulnerability.id from ScanRepeatFindingMap map where map.scan = scan))"; private String fromClause = "from Scan scan where scan.id = :scanId"; private SessionFactory sessionFactory; @Autowired public HibernateScanDao(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @SuppressWarnings("unchecked") @Override public Map<String,Object> getFindingSeverityMap(Scan scan) { return (Map<String, Object>) sessionFactory.getCurrentSession().createQuery( "select new map( " + idStart + selectStart + "1" + vulnIds + " as info, " + selectStart + "2" + vulnIds + " as low, " + selectStart + "3" + vulnIds + " as medium, " + selectStart + "4" + vulnIds + " as high, " + selectStart + "5" + vulnIds + " as critical) " + fromClause ).setInteger("scanId", scan.getId()).uniqueResult(); } @SuppressWarnings("unchecked") @Override public Map<String,Object> getMapSeverityMap(Scan scan) { return (Map<String, Object>) sessionFactory.getCurrentSession().createQuery( "select new map( " + idStart + selectStart + "1" + mapVulnIds + " as info, " + selectStart + "2" + mapVulnIds + " as low, " + selectStart + "3" + mapVulnIds + " as medium, " + selectStart + "4" + mapVulnIds + " as high, " + selectStart + "5" + mapVulnIds + " as critical) " + fromClause ).setInteger("scanId", scan.getId()).uniqueResult(); } @Override @SuppressWarnings("unchecked") public List<Scan> retrieveAll() { return sessionFactory.getCurrentSession() .createQuery("from Scan scan order by scan.importTime desc").list(); } @Override @SuppressWarnings("unchecked") public List<Scan> retrieveByApplicationIdList(List<Integer> applicationIdList) { return sessionFactory.getCurrentSession() .createQuery("from Scan scan where scan.application.id in (:idList)") .setParameterList("idList", applicationIdList) .list(); } @Override public Scan retrieveById(int id) { return (Scan) sessionFactory.getCurrentSession().get(Scan.class, id); } @Override @Transactional public void saveOrUpdate(Scan scan) { sessionFactory.getCurrentSession().saveOrUpdate(scan); } @Override public void delete(Scan scan) { sessionFactory.getCurrentSession().save(new DeletedScan(scan)); sessionFactory.getCurrentSession().delete(scan); } @Override public void deleteMap(ScanCloseVulnerabilityMap map) { sessionFactory.getCurrentSession().save(new DeletedCloseMap(map)); sessionFactory.getCurrentSession().delete(map); } @Override public void deleteMap(ScanReopenVulnerabilityMap map) { sessionFactory.getCurrentSession().save(new DeletedReopenMap(map)); sessionFactory.getCurrentSession().delete(map); } @Override public void deleteMap(ScanRepeatFindingMap map) { sessionFactory.getCurrentSession().save(new DeletedRepeatFindingMap(map)); sessionFactory.getCurrentSession().delete(map); } @Override public long getFindingCount(Integer scanId) { Long actualFindings = (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .add(Restrictions.isNotNull("vulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); Long mappings = (Long) sessionFactory.getCurrentSession() .createCriteria(ScanRepeatFindingMap.class) .createAlias("finding", "finding") .add(Restrictions.isNotNull("finding.vulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); return actualFindings + mappings; } @Override public long getFindingCountUnmapped(Integer scanId) { Long actualFindings = (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .add(Restrictions.isNull("vulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); Long mappings = (Long) sessionFactory.getCurrentSession() .createCriteria(ScanRepeatFindingMap.class) .createAlias("finding", "finding") .add(Restrictions.isNull("finding.vulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); return actualFindings + mappings; } // These should probably be saved in the scans and then updated when necessary (scan deletions, database updates) // That could be messy but querying the database every time is not absolutely necessary. @Override public long getTotalNumberSkippedResults(Integer scanId) { Object response = sessionFactory.getCurrentSession() .createQuery("select sum( finding.numberMergedResults ) " + "from Finding finding where scan = :scan") .setInteger("scan", scanId) .uniqueResult(); long totalMergedResults = 0, totalResults = 0; if (response != null) { totalMergedResults = (Long) response; } response = (Long) sessionFactory.getCurrentSession() .createQuery("select count(*) from Finding finding where scan = :scan") .setInteger("scan", scanId) .uniqueResult(); if (response != null) { totalResults = (Long) response; } return totalMergedResults - totalResults; } @Override public long getNumberWithoutChannelVulns(Integer scanId) { return (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .add(Restrictions.isNull("channelVulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); } @Override public long getNumberWithoutGenericMappings(Integer scanId) { return (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .createAlias("channelVulnerability", "vuln") .add(Restrictions.isEmpty( "vuln.vulnerabilityMaps" )) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); } @Override public long getTotalNumberFindingsMergedInScan(Integer scanId) { long numUniqueVulnerabilities = (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .createAlias("vulnerability", "vuln") .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.countDistinct("vuln.id")) .uniqueResult(); long numFindingsWithVulnerabilities = (Long) sessionFactory.getCurrentSession() .createCriteria(Finding.class) .add(Restrictions.isNotNull("vulnerability")) .add(Restrictions.eq("scan.id", scanId)) .setProjection(Projections.rowCount()) .uniqueResult(); return numFindingsWithVulnerabilities - numUniqueVulnerabilities; } @Override @SuppressWarnings("unchecked") public void deleteFindingsAndScan(Scan scan) { if (scan == null) return; List<Long> surfaceLocationIds = sessionFactory.getCurrentSession() .createQuery("select surfaceLocation.id from Finding where scan = :scan)") .setInteger("scan", scan.getId()) .list(); List<DataFlowElement> dataFlowElements = sessionFactory.getCurrentSession() .createQuery("from DataFlowElement element " + "where element.finding in (select id from Finding where scan = :scan)") .setInteger("scan", scan.getId()) .list(); for (DataFlowElement dataFlowElement : dataFlowElements) { sessionFactory.getCurrentSession().save(new DeletedDataFlowElement(dataFlowElement)); sessionFactory.getCurrentSession().delete(dataFlowElement); } List<SurfaceLocation> surfaceLocations = null; if (surfaceLocationIds != null && surfaceLocationIds.size() > 0) { surfaceLocations = sessionFactory.getCurrentSession() .createQuery("from SurfaceLocation " + "where id in (:ids)") .setParameterList("ids", surfaceLocationIds) .list(); for (SurfaceLocation surfaceLocation : surfaceLocations) { sessionFactory.getCurrentSession().save(new DeletedSurfaceLocation(surfaceLocation)); } } List<Finding> findings = sessionFactory.getCurrentSession() .createQuery("from Finding where scan = :scan)") .setInteger("scan", scan.getId()) .list(); for (Finding finding : findings) { sessionFactory.getCurrentSession().save(new DeletedFinding(finding)); sessionFactory.getCurrentSession().delete(finding); } findings = null; if (surfaceLocations != null) { for (SurfaceLocation surfaceLocation : surfaceLocations) { sessionFactory.getCurrentSession().delete(surfaceLocation); } } sessionFactory.getCurrentSession().save(new DeletedScan(scan)); sessionFactory.getCurrentSession().delete(scan); } @SuppressWarnings("unchecked") @Override public Map<String, Object> getCountsForScans(List<Integer> ids) { if (ids == null || ids.isEmpty()) return new HashMap<String, Object>(); String selectStart = "(select count(*) from Vulnerability vulnerability where vulnerability.isFalsePositive = false and " + "(vulnerability.active = true OR vulnerability.foundByScanner = true) AND " + "(vulnerability.genericSeverity.intValue = "; String vulnIds = " and (vulnerability in (select finding.vulnerability.id from Finding finding where finding.scan.id in "; String orMapIds = " or vulnerability in (select map.finding.vulnerability.id from ScanRepeatFindingMap map where map.scan.id in "; return (Map<String, Object>) sessionFactory.getCurrentSession().createQuery( "select new map( scan.id as id, " + selectStart + "2" + vulnIds + "(:scanIds2))" + orMapIds + "(:scanIds22))))) as low, " + selectStart + "3" + vulnIds + "(:scanIds3))" + orMapIds + "(:scanIds32))))) as medium, " + selectStart + "4" + vulnIds + "(:scanIds4))" + orMapIds + "(:scanIds42))))) as high, " + selectStart + "5" + vulnIds + "(:scanIds5))" + orMapIds + "(:scanIds52))))) as critical)" + " from Scan scan where scan.id = :scanId" ) .setParameterList("scanIds2", ids) .setParameterList("scanIds3", ids) .setParameterList("scanIds4", ids) .setParameterList("scanIds5", ids) .setParameterList("scanIds22", ids) .setParameterList("scanIds32", ids) .setParameterList("scanIds42", ids) .setParameterList("scanIds52", ids) .setInteger("scanId", ids.get(0)) .uniqueResult(); } @SuppressWarnings("unchecked") @Override public List<Scan> retrieveMostRecent(int number, Set<Integer> authenticatedAppIds, Set<Integer> authenticatedTeamIds) { Criteria baseCriteria = getBaseScanCriteria() .addOrder(Order.desc("id")) .setMaxResults(number); Criteria result = addFiltering(baseCriteria, authenticatedTeamIds, authenticatedAppIds); if (result == null) { return new ArrayList<Scan>(); } else { return baseCriteria.list(); } } @SuppressWarnings("unchecked") @Override public List<Scan> retrieveMostRecent(int number) { return getBaseScanCriteria() .addOrder(Order.desc("id")) .setMaxResults(number) .list(); } @Override @SuppressWarnings("unchecked") public List<Scan> getTableScans(Integer page) { return getBaseScanCriteria() .setFirstResult((page - 1) * 100) .setMaxResults(100) .addOrder(Order.desc("importTime")) .list(); } @Override @SuppressWarnings("unchecked") public List<Scan> getTableScans(Integer page, Set<Integer> authenticatedAppIds, Set<Integer> authenticatedTeamIds) { Criteria criteria = getBaseScanCriteria() .setFirstResult((page - 1) * 100) .setMaxResults(100) .addOrder(Order.desc("importTime")); Criteria filteredCriteria = addFiltering(criteria, authenticatedTeamIds, authenticatedAppIds); if (filteredCriteria == null) { return new ArrayList<Scan>(); } else { return criteria.list(); } } @Override public int getScanCount() { Long result = (Long) getBaseScanCriteria() .setProjection(Projections.rowCount()) .uniqueResult(); return safeLongToInt(result); } @Override public int getScanCount(Set<Integer> authenticatedAppIds, Set<Integer> authenticatedTeamIds) { Criteria criteria = getBaseScanCriteria() .setProjection(Projections.rowCount()); Criteria filteredCriteria = addFiltering(criteria, authenticatedTeamIds, authenticatedAppIds); if (filteredCriteria == null) { return 0; } else { return safeLongToInt((Long) filteredCriteria.uniqueResult()); } } private Criteria getBaseScanCriteria() { return sessionFactory.getCurrentSession() .createCriteria(Scan.class) .createAlias("application", "app") .add(Restrictions.eq("app.active", true)); } private Criteria addFiltering(Criteria criteria, Set<Integer> teamIds, Set<Integer> appIds) { boolean useAppIds = appIds != null && !appIds.isEmpty(), useTeamIds = teamIds != null && !teamIds.isEmpty(); if (!useAppIds && !useTeamIds) { return null; } if (useAppIds && useTeamIds) { criteria.createAlias("app.organization", "team") .add(Restrictions.eq("team.active", true)) .add(Restrictions.or( Restrictions.in("app.id", appIds), Restrictions.in("team.id", teamIds) )); } else if (useAppIds) { criteria .add(Restrictions.in("app.id", appIds)); } else if (useTeamIds) { criteria.createAlias("app.organization", "team") .add(Restrictions.in("team.id", teamIds)) .add(Restrictions.eq("team.active", true)); } return criteria; } private static int safeLongToInt(long l) { if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } return (int) l; } }