com.checkmarx.sdk.service.CxService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cx-spring-boot-sdk Show documentation
Show all versions of cx-spring-boot-sdk Show documentation
Checkmarx Java Spring Boot SDK
package com.checkmarx.sdk.service;
import com.checkmarx.sdk.config.CxProperties;
import com.checkmarx.sdk.dto.Filter;
import com.checkmarx.sdk.dto.ScanResults;
import com.checkmarx.sdk.dto.cx.*;
import com.checkmarx.sdk.dto.cx.xml.*;
import com.checkmarx.sdk.exception.CheckmarxLegacyException;
import com.checkmarx.sdk.exception.CheckmarxException;
import com.checkmarx.sdk.config.Constants;
import com.checkmarx.sdk.utils.ScanUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
/**
* Class used to orchestrate submitting scans and retrieving results
*/
@Service
public class CxService implements CxClient{
private static final String UNKNOWN = "-1";
private static final Integer UNKNOWN_INT = -1;
private static final Integer SCAN_STATUS_NEW = 1;
private static final Integer SCAN_STATUS_PRESCAN = 2;
private static final Integer SCAN_STATUS_QUEUED = 3;
private static final Integer SCAN_STATUS_SCANNING = 4;
private static final Integer SCAN_STATUS_POST_SCAN = 6;
private static final Integer SCAN_STATUS_FINISHED = 7;
private static final Integer SCAN_STATUS_CANCELED = 8;
private static final Integer SCAN_STATUS_FAILED = 9;
private static final Integer SCAN_STATUS_SOURCE_PULLING = 10;
private static final Integer SCAN_STATUS_NONE = 1001;
/*
report statuses - there are only 2:
InProcess (1)
Created (2)
*/
public static final Integer REPORT_STATUS_CREATED = 2;
private static final Map STATUS_MAP = ImmutableMap.of(
"TO VERIFY", 1,
"CONFIRMED", 2,
"URGENT", 3
);
private static final Logger log = org.slf4j.LoggerFactory.getLogger(CxService.class);
private static final String TEAMS = "/auth/teams";
private static final String TEAM = "/auth/teams/{id}";
private static final String TEAM_LDAP_MAPPINGS_UPDATE = "/auth/LDAPServers/{id}/TeamMappings";
private static final String TEAM_LDAP_MAPPINGS = "/auth/LDAPTeamMappings?ldapServerId={id}";
private static final String TEAM_LDAP_MAPPINGS_DELETE = "/auth/LDAPTeamMappings/{id}";
private static final String ROLE = "/auth/Roles";
private static final String ROLE_LDAP_MAPPING = "/auth/LDAPServers/{id}/RoleMappings";
private static final String ROLE_LDAP_MAPPINGS = "/auth/LDAPRoleMappings?ldapServerId={id}";
private static final String ROLE_LDAP_MAPPINGS_DELETE = "/auth/LDAPRoleMappings/{id}";
private static final String LDAP_SERVER = "/auth/LDAPServers";
private static final String PROJECTS = "/projects";
private static final String PROJECT = "/projects/{id}";
private static final String PROJECT_SOURCE = "/projects/{id}/sourceCode/remoteSettings/git";
private static final String PROJECT_SOURCE_FILE = "/projects/{id}/sourceCode/attachments";
private static final String PROJECT_EXCLUDE = "/projects/{id}/sourceCode/excludeSettings";
private static final String PRESETS = "/sast/presets";
private static final String SCAN_CONFIGURATIONS = "/sast/engineConfigurations";
private static final String SCAN_SETTINGS = "/sast/scanSettings";
private static final String SCAN = "/sast/scans";
private static final String SCAN_SUMMARY = "/sast/scans/{id}/resultsStatistics";
private static final String PROJECT_SCANS = "/sast/scans?projectId={pid}";
private static final String SCAN_STATUS = "/sast/scans/{id}";
private static final String REPORT = "/reports/sastScan";
private static final String REPORT_DOWNLOAD = "/reports/sastScan/{id}";
private static final String REPORT_STATUS = "/reports/sastScan/{id}/status";
private static final String OSA_VULN = "Vulnerable_Library";
private final CxProperties cxProperties;
private final CxLegacyService cxLegacyService;
private final CxAuthClient authClient;
private final RestTemplate restTemplate;
public CxService(CxAuthClient authClient, CxProperties cxProperties, CxLegacyService cxLegacyService, @Qualifier("cxRestTemplate") RestTemplate restTemplate) {
this.authClient = authClient;
this.cxProperties = cxProperties;
this.cxLegacyService = cxLegacyService;
this.restTemplate = restTemplate;
}
/**
* Create Scan for a projectId
*
* @param projectId
* @param incremental
* @param isPublic
* @param forceScan
* @param comment
* @return
*/
public Integer createScan(Integer projectId, boolean incremental, boolean isPublic, boolean forceScan, String comment) {
CxScan scan = CxScan.builder()
.projectId(projectId)
.isIncremental(incremental)
.forceScan(forceScan)
.isPublic(isPublic)
.comment(comment)
.build();
HttpEntity requestEntity = new HttpEntity<>(scan, authClient.createAuthHeaders());
log.info("Creating Scan for project Id {}", projectId);
try {
String response = restTemplate.postForObject(cxProperties.getUrl().concat(SCAN), requestEntity, String.class);
JSONObject obj = new JSONObject(response);
String id = obj.get("id").toString();
log.info("Scan created with Id {} for project Id {}", id, projectId);
return Integer.parseInt(id);
} catch (HttpStatusCodeException e) {
log.error("Error occurred while creating Scan for project {}, http error {}", projectId, e.getStatusCode());
log.error(ExceptionUtils.getStackTrace(e));
}
return UNKNOWN_INT;
}
@Override
public Integer getLastScanId(Integer projectId) {
HttpEntity requestEntity = new HttpEntity<>(authClient.createAuthHeaders());
log.info("Finding last Scan Id for project Id {}", projectId);
try {
ResponseEntity response = restTemplate.exchange(cxProperties.getUrl().concat(SCAN)
.concat("?projectId=").concat(projectId.toString().concat("&scanStatus=")
.concat(SCAN_STATUS_FINISHED.toString()).concat("&last=1")),
HttpMethod.GET, requestEntity, String.class);
JSONArray arr = new JSONArray(response.getBody());
if (arr.length() < 1) {
return UNKNOWN_INT;
}
JSONObject obj = arr.getJSONObject(0);
String id = obj.get("id").toString();
log.info("Scan found with Id {} for project Id {}", id, projectId);
return Integer.parseInt(id);
} catch (HttpStatusCodeException e) {
log.error("Error occurred while creating Scan for project {}, http error {}", projectId, e.getStatusCode());
log.error(ExceptionUtils.getStackTrace(e));
}
return UNKNOWN_INT;
}
/**
* Fetches scan data based on given scan identifier, as a {@link JSONObject}.
* @param scanId scan ID to use
* @return populated {@link JSONObject} if scan data was fetched; empty otherwise.
*/
@Override
public JSONObject getScanData(String scanId) {
HttpEntity requestEntity = new HttpEntity<>(authClient.createAuthHeaders());
JSONObject scanData = new JSONObject();
log.info("Fetching Scan data for Id {}", scanId);
try {
ResponseEntity response = restTemplate.exchange(
cxProperties.getUrl().concat(SCAN).concat("/").concat(scanId),
HttpMethod.GET, requestEntity, String.class);
scanData = new JSONObject(response.getBody());
} catch (HttpStatusCodeException e) {
log.error("Error occurred while fetching Scan data for scan Id {}, http error {}", scanId, e.getStatusCode());
log.error(ExceptionUtils.getStackTrace(e));
}
return scanData;
}
@Override
public LocalDateTime getLastScanDate(Integer projectId) {
HttpEntity requestEntity = new HttpEntity<>(authClient.createAuthHeaders());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
log.info("Finding last Scan Id for project Id {}", projectId);
try {
ResponseEntity response = restTemplate.exchange(cxProperties.getUrl().concat(SCAN)
.concat("?projectId=").concat(projectId.toString().concat("&scanStatus=").concat(SCAN_STATUS_FINISHED.toString())
.concat("&last=").concat(cxProperties.getIncrementalNumScans().toString())),
HttpMethod.GET, requestEntity, String.class);
JSONArray arr = new JSONArray(response.getBody());
for (int i = 0; i < arr.length(); i++) {
JSONObject scan = arr.getJSONObject(i);
if (!scan.getBoolean("isIncremental")) {
JSONObject dateAndTime = scan.getJSONObject("dateAndTime");
log.debug("Last full scan was {}", dateAndTime);
//example: "finishedOn": "2018-06-18T01:09:12.707", Grab only first 19 digits due to inconsistency of checkmarx results
LocalDateTime d;
try {
String finishedOn = dateAndTime.getString("finishedOn");
finishedOn = finishedOn.substring(0, 19);
log.debug("finishedOn: {}", finishedOn);
d = LocalDateTime.parse(finishedOn, formatter);
return d;
} catch (DateTimeParseException e) {
log.warn("Error Parsing last finished scan time {}", e.getParsedString());
log.error(ExceptionUtils.getStackTrace(e));
return null;
}
}
}
} catch (HttpStatusCodeException e) {
log.error("Error occurred while creating Scan for project {}, http error {}", projectId, e.getStatusCode());
log.error(ExceptionUtils.getStackTrace(e));
} catch (NullPointerException e) {
log.error("Error parsing JSON response for dateAndTime status. {}", ExceptionUtils.getMessage(e));
}
return null;
}
/**
* Get the status of a given scanId
*
* @param scanId
* @return
*/
@Override
public Integer getScanStatus(Integer scanId) {
HttpEntity httpEntity = new HttpEntity<>(authClient.createAuthHeaders());
log.debug("Retrieving xml status of xml Id {}", scanId);
try {
ResponseEntity projects = restTemplate.exchange(cxProperties.getUrl().concat(SCAN_STATUS), HttpMethod.GET, httpEntity, String.class, scanId);
JSONObject obj = new JSONObject(projects.getBody());
JSONObject status = obj.getJSONObject("status");
log.debug("status id {}, status name {}", status.getInt("id"), status.getString("name"));
return status.getInt("id");
} catch (HttpStatusCodeException e) {
log.error("HTTP Status Code of {} while getting xml status for xml Id {}", e.getStatusCode(), scanId);
log.error(ExceptionUtils.getStackTrace(e));
} catch (JSONException e) {
log.error("Error processing JSON Response");
log.error(ExceptionUtils.getStackTrace(e));
}
return UNKNOWN_INT;
}
/**
* Generate a scan report request (xml) based on ScanId
*
* @param scanId
* @return
*/
@Override
public Integer createScanReport(Integer scanId) {
String strJSON = "{'reportType':'XML', 'scanId':%d}";
strJSON = String.format(strJSON, scanId);
HttpEntity requestEntity = new HttpEntity<>(strJSON, authClient.createAuthHeaders());
try {
log.info("Creating report for xml Id {}", scanId);
ResponseEntity response = restTemplate.exchange(cxProperties.getUrl().concat(REPORT), HttpMethod.POST, requestEntity, String.class);
JSONObject obj = new JSONObject(response.getBody());
Integer id = obj.getInt("reportId");
log.info("Report with Id {} created", id);
return id;
} catch (HttpStatusCodeException e) {
log.error("HTTP Status Code of {} while creating xml report for xml Id {}", e.getStatusCode(), scanId);
log.error(ExceptionUtils.getStackTrace(e));
} catch (JSONException e) {
log.error("Error processing JSON Response");
log.error(ExceptionUtils.getStackTrace(e));
}
return UNKNOWN_INT;
}
/**
* Get the status of a report being generated by reportId
*
* @param reportId
* @return
*/
@Override
public Integer getReportStatus(Integer reportId) {
HttpEntity httpEntity = new HttpEntity<>(authClient.createAuthHeaders());
log.info("Retrieving report status of report Id {}", reportId);
try {
ResponseEntity projects = restTemplate.exchange(cxProperties.getUrl().concat(REPORT_STATUS), HttpMethod.GET, httpEntity, String.class, reportId);
JSONObject obj = new JSONObject(projects.getBody());
JSONObject status = obj.getJSONObject("status");
log.debug("Report status is {} - {} for report Id {}", status.getInt("id"), status.getString("value"), reportId);
return status.getInt("id");
} catch (HttpStatusCodeException e) {
log.error("HTTP Status Code of {} while getting report status for report Id {}", e.getStatusCode(), reportId);
log.error(ExceptionUtils.getStackTrace(e));
} catch (JSONException e) {
log.error("Error processing JSON Response");
log.error(ExceptionUtils.getStackTrace(e));
}
return UNKNOWN_INT;
}
private void waitForReportCreateOrFail(Integer reportId) throws CheckmarxException, InterruptedException {
Thread.sleep(cxProperties.getReportPolling());
int timer = 0;
while (!getReportStatus(reportId).equals(CxService.REPORT_STATUS_CREATED)) {
Thread.sleep(cxProperties.getReportPolling());
timer += cxProperties.getReportPolling();
if (timer >= cxProperties.getReportTimeout()) {
log.error("Report Generation timeout. {}", cxProperties.getReportTimeout());
throw new CheckmarxException("Timeout exceeded during report generation");
}
}
}
/**
* Retrieve the report by reportId, mapped to ScanResults DTO, applying filtering as requested
*
* @param scanId
* @param filter
* @return
* @throws CheckmarxException
*/
public ScanResults getReportContentByScanId(Integer scanId, List filter) throws CheckmarxException{
Integer reportId = createScanReport(scanId);
try {
waitForReportCreateOrFail(reportId);
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(ExceptionUtils.getStackTrace(e));
Thread.currentThread().interrupt();
throw new CheckmarxException("Interrupted Exception Occurred");
}
return getReportContent(reportId, filter);
}
/**
* Retrieve the report by reportId, mapped to ScanResults DTO, applying filtering as requested
*
* @param reportId
* @param filter
* @return
* @throws CheckmarxException
*/
@Override
public ScanResults getReportContent(Integer reportId, List filter) throws CheckmarxException {
HttpHeaders headers = authClient.createAuthHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity httpEntity = new HttpEntity<>(headers);
String session = null;
try {
/* login to legacy SOAP CX Client to retrieve description */
session = cxLegacyService.login(cxProperties.getUsername(), cxProperties.getPassword());
} catch (CheckmarxLegacyException e) {
log.error("Error occurring while logging into Legacy SOAP based WebService - issue description will remain blank");
}
log.info("Retrieving report contents of report Id {} in XML format", reportId);
try {
ResponseEntity resultsXML = restTemplate.exchange(cxProperties.getUrl().concat(REPORT_DOWNLOAD), HttpMethod.GET, httpEntity, String.class, reportId);
String xml = resultsXML.getBody();
log.debug("Report length: {}", xml.length());
log.debug("Headers: {}", resultsXML.getHeaders().toSingleValueMap().toString());
log.info("Report downloaded for report Id {}", reportId);
log.trace("XML String Output: {}", xml);
log.trace("Base64:", Base64.getEncoder().encodeToString(resultsXML.toString().getBytes()));
/*Remove any chars before the start xml tag*/
xml = xml.trim().replaceFirst("^([\\W]+)<", "<");
log.debug("Report length: {}", xml.length());
String xml2 = ScanUtils.cleanStringUTF8_2(xml);
log.trace("XML2: {}", xml2);
InputStream xmlStream = new ByteArrayInputStream(Objects.requireNonNull(xml2.getBytes()));
/* protect against XXE */
JAXBContext jc = JAXBContext.newInstance(CxXMLResultsType.class);
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false);
List xIssueList = new ArrayList<>();
CxXMLResultsType cxResults;
try {
XMLStreamReader xsr = xif.createXMLStreamReader(xmlStream);
Unmarshaller unmarshaller = jc.createUnmarshaller();
cxResults = (CxXMLResultsType) unmarshaller.unmarshal(xsr);
}catch (UnmarshalException e){
log.warn("Issue occurred performing unmashall step - trying again {}", ExceptionUtils.getMessage(e));
if(resultsXML.getBody() != null) {
log.error("Writing raw response from CX to {}", "CX_".concat(String.valueOf(reportId)));
ScanUtils.writeByte("CX_".concat(String.valueOf(reportId)), resultsXML.getBody().getBytes());
xml2 = ScanUtils.cleanStringUTF8(xml);
xmlStream = new ByteArrayInputStream(Objects.requireNonNull(xml2.getBytes()));
XMLStreamReader xsr = xif.createXMLStreamReader(xmlStream);
Unmarshaller unmarshaller = jc.createUnmarshaller();
cxResults = (CxXMLResultsType) unmarshaller.unmarshal(xsr);
}
else{
log.error("CX Response for report {} was null", reportId);
throw new CheckmarxException("CX report was empty (null)");
}
}
ScanResults.ScanResultsBuilder cxScanBuilder = ScanResults.builder();
cxScanBuilder.projectId(cxResults.getProjectId());
cxScanBuilder.team(cxResults.getTeam());
cxScanBuilder.project(cxResults.getProjectName());
cxScanBuilder.link(cxResults.getDeepLink());
cxScanBuilder.files(cxResults.getFilesScanned());
cxScanBuilder.loc(cxResults.getLinesOfCodeScanned());
cxScanBuilder.scanType(cxResults.getScanType());
Map summary = getIssues(filter, session, xIssueList, cxResults);
cxScanBuilder.xIssues(xIssueList);
cxScanBuilder.additionalDetails(getAdditionalScanDetails(cxResults));
CxScanSummary scanSummary = getScanSummaryByScanId(Integer.valueOf(cxResults.getScanId()));
cxScanBuilder.scanSummary(scanSummary);
ScanResults results = cxScanBuilder.build();
//Add the summary map (severity, count)
results.getAdditionalDetails().put(Constants.SUMMARY_KEY, summary);
if (cxProperties.getPreserveXml()) {
results.setOutput(xml);
}
return results;
} catch (HttpStatusCodeException e) {
log.error("HTTP Status Code of {} while getting downloading report contents of report Id {}", e.getStatusCode(), reportId);
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
} catch (XMLStreamException | JAXBException e) {
log.error("Error with XML report");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
} catch (NullPointerException e) {
log.info("Null Error");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
}
}
/**
* Retrieve the report by reportId, mapped to ScanResults DTO, applying filtering as requested
*
* @param reportId
* @return
* @throws CheckmarxException
*/
@Override
public CxXMLResultsType getXmlReportContent(Integer reportId) throws CheckmarxException {
HttpHeaders headers = authClient.createAuthHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity httpEntity = new HttpEntity<>(headers);
String session = null;
try {
/* login to legacy SOAP CX Client to retrieve description */
session = cxLegacyService.login(cxProperties.getUsername(), cxProperties.getPassword());
} catch (CheckmarxLegacyException e) {
log.error("Error occurring while logging into Legacy SOAP based WebService - issue description will remain blank");
}
log.info("Retrieving report contents of report Id {} in XML format", reportId);
try {
ResponseEntity resultsXML = restTemplate.exchange(cxProperties.getUrl().concat(REPORT_DOWNLOAD), HttpMethod.GET, httpEntity, String.class, reportId);
String xml = resultsXML.getBody();
log.debug("Report length: {}", xml.length());
log.debug("Headers: {}", resultsXML.getHeaders().toSingleValueMap().toString());
log.info("Report downloaded for report Id {}", reportId);
log.debug("XML String Output: {}", xml);
log.debug("Base64:", Base64.getEncoder().encodeToString(resultsXML.toString().getBytes()));
/*Remove any chars before the start xml tag*/
xml = xml.trim().replaceFirst("^([\\W]+)<", "<");
log.debug("Report length: {}", xml.length());
String xml2 = ScanUtils.cleanStringUTF8_2(xml);
log.trace("XML2: {}", xml2);
InputStream xmlStream = new ByteArrayInputStream(Objects.requireNonNull(xml2.getBytes()));
/* protect against XXE */
JAXBContext jc = JAXBContext.newInstance(CxXMLResultsType.class);
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false);
List xIssueList = new ArrayList<>();
CxXMLResultsType cxResults;
try {
XMLStreamReader xsr = xif.createXMLStreamReader(xmlStream);
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (CxXMLResultsType) unmarshaller.unmarshal(xsr);
}catch (UnmarshalException e){
log.warn("Issue occurred performing unmashall step - trying again {}", ExceptionUtils.getMessage(e));
if(resultsXML.getBody() != null) {
log.error("Writing raw response from CX to {}", "CX_".concat(String.valueOf(reportId)));
ScanUtils.writeByte("CX_".concat(String.valueOf(reportId)), resultsXML.getBody().getBytes());
xml2 = ScanUtils.cleanStringUTF8(xml);
xmlStream = new ByteArrayInputStream(Objects.requireNonNull(xml2.getBytes()));
XMLStreamReader xsr = xif.createXMLStreamReader(xmlStream);
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (CxXMLResultsType) unmarshaller.unmarshal(xsr);
}
else{
log.error("CX Response for report {} was null", reportId);
throw new CheckmarxException("CX report was empty (null)");
}
}
} catch (HttpStatusCodeException e) {
log.error("HTTP Status Code of {} while getting downloading report contents of report Id {}", e.getStatusCode(), reportId);
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
} catch (XMLStreamException | JAXBException e) {
log.error("Error with XML report");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
} catch (NullPointerException e) {
log.info("Null Error");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results for report Id ".concat(reportId.toString()));
}
}
/**
* Creates a map of additional scan details, such as scanId, scan start date, scan risk,
* scan risk severity, number of failed LOC, etc.
*
* @param cxResults the source to use
* @return a map of additional scan details
*/
protected Map getAdditionalScanDetails(CxXMLResultsType cxResults) {
// Add additional data from the results
Map additionalDetails = new HashMap();
additionalDetails.put("scanId", cxResults.getScanId());
additionalDetails.put("scanStartDate", cxResults.getScanStart());
JSONObject jsonObject = getScanData(cxResults.getScanId());
if (jsonObject != null) {
additionalDetails.put("scanRisk", String.valueOf(jsonObject.getInt("scanRisk")));
additionalDetails.put("scanRiskSeverity", String.valueOf(jsonObject.getInt("scanRiskSeverity")));
JSONObject scanState = jsonObject.getJSONObject("scanState");
if (scanState != null) {
additionalDetails.put("numFailedLoc", String.valueOf(scanState.getInt("failedLinesOfCode")));
}
}
// Add custom field values if requested
Map customFields = getCustomFields(Integer.valueOf(cxResults.getProjectId()));
additionalDetails.put("customFields", customFields);
return additionalDetails;
}
/**
* Returns custom field values read from a Checkmarx project, based on given projectId.
*
* @param projectId ID of project to lookup from Checkmarx
* @return Map of custom field names to values
*/
public Map getCustomFields(Integer projectId) {
Map customFields = new HashMap();
log.info("Fetching custom fields from project ID ".concat(projectId.toString()));
CxProject cxProject = getProject(Integer.valueOf(projectId));
if (cxProject != null) {
for (CxProject.CustomField customField : cxProject.getCustomFields()) {
customFields.put(customField.getName(), customField.getValue());
}
} else {
log.error("Could not find project with ID ".concat(projectId.toString()));
}
return customFields;
}
/**
* Parse CX report file, mapped to ScanResults DTO, applying filtering as requested
*
* @param file
* @param filter
* @return
* @throws CheckmarxException
*/
public ScanResults getReportContent(File file, List filter) throws CheckmarxException {
if (file == null) {
throw new CheckmarxException("File not provided for processing of results");
}
String session = null;
try {
if (!cxProperties.getOffline()) {
session = cxLegacyService.login(cxProperties.getUsername(), cxProperties.getPassword());
}
} catch (CheckmarxLegacyException e) {
log.error("Error occurring while logging into Legacy SOAP based WebService - issue description will remain blank");
}
try {
/* protect against XXE */
JAXBContext jc = JAXBContext.newInstance(CxXMLResultsType.class);
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, false);
Unmarshaller unmarshaller = jc.createUnmarshaller();
List issueList = new ArrayList<>();
CxXMLResultsType cxResults = (CxXMLResultsType) unmarshaller.unmarshal(file);
ScanResults.ScanResultsBuilder cxScanBuilder = ScanResults.builder();
cxScanBuilder.projectId(cxResults.getProjectId());
cxScanBuilder.team(cxResults.getTeam());
cxScanBuilder.project(cxResults.getProjectName());
cxScanBuilder.link(cxResults.getDeepLink());
cxScanBuilder.files(cxResults.getFilesScanned());
cxScanBuilder.loc(cxResults.getLinesOfCodeScanned());
cxScanBuilder.scanType(cxResults.getScanType());
getIssues(filter, session, issueList, cxResults);
cxScanBuilder.xIssues(issueList);
cxScanBuilder.additionalDetails(getAdditionalScanDetails(cxResults));
if (!cxProperties.getOffline() && !ScanUtils.empty(cxResults.getScanId())) {
CxScanSummary scanSummary = getScanSummary(Integer.valueOf(cxResults.getScanId()));
cxScanBuilder.scanSummary(scanSummary);
}
return cxScanBuilder.build();
} catch (JAXBException e) {
log.error("Error with XML report");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results");
} catch (NullPointerException e) {
log.info("Null error");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results");
}
}
/**
* @param vulnsFile
* @param libsFile
* @param filter
* @return
* @throws CheckmarxException
*/
public ScanResults getOsaReportContent(File vulnsFile, File libsFile, List filter) throws CheckmarxException {
if (vulnsFile == null || libsFile == null) {
throw new CheckmarxException("Files not provided for processing of OSA results");
}
try {
ObjectMapper objectMapper = new ObjectMapper();
List issueList = new ArrayList<>();
//convert json string to object
List osaVulns = objectMapper.readValue(vulnsFile, new TypeReference>() {
});
List osaLibs = objectMapper.readValue(libsFile, new TypeReference>() {
});
Map libsMap = getOsaLibsMap(osaLibs);
Map severityMap = ImmutableMap.of(
"LOW", 1,
"MEDIUM", 2,
"HIGH", 3
);
for (CxOsa o : osaVulns) {
if (filterOsa(filter, o) && libsMap.containsKey(o.getLibraryId())) {
CxOsaLib lib = libsMap.get(o.getLibraryId());
String filename = lib.getName();
ScanResults.XIssue issue = ScanResults.XIssue.builder()
.file(filename)
.vulnerability(OSA_VULN)
.severity(o.getSeverity().getName())
.cve(o.getCveName())
.build();
ScanResults.OsaDetails details = ScanResults.OsaDetails.builder()
.severity(o.getSeverity().getName())
.cve(o.getCveName())
.description(o.getDescription())
.recommendation(o.getRecommendations())
.url(o.getUrl())
.version(lib.getVersion())
.build();
//update
if (issueList.contains(issue)) {
issue = issueList.get(issueList.indexOf(issue));
//bump up the severity if required
if (severityMap.get(issue.getSeverity().toUpperCase(Locale.ROOT)) < severityMap.get(o.getSeverity().getName().toUpperCase(Locale.ROOT))) {
issue.setSeverity(o.getSeverity().getName());
}
issue.setCve(issue.getCve().concat(",").concat(o.getCveName()));
issue.getOsaDetails().add(details);
} else {//new
List dList = new ArrayList<>();
dList.add(details);
issue.setOsaDetails(dList);
issueList.add(issue);
}
}
}
return ScanResults.builder()
.osa(true)
.xIssues(issueList)
.build();
} catch (IOException e) {
log.error("Error parsing JSON OSA report");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results");
} catch (NullPointerException e) {
log.info("Null error");
log.error(ExceptionUtils.getStackTrace(e));
throw new CheckmarxException("Error while processing scan results");
}
}
@Override
public String getIssueDescription(Long scanId, Long pathId) {
return null;
}
private boolean filterOsa(List filters, CxOsa osa) {
boolean all = true;
for (Filter f : filters) {
if (f.getType().equals(Filter.Type.SEVERITY)) {
all = false; //if no SEVERITY filters, everything is applied
if (f.getValue().equalsIgnoreCase(osa.getSeverity().getName())) {
return true;
}
}
}
return all;
}
private Map getOsaLibsMap(List libs) {
Map libMap = new HashMap<>();
for (CxOsaLib o : libs) {
libMap.put(o.getId(), o);
}
return libMap;
}
private List getOSAVulnsByLibId(List osaVulns, String libId) {
List vulns = new ArrayList<>();
for (CxOsa v : osaVulns) {
if (v.getLibraryId().equals(libId)) {
vulns.add(v);
}
}
return vulns;
}
/**
* @param filter
* @param session
* @param cxIssueList
* @param cxResults
*/
private Map getIssues(List filter, String session, List cxIssueList, CxXMLResultsType cxResults) {
Map summary = new HashMap<>();
for (QueryType q : cxResults.getQuery()) {
if (checkFilter(q, filter)) {
ScanResults.XIssue.XIssueBuilder xIssueBuilder = ScanResults.XIssue.builder();
/*Top node of each issue*/
for (ResultType r : q.getResult()) {
if (r.getFalsePositive().equalsIgnoreCase("FALSE") && checkFilter(r, filter)) {
/*Map issue details*/
xIssueBuilder.cwe(q.getCweId());
xIssueBuilder.language(q.getLanguage());
xIssueBuilder.severity(q.getSeverity());
xIssueBuilder.vulnerability(q.getName());
xIssueBuilder.file(r.getFileName());
xIssueBuilder.severity(r.getSeverity());
xIssueBuilder.link(r.getDeepLink());
// Add additional details
Map additionalDetails = getAdditionalIssueDetails(q, r);
xIssueBuilder.additionalDetails(additionalDetails);
Map details = new HashMap<>();
try {
/* Call the CX SOAP Service to get Issue Description*/
if (session != null) {
try {
xIssueBuilder.description(this.getIssueDescription(session, Long.parseLong(cxResults.getScanId()), Long.parseLong(r.getPath().getPathId())));
} catch (HttpStatusCodeException e) {
xIssueBuilder.description("");
}
} else {
xIssueBuilder.description("");
}
details.put(Integer.parseInt(r.getPath().getPathNode().get(0).getLine()),
r.getPath().getPathNode().get(0).getSnippet().getLine().getCode());
xIssueBuilder.similarityId(r.getPath().getSimilarityId());
} catch (NullPointerException e) {
log.warn("Problem grabbing snippet. Snippet may not exist for finding for Node ID");
/*Defaulting to initial line number with no snippet*/
details.put(Integer.parseInt(r.getLine()), null);
}
xIssueBuilder.details(details);
ScanResults.XIssue issue = xIssueBuilder.build();
checkForDuplicateIssue(cxIssueList, r, details, issue, summary);
}
}
}
}
return summary;
}
private Map getAdditionalIssueDetails(QueryType q, ResultType r) {
Map additionalDetails = new HashMap();
additionalDetails.put("categories", q.getCategories());
String descUrl = ScanUtils.getHostWithProtocol(r.getDeepLink()) +
"/CxWebClient/ScanQueryDescription.aspx?queryID=" + q.getId() +
"&queryVersionCode=" + q.getQueryVersionCode() +
"&queryTitle=" + q.getName();
additionalDetails.put("recommendedFix", descUrl);
List