From 97f7cd94dde055cb2e8384d350a5d18e7cc9ab3d Mon Sep 17 00:00:00 2001 From: Shaul Valero Date: Sun, 19 Aug 2018 11:49:24 +0300 Subject: [PATCH 001/473] Added CxGlobalMessage --- .../com/cx/restclient/CxShragaClient.java | 3 ++- .../cx/restclient/common/CxGlobalMessage.java | 25 +++++++++++++++++++ .../com/cx/restclient/osa/utils/OSAUtils.java | 5 ++-- 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/cx/restclient/common/CxGlobalMessage.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 29eecff5..ff3ba042 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,5 +1,6 @@ package com.cx.restclient; +import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; @@ -122,7 +123,7 @@ public ThresholdResult getThresholdResult() { public boolean isPolicyViolated(StringBuilder failDescription) { boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaViolations().size() > 0; if(isPolicyViolated) { - failDescription.append("Project policy status: violated").append("\n");; + failDescription.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); } return isPolicyViolated; } diff --git a/src/main/java/com/cx/restclient/common/CxGlobalMessage.java b/src/main/java/com/cx/restclient/common/CxGlobalMessage.java new file mode 100644 index 00000000..d0602b60 --- /dev/null +++ b/src/main/java/com/cx/restclient/common/CxGlobalMessage.java @@ -0,0 +1,25 @@ +package com.cx.restclient.common; + +/** + * Created by shaulv on 8/9/2018. + */ +public enum CxGlobalMessage { + + PROJECT_POLICY_STATUS("Project policy status : %s"), + PROJECT_POLICY_VIOLATED_STATUS("Project policy status : violated"), + PROJECT_POLICY_COMPLAINT_STATUS("Project policy status : compliant"); + + private String message; + + private CxGlobalMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public String formatMessage(Object... args) { + return String.format(message, args); + } +} diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 2e0395b2..d50e268d 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -1,5 +1,6 @@ package com.cx.restclient.osa.utils; +import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; @@ -126,9 +127,9 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info(""); if (enableViolations) { if (osaResults.getOsaPolicies().isEmpty()){ - log.info("Project policy status: compliant"); + log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); }else{ - log.info("Project policy status: violated"); + log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); } } From 7bb03924b3e7319921453859bb0e568ffd4d7865 Mon Sep 17 00:00:00 2001 From: cxEyala Date: Sun, 19 Aug 2018 12:14:43 +0300 Subject: [PATCH 002/473] change text in report, upgrade fs agent to 18.7.2 --- pom.xml | 2 +- src/main/resources/com/cx/report/report.ftl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 86282af6..d1a9913d 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ org.whitesource whitesource-fs-agent - 18.7.1 + 18.7.2 javax.xml.bind diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index afc4f8cb..9de31fb4 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -942,18 +942,18 @@

- Checkmarx Scan Failed + Checkmarx scan ended successfully with the following issues:

    <#if policyViolated>
  • ${osa.osaPolicies?size} ${policyLabel} Violated
  • <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> -
  • CxSAST and CxOSA Vulnerability Thresholds Exceeded
  • +
  • Exceeded CxSAST and CxOSA Vulnerability Thresholds
  • <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> -
  • CxSAST Vulnerability Threshold Exceeded
  • +
  • Exceeded CxSAST Vulnerability Threshold
  • <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> -
  • CxOSA Vulnerability Threshold Exceeded
  • +
  • Exceeded CxOSA Vulnerability Threshold
From d7c4de7fe521bf9bc86e36e0ce63f51e3632a06d Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 19 Aug 2018 16:28:27 +0300 Subject: [PATCH 003/473] fix the report violation title-color --- .../java/com/cx/restclient/common/summary/SummaryUtils.java | 2 +- src/main/resources/com/cx/report/report.ftl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index e7fd16c2..6ba09564 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -20,7 +20,7 @@ public abstract class SummaryUtils { public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { Configuration cfg = new Configuration(new Version("2.3.23")); - cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report/"); + cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); Template template = cfg.getTemplate("report.ftl"); Map templateData = new HashMap(); diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index 9de31fb4..18301a48 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -859,14 +859,14 @@ padding-left: 16px; } - .scan-status .title-scan-status.failure { + .scan-status .content-scan-status .title-scan-status.failure { padding-top: 7px; color: #DD3D56; padding-bottom: 3px; } } - .scan-status .title-scan-status.success { + .scan-status .content-scan-status .title-scan-status.success { color: #38d87d; } From fec02e1d103c0125994282fbf7c851828eebde32 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 19 Aug 2018 16:31:57 +0300 Subject: [PATCH 004/473] change violation text-bar on failure --- src/main/resources/com/cx/report/report.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index 18301a48..28f93325 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -942,7 +942,7 @@

- Checkmarx scan ended successfully with the following issues: + Checkmarx scan found the following issues:

    <#if policyViolated> From 96cf081b24b63334988379160f42529d6a6f1c62 Mon Sep 17 00:00:00 2001 From: Shaul Valero Date: Wed, 22 Aug 2018 13:56:41 +0300 Subject: [PATCH 005/473] Support TLS 1.2 --- .../com/cx/restclient/httpClient/CxHttpClient.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 4c6cc71c..efe8662c 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -16,6 +16,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; @@ -69,6 +70,7 @@ public CxHttpClient(String hostname, String username, String password, String or this.cxOrigin = origin; //create httpclient HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); + setSSLTls(builder, "TLSv1.2", logi); if (disableSSLValidation) { builder = disableCertificateValidation(builder, logi); } @@ -176,4 +178,14 @@ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws Ce return builder; } + private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { + SSLContext sslContext = null; + try { + sslContext = SSLContextBuilder.create().useProtocol(protocol).build(); + builder.setSslcontext(sslContext); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + log.warn("Failed to set SSL TLS : " + e.getMessage()); + } + } + } From 5d7d441f222008bd1de914dd11c4cdf6fb696b76 Mon Sep 17 00:00:00 2001 From: "DM\\shaulv" Date: Thu, 30 Aug 2018 15:39:30 +0300 Subject: [PATCH 006/473] setResolveDependencies false for python and nuget --- .../com/cx/restclient/osa/utils/OSAUtils.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index d50e268d..e1962ab7 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -93,11 +93,9 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("npm.runPreStep", "true"); ret.put("bower.runPreStep", "false"); ret.put("npm.ignoreScripts", "true"); - - ret.put("nuget.resolveDependencies", "true"); - ret.put("nuget.restoreDependencies", "true"); - ret.put("python.resolveDependencies", "true"); - ret.put("python.ignorePipInstallErrors", "true"); + setResolveDependencies(ret,"true"); + }else { + setResolveDependencies(ret,"false"); } ret.put("d", scanFolder); @@ -105,6 +103,13 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S return ret; } + private static void setResolveDependencies(Properties ret, String resolveDependencies) { + ret.put("nuget.resolveDependencies", resolveDependencies); + ret.put("nuget.restoreDependencies", resolveDependencies); + ret.put("python.resolveDependencies", resolveDependencies); + ret.put("python.ignorePipInstallErrors", resolveDependencies); + } + public static void printOSAResultsToConsole(OSAResults osaResults, boolean enableViolations, Logger log) { OSASummaryResults osaSummaryResults = osaResults.getResults(); log.info("----------------------------Checkmarx Scan Results(CxOSA):-------------------------------"); From 129edf4d533c2264e2657a7f49e924be874848bb Mon Sep 17 00:00:00 2001 From: "DM\\shaulv" Date: Thu, 27 Sep 2018 10:37:14 +0300 Subject: [PATCH 007/473] CxHttpClient -builder.useSystemProperties --- src/main/java/com/cx/restclient/httpClient/CxHttpClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index efe8662c..a329f695 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -74,6 +74,7 @@ public CxHttpClient(String hostname, String username, String password, String or if (disableSSLValidation) { builder = disableCertificateValidation(builder, logi); } + builder.useSystemProperties(); apacheClient = builder.build(); } From 8a10f45bc2ad32c9445b775cffbf5a6d9fda5ce0 Mon Sep 17 00:00:00 2001 From: Tfs Date: Thu, 27 Sep 2018 10:54:23 +0300 Subject: [PATCH 008/473] Version_Updated_9.00.0 --- pom.xml | 521 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 256 insertions(+), 265 deletions(-) diff --git a/pom.xml b/pom.xml index d1a9913d..c4201b4f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,269 +1,260 @@ - - - 4.0.0 - com.checkmarx - cx-client-common - 8.90.0-SNAPSHOT - jar - - Checkmarx Client - Enables web services access Checkmarx SAST and OSA scan. - https://www.checkmarx.com - - scm:git:git://github.com/CxRepositories/Cx-Client-Common.git - scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git - https://github.com/CxRepositories/Cx-Client-Common/tree/master - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - UTF-8 - - - Checkmarx - https://www.checkmarx.com/ - - + + + 4.0.0 + com.checkmarx + cx-client-common + 9.00.0-SNAPSHOT + jar + Checkmarx Client + Enables web services access Checkmarx SAST and OSA scan. + https://www.checkmarx.com + + scm:git:git://github.com/CxRepositories/Cx-Client-Common.git + scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git + https://github.com/CxRepositories/Cx-Client-Common/tree/master + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + UTF-8 + + + Checkmarx + https://www.checkmarx.com/ + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.3 + + + default-descriptor + + descriptor + report + + process-classes + + + help-descriptor + + helpmojo + + process-classes + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.7 + 1.7 + + + + + + + org.freemarker + freemarker + 2.3.23 + + + org.apache.httpcomponents + httpmime + 4.4.1 + + + org.apache.httpcomponents + httpcore + 4.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.8.9 + + + org.whitesource + whitesource-fs-agent + 18.7.2 + + + javax.xml.bind + jaxb-api + + + ch.qos.logback + logback-classic + + + org.slf4j + slf4j-api + + + + + org.slf4j + slf4j-api + 1.7.5 + provided + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.3 + + + + + + release + - - org.apache.maven.plugins - maven-plugin-plugin - 3.3 - - - default-descriptor - - descriptor - report - - process-classes - - - help-descriptor - - helpmojo - - process-classes - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.7 - 1.7 - - + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar-no-fork + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + C:\Program Files (x86)\GNU\GnuPG\gpg2.exe + Checkmarx123456 + + + + sign-artifacts + verify + + sign + + + + - - - - - org.freemarker - freemarker - 2.3.23 - - - org.apache.httpcomponents - httpmime - 4.4.1 - - - org.apache.httpcomponents - httpcore - 4.4 - - - com.fasterxml.jackson.core - jackson-databind - 2.8.9 - - - org.whitesource - whitesource-fs-agent - 18.7.2 - - - javax.xml.bind - jaxb-api - - - ch.qos.logback - logback-classic - - - org.slf4j - slf4j-api - - - - - org.slf4j - slf4j-api - 1.7.5 - provided - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.3 - - - - - - - release - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar-no-fork - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - C:\Program Files (x86)\GNU\GnuPG\gpg2.exe - Checkmarx123456 - - - - sign-artifacts - verify - - sign - - - - - - - - - - - - Gal Or Nussbaum - gal.nussbaum@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - https://www.linkedin.com/in/gal-nussbaum-68b3a76a/de - - - - Dor Golan - dor.golan@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Shaul Valero - shaul.valero@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Dev TL - Developer - - - http://i.imgur.com/44Iil53.png - + + + + + + Gal Or Nussbaum + gal.nussbaum@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + https://www.linkedin.com/in/gal-nussbaum-68b3a76a/de + + + + Dor Golan + dor.golan@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + http://i.imgur.com/44Iil53.png + + + + Shaul Valero + shaul.valero@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Dev TL + Developer + + + http://i.imgur.com/44Iil53.png + + + + Eyal Amor + eyal.amor@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + http://i.imgur.com/44Iil53.png + + + + Yair David + Yair.David@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + QA Manager + + + http://i.imgur.com/EVIS8LO.jpg + - - Eyal Amor - eyal.amor@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Yair David - Yair.David@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - QA Manager - - - http://i.imgur.com/EVIS8LO.jpg - - - + \ No newline at end of file From 6a4ed92f0086b51718a656fe5d8431a415eca808 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 8 Oct 2018 13:42:59 +0300 Subject: [PATCH 009/473] add hideResults Param --- .../com/cx/restclient/configuration/CxScanConfig.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 7e70e169..5df248a1 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -24,6 +24,7 @@ public class CxScanConfig implements Serializable { private String teamPath; private String teamId; private Boolean denyProject = false; + private Boolean hideResults = false; private Boolean isPublic = true; private Boolean forceScan = false; private String presetName; @@ -443,4 +444,12 @@ public String getCxARMUrl() { public void setCxARMUrl(String cxARMUrl) { this.cxARMUrl = cxARMUrl; } + + public Boolean getHideResults() { + return hideResults; + } + + public void setHideResults(Boolean hideResults) { + this.hideResults = hideResults; + } } From 12e3cdbff00afe1d79008b5407d24bf3a92d247e Mon Sep 17 00:00:00 2001 From: TaliB Date: Mon, 15 Oct 2018 13:57:53 +0300 Subject: [PATCH 010/473] SSL context do not override the machine context #149421 --- .../com/cx/restclient/httpClient/CxHttpClient.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index a329f695..eb71872d 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -16,11 +16,11 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; -import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -180,11 +180,11 @@ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws Ce } private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { - SSLContext sslContext = null; try { - sslContext = SSLContextBuilder.create().useProtocol(protocol).build(); - builder.setSslcontext(sslContext); - } catch (NoSuchAlgorithmException | KeyManagementException e) { + final SSLContext sslContext =SSLContext.getInstance(protocol); + sslContext.init(null,null,null); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { log.warn("Failed to set SSL TLS : " + e.getMessage()); } } From f83d17d40cf6eafa87e6d91c5a02153d57c7e1ec Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 21 Oct 2018 12:15:36 +0300 Subject: [PATCH 011/473] add avoid duplicated fix --- .../java/com/cx/restclient/CxSASTClient.java | 34 ++++++++++++++++++- .../configuration/CxScanConfig.java | 11 +++++- .../cx/restclient/sast/dto/QueueStatus.java | 18 ++++++++++ .../cx/restclient/sast/utils/SASTParam.java | 1 + 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sast/dto/QueueStatus.java diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 9cc88e00..68005d8d 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -84,8 +84,12 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce //CREATE SAST scan long createSASTScan(long projectId) throws IOException, CxClientException { log.info("-----------------------------------Create CxSAST Scan:------------------------------------"); - ScanSettingResponse scanSettingResponse = getScanSetting(projectId); + if (config.isAvoidDuplicateProjectScans()!= null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { + throw new CxClientException("\nAvoid duplicate project scans in queue\n"); + } + + ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); scanSettingRequest.setEngineConfigurationId(scanSettingResponse.getEngineConfiguration().getId());//todo check for null scanSettingRequest.setProjectId(projectId); @@ -194,6 +198,30 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { //**------ Private Methods ------**// + private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { + List queuedScans = getQueueScans(projectId); + for (ResponseQueueScanStatus scan : queuedScans) { + if (isStatusToAvoid(scan.getStage().getValue()) && scan.getProject().getId() == projectId) { + return true; + } + } + return false; + } + + private boolean isStatusToAvoid(String status) { + QueueStatus qStatus = QueueStatus.valueOf(status); + + switch (qStatus) { + case New: + case PreScan: + case SourcePullingAndDeployment: + case Queued: + case Scanning: + case PostScan: + return true; + } + return false; + } private ScanSettingResponse getScanSetting(long projectId) throws IOException, CxClientException { return httpClient.getRequest(SAST_GET_SCAN_SETTINGS.replace("{projectId}", Long.toString(projectId)), CONTENT_TYPE_APPLICATION_JSON_V1, ScanSettingResponse.class, 200, "Scan setting", false); @@ -226,6 +254,10 @@ private List getLatestSASTStatus(long projectId) throws CxClie return (List) httpClient.getRequest(SAST_GET_PROJECT_SCANS.replace("{projectId}", Long.toString(projectId)), CONTENT_TYPE_APPLICATION_JSON_V1, LastScanResponse.class, 200, "last SAST scan ID", true); } + private List getQueueScans(long projectId) throws IOException, CxClientException { + return (List) httpClient.getRequest(SAST_GET_QUEUED_SCANS.replace("{projectId}", Long.toString(projectId)), CONTENT_TYPE_APPLICATION_JSON_V1, ResponseQueueScanStatus.class, 200, "scans in the queue. (projectId: )" + projectId, true); + } + private CreateReportResponse createScanReport(CreateReportRequest reportRequest) throws CxClientException, IOException { StringEntity entity = new StringEntity(convertToJson(reportRequest)); return httpClient.postRequest(SAST_CREATE_REPORT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CreateReportResponse.class, 202, "to create " + reportRequest.getReportType() + " scan report"); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 7e70e169..6852bf1d 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -56,7 +56,7 @@ public class CxScanConfig implements Serializable { private Integer osaLowThreshold; private Properties osaFsaConfig; //for MAVEN private String osaDependenciesJson; - + private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; private String cxARMUrl; @@ -443,4 +443,13 @@ public String getCxARMUrl() { public void setCxARMUrl(String cxARMUrl) { this.cxARMUrl = cxARMUrl; } + + + public Boolean isAvoidDuplicateProjectScans() { + return avoidDuplicateProjectScans; + } + + public void setAvoidDuplicateProjectScans(Boolean avoidDuplicateProjectScans) { + this.avoidDuplicateProjectScans = avoidDuplicateProjectScans; + } } diff --git a/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java b/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java new file mode 100644 index 00000000..e7eaf3b2 --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java @@ -0,0 +1,18 @@ +package com.cx.restclient.sast.dto; + +/** + * Created by Galn on 05/02/2018. + */ + +public enum QueueStatus { + New, + PreScan, + SourcePullingAndDeployment, + Queued, + Scanning, + PostScan, + Finished, + Canceled, + Failed; +} + diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 0ca54d5d..eb4103b7 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -15,6 +15,7 @@ public class SASTParam { public static final String SAST_GET_All_PROJECTS = "projects";// Get project) public static final String SAST_ZIP_ATTACHMENTS = "projects/{projectId}/sourceCode/attachments";//Attach ZIP file public static final String SAST_GET_PROJECT_SCANS = "sast/scans?projectId={projectId}"; + public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; //Once it has results From bdf4d202364e311154b8c4a89c205aaf012cad97 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 28 Oct 2018 11:46:07 +0200 Subject: [PATCH 012/473] adding ignoreJsonProperties to dto's --- src/main/java/com/cx/restclient/dto/BaseStatus.java | 3 +++ src/main/java/com/cx/restclient/dto/LoginRequest.java | 3 +++ src/main/java/com/cx/restclient/dto/Team.java | 4 ++++ src/main/java/com/cx/restclient/dto/TokenLoginResponse.java | 3 +++ 4 files changed, 13 insertions(+) diff --git a/src/main/java/com/cx/restclient/dto/BaseStatus.java b/src/main/java/com/cx/restclient/dto/BaseStatus.java index b9376e68..8d853702 100644 --- a/src/main/java/com/cx/restclient/dto/BaseStatus.java +++ b/src/main/java/com/cx/restclient/dto/BaseStatus.java @@ -1,8 +1,11 @@ package com.cx.restclient.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by Galn on 13/02/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class BaseStatus { private String baseId; private Status baseStatus; diff --git a/src/main/java/com/cx/restclient/dto/LoginRequest.java b/src/main/java/com/cx/restclient/dto/LoginRequest.java index cbf9e413..abfa1cc5 100644 --- a/src/main/java/com/cx/restclient/dto/LoginRequest.java +++ b/src/main/java/com/cx/restclient/dto/LoginRequest.java @@ -1,9 +1,12 @@ package com.cx.restclient.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by: Dorg. * Date: 08/09/2016. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class LoginRequest { private String username; diff --git a/src/main/java/com/cx/restclient/dto/Team.java b/src/main/java/com/cx/restclient/dto/Team.java index 8af500c4..2f389cdd 100644 --- a/src/main/java/com/cx/restclient/dto/Team.java +++ b/src/main/java/com/cx/restclient/dto/Team.java @@ -1,8 +1,12 @@ package com.cx.restclient.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by Galn on 14/02/2018. */ + +@JsonIgnoreProperties(ignoreUnknown = true) public class Team { public String id; public String fullName; diff --git a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java index ffec9c1b..a7e0f39f 100644 --- a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java +++ b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java @@ -1,8 +1,11 @@ package com.cx.restclient.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by Galn on 19/03/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class TokenLoginResponse { private String access_token; private long expires_in; From 4235d8641e2fc48ad6aa87995f4c5d84e9f2138b Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 28 Oct 2018 11:59:46 +0200 Subject: [PATCH 013/473] add Whitesource part to the log --- src/main/java/com/cx/restclient/CxOSAClient.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 8366665e..86ae93ee 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -8,6 +8,8 @@ import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.*; import com.cx.restclient.osa.utils.OSAUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; @@ -69,7 +71,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio return sendOSAScan(osaDependenciesJson, projectId); } - private String resolveOSADependencies() { + private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { @@ -81,8 +83,16 @@ private String resolveOSADependencies() { config.getOsaRunInstall(), log); } + ObjectMapper mapper = new ObjectMapper(); + log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); + log.info("#########################################"); + log.info(" WhiteSource- Starting FSA component scan:"); + log.info("#########################################"); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); + log.info("##########################################"); + log.info(" WhiteSource- FSA component scan ended."); + log.info("##########################################"); OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); return osaDependenciesJson; From 9d0596c26f673e66ae9b4b499b84fafc48646f37 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Wed, 7 Nov 2018 16:01:36 +0200 Subject: [PATCH 014/473] adding "\" to teamPath if not exists --- .../java/com/cx/restclient/configuration/CxScanConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 080e53f5..5a6ca257 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -158,6 +158,9 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { + if(!teamPath.startsWith("\\")){ + teamPath = "\\" + teamPath; + } this.teamPath = teamPath; } From 53d05be2597964f9acedb50e0238fba0bae549d6 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 18 Nov 2018 15:32:27 +0200 Subject: [PATCH 015/473] unified sast osa violations --- pom.xml | 4 +- .../java/com/cx/restclient/CxOSAClient.java | 8 +- .../java/com/cx/restclient/CxSASTClient.java | 23 +- .../com/cx/restclient/CxShragaClient.java | 22 +- .../common/summary/SummaryUtils.java | 22 +- .../com/cx/restclient/cxArm/dto/Policy.java | 35 +- .../com/cx/restclient/cxArm/dto/Rule.java | 19 + .../cx/restclient/cxArm/dto/Violation.java | 15 - .../cx/restclient/cxArm/utils/CxARMUtils.java | 49 +- .../com/cx/restclient/osa/dto/OSAResults.java | 23 +- .../com/cx/restclient/osa/utils/OSAUtils.java | 8 - .../cx/restclient/sast/dto/SASTResults.java | 38 +- .../cx/restclient/sast/utils/SASTParam.java | 1 + .../cx/restclient/sast/utils/SASTUtils.java | 8 +- src/main/resources/com/cx/report/report.ftl | 431 +++++++++--------- 15 files changed, 405 insertions(+), 301 deletions(-) create mode 100644 src/main/java/com/cx/restclient/cxArm/dto/Rule.java diff --git a/pom.xml b/pom.xml index c4201b4f..20e21652 100644 --- a/pom.xml +++ b/pom.xml @@ -55,8 +55,8 @@ maven-compiler-plugin 3.7.0 - 1.7 - 1.7 + 1.8 + 1.8 diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 86ae93ee..0d1b6763 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -19,7 +19,7 @@ import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.osa.utils.OSAParam.*; @@ -134,10 +134,8 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus private void resolveOSAViolation(OSAResults osaResults, long projectId){ try { - for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value())) { - osaResults.getOsaPolicies().add(policy.getPolicyName()); - osaResults.addAllViolations(policy.getViolations()); - } + getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) + .forEach(osaResults::addPolicy); }catch (Exception ex) { log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 68005d8d..4841b9dd 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -9,6 +9,7 @@ import com.cx.restclient.sast.dto.*; import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; @@ -20,10 +21,12 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import static com.cx.restclient.cxArm.dto.CxProviders.SAST; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; import static com.cx.restclient.httpClient.utils.ContentType.*; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -133,9 +136,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr //retrieve SAST scan results sastResults = retrieveSASTResults(scanId, projectId); - /* if (config.getEnablePolicyViolations()) { + if (config.getEnablePolicyViolations()) { resolveSASTViolation(sastResults, projectId); - }*/ + } SASTUtils.printSASTResultsToConsole(sastResults, config.getEnablePolicyViolations(), log); //PDF report @@ -144,8 +147,13 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr byte[] pdfReport = getScanReport(sastResults.getScanId(), ReportType.PDF, CONTENT_TYPE_APPLICATION_PDF_V1); sastResults.setPDFReport(pdfReport); if (config.getReportsDir() != null) { - String pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), log); + String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); + String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; + pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); + if (!StringUtils.isEmpty(config.getCxOrigin()) && config.getCxOrigin().equalsIgnoreCase("jenkins")) { + // sastResults.setSastPDFLink(); + } } } return sastResults; @@ -153,10 +161,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr private void resolveSASTViolation(SASTResults sastResults, long projectId) { try { - for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, SAST.value())) { - sastResults.getSastPolicies().add(policy.getPolicyName()); - sastResults.addAllViolations(policy.getViolations()); - } + + getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, SAST.value()) + .forEach(sastResults::addPolicy); }catch (Exception ex) { log.error("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index ff3ba042..30a2c38d 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -11,6 +11,7 @@ import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; +import org.apache.commons.lang.StringUtils; import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; @@ -121,13 +122,32 @@ public ThresholdResult getThresholdResult() { } public boolean isPolicyViolated(StringBuilder failDescription) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaViolations().size() > 0; + boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaPolicies().size() > 0; if(isPolicyViolated) { failDescription.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); } return isPolicyViolated; } + public void printIsProjectViolated(){ + log.info("-----------------------------------------------------------------------------------------"); + if (config.getEnablePolicyViolations()) { + if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()){ + log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); + log.info("-----------------------------------------------------------------------------------------"); + }else { + log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); + if (!sastResults.getSastPolicies().isEmpty()) { + + log.info("SAST violated policies names: " + sastResults.getSastPoliciesNames()); + } + if (!osaResults.getOsaPolicies().isEmpty()) { + log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); + } + log.info("-----------------------------------------------------------------------------------------"); + } + } + } private CxArmConfig getCxARMConfig() throws IOException, CxClientException { return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 6ba09564..21af5e5f 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -32,6 +32,7 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu boolean buildFailed = false; boolean policyViolated = false; + int policyViolatedCount = 0; //sast: if (config.getSastEnabled() && sastResults.isSastResultsReady()) { boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); @@ -76,9 +77,7 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); templateData.put("osaThresholdExceeded", osaThresholdExceeded); buildFailed |= osaThresholdExceeded; - if (config.getEnablePolicyViolations() && !osaResults.getOsaViolations().isEmpty()){ - policyViolated = true; - } + //calculate osa bars: OSASummaryResults osaSummaryResults = osaResults.getResults(); int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); @@ -96,8 +95,21 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu templateData.put("osaLowTotalHeight", osaLowTotalHeight); } - String policyLabel = osaResults.getOsaPolicies().size() == 1? "Policy": "Policies"; - templateData.put("policyLabel", policyLabel); + + if (config.getEnablePolicyViolations()) { + if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { + policyViolated = true; + policyViolatedCount++; + } + if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { + policyViolated = true; + policyViolatedCount++; + } + String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; + templateData.put("policyLabel", policyLabel); + templateData.put("policyViolatedCount", policyViolatedCount); + } + templateData.put("policyViolated", policyViolated); buildFailed |= policyViolated; diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Policy.java b/src/main/java/com/cx/restclient/cxArm/dto/Policy.java index 3b73a1f0..2696232d 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Policy.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Policy.java @@ -13,8 +13,22 @@ public class Policy implements Serializable { long policyId; String policyName; + String ruleName; + String firstDetectionDate; List violations = new ArrayList(); + public Policy() { + } + + + public Policy(long policyId, String policyName, String ruleName, List violations, String firstDate) { + this.policyId = policyId; + this.policyName = policyName; + this.ruleName = ruleName; + this.violations = violations; + this.firstDetectionDate = firstDate; + } + public long getPolicyId() { return policyId; } @@ -36,11 +50,22 @@ public List getViolations() { } public void setViolations(List violations) { - //Add the policy name to the violations for the report - for(Violation violation: violations){ - violation.setPolicyName(policyName); - } - this.violations = violations; } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getFirstDetectionDate() { + return firstDetectionDate; + } + + public void setFirstDetectionDate(String firstDetectionDate) { + this.firstDetectionDate = firstDetectionDate; + } } diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Rule.java b/src/main/java/com/cx/restclient/cxArm/dto/Rule.java new file mode 100644 index 00000000..d0028a8c --- /dev/null +++ b/src/main/java/com/cx/restclient/cxArm/dto/Rule.java @@ -0,0 +1,19 @@ +package com.cx.restclient.cxArm.dto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Galn on 11/11/2018. + */ +public class Rule { + List violations = new ArrayList(); + + public List getViolations() { + return violations; + } + + public void setViolations(List violations) { + this.violations = violations; + } +} diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java index ced4e617..5d214660 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java @@ -47,9 +47,6 @@ public class Violation { private String policyName; - private String detectionDate; - - public String getRuleId() { return ruleId; } @@ -193,16 +190,4 @@ public String getPolicyName() { public void setPolicyName(String policyName) { this.policyName = policyName; } - - public String getDetectionDate() { - if (detectionDate == null){ - String date = new Date(firstDetectionDateByArm).toString(); - detectionDate = formatDate(date, "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); - } - return detectionDate; - } - - public void setDetectionDate(String detectionDate) { - this.detectionDate = detectionDate; - } } diff --git a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java index 569814f4..d8d91db7 100644 --- a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java +++ b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java @@ -1,21 +1,66 @@ package com.cx.restclient.cxArm.utils; import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.cxArm.dto.Violation; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import java.io.IOException; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static com.cx.restclient.common.CxPARAM.CX_ARM_VIOLATION; +import static com.cx.restclient.common.ShragaUtils.formatDate; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; /** * Created by Galn on 7/30/2018. */ public abstract class CxARMUtils { - public static List getProjectViolations(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { + public static List getProjectViolatedPolicies(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { String relativePath = CX_ARM_VIOLATION.replace("{projectId}", Long.toString(projectId)).replace("{provider}", provider); return (List) httpClient.getRequest(cxARMUrl, relativePath, CONTENT_TYPE_APPLICATION_JSON_V1, null, Policy.class, 200, "CxARM violations", true); } + + public static List getPolicyList(Policy policy) { + List policies = new ArrayList(); + Map> rules = resolveRules(policy.getViolations()); + Iterator it = rules.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + List violations = (List) pair.getValue(); + String firstDate = resolveFirstDate(violations); + policies.add(new Policy(policy.getPolicyId(), policy.getPolicyName(), pair.getKey().toString(), violations, firstDate)); + it.remove(); // avoids a ConcurrentModificationException + } + + return policies; + } + + private static Map> resolveRules(List violations) { + Map> rules = violations.stream().collect( + Collectors.toMap(Violation::getRuleName, e -> { + List ary = new ArrayList(); + ary.add(e); + return ary; + }, + (left, right) -> { + left.addAll(right); + return left; + })); + + return rules; + } + + private static String resolveFirstDate(List violations) { + Date firstDetectionDate = new Date(violations.get(0).getFirstDetectionDateByArm()); + for (Violation violation : violations) { + Date date = new Date(violation.getFirstDetectionDateByArm()); + if (date.before(firstDetectionDate)) { + firstDetectionDate = date; + } + } + String firstDate = formatDate(firstDetectionDate.toString(), "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); + return firstDate; + } } diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java index 05466be2..18934abc 100644 --- a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java +++ b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java @@ -1,6 +1,6 @@ package com.cx.restclient.osa.dto; -import com.cx.restclient.cxArm.dto.Violation; +import com.cx.restclient.cxArm.dto.Policy; import java.io.Serializable; import java.util.ArrayList; @@ -9,6 +9,7 @@ import java.util.Map; import static com.cx.restclient.common.ShragaUtils.formatDate; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; /** * Created by Galn on 07/02/2018. @@ -28,9 +29,7 @@ public class OSAResults implements Serializable { private String scanStartTime; private String scanEndTime; - private List osaPolicies = new ArrayList<>(); - private List osaViolations = new ArrayList<>(); - + private List osaPolicies = new ArrayList<>(); public OSAResults() { } @@ -182,23 +181,15 @@ public void setScanEndTime(String scanEndTime) { this.scanEndTime = scanEndTime; } - public List getOsaViolations() { - return osaViolations; - } - - public void setOsaViolations(List osaViolations) { - this.osaViolations = osaViolations; - } - - public void addAllViolations(List violations) { - this.osaViolations.addAll(violations); + public void addPolicy(Policy policy) { + this.osaPolicies.addAll(getPolicyList(policy)); } - public List getOsaPolicies() { + public List getOsaPolicies() { return osaPolicies; } - public void setOsaPolicies(List osaPolicies) { + public void setOsaPolicies(List osaPolicies) { this.osaPolicies = osaPolicies; } } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index e1962ab7..80801e74 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -130,14 +130,6 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info("Vulnerable and updated: " + osaSummaryResults.getVulnerableAndUpdated()); log.info("Non-vulnerable libraries: " + osaSummaryResults.getNonVulnerableLibraries()); log.info(""); - if (enableViolations) { - if (osaResults.getOsaPolicies().isEmpty()){ - log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); - }else{ - log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); - log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); - } - } log.info("OSA scan results location: " + osaResults.getOsaProjectSummaryLink()); log.info("-----------------------------------------------------------------------------------------"); } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 8b9fbdd1..6d64969a 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -9,6 +9,7 @@ import java.text.SimpleDateFormat; import java.util.*; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -48,8 +49,7 @@ public class SASTResults implements Serializable { private byte[] PDFReport; private String pdfFileName; - private List sastPolicies = new ArrayList<>(); - private List sastViolations = new ArrayList<>(); + private List sastPolicies = new ArrayList<>(); public enum Severity { @@ -101,6 +101,10 @@ public void setResults(long scanId, SASTStatisticsResponse statisticsResults, St setSastProjectLink(url, projectId); } + public void addPolicy(Policy policy) { + this.sastPolicies.addAll(getPolicyList(policy)); + } + public long getScanId() { return scanId; } @@ -205,6 +209,10 @@ public void setSastPDFLink(String sastPDFLink) { this.sastPDFLink = sastPDFLink; } + public void setSastPDFLink(String url, long projectId) { + this.sastPDFLink = String.format(url + PROJECT_LINK_FORMAT, projectId); + } + public String getScanStart() { return scanStart; } @@ -348,27 +356,25 @@ private Date createTimeDate(String scanTime) throws ParseException { } private Date createEndDate(Date scanStartDate, Date scanTimeDate) { - long time /*no c*/ = scanStartDate.getTime() + scanTimeDate.getTime(); + long time = scanStartDate.getTime() + scanTimeDate.getTime(); return new Date(time); } - public List getSastViolations() { - return sastViolations; - } - - public void setSastViolations(List sastViolations) { - this.sastViolations = sastViolations; - } - - public void addAllViolations(List violations) { - this.sastViolations.addAll(violations); + public List getSastPolicies() { + return sastPolicies; } - public List getSastPolicies() { - return sastPolicies; + public String getSastPoliciesNames() { + String str =""; + for (Policy policy : sastPolicies){ + str += "," + policy.getPolicyName(); + } + str = str.substring(1, str.length()); + return str; } - public void setSastPolicies(List sastPolicies) { + public void setSastPolicies(List sastPolicies) { this.sastPolicies = sastPolicies; } + } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index eb4103b7..9c910216 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -32,6 +32,7 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; + public static final String PDF_LINK_FORMAT = "todo"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 403541d8..7eb55c5c 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -62,22 +62,18 @@ public static void printSASTResultsToConsole(SASTResults sastResults,boolean ena log.info("Low severity results: " + sastResults.getLow() + lowNew); log.info("Information severity results: " + sastResults.getInformation() + infoNew); log.info(""); - /* if (enableViolations && !sastResults.getSastPolicies().isEmpty()) { - log.info("SAST violated policies names: " + StringUtils.join(sastResults.getSastPolicies(), ',')); - }*/ log.info("Scan results location: " + sastResults.getSastScanLink()); log.info("------------------------------------------------------------------------------------------\n"); } //PDF Report - public static String writePDFReport(byte[] scanReport, File workspace, Logger log) { - String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); - String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; + public static String writePDFReport(byte[] scanReport, File workspace, String pdfFileName, Logger log) { try { FileUtils.writeByteArrayToFile(new File(workspace + CX_REPORT_LOCATION, pdfFileName), scanReport); log.info("PDF report location: " + workspace + CX_REPORT_LOCATION + File.separator + pdfFileName); } catch (Exception e) { log.error("Failed to write PDF report to workspace: ", e.getMessage()); + pdfFileName =""; } return pdfFileName; } diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index 28f93325..d500f791 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -34,7 +34,7 @@ background-color: #372F51; } - .cx-report .legend-color-box.new-legend-color { + .cx-report .legend-color-box.new-legend-color { background: linear-gradient(45deg, white 25%, #373050 25%, #373050 50%, white 50%, white 75%, #373050 75%); background-size: 4px 4px; } @@ -371,10 +371,9 @@ background-color: #373050; } - - .threshold-exceeded, - .threshold-compliance, - .policy-compliance{ + .threshold-exceeded, + .threshold-compliance, + .policy-compliance { min-width: 100%; display: inline-flex; font-size: 14px; @@ -383,21 +382,21 @@ padding: 4px 9px; } - .threshold-exceeded { + .threshold-exceeded { background-color: #DA2945; color: white; border-radius: 2px; font-weight: bold; } - .policy-compliance{ + + .policy-compliance { border-radius: 2px; font-weight: bold; } - .threshold-exceeded-icon, .threshold-compliance-icon, - .policy-compliance{ + .policy-compliance { display: inline-flex; padding-right: 6px; margin: auto 0; @@ -426,11 +425,10 @@ overflow: hidden; } - .cx-report .results-report .libraries-vulnerable-number{ + .cx-report .results-report .libraries-vulnerable-number { font-size: 18px; } - .cx-report .results-report .libraries-vulnerable-text { font-size: 12px; line-height: 15px; @@ -627,6 +625,7 @@ .cx-report .html-report.download-icon { margin-right: 6px; } + .cx-report .pdf-report.download-icon, { margin-right: 6px; border-left: solid 1px #d5d5d @@ -808,6 +807,7 @@ margin-top: -3px; margin-left: -7px; } + .scan-status .content-scan-status li { margin-top: 6px; margin-bottom: 6px; @@ -826,7 +826,6 @@ padding-top: 10px; } - .scan-status .indicator-scan-status.success { background-color: #38d87d; } @@ -839,7 +838,7 @@ margin-top: 5px; text-align: center; border: 1px solid #ffffff; - padding-left: 5px ; + padding-left: 5px; } .scan-status .indicator-scan-status.success .icon-scan-status { @@ -850,9 +849,6 @@ border: 1px solid #DD3D56; } - - - .scan-status .title-scan-status { font-size: 12px; font-weight: bold; @@ -909,91 +905,96 @@ -
    Checkmarx Report
    - <#if buildFailed> -
    -
    -
    - - - error - Created with Sketch. - - - - - - - - - - - - - - - -
    -
    -
    -

    - Checkmarx scan found the following issues: -

    -
      - <#if policyViolated> -
    • ${osa.osaPolicies?size} ${policyLabel} Violated
    • - + <#if buildFailed> +
      +
      +
      + + + error + Created with Sketch. + + + + + + + + + + + + + + + +
      +
      +
      +

      + Checkmarx scan found the following issues: +

      +
        + <#if policyViolated> +
      • ${policyViolatedCount} ${policyLabel} Violated
      • + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded>
      • Exceeded CxSAST and CxOSA Vulnerability Thresholds
      • - <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> -
      • Exceeded CxSAST Vulnerability Threshold
      • - <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> -
      • Exceeded CxOSA Vulnerability Threshold
      • - -
      -
      -
      - <#else> -
      -
      -
      - - - OK - Created with Sketch. - - - - - - - - - - - - - - - - - - -
      -
      -
      -

      - Checkmarx Scan Passed -

      -
      -
      - - + <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> +
    • Exceeded CxSAST Vulnerability Threshold
    • + <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> +
    • Exceeded CxOSA Vulnerability Threshold
    • + +
    +
    +
    + <#else> +
    +
    +
    + + + OK + Created with Sketch. + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    + Checkmarx Scan Passed +

    +
    +
    + +
    @@ -1013,7 +1014,7 @@ - - <#if sast.sastViolations?size gt 0> -
    -
    -
    - - - Policy violation - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    Policy Violations
    -
    ${sast.sastViolations?size}
    -
    - -
    -
@@ -2184,7 +2125,8 @@
Libraries:
-
${osa.results.totalLibraries}
+
${osa.results.totalLibraries}
@@ -2358,72 +2300,137 @@ - <#if osa.osaViolations?size gt 0> -
-
-
- - - Policy violation - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+
+ + + + <#if (config.osaEnabled || config.sastEnabled) && policyViolated> + + <#if policyViolatedCount gt 0> +
+ +
+
+
+
+ + + Policy violation + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Policy Violations
+
${policyViolatedCount}
+
+ + + + + + + + + <#if sast.sastPolicies?size gt 0> + <#list sast.sastPolicies as sastPoliciy> + + + + + + + + <#if osa.osaPolicies?size gt 0> + <#list osa.osaPolicies as osaPolicy> + + + + + + + +
PolicyRuleType# of Rule ViolationsFirst Detection Date
${sastPoliciy.policyName}${sastPoliciy.ruleName}SAST${sastPoliciy.violations?size}${sastPoliciy.firstDetectionDate}${osaPolicy.policyName}${osaPolicy.ruleName}OSA${osaPolicy.violations?size}${osaPolicy.firstDetectionDate}
+
-
+
+ + + + + + From bbde1a5967f5076071b0feaea6598351b3c59560 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 18 Nov 2018 16:12:27 +0200 Subject: [PATCH 016/473] change the "getBuildFailure" method --- .../com/cx/restclient/CxShragaClient.java | 10 ++++--- .../cx/restclient/dto/ThresholdResult.java | 30 ------------------- 2 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 src/main/java/com/cx/restclient/dto/ThresholdResult.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 30a2c38d..39fa3de1 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -5,7 +5,6 @@ import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; import com.cx.restclient.dto.Team; -import com.cx.restclient.dto.ThresholdResult; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -115,10 +114,13 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc return osaResults; } - public ThresholdResult getThresholdResult() { + public String getBuildFailureResult() { StringBuilder res = new StringBuilder(""); - boolean isFail = isThresholdExceeded(config, sastResults, osaResults, res); - return new ThresholdResult(isFail, res.toString()); + isThresholdExceeded(config, sastResults, osaResults, res); + if (config.getEnablePolicyViolations()){ + isPolicyViolated(res); + } + return res.toString(); } public boolean isPolicyViolated(StringBuilder failDescription) { diff --git a/src/main/java/com/cx/restclient/dto/ThresholdResult.java b/src/main/java/com/cx/restclient/dto/ThresholdResult.java deleted file mode 100644 index 35671989..00000000 --- a/src/main/java/com/cx/restclient/dto/ThresholdResult.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cx.restclient.dto; - -/** - * Created by Galn on 4/10/2018. - */ -public class ThresholdResult { - private boolean isFail; - private String failDescription; - - public ThresholdResult(boolean isFail, String failDescription) { - this.isFail = isFail; - this.failDescription = failDescription; - } - - public boolean isFail() { - return isFail; - } - - public void setFail(boolean fail) { - isFail = fail; - } - - public String getFailDescription() { - return failDescription; - } - - public void setFailDescription(String failDescription) { - this.failDescription = failDescription; - } -} From bf073bdbae9d8248c960692f34cffd1b9dadd9a1 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 18 Nov 2018 16:48:55 +0200 Subject: [PATCH 017/473] Change CI Plugins report top-bar status behavior --- .../common/summary/SummaryUtils.java | 156 +++++++++--------- src/main/resources/com/cx/report/report.ftl | 17 +- 2 files changed, 89 insertions(+), 84 deletions(-) diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 21af5e5f..45611afc 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -34,92 +34,100 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu boolean policyViolated = false; int policyViolatedCount = 0; //sast: - if (config.getSastEnabled() && sastResults.isSastResultsReady()) { - boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); - boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); - templateData.put("sastThresholdExceeded", sastThresholdExceeded); - templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); - buildFailed = sastThresholdExceeded || sastNewResultsExceeded; - //calculate sast bars: - float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); - float sastBarNorm = maxCount * 10f / 9f; - - //sast high bars - float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; - float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); - float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; - templateData.put("sastHighTotalHeight", sastHighTotalHeight); - templateData.put("sastHighNewHeight", sastHighNewHeight); - templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); + if (config.getSastEnabled()) { + if (sastResults.isSastResultsReady()) { + boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); + boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); + templateData.put("sastThresholdExceeded", sastThresholdExceeded); + templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); + buildFailed = sastThresholdExceeded || sastNewResultsExceeded; + //calculate sast bars: + float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); + float sastBarNorm = maxCount * 10f / 9f; + + //sast high bars + float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; + float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); + float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; + templateData.put("sastHighTotalHeight", sastHighTotalHeight); + templateData.put("sastHighNewHeight", sastHighNewHeight); + templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); /*if (config.getEnablePolicyViolations() && !sastResults.getSastViolations().isEmpty()){ policyViolated = true; }*/ - //sast medium bars - float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; - float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); - float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; - templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); - templateData.put("sastMediumNewHeight", sastMediumNewHeight); - templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); - - //sast low bars - float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; - float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); - float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; - templateData.put("sastLowTotalHeight", sastLowTotalHeight); - templateData.put("sastLowNewHeight", sastLowNewHeight); - templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); + //sast medium bars + float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; + float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); + float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; + templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); + templateData.put("sastMediumNewHeight", sastMediumNewHeight); + templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); + + //sast low bars + float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; + float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); + float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; + templateData.put("sastLowTotalHeight", sastLowTotalHeight); + templateData.put("sastLowNewHeight", sastLowNewHeight); + templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); + } else { + buildFailed = true; + } } - //osa: - if (config.getOsaEnabled() && osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); - templateData.put("osaThresholdExceeded", osaThresholdExceeded); - buildFailed |= osaThresholdExceeded; - - //calculate osa bars: - OSASummaryResults osaSummaryResults = osaResults.getResults(); - int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); - int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); - int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); - float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); - float osaBarNorm = osaMaxCount * 10f / 9f; - - float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; - float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; - float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; - - templateData.put("osaHighTotalHeight", osaHighTotalHeight); - templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); - templateData.put("osaLowTotalHeight", osaLowTotalHeight); - } + //osa: + if (config.getOsaEnabled()) { + if (osaResults.isOsaResultsReady()) { + boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); + templateData.put("osaThresholdExceeded", osaThresholdExceeded); + buildFailed |= osaThresholdExceeded; + + //calculate osa bars: + OSASummaryResults osaSummaryResults = osaResults.getResults(); + int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); + int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); + int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); + float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); + float osaBarNorm = osaMaxCount * 10f / 9f; + + float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; + float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; + float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; + + templateData.put("osaHighTotalHeight", osaHighTotalHeight); + templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); + templateData.put("osaLowTotalHeight", osaLowTotalHeight); + } else { + buildFailed = true; + } + } - if (config.getEnablePolicyViolations()) { - if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { - policyViolated = true; - policyViolatedCount++; + if (config.getEnablePolicyViolations()) { + if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { + policyViolated = true; + policyViolatedCount++; + } + if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { + policyViolated = true; + policyViolatedCount++; + } + String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; + templateData.put("policyLabel", policyLabel); + templateData.put("policyViolatedCount", policyViolatedCount); } - if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { - policyViolated = true; - policyViolatedCount++; - } - String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; - templateData.put("policyLabel", policyLabel); - templateData.put("policyViolatedCount", policyViolatedCount); - } - templateData.put("policyViolated", policyViolated); - buildFailed |= policyViolated; - templateData.put("buildFailed", buildFailed); + templateData.put("policyViolated", policyViolated); + buildFailed |= policyViolated; + templateData.put("buildFailed", buildFailed); - //generate the report: - StringWriter writer = new StringWriter(); - template.process(templateData, writer); - return writer.toString(); - } + //generate the report: + StringWriter writer = new StringWriter(); + template.process(templateData, writer); + return writer.toString(); + } private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { int minimalVisibilityHeight = 5; diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index d500f791..a0f33183 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -946,6 +946,12 @@ <#if policyViolated>
  • ${policyViolatedCount} ${policyLabel} Violated
  • + <#if config.sastEnabled && !sast.sastResultsReady> +
  • SAST Scan Failed
  • + + <#if config.osaEnabled && !osa.osaResultsReady> +
  • OSA Scan Failed
  • + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded>
  • Exceeded CxSAST and CxOSA Vulnerability Thresholds
  • <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> @@ -1390,15 +1396,6 @@ No Known Vulnerability Libraries - -
    -
    -
    ${osa.osaViolations?size }
    -
    -
    - Policy Violated Libraries -
    -
    @@ -1966,7 +1963,7 @@ <#if config.osaEnabled && osa.osaResultsReady> - <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0 || osa.osaViolations?size gt 0> + <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0>
    From 078077f382e1c0c599a1793a823ce20d4327c082 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 18 Nov 2018 18:32:42 +0200 Subject: [PATCH 018/473] policy violation- unified report last fixes --- .../com/cx/restclient/CxShragaClient.java | 8 +++-- .../common/summary/SummaryUtils.java | 4 +-- .../cx/restclient/cxArm/utils/CxARMUtils.java | 9 ++++++ .../cx/restclient/sast/dto/SASTResults.java | 9 ------ src/main/resources/com/cx/report/report.ftl | 31 +++++++++++-------- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 39fa3de1..30b31165 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -23,6 +23,7 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.common.ShragaUtils.isThresholdExceeded; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -133,6 +134,8 @@ public boolean isPolicyViolated(StringBuilder failDescription) { public void printIsProjectViolated(){ log.info("-----------------------------------------------------------------------------------------"); + log.info("Policy Management: "); + log.info("--------------------"); if (config.getEnablePolicyViolations()) { if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()){ log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); @@ -140,11 +143,10 @@ public void printIsProjectViolated(){ }else { log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); if (!sastResults.getSastPolicies().isEmpty()) { - - log.info("SAST violated policies names: " + sastResults.getSastPoliciesNames()); + log.info("SAST violated policies names: " + getPoliciesNames(sastResults.getSastPolicies())); } if (!osaResults.getOsaPolicies().isEmpty()) { - log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); + log.info("OSA violated policies names: " + getPoliciesNames(osaResults.getOsaPolicies())); } log.info("-----------------------------------------------------------------------------------------"); } diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 45611afc..ddb7710d 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -107,11 +107,11 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu if (config.getEnablePolicyViolations()) { if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { policyViolated = true; - policyViolatedCount++; + policyViolatedCount += sastResults.getSastPolicies().size(); } if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { policyViolated = true; - policyViolatedCount++; + policyViolatedCount += osaResults.getOsaPolicies().size(); } String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; templateData.put("policyLabel", policyLabel); diff --git a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java index d8d91db7..ebab3f83 100644 --- a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java +++ b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java @@ -63,4 +63,13 @@ private static String resolveFirstDate(List violations) { String firstDate = formatDate(firstDetectionDate.toString(), "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); return firstDate; } + + public static String getPoliciesNames(List policies) { + String str =""; + for (Policy policy : policies){ + str += "," + policy.getPolicyName(); + } + str = str.substring(1, str.length()); + return str; + } } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 6d64969a..c9f85554 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -364,15 +364,6 @@ public List getSastPolicies() { return sastPolicies; } - public String getSastPoliciesNames() { - String str =""; - for (Policy policy : sastPolicies){ - str += "," + policy.getPolicyName(); - } - str = str.substring(1, str.length()); - return str; - } - public void setSastPolicies(List sastPolicies) { this.sastPolicies = sastPolicies; } diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index a0f33183..89d3dfe7 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -853,6 +853,7 @@ font-size: 12px; font-weight: bold; padding-left: 16px; + padding-top: 10px; } .scan-status .content-scan-status .title-scan-status.failure { @@ -943,15 +944,15 @@ Checkmarx scan found the following issues:

      - <#if policyViolated> -
    • ${policyViolatedCount} ${policyLabel} Violated
    • - <#if config.sastEnabled && !sast.sastResultsReady>
    • SAST Scan Failed
    • <#if config.osaEnabled && !osa.osaResultsReady>
    • OSA Scan Failed
    • + <#if policyViolated> +
    • ${policyViolatedCount} ${policyLabel} Violated
    • + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded>
    • Exceeded CxSAST and CxOSA Vulnerability Thresholds
    • <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> @@ -2400,20 +2401,24 @@ <#if sast.sastPolicies?size gt 0> <#list sast.sastPolicies as sastPoliciy> - ${sastPoliciy.policyName} - ${sastPoliciy.ruleName} - SAST - ${sastPoliciy.violations?size} - ${sastPoliciy.firstDetectionDate} + + ${sastPoliciy.policyName} + ${sastPoliciy.ruleName} + SAST + ${sastPoliciy.violations?size} + ${sastPoliciy.firstDetectionDate} + <#if osa.osaPolicies?size gt 0> <#list osa.osaPolicies as osaPolicy> - ${osaPolicy.policyName} - ${osaPolicy.ruleName} - OSA - ${osaPolicy.violations?size} - ${osaPolicy.firstDetectionDate} + + ${osaPolicy.policyName} + ${osaPolicy.ruleName} + OSA + ${osaPolicy.violations?size} + ${osaPolicy.firstDetectionDate} + From c5ef79c9851a106504dc4838b0d2c6ecc81ca5d2 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Mon, 19 Nov 2018 16:26:03 +0200 Subject: [PATCH 019/473] fix the getBuildFailureResult method --- src/main/java/com/cx/restclient/CxShragaClient.java | 7 ++++--- .../cx/restclient/common/summary/SummaryUtils.java | 13 +++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 30b31165..f9ade007 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -23,6 +23,7 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.common.ShragaUtils.isThresholdExceeded; +import static com.cx.restclient.common.ShragaUtils.isThresholdForNewResultExceeded; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; @@ -118,9 +119,9 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc public String getBuildFailureResult() { StringBuilder res = new StringBuilder(""); isThresholdExceeded(config, sastResults, osaResults, res); - if (config.getEnablePolicyViolations()){ - isPolicyViolated(res); - } + isThresholdForNewResultExceeded(config, sastResults, res); + isPolicyViolated(res); + return res.toString(); } diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index ddb7710d..c2ea71ab 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -2,6 +2,7 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; import com.cx.restclient.sast.dto.SASTResults; @@ -14,6 +15,7 @@ import java.io.StringWriter; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; public abstract class SummaryUtils { @@ -105,14 +107,21 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu if (config.getEnablePolicyViolations()) { + Map policies = new HashMap(); + if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { policyViolated = true; - policyViolatedCount += sastResults.getSastPolicies().size(); + policies = sastResults.getSastPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName,Policy::getRuleName)); } if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { policyViolated = true; - policyViolatedCount += osaResults.getOsaPolicies().size(); + policies = osaResults.getOsaPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName,Policy::getRuleName)); } + + + policyViolatedCount = policies.size(); String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; templateData.put("policyLabel", policyLabel); templateData.put("policyViolatedCount", policyViolatedCount); From ef6d4048a52adfa2856476854cc3a8b501175ef6 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Mon, 19 Nov 2018 16:33:03 +0200 Subject: [PATCH 020/473] fix the getBuildFailureResult method --- .../java/com/cx/restclient/CxShragaClient.java | 17 ----------------- .../com/cx/restclient/common/ShragaUtils.java | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index f9ade007..35f5d8f0 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -116,23 +116,6 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc return osaResults; } - public String getBuildFailureResult() { - StringBuilder res = new StringBuilder(""); - isThresholdExceeded(config, sastResults, osaResults, res); - isThresholdForNewResultExceeded(config, sastResults, res); - isPolicyViolated(res); - - return res.toString(); - } - - public boolean isPolicyViolated(StringBuilder failDescription) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaPolicies().size() > 0; - if(isPolicyViolated) { - failDescription.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); - } - return isPolicyViolated; - } - public void printIsProjectViolated(){ log.info("-----------------------------------------------------------------------------------------"); log.info("Policy Management: "); diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index a3a37e9c..e2c901de 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -18,6 +18,23 @@ */ public abstract class ShragaUtils { //Util methods + public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, OSAResults osaResults) { + StringBuilder res = new StringBuilder(""); + isThresholdExceeded(config, sastResults, osaResults, res); + isThresholdForNewResultExceeded(config, sastResults, res); + isPolicyViolated(config, sastResults, osaResults, res); + + return res.toString(); + } + + public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { + boolean isPolicyViolated = config.getEnablePolicyViolations() && (osaResults.getOsaPolicies().size() > 0 || sastResults.getSastPolicies().size() > 0); + if(isPolicyViolated) { + res.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); + } + return isPolicyViolated; + } + public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { boolean thresholdExceeded = false; if (config.isSASTThresholdEffectivelyEnabled() && sastResults != null && sastResults.isSastResultsReady()) { From f81afeddb1bcfb1468beffd8a769c24118c7987c Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Wed, 21 Nov 2018 17:04:26 +0200 Subject: [PATCH 021/473] Unified policy report - add waiter to sast violation --- .../java/com/cx/restclient/CxOSAClient.java | 10 +- .../java/com/cx/restclient/CxSASTClient.java | 71 ++++++++++-- .../com/cx/restclient/CxShragaClient.java | 3 - .../com/cx/restclient/common/ShragaUtils.java | 2 +- .../java/com/cx/restclient/common/Waiter.java | 50 +++++---- .../common/summary/SummaryUtils.java | 106 +++++++++--------- .../cx/restclient/cxArm/utils/CxARMUtils.java | 2 +- .../cx/restclient/sast/dto/CxARMStatus.java | 48 ++++++++ .../restclient/sast/dto/CxARMStatusEnum.java | 25 +++++ .../cx/restclient/sast/utils/SASTParam.java | 2 + src/main/resources/com/cx/report/report.ftl | 2 +- 11 files changed, 227 insertions(+), 94 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java create mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 0d1b6763..4ad61d81 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -85,14 +85,14 @@ private String resolveOSADependencies() throws JsonProcessingException { } ObjectMapper mapper = new ObjectMapper(); log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); - log.info("#########################################"); + log.info("############################################"); log.info(" WhiteSource- Starting FSA component scan:"); - log.info("#########################################"); + log.info("############################################"); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); - log.info("##########################################"); + log.info("#############################################"); log.info(" WhiteSource- FSA component scan ended."); - log.info("##########################################"); + log.info("#############################################"); OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); return osaDependenciesJson; @@ -217,7 +217,7 @@ private void printOSAProgress(OSAScanStatus scanStatus, long startTime) { } private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClientException { - if (Status.FAILED == scanStatus.getBaseStatus()) { + if (scanStatus ==null || Status.FAILED == scanStatus.getBaseStatus()) { String failedMsg = scanStatus.getState() == null ? "" : "status [" + scanStatus.getState().getName() + "]. Reason: " + scanStatus.getState().getFailureReason(); throw new CxClientException("OSA scan cannot be completed. " + failedMsg); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 4841b9dd..91186fb3 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -42,6 +42,7 @@ class CxSASTClient { private CxHttpClient httpClient; private CxScanConfig config; private int reportTimeoutSec = 500; + private int cxARMTimeoutSec = 500; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { @@ -76,6 +77,23 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; + private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 5) { + @Override + public CxARMStatus getStatus(String id) throws CxClientException, IOException { + return getCxARMStatus(id); + } + + @Override + public void printProgress(CxARMStatus cxARMStatus) { + printCxARMProgress(cxARMStatus, getStartTimeSec()); + } + + @Override + public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientException { + return resolveCxARMStatus(cxARMStatus); + } + }; + CxSASTClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; this.httpClient = client; @@ -88,7 +106,7 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce long createSASTScan(long projectId) throws IOException, CxClientException { log.info("-----------------------------------Create CxSAST Scan:------------------------------------"); - if (config.isAvoidDuplicateProjectScans()!= null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { + if (config.isAvoidDuplicateProjectScans() != null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { throw new CxClientException("\nAvoid duplicate project scans in queue\n"); } @@ -136,7 +154,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr //retrieve SAST scan results sastResults = retrieveSASTResults(scanId, projectId); - if (config.getEnablePolicyViolations()) { + if (config.getEnablePolicyViolations()) { resolveSASTViolation(sastResults, projectId); } SASTUtils.printSASTResultsToConsole(sastResults, config.getEnablePolicyViolations(), log); @@ -152,7 +170,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); if (!StringUtils.isEmpty(config.getCxOrigin()) && config.getCxOrigin().equalsIgnoreCase("jenkins")) { - // sastResults.setSastPDFLink(); + // sastResults.setSastPDFLink(); } } } @@ -161,10 +179,10 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr private void resolveSASTViolation(SASTResults sastResults, long projectId) { try { - + cxARMWaiter.waitForTaskToFinish(Long.toString(projectId), cxARMTimeoutSec, log); getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, SAST.value()) .forEach(sastResults::addPolicy); - }catch (Exception ex) { + } catch (Exception ex) { log.error("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); } } @@ -216,7 +234,7 @@ private boolean projectHasQueuedScans(long projectId) throws IOException, CxClie } private boolean isStatusToAvoid(String status) { - QueueStatus qStatus = QueueStatus.valueOf(status); + QueueStatus qStatus = QueueStatus.valueOf(status); switch (qStatus) { case New: @@ -315,7 +333,7 @@ private void printSASTProgress(ResponseQueueScanStatus scanStatus, long startTim } private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { - if (Status.SUCCEEDED == scanStatus.getBaseStatus()) { + if (scanStatus != null && Status.SUCCEEDED == scanStatus.getBaseStatus()) { log.info("SAST scan finished successfully."); return scanStatus; } else { @@ -326,14 +344,14 @@ private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanSt //Report Waiter - overload methods private ReportStatus getReportStatus(String reportId) throws CxClientException, IOException { ReportStatus reportStatus = httpClient.getRequest(SAST_GET_REPORT_STATUS.replace("{reportId}", reportId), CONTENT_TYPE_APPLICATION_JSON_V1, ReportStatus.class, 200, " report status", false); - + reportStatus.setBaseId(reportId); String currentStatus = reportStatus.getStatus().getValue(); if (currentStatus.equals(ReportStatusEnum.INPROCESS.value())) { reportStatus.setBaseStatus(Status.IN_PROGRESS); } else if (currentStatus.equals(ReportStatusEnum.FAILED.value())) { reportStatus.setBaseStatus(Status.FAILED); } else { - reportStatus.setBaseStatus(Status.SUCCEEDED); + reportStatus.setBaseStatus(Status.SUCCEEDED); //todo fix it!! } return reportStatus; @@ -345,10 +363,43 @@ private void printReportProgress(ReportStatus reportStatus, long startTime) { } private ReportStatus resolveReportStatus(ReportStatus reportStatus) throws CxClientException { - if (Status.SUCCEEDED == reportStatus.getBaseStatus()) { + if (reportStatus != null && Status.SUCCEEDED == reportStatus.getBaseStatus()) { return reportStatus; } else { throw new CxClientException("Generation of scan report [id=" + reportStatus.getBaseId() + "] failed."); } } + + + //CxARM Waiter - overload methods + private CxARMStatus getCxARMStatus(String projectId) throws CxClientException, IOException { + CxARMStatus cxARMStatus = httpClient.getRequest(SAST_GET_CXARM_STATUS.replace("{projectId}", projectId), CONTENT_TYPE_APPLICATION_JSON_V1, CxARMStatus.class, 200, " cxARM status", false); + cxARMStatus.setBaseId(projectId); + + String currentStatus = cxARMStatus.getStatus(); + if (currentStatus.equals(CxARMStatusEnum.IN_PROGRESS.value())) { + cxARMStatus.setBaseStatus(Status.IN_PROGRESS); + } else if (currentStatus.equals(CxARMStatusEnum.FAILED.value())) { + cxARMStatus.setBaseStatus(Status.FAILED); + } else if (currentStatus.equals(CxARMStatusEnum.FINISHED.value())) { + cxARMStatus.setBaseStatus(Status.SUCCEEDED); + } else { + cxARMStatus.setBaseStatus(Status.FAILED); + } + + return cxARMStatus; + } + + private void printCxARMProgress(CxARMStatus cxARMStatus, long startTime) { + log.info("Waiting for server to get Policy violations. " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran + } + + private CxARMStatus resolveCxARMStatus(CxARMStatus cxARMStatus) throws CxClientException { + if (cxARMStatus != null && Status.SUCCEEDED == cxARMStatus.getBaseStatus()) { + return cxARMStatus; + } else { + throw new CxClientException("Getting policy violations of project [id=" + cxARMStatus.getBaseId() + "] failed."); //todo Liran + } + } + } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 35f5d8f0..ee8aadf1 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -10,7 +10,6 @@ import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; -import org.apache.commons.lang.StringUtils; import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; @@ -22,8 +21,6 @@ import java.util.Properties; import static com.cx.restclient.common.CxPARAM.*; -import static com.cx.restclient.common.ShragaUtils.isThresholdExceeded; -import static com.cx.restclient.common.ShragaUtils.isThresholdForNewResultExceeded; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index e2c901de..6dd0df4b 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -28,7 +28,7 @@ public static String getBuildFailureResult(CxScanConfig config, SASTResults sast } public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && (osaResults.getOsaPolicies().size() > 0 || sastResults.getSastPolicies().size() > 0); + boolean isPolicyViolated = config.getEnablePolicyViolations() && ((osaResults!=null && osaResults.getOsaPolicies().size() > 0) || (sastResults != null && sastResults.getSastPolicies().size() > 0)); if(isPolicyViolated) { res.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); } diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index e852695f..648c8502 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -13,7 +13,7 @@ */ public abstract class Waiter { - private int retry = 5; + private int retry = 3; private String scanType; private int sleepIntervalSec; @@ -29,29 +29,35 @@ public Waiter(String scanType, int interval) { public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { startTimeSec = System.currentTimeMillis() / 1000; long elapsedTimeSec = 0L; - status = Status.IN_PROGRESS; - T obj = null; - - while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { - Thread.sleep(sleepIntervalSec * 1000); - try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - } catch (Exception e) { - log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); - if (retry <= 0) { - throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + T obj; + + try { + obj = getStatus(taskId); + status = ((BaseStatus) obj).getBaseStatus(); + + while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { + Thread.sleep(sleepIntervalSec * 1000); + try { + obj = getStatus(taskId); + status = ((BaseStatus) obj).getBaseStatus(); + } catch (Exception e) { + log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); + retry--; + if (retry <= 0) { + throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + } + continue; } - retry--; - continue; - } - elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; - printProgress(obj); + elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; + printProgress(obj); - } - if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { - throw new CxClientException( "Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); - } + } + if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { + throw new CxClientException("Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); + } + } catch (Exception e) { + throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + } return resolveStatus(obj); } diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index c2ea71ab..d1837409 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -78,65 +78,69 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu } } - //osa: - if (config.getOsaEnabled()) { - if (osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); - templateData.put("osaThresholdExceeded", osaThresholdExceeded); - buildFailed |= osaThresholdExceeded; - - //calculate osa bars: - OSASummaryResults osaSummaryResults = osaResults.getResults(); - int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); - int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); - int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); - float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); - float osaBarNorm = osaMaxCount * 10f / 9f; - - float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; - float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; - float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; - - templateData.put("osaHighTotalHeight", osaHighTotalHeight); - templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); - templateData.put("osaLowTotalHeight", osaLowTotalHeight); - } else { - buildFailed = true; - } + //osa: + if (config.getOsaEnabled()) { + if (osaResults.isOsaResultsReady()) { + boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); + templateData.put("osaThresholdExceeded", osaThresholdExceeded); + buildFailed |= osaThresholdExceeded; + + //calculate osa bars: + OSASummaryResults osaSummaryResults = osaResults.getResults(); + int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); + int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); + int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); + float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); + float osaBarNorm = osaMaxCount * 10f / 9f; + + float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; + float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; + float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; + + templateData.put("osaHighTotalHeight", osaHighTotalHeight); + templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); + templateData.put("osaLowTotalHeight", osaLowTotalHeight); + } else { + buildFailed = true; } + } - if (config.getEnablePolicyViolations()) { - Map policies = new HashMap(); - - if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { - policyViolated = true; - policies = sastResults.getSastPolicies().stream().collect( - Collectors.toMap(Policy::getPolicyName,Policy::getRuleName)); - } - if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { - policyViolated = true; - policies = osaResults.getOsaPolicies().stream().collect( - Collectors.toMap(Policy::getPolicyName,Policy::getRuleName)); - } - + if (config.getEnablePolicyViolations()) { + Map policies = new HashMap(); - policyViolatedCount = policies.size(); - String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; - templateData.put("policyLabel", policyLabel); - templateData.put("policyViolatedCount", policyViolatedCount); + if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { + policyViolated = true; + policies = sastResults.getSastPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName, + Policy::getRuleName, + (left, right) -> { + return left; + } + )); } + if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { + policyViolated = true; + policies = osaResults.getOsaPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName, Policy::getRuleName)); + } + + policyViolatedCount = policies.size(); + String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; + templateData.put("policyLabel", policyLabel); + templateData.put("policyViolatedCount", policyViolatedCount); + } - templateData.put("policyViolated", policyViolated); - buildFailed |= policyViolated; - templateData.put("buildFailed", buildFailed); + templateData.put("policyViolated", policyViolated); + buildFailed |= policyViolated; + templateData.put("buildFailed", buildFailed); - //generate the report: - StringWriter writer = new StringWriter(); - template.process(templateData, writer); - return writer.toString(); - } + //generate the report: + StringWriter writer = new StringWriter(); + template.process(templateData, writer); + return writer.toString(); + } private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { int minimalVisibilityHeight = 5; diff --git a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java index ebab3f83..fa416ba4 100644 --- a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java +++ b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java @@ -67,7 +67,7 @@ private static String resolveFirstDate(List violations) { public static String getPoliciesNames(List policies) { String str =""; for (Policy policy : policies){ - str += "," + policy.getPolicyName(); + str += ", " + policy.getPolicyName(); } str = str.substring(1, str.length()); return str; diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java new file mode 100644 index 00000000..691ee0d3 --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java @@ -0,0 +1,48 @@ +package com.cx.restclient.sast.dto; + + +import com.cx.restclient.dto.BaseStatus; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by Galn on 07/03/2018. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CxARMStatus extends BaseStatus { + private CxID project; + private CxID scan; + String status; + String lastSync; + + public CxID getProject() { + return project; + } + + public void setProject(CxID project) { + this.project = project; + } + + public CxID getScan() { + return scan; + } + + public void setScan(CxID scan) { + this.scan = scan; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getLastSync() { + return lastSync; + } + + public void setLastSync(String lastSync) { + this.lastSync = lastSync; + } +} diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java new file mode 100644 index 00000000..caf256aa --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java @@ -0,0 +1,25 @@ +package com.cx.restclient.sast.dto; + +/** + * Created by Galn on 07/03/2018. + */ +public enum CxARMStatusEnum { + + IN_PROGRESS("InProgress"), + FINISHED("Finished"), + FAILED("Failed"), + NONE("None"); + + private final String value; + + CxARMStatusEnum(String value) { + this.value = value; + } + + public String value() { + return this.value; + } + + +} + diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 9c910216..44d275f5 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -23,6 +23,8 @@ public class SASTParam { public static final String SAST_CREATE_REPORT = "reports/sastScan/"; //Create new report (get ID) public static final String SAST_GET_REPORT_STATUS = "reports/sastScan/{reportId}/status"; //Get report status public static final String SAST_GET_REPORT = "reports/sastScan/{reportId}"; //Get report status + public static final String SAST_GET_CXARM_STATUS = "sast/projects/{projectId}/publisher/policyFindings/status"; //Get report status + //ZIP PARAMS public static final long MAX_ZIP_SIZE_BYTES = 2147483648L; diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index 89d3dfe7..f9b32bff 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -2388,7 +2388,7 @@
    -
    Policy Violations
    +
    Violated ${policyLabel}
    ${policyViolatedCount}
    From 13a29512603f9d3d72ab9bb21ba7621e749a4087 Mon Sep 17 00:00:00 2001 From: adig Date: Thu, 22 Nov 2018 10:39:54 +0200 Subject: [PATCH 022/473] Bug #157478 - [jenkins] - no pdf report link in the report (partial fix) --- src/main/java/com/cx/restclient/CxSASTClient.java | 6 ++++-- src/main/java/com/cx/restclient/sast/dto/SASTResults.java | 5 +++-- src/main/java/com/cx/restclient/sast/utils/SASTParam.java | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 91186fb3..7d54e601 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -38,6 +38,7 @@ */ class CxSASTClient { + public static final String JENKINS = "jenkins"; private Logger log; private CxHttpClient httpClient; private CxScanConfig config; @@ -169,8 +170,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); - if (!StringUtils.isEmpty(config.getCxOrigin()) && config.getCxOrigin().equalsIgnoreCase("jenkins")) { - // sastResults.setSastPDFLink(); + if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { + //TODO need to find where to retrieve buildID (PDF link is http://localhost:8080/job/AAA/3/checkmarx/pdfReport) + sastResults.setSastPDFLink(config.getUrl(), config.getProjectName(), "0"); } } } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index c9f85554..a7dd2075 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -10,6 +10,7 @@ import java.util.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; +import static com.cx.restclient.sast.utils.SASTParam.PDF_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -209,8 +210,8 @@ public void setSastPDFLink(String sastPDFLink) { this.sastPDFLink = sastPDFLink; } - public void setSastPDFLink(String url, long projectId) { - this.sastPDFLink = String.format(url + PROJECT_LINK_FORMAT, projectId); + public void setSastPDFLink(String url, String projectName, String buildId) { + this.sastPDFLink = String.format(url + PDF_LINK_FORMAT, projectName, buildId); } public String getScanStart() { diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 44d275f5..ffe2fc8f 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -34,7 +34,7 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "todo"; + public static final String PDF_LINK_FORMAT = "/job/%d/%d/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; From 030c786b746c8507e8986b834c92c05ed61984e8 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 25 Nov 2018 19:41:51 +0200 Subject: [PATCH 023/473] adding: 1. sso login 2. the method - create remote source scan - was added for TFS/SVN/Git/Shared folder --- .../java/com/cx/restclient/CxSASTClient.java | 70 +++++++++++- .../com/cx/restclient/CxShragaClient.java | 2 +- .../com/cx/restclient/common/CxPARAM.java | 6 +- .../configuration/CxScanConfig.java | 100 ++++++++++++++++++ .../restclient/dto/RemoteSourceRequest.java | 96 +++++++++++++++++ .../cx/restclient/dto/RemoteSourceTypes.java | 25 +++++ .../restclient/httpClient/CxHttpClient.java | 67 ++++++++++-- .../cx/restclient/sast/utils/SASTParam.java | 4 + 8 files changed, 354 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java create mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 7d54e601..361b1922 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -2,13 +2,15 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.dto.RemoteSourceRequest; +import com.cx.restclient.dto.RemoteSourceTypes; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.sast.dto.*; import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; +import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; @@ -16,6 +18,7 @@ import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.InputStreamBody; +import org.json.JSONObject; import org.slf4j.Logger; import java.io.File; @@ -23,7 +26,9 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.cx.restclient.cxArm.dto.CxProviders.SAST; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -106,10 +111,17 @@ public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientExcepti //CREATE SAST scan long createSASTScan(long projectId) throws IOException, CxClientException { log.info("-----------------------------------Create CxSAST Scan:------------------------------------"); - if (config.isAvoidDuplicateProjectScans() != null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { throw new CxClientException("\nAvoid duplicate project scans in queue\n"); } + if (config.getRemoteType() != null) { //scan is local + return createLocalSASTScan(projectId); + }else{ + return createRemoteSourceScan(projectId); + } + } + + private long createLocalSASTScan(long projectId) throws IOException, CxClientException { ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); @@ -143,6 +155,55 @@ long createSASTScan(long projectId) throws IOException, CxClientException { return createScanResponse.getId(); } + private long createRemoteSourceScan(long projectId) throws IOException, CxClientException { + HttpEntity entity; + RemoteSourceRequest req = new RemoteSourceRequest(config); + RemoteSourceTypes type = req.getType(); + + switch (type) { + case SVN: + case TFS: + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null); + builder.addTextBody("absoluteUrl", req.getUrl(), ContentType.APPLICATION_JSON); + builder.addTextBody("port", String.valueOf(req.getPort()), ContentType.APPLICATION_JSON); + builder.addTextBody("paths", StringUtils.join(req.getPaths(), ";"), ContentType.APPLICATION_JSON); + entity = builder.build(); + break; + case PERFORCE: + if (config.getPreforceMode() != null) { + req.setBrowseMode("Workspace"); + } else { + req.setBrowseMode("Depot"); + } + entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); + break; + case SHARED: + entity = new StringEntity(new Gson().toJson(req), ContentType.APPLICATION_JSON); + break; + case GIT: + if (req.getPrivateKey().length < 1) { + Map content = new HashMap<>(); + content.put("url", config.getRemoteSrcUrl()); + content.put("branch", config.getRemoteSrcBranch()); + entity = new StringEntity(new JSONObject(content).toString(), ContentType.APPLICATION_JSON); + } else { + builder = MultipartEntityBuilder.create(); + builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); + builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); + entity = builder.build(); + } + break; + default: + log.error("todo"); + entity = new StringEntity(""); + + } + return createRemoteSourceScan(projectId, entity, type.value()).getId(); + } + + //GET SAST results + reports public SASTResults waitForSASTResults(long scanId, long projectId) throws InterruptedException, IOException, CxClientException { SASTResults sastResults; @@ -273,6 +334,11 @@ private CxID createScan(CreateScanRequest request) throws CxClientException, IOE return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } + + private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { + return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + } + private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientException, IOException { return httpClient.getRequest(SAST_SCAN_RESULTS_STATISTICS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, SASTStatisticsResponse.class, 200, "SAST scan statistics", false); } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index ee8aadf1..5c386965 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -54,7 +54,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), log); + config.isDisableCertificateValidation(),config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index 7536a0f8..d540eefd 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -7,13 +7,15 @@ */ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; + public static final String SSO_AUTHENTICATION = "auth/ssologin"; public static final String ORIGIN_HEADER = "cxOrigin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; public static final String CREATE_PROJECT = "projects";//Create new project (default preset and configuration) + public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; - public static final String CX_ARM_URL ="/Configurations/Portal"; - public static final String CX_ARM_VIOLATION ="/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + public static final String CX_ARM_URL = "/Configurations/Portal"; + public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 5a6ca257..bfa43d57 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,6 +1,8 @@ package com.cx.restclient.configuration; +import com.cx.restclient.dto.RemoteSourceTypes; + import java.io.File; import java.io.Serializable; import java.util.Properties; @@ -14,7 +16,10 @@ public class CxScanConfig implements Serializable { private Boolean osaEnabled = false; private String cxOrigin; + private boolean disableCertificateValidation = false; + private boolean useSSOLogin = false; + private String sourceDir; private File reportsDir; private String username; @@ -61,6 +66,17 @@ public class CxScanConfig implements Serializable { private boolean enablePolicyViolations = false; private String cxARMUrl; + private String[] paths; + //remote source control + RemoteSourceTypes remoteType = null; + private String remoteSrcUser; + private String remoteSrcPass; + private String remoteSrcUrl; + private int remoteSrcPort; + private byte[] remoteSrcKeyFile; + private String remoteSrcBranch; + private String preforceMode; + public CxScanConfig() { } @@ -105,6 +121,18 @@ public void setDisableCertificateValidation(boolean disableCertificateValidation this.disableCertificateValidation = disableCertificateValidation; } + public boolean isUseSSOLogin() { + return useSSOLogin; + } + + public void setUseSSOLogin(boolean useSSOLogin) { + this.useSSOLogin = useSSOLogin; + } + + public Boolean getAvoidDuplicateProjectScans() { + return avoidDuplicateProjectScans; + } + public String getSourceDir() { return sourceDir; } @@ -464,4 +492,76 @@ public Boolean isAvoidDuplicateProjectScans() { public void setAvoidDuplicateProjectScans(Boolean avoidDuplicateProjectScans) { this.avoidDuplicateProjectScans = avoidDuplicateProjectScans; } + + public String getRemoteSrcUser() { + return remoteSrcUser; + } + + public void setRemoteSrcUser(String remoteSrcUser) { + this.remoteSrcUser = remoteSrcUser; + } + + public String getRemoteSrcPass() { + return remoteSrcPass; + } + + public void setRemoteSrcPass(String remoteSrcPass) { + this.remoteSrcPass = remoteSrcPass; + } + + public String getRemoteSrcUrl() { + return remoteSrcUrl; + } + + public void setRemoteSrcUrl(String remoteSrcUrl) { + this.remoteSrcUrl = remoteSrcUrl; + } + + public int getRemoteSrcPort() { + return remoteSrcPort; + } + + public void setRemoteSrcPort(int remoteSrcPort) { + this.remoteSrcPort = remoteSrcPort; + } + + public byte[] getRemoteSrcKeyFile() { + return remoteSrcKeyFile; + } + + public void setRemoteSrcKeyFile(byte[] remoteSrcKeyFile) { + this.remoteSrcKeyFile = remoteSrcKeyFile; + } + + public RemoteSourceTypes getRemoteType() { + return remoteType; + } + + public void setRemoteType(RemoteSourceTypes remoteType) { + this.remoteType = remoteType; + } + + public String[] getPaths() { + return paths; + } + + public void setPaths(String[] paths) { + this.paths = paths; + } + + public String getRemoteSrcBranch() { + return remoteSrcBranch; + } + + public void setRemoteSrcBranch(String remoteSrcBranch) { + this.remoteSrcBranch = remoteSrcBranch; + } + + public String getPreforceMode() { + return preforceMode; + } + + public void setPreforceMode(String preforceMode) { + this.preforceMode = preforceMode; + } } diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java new file mode 100644 index 00000000..095b5f60 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -0,0 +1,96 @@ +package com.cx.restclient.dto; + +import com.cx.restclient.configuration.CxScanConfig; + +/** + * Created by Galn on 11/25/2018. + */ +public class RemoteSourceRequest { + String url; + int port; + private byte[] privateKey; + private String[] paths; + private String userName; + private String password; + private RemoteSourceTypes type; + private transient String browseMode; + ; + + public RemoteSourceRequest() { + } + + public RemoteSourceRequest(CxScanConfig config) { + this.userName = config.getRemoteSrcUser(); + this.password = config.getRemoteSrcPass(); + this.url = config.getRemoteSrcUrl(); + this.port = config.getRemoteSrcPort(); + this.privateKey = config.getRemoteSrcKeyFile(); + this.paths = config.getPaths(); + this.type = config.getRemoteType(); + } + + public String getUrl() { + return url; + } + + public void setUrl(String absoluteUrl) { + this.url = absoluteUrl; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public byte[] getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + + public String[] getPaths() { + return paths; + } + + public void setPaths(String[] paths) { + this.paths = paths; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public RemoteSourceTypes getType() { + return type; + } + + public void setType(RemoteSourceTypes type) { + this.type = type; + } + + public String getBrowseMode() { + return browseMode; + } + + public void setBrowseMode(String browseMode) { + this.browseMode = browseMode; + } + +} diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java b/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java new file mode 100644 index 00000000..02f07277 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java @@ -0,0 +1,25 @@ +package com.cx.restclient.dto; + +/** + * Created by: Galn. + * Date: 25/11/2016. + */ +public enum RemoteSourceTypes { + + SHARED("shared"), + SVN("svn"), + GIT("git"), + TFS("tfs"), + PERFORCE("perforce"); + + private String value; + + RemoteSourceTypes(String value) { + this.value = value; + } + + public String value() { + return value; + } + +} \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index eb71872d..45ed0604 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -7,12 +7,15 @@ import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import org.apache.http.*; +import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; import org.apache.http.client.utils.HttpClientUtils; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; @@ -34,8 +37,7 @@ import java.util.ArrayList; import java.util.List; -import static com.cx.restclient.common.CxPARAM.AUTHENTICATION; -import static com.cx.restclient.common.CxPARAM.ORIGIN_HEADER; +import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; @@ -52,6 +54,12 @@ public class CxHttpClient { private final String password; private String cxOrigin; + private CookieStore cookieStore; + private String cookies; + private String csrfToken; + + private Boolean useSSo = false; + private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { @@ -59,10 +67,35 @@ public void process(HttpRequest httpRequest, HttpContext httpContext) throws Htt if (token != null) { httpRequest.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); } + if (csrfToken != null) { + httpRequest.addHeader(CSRF_TOKEN_HEADER, csrfToken); + } + if (cookies != null) { + httpRequest.addHeader("cookie", cookies); + } } }; - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, Logger logi) throws MalformedURLException { + + private final HttpResponseInterceptor responseFilter = new HttpResponseInterceptor() { + + public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { + for (org.apache.http.cookie.Cookie c : cookieStore.getCookies()) { + if (CSRF_TOKEN_HEADER.equals(c.getName())) { + csrfToken = c.getValue(); + } + } + Header[] setCookies = httpResponse.getHeaders("Set-Cookie"); + StringBuilder sb = new StringBuilder(); + for (Header h : setCookies) { + sb.append(h.getValue()).append(";"); + } + cookies = (cookies == null ? "" : cookies) + sb.toString(); + } + }; + + + public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { this.logi = logi; this.username = username; this.password = password; @@ -70,6 +103,11 @@ public CxHttpClient(String hostname, String username, String password, String or this.cxOrigin = origin; //create httpclient HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); + if (isSSO) { + this.useSSo = true; + cookieStore = new BasicCookieStore(); + builder.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); + } setSSLTls(builder, "TLSv1.2", logi); if (disableSSLValidation) { builder = disableCertificateValidation(builder, logi); @@ -79,9 +117,16 @@ public CxHttpClient(String hostname, String username, String password, String or } public void login() throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + + if (useSSo) { + HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); + request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } else { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + + } } private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { @@ -98,7 +143,7 @@ private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEn //GET REQUEST public T getRequest(String relPath, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { - return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); + return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); } public T getRequest(String rootURL, String relPath, String acceptHeader, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { @@ -144,7 +189,7 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity //extract response as object and return the link return convertToObject(response, responseType, isCollection); - } catch (UnknownHostException e){ + } catch (UnknownHostException e) { throw new CxHTTPClientException(ErrorMessage.CHECKMARX_SERVER_CONNECTION_FAILED.getErrorMessage()); } catch (CxTokenExpiredException ex) { if (retry) { @@ -181,10 +226,10 @@ public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws Ce private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { try { - final SSLContext sslContext =SSLContext.getInstance(protocol); - sslContext.init(null,null,null); + final SSLContext sslContext = SSLContext.getInstance(protocol); + sslContext.init(null, null, null); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); - } catch (NoSuchAlgorithmException | KeyManagementException e) { + } catch (NoSuchAlgorithmException | KeyManagementException e) { log.warn("Failed to set SSL TLS : " + e.getMessage()); } } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index ffe2fc8f..c899a64a 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -18,6 +18,10 @@ public class SASTParam { public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; + public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/{projectId}/sourceCode/remoteSettings/{sourceType}";//todo maybe with ssh? + + + //Once it has results public static final String SAST_SCAN_RESULTS_STATISTICS = "sast/scans/{scanId}/resultsStatistics"; public static final String SAST_CREATE_REPORT = "reports/sastScan/"; //Create new report (get ID) From 945059e7364da052992da699145572dcf614ba3c Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 26 Nov 2018 11:51:20 +0200 Subject: [PATCH 024/473] PDF bug - add jenkins job number --- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- .../cx/restclient/configuration/CxScanConfig.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 361b1922..9528318b 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -233,7 +233,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr sastResults.setPdfFileName(pdfFileName); if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { //TODO need to find where to retrieve buildID (PDF link is http://localhost:8080/job/AAA/3/checkmarx/pdfReport) - sastResults.setSastPDFLink(config.getUrl(), config.getProjectName(), "0"); + sastResults.setSastPDFLink(config.getUrl(), config.getProjectName(), Integer.toString(config.getJenkinsJob())); } } } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index bfa43d57..65c82fc4 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -77,6 +77,8 @@ public class CxScanConfig implements Serializable { private String remoteSrcBranch; private String preforceMode; + //for Jenkins PDF rport + private int jenkinsJob; public CxScanConfig() { } @@ -186,7 +188,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if(!teamPath.startsWith("\\")){ + if (!teamPath.startsWith("\\")) { teamPath = "\\" + teamPath; } this.teamPath = teamPath; @@ -564,4 +566,12 @@ public String getPreforceMode() { public void setPreforceMode(String preforceMode) { this.preforceMode = preforceMode; } + + public int getJenkinsJob() { + return jenkinsJob; + } + + public void setJenkinsJob(int jenkinsJob) { + this.jenkinsJob = jenkinsJob; + } } From a9ca75f907ce9c5669dea5af105f3d8c65491368 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 26 Nov 2018 14:58:35 +0200 Subject: [PATCH 025/473] remove "/" - last fix --- .../java/com/cx/restclient/configuration/CxScanConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 65c82fc4..a28aec3a 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,6 +2,7 @@ import com.cx.restclient.dto.RemoteSourceTypes; +import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.Serializable; @@ -188,7 +189,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if (!teamPath.startsWith("\\")) { + if (!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")) { teamPath = "\\" + teamPath; } this.teamPath = teamPath; From 2c46eb89bf1f87fb56169395154556f4c5cab9de Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 26 Nov 2018 15:29:47 +0200 Subject: [PATCH 026/473] fix local scan flow --- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 9528318b..46b32c82 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -114,7 +114,7 @@ long createSASTScan(long projectId) throws IOException, CxClientException { if (config.isAvoidDuplicateProjectScans() != null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { throw new CxClientException("\nAvoid duplicate project scans in queue\n"); } - if (config.getRemoteType() != null) { //scan is local + if (config.getRemoteType() == null) { //scan is local return createLocalSASTScan(projectId); }else{ return createRemoteSourceScan(projectId); From 161a2f537dd001aa11277c6e1ba7c57e78a01c82 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 26 Nov 2018 15:48:46 +0200 Subject: [PATCH 027/473] add pdf link for jenkins --- src/main/java/com/cx/restclient/CxSASTClient.java | 1 - src/main/java/com/cx/restclient/sast/utils/SASTParam.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 46b32c82..3a2ea609 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -232,7 +232,6 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { - //TODO need to find where to retrieve buildID (PDF link is http://localhost:8080/job/AAA/3/checkmarx/pdfReport) sastResults.setSastPDFLink(config.getUrl(), config.getProjectName(), Integer.toString(config.getJenkinsJob())); } } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index c899a64a..1b9d0924 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -38,7 +38,7 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "/job/%d/%d/checkmarx/pdfReport"; + public static final String PDF_LINK_FORMAT = "/job/%s/%s/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; From 756972fbb1fc4bdbc3e8bb0972d8e62b2d4501f8 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Wed, 28 Nov 2018 11:27:03 +0200 Subject: [PATCH 028/473] add pdf link for jenkins --- src/main/java/com/cx/restclient/sast/dto/SASTResults.java | 2 +- src/main/java/com/cx/restclient/sast/utils/SASTParam.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index a7dd2075..e770ada2 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -211,7 +211,7 @@ public void setSastPDFLink(String sastPDFLink) { } public void setSastPDFLink(String url, String projectName, String buildId) { - this.sastPDFLink = String.format(url + PDF_LINK_FORMAT, projectName, buildId); + this.sastPDFLink = String.format(PDF_LINK_FORMAT, projectName, buildId); } public String getScanStart() { diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 1b9d0924..11816994 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -38,7 +38,7 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "/job/%s/%s/checkmarx/pdfReport"; + public static final String PDF_LINK_FORMAT = "{jenkinsUrl}/job/%s/%s/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; From 777417c8c394168d2578a7499f024b2816945ec6 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 29 Nov 2018 14:10:03 +0200 Subject: [PATCH 029/473] add pdf link for jenkins --- src/main/java/com/cx/restclient/CxOSAClient.java | 3 +-- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- src/main/java/com/cx/restclient/sast/utils/SASTParam.java | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 4ad61d81..f5a5adad 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -90,11 +90,10 @@ private String resolveOSADependencies() throws JsonProcessingException { log.info("############################################"); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); + OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); log.info("#############################################"); log.info(" WhiteSource- FSA component scan ended."); log.info("#############################################"); - OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); - return osaDependenciesJson; } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 3a2ea609..c493482b 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -232,7 +232,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { - sastResults.setSastPDFLink(config.getUrl(), config.getProjectName(), Integer.toString(config.getJenkinsJob())); + sastResults.setSastPDFLink(JENKINS_URL_FORMAT, config.getProjectName(), Integer.toString(config.getJenkinsJob())); } } } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 11816994..4a8c1759 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -38,9 +38,10 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "{jenkinsUrl}/job/%s/%s/checkmarx/pdfReport"; + public static final String PDF_LINK_FORMAT = "/job/%s/%s/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; + public static final String JENKINS_URL_FORMAT = "{JenkinsBaseURL}"; } From ceb7ca7045d02091b0dc309ef23ff5f03ed6573e Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 29 Nov 2018 17:18:21 +0200 Subject: [PATCH 030/473] add pdf link for jenkins --- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- src/main/java/com/cx/restclient/sast/dto/SASTResults.java | 2 +- src/main/java/com/cx/restclient/sast/utils/SASTParam.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index c493482b..dd74c693 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -232,7 +232,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { - sastResults.setSastPDFLink(JENKINS_URL_FORMAT, config.getProjectName(), Integer.toString(config.getJenkinsJob())); + sastResults.setSastPDFLink(config.getProjectName(), Integer.toString(config.getJenkinsJob())); } } } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index e770ada2..955592ac 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -210,7 +210,7 @@ public void setSastPDFLink(String sastPDFLink) { this.sastPDFLink = sastPDFLink; } - public void setSastPDFLink(String url, String projectName, String buildId) { + public void setSastPDFLink(String projectName, String buildId) { this.sastPDFLink = String.format(PDF_LINK_FORMAT, projectName, buildId); } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 4a8c1759..1b9d0924 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -42,6 +42,5 @@ public class SASTParam { //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; - public static final String JENKINS_URL_FORMAT = "{JenkinsBaseURL}"; } From 168160861ea98198871bd881ff3f6530b25239ec Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Tue, 4 Dec 2018 14:33:21 +0200 Subject: [PATCH 031/473] fix the duplicate key issue --- .../java/com/cx/restclient/common/summary/SummaryUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index d1837409..ce9715d1 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -119,10 +119,14 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu } )); } + if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { policyViolated = true; policies = osaResults.getOsaPolicies().stream().collect( - Collectors.toMap(Policy::getPolicyName, Policy::getRuleName)); + Collectors.toMap(Policy::getPolicyName, Policy::getRuleName, + (left, right) -> { + return left; + })); } policyViolatedCount = policies.size(); From eac36332b600f75d73425341a36cc3284020d19d Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Tue, 11 Dec 2018 12:56:20 +0200 Subject: [PATCH 032/473] fix number of violated policies --- .../com/cx/restclient/common/summary/SummaryUtils.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index ce9715d1..af4a55b0 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -54,9 +54,6 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu templateData.put("sastHighTotalHeight", sastHighTotalHeight); templateData.put("sastHighNewHeight", sastHighNewHeight); templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); - /*if (config.getEnablePolicyViolations() && !sastResults.getSastViolations().isEmpty()){ - policyViolated = true; - }*/ //sast medium bars float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; @@ -122,11 +119,11 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { policyViolated = true; - policies = osaResults.getOsaPolicies().stream().collect( + policies.putAll(osaResults.getOsaPolicies().stream().collect( Collectors.toMap(Policy::getPolicyName, Policy::getRuleName, (left, right) -> { return left; - })); + }))); } policyViolatedCount = policies.size(); From 0b2ae6234d45e3e0e9ce97dfaf58cfa7e41216ca Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Wed, 12 Dec 2018 12:32:15 +0200 Subject: [PATCH 033/473] fix timeOut of CxArm --- src/main/java/com/cx/restclient/CxSASTClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index dd74c693..f375a974 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -48,7 +48,7 @@ class CxSASTClient { private CxHttpClient httpClient; private CxScanConfig config; private int reportTimeoutSec = 500; - private int cxARMTimeoutSec = 500; + private int cxARMTimeoutSec = 1200; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { @@ -83,7 +83,7 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; - private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 5) { + private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20) { @Override public CxARMStatus getStatus(String id) throws CxClientException, IOException { return getCxARMStatus(id); @@ -458,7 +458,7 @@ private CxARMStatus getCxARMStatus(String projectId) throws CxClientException, I } private void printCxARMProgress(CxARMStatus cxARMStatus, long startTime) { - log.info("Waiting for server to get Policy violations. " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran + log.info("Waiting for server to retrieve policy violations. (This can take up to 15 minutes.) " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran } private CxARMStatus resolveCxARMStatus(CxARMStatus cxARMStatus) throws CxClientException { From 4a7a0a296037b9d2cd2022e7845577297664db4e Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 17 Dec 2018 16:48:14 +0200 Subject: [PATCH 034/473] fix timeOut of CxArm --- src/main/java/com/cx/restclient/CxSASTClient.java | 4 ++-- src/main/java/com/cx/restclient/dto/ScanResults.java | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index f375a974..56cf3b17 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -48,7 +48,7 @@ class CxSASTClient { private CxHttpClient httpClient; private CxScanConfig config; private int reportTimeoutSec = 500; - private int cxARMTimeoutSec = 1200; + private int cxARMTimeoutSec = 1000; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { @@ -458,7 +458,7 @@ private CxARMStatus getCxARMStatus(String projectId) throws CxClientException, I } private void printCxARMProgress(CxARMStatus cxARMStatus, long startTime) { - log.info("Waiting for server to retrieve policy violations. (This can take up to 15 minutes.) " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran + log.info("Waiting for server to retrieve policy violations. " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran } private CxARMStatus resolveCxARMStatus(CxARMStatus cxARMStatus) throws CxClientException { diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index c4d67649..a0c5b63c 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -7,9 +7,8 @@ import java.io.Serializable; public class ScanResults implements Serializable { - - private SASTResults sastResults; - private OSAResults osaResults; + private SASTResults sastResults = new SASTResults(); + private OSAResults osaResults = new OSAResults(); private Exception sastCreateException = null; private Exception sastWaitException = null; From f1f7a122af80d9dcb20f9ef3d0c3868b69ad5eb8 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Mon, 31 Dec 2018 17:46:41 +0200 Subject: [PATCH 035/473] update fsa 18.10.2 --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxOSAClient.java | 11 ++--------- src/main/java/com/cx/restclient/CxShragaClient.java | 4 ++-- .../java/com/cx/restclient/common/ShragaUtils.java | 4 ++-- .../com/cx/restclient/configuration/CxScanConfig.java | 8 ++++---- .../java/com/cx/restclient/osa/utils/OSAUtils.java | 5 +++-- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index 20e21652..1ff85f70 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ org.whitesource whitesource-fs-agent - 18.7.2 + 18.10.2 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index f5a5adad..1ba9edf2 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -2,7 +2,6 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -13,10 +12,10 @@ import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; +import org.whitesource.fs.FSAConfigProperties; import java.io.IOException; import java.util.List; -import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -73,7 +72,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); - Properties scannerProperties = config.getOsaFsaConfig(); + FSAConfigProperties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { scannerProperties = OSAUtils.generateOSAScanConfiguration( config.getOsaFolderExclusions(), @@ -85,15 +84,9 @@ private String resolveOSADependencies() throws JsonProcessingException { } ObjectMapper mapper = new ObjectMapper(); log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); - log.info("############################################"); - log.info(" WhiteSource- Starting FSA component scan:"); - log.info("############################################"); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); - log.info("#############################################"); - log.info(" WhiteSource- FSA component scan ended."); - log.info("#############################################"); return osaDependenciesJson; } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 5c386965..05127e5d 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -13,12 +13,12 @@ import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; +import org.whitesource.fs.FSAConfigProperties; import java.io.IOException; import java.net.MalformedURLException; import java.net.URLEncoder; import java.util.List; -import java.util.Properties; import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; @@ -216,7 +216,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index 6dd0df4b..966c89ca 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -139,10 +139,10 @@ public static Map> convertPatternsToLists(String filterPatt for (String filter : filters) { if (StringUtils.isNotEmpty(filter)) { if (!filter.startsWith("!")) { - inclusions.add(filter); + inclusions.add(filter.trim()); } else if (filter.length() > 1) { filter = filter.substring(1); // Trim the "!" - exclusions.add(filter); + exclusions.add(filter.trim()); } } } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index a28aec3a..0452a577 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -3,10 +3,10 @@ import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; +import org.whitesource.fs.FSAConfigProperties; import java.io.File; import java.io.Serializable; -import java.util.Properties; /** * Created by galn on 21/12/2016. @@ -61,7 +61,7 @@ public class CxScanConfig implements Serializable { private Integer osaHighThreshold; private Integer osaMediumThreshold; private Integer osaLowThreshold; - private Properties osaFsaConfig; //for MAVEN + private FSAConfigProperties osaFsaConfig; //for MAVEN private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; @@ -427,11 +427,11 @@ public void setOsaLowThreshold(Integer osaLowThreshold) { this.osaLowThreshold = osaLowThreshold; } - public Properties getOsaFsaConfig() { + public FSAConfigProperties getOsaFsaConfig() { return osaFsaConfig; } - public void setOsaFsaConfig(Properties osaFsaConfig) { + public void setOsaFsaConfig(FSAConfigProperties osaFsaConfig) { this.osaFsaConfig = osaFsaConfig; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 80801e74..cfd32b6e 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -8,6 +8,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; +import org.whitesource.fs.FSAConfigProperties; import java.io.File; import java.nio.charset.Charset; @@ -49,8 +50,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { - Properties ret = new Properties(); + public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + FSAConfigProperties ret = new FSAConfigProperties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); From fe85af4f2198ed90f3311f090396ce508c1fb523 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 6 Jan 2019 11:37:54 +0200 Subject: [PATCH 036/473] downgrade fsa to18.7.2 --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxOSAClient.java | 5 +++-- src/main/java/com/cx/restclient/CxShragaClient.java | 4 ++-- .../com/cx/restclient/configuration/CxScanConfig.java | 8 ++++---- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 5 ++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 1ff85f70..20e21652 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ org.whitesource whitesource-fs-agent - 18.10.2 + 18.7.2 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 1ba9edf2..7e34e521 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -2,6 +2,7 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -12,10 +13,10 @@ import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; -import org.whitesource.fs.FSAConfigProperties; import java.io.IOException; import java.util.List; +import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -72,7 +73,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); - FSAConfigProperties scannerProperties = config.getOsaFsaConfig(); + Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { scannerProperties = OSAUtils.generateOSAScanConfiguration( config.getOsaFolderExclusions(), diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 05127e5d..5c386965 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -13,12 +13,12 @@ import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; -import org.whitesource.fs.FSAConfigProperties; import java.io.IOException; import java.net.MalformedURLException; import java.net.URLEncoder; import java.util.List; +import java.util.Properties; import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; @@ -216,7 +216,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 0452a577..a28aec3a 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -3,10 +3,10 @@ import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; -import org.whitesource.fs.FSAConfigProperties; import java.io.File; import java.io.Serializable; +import java.util.Properties; /** * Created by galn on 21/12/2016. @@ -61,7 +61,7 @@ public class CxScanConfig implements Serializable { private Integer osaHighThreshold; private Integer osaMediumThreshold; private Integer osaLowThreshold; - private FSAConfigProperties osaFsaConfig; //for MAVEN + private Properties osaFsaConfig; //for MAVEN private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; @@ -427,11 +427,11 @@ public void setOsaLowThreshold(Integer osaLowThreshold) { this.osaLowThreshold = osaLowThreshold; } - public FSAConfigProperties getOsaFsaConfig() { + public Properties getOsaFsaConfig() { return osaFsaConfig; } - public void setOsaFsaConfig(FSAConfigProperties osaFsaConfig) { + public void setOsaFsaConfig(Properties osaFsaConfig) { this.osaFsaConfig = osaFsaConfig; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index cfd32b6e..80801e74 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -8,7 +8,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; -import org.whitesource.fs.FSAConfigProperties; import java.io.File; import java.nio.charset.Charset; @@ -50,8 +49,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { - FSAConfigProperties ret = new FSAConfigProperties(); + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); From e50662cee85504201fa376f1f4e3a985838a5a85 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 15 Jan 2019 15:32:31 +0200 Subject: [PATCH 037/473] fix the / in get teams --- src/main/java/com/cx/restclient/CxShragaClient.java | 10 ++++------ src/main/java/com/cx/restclient/common/CxPARAM.java | 4 ++++ .../com/cx/restclient/configuration/CxScanConfig.java | 2 +- .../com/cx/restclient/osa/dto/CVEReportTableRow.java | 3 +++ src/main/java/com/cx/restclient/osa/dto/Content.java | 2 ++ .../cx/restclient/osa/dto/CreateOSAScanRequest.java | 2 ++ .../java/com/cx/restclient/osa/dto/LoginRequest.java | 3 +++ .../com/cx/restclient/osa/dto/ScanConfiguration.java | 2 ++ .../restclient/sast/dto/UpdateScanStatusRequest.java | 3 +++ 9 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 5c386965..4c017c7f 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -67,13 +67,13 @@ public CxShragaClient(String serverUrl, String username, String password, String public void init() throws CxClientException, IOException { log.info("Initializing Cx client"); login(); + resolveTeam(); if (config.getSastEnabled()) { resolvePreset(); } if (config.getEnablePolicyViolations()){ resolveCxARMUrl(); } - resolveTeam(); resolveProject(); } @@ -170,9 +170,10 @@ public void login() throws IOException, CxClientException { } public String getTeamIdByName(String teamName) throws CxClientException, IOException { + teamName = teamName.replace("\\","/"); List allTeams = getTeamList(); for (Team team : allTeams) { - if (team.getFullName().equalsIgnoreCase(teamName)) { //TODO caseSenesitive + if (team.getFullName().replace("\\","/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } @@ -269,10 +270,7 @@ private void resolveProject() throws IOException, CxClientException { List projects = getProjectByName(config.getProjectName(), config.getTeamId()); if (projects == null || projects.isEmpty()) { // Project is new if (config.getDenyProject()) { - String errMsg = "Creation of the new project [" + config.getProjectName() + "] is not authorized. " + - "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + - " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; - throw new CxClientException(errMsg); + throw new CxClientException(DENY_NEW_PROJECT_ERROR.replace("{projectName}" , config.getProjectName())); } //Create newProject CreateProjectRequest request = new CreateProjectRequest(config.getProjectName(), config.getTeamId(), config.getPublic()); diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index d540eefd..b9e3343d 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -18,4 +18,8 @@ public abstract class CxPARAM { public static final String CX_ARM_URL = "/Configurations/Portal"; public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + + public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index a28aec3a..ebbef1d3 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -189,7 +189,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if (!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")) { + if(!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")&& !teamPath.startsWith(("/"))){ teamPath = "\\" + teamPath; } this.teamPath = teamPath; diff --git a/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java b/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java index 603c05ad..a4ca709a 100644 --- a/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java +++ b/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java @@ -1,7 +1,10 @@ package com.cx.restclient.osa.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import java.io.Serializable; +@JsonIgnoreProperties(ignoreUnknown = true) public class CVEReportTableRow implements Serializable { private String name; diff --git a/src/main/java/com/cx/restclient/osa/dto/Content.java b/src/main/java/com/cx/restclient/osa/dto/Content.java index 1dc12eda..c50b4219 100644 --- a/src/main/java/com/cx/restclient/osa/dto/Content.java +++ b/src/main/java/com/cx/restclient/osa/dto/Content.java @@ -1,8 +1,10 @@ package com.cx.restclient.osa.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRawValue; +@JsonIgnoreProperties(ignoreUnknown = true) public class Content { @JsonRawValue diff --git a/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java b/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java index c4a1b0a3..f603667b 100644 --- a/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java +++ b/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java @@ -1,7 +1,9 @@ package com.cx.restclient.osa.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public class CreateOSAScanRequest { @JsonProperty("ProjectId") diff --git a/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java b/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java index e0df293b..1807469c 100644 --- a/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java +++ b/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java @@ -1,9 +1,12 @@ package com.cx.restclient.osa.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by: Dorg. * Date: 08/09/2016. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class LoginRequest { private String username; diff --git a/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java b/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java index 2970b248..05ef2e5a 100644 --- a/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java +++ b/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java @@ -2,12 +2,14 @@ import com.cx.restclient.sast.dto.Project; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.File; /** * Created by galn on 21/12/2016. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class ScanConfiguration { private boolean SASTEnabled; diff --git a/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java b/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java index 3f27b915..df54beef 100644 --- a/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java +++ b/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java @@ -1,8 +1,11 @@ package com.cx.restclient.sast.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by Galn on 18/03/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class UpdateScanStatusRequest { private String status; From 2ae284cbce1218900d87c1c28a2649b77abe8ce8 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 24 Jan 2019 18:39:41 +0200 Subject: [PATCH 038/473] big xml test --- src/main/java/com/cx/restclient/CxSASTClient.java | 4 ++++ src/main/java/com/cx/restclient/CxShragaClient.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 56cf3b17..a4cc9d0c 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -284,6 +284,10 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { } + public void getProjecti(long scanId) throws InterruptedException, CxClientException, IOException { + byte[] cxReport = getScanReport(scanId, ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); + CxXMLResults reportObj = convertToXMLResult(cxReport); + } //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { List queuedScans = getQueueScans(projectId); diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 4c017c7f..3f867d32 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -162,6 +162,12 @@ public void close() { httpClient.close(); } + + public void getProject(long scanId) throws InterruptedException, CxClientException, IOException { + log.info("gettt Projects"); + sastClient.getProjecti(scanId); + } + //HELP config Methods public void login() throws IOException, CxClientException { // perform login to server From 5dd2bcb3538bc2a8a7f2ceafb6f036f3a21d61d0 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 24 Jan 2019 19:27:23 +0200 Subject: [PATCH 039/473] big xml test --- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- src/main/java/com/cx/restclient/common/Waiter.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index a4cc9d0c..6232b8e0 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -47,7 +47,7 @@ class CxSASTClient { private Logger log; private CxHttpClient httpClient; private CxScanConfig config; - private int reportTimeoutSec = 500; + private int reportTimeoutSec = 1500; private int cxARMTimeoutSec = 1000; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index 648c8502..d16013e5 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -34,6 +34,7 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) try { obj = getStatus(taskId); status = ((BaseStatus) obj).getBaseStatus(); + log.info(status.value()); while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { Thread.sleep(sleepIntervalSec * 1000); From 9fdbb829d7ecec352cabbacdef5d0aac0d059602 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 24 Jan 2019 19:37:30 +0200 Subject: [PATCH 040/473] big xml test --- src/main/java/com/cx/restclient/CxSASTClient.java | 4 ++-- src/main/java/com/cx/restclient/common/Waiter.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 6232b8e0..d1a39e02 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -47,7 +47,7 @@ class CxSASTClient { private Logger log; private CxHttpClient httpClient; private CxScanConfig config; - private int reportTimeoutSec = 1500; + private int reportTimeoutSec = 5000; private int cxARMTimeoutSec = 1000; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override @@ -66,7 +66,7 @@ public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) } }; - private Waiter reportWaiter = new Waiter("Scan report", 5) { + private Waiter reportWaiter = new Waiter("Scan report", 10) { @Override public ReportStatus getStatus(String id) throws CxClientException, IOException { return getReportStatus(id); diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index d16013e5..e7aced10 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -34,13 +34,14 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) try { obj = getStatus(taskId); status = ((BaseStatus) obj).getBaseStatus(); - log.info(status.value()); + while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { Thread.sleep(sleepIntervalSec * 1000); try { obj = getStatus(taskId); status = ((BaseStatus) obj).getBaseStatus(); + log.info(status.value()); } catch (Exception e) { log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); retry--; From 2831f9a8297d10465cb0b9df629cf2dbc2cbe7ae Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Thu, 24 Jan 2019 19:53:37 +0200 Subject: [PATCH 041/473] big xml test --- src/main/java/com/cx/restclient/CxSASTClient.java | 10 ++++++++-- src/main/java/com/cx/restclient/CxShragaClient.java | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index d1a39e02..3ea12ee5 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -284,9 +284,15 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { } - public void getProjecti(long scanId) throws InterruptedException, CxClientException, IOException { - byte[] cxReport = getScanReport(scanId, ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); + public SASTResults getProjecti(long scanId) throws InterruptedException, CxClientException, IOException { + SASTResults sastResults = new SASTResults(); + byte[] cxReport = getScanReport(scanId, ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); CxXMLResults reportObj = convertToXMLResult(cxReport); + sastResults.setScanDetailedReport(reportObj); + sastResults.setRawXMLReport(cxReport); + sastResults.setSastResultsReady(true); + return sastResults; + } //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 3f867d32..dd9c0bfc 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -163,9 +163,9 @@ public void close() { } - public void getProject(long scanId) throws InterruptedException, CxClientException, IOException { + public SASTResults getProject(long scanId) throws InterruptedException, CxClientException, IOException { log.info("gettt Projects"); - sastClient.getProjecti(scanId); + return sastClient.getProjecti(scanId); } //HELP config Methods From 50a0212cc7ade009d7a5564bc76b3ea5c336c61b Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Wed, 6 Feb 2019 15:15:15 +0200 Subject: [PATCH 042/473] update fsa 18.10.2 --- pom.xml | 4 ++-- src/main/java/com/cx/restclient/CxOSAClient.java | 5 +++-- src/main/java/com/cx/restclient/CxSASTClient.java | 12 ------------ src/main/java/com/cx/restclient/CxShragaClient.java | 11 +++-------- src/main/java/com/cx/restclient/common/Waiter.java | 2 +- .../cx/restclient/configuration/CxScanConfig.java | 8 ++++---- .../java/com/cx/restclient/osa/utils/OSAUtils.java | 6 ++++-- 7 files changed, 17 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index 20e21652..69a5dc72 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.0-SNAPSHOT + 9.00.1-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -85,7 +85,7 @@ org.whitesource whitesource-fs-agent - 18.7.2 + 18.10.2 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 7e34e521..387b7d35 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -13,10 +13,11 @@ import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; +import org.whitesource.fs.FSAConfigProperties; + import java.io.IOException; import java.util.List; -import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -73,7 +74,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); - Properties scannerProperties = config.getOsaFsaConfig(); + FSAConfigProperties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { scannerProperties = OSAUtils.generateOSAScanConfiguration( config.getOsaFolderExclusions(), diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 3ea12ee5..03201658 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -282,18 +282,6 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { httpClient.patchRequest(SAST_QUEUE_SCAN_STATUS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, entity, 200, "cancel SAST scan"); log.info("SAST Scan canceled. (scanId: " + scanId + ")"); } - - - public SASTResults getProjecti(long scanId) throws InterruptedException, CxClientException, IOException { - SASTResults sastResults = new SASTResults(); - byte[] cxReport = getScanReport(scanId, ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); - CxXMLResults reportObj = convertToXMLResult(cxReport); - sastResults.setScanDetailedReport(reportObj); - sastResults.setRawXMLReport(cxReport); - sastResults.setSastResultsReady(true); - return sastResults; - - } //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { List queuedScans = getQueueScans(projectId); diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index dd9c0bfc..f197cd3d 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -18,7 +18,8 @@ import java.net.MalformedURLException; import java.net.URLEncoder; import java.util.List; -import java.util.Properties; + +import org.whitesource.fs.FSAConfigProperties; import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; @@ -162,12 +163,6 @@ public void close() { httpClient.close(); } - - public SASTResults getProject(long scanId) throws InterruptedException, CxClientException, IOException { - log.info("gettt Projects"); - return sastClient.getProjecti(scanId); - } - //HELP config Methods public void login() throws IOException, CxClientException { // perform login to server @@ -223,7 +218,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index e7aced10..18fdf4bf 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -59,7 +59,7 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) } } catch (Exception e) { throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); - } + } return resolveStatus(obj); } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index ebbef1d3..fcba41b7 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -6,7 +6,7 @@ import java.io.File; import java.io.Serializable; -import java.util.Properties; +import org.whitesource.fs.FSAConfigProperties; /** * Created by galn on 21/12/2016. @@ -61,7 +61,7 @@ public class CxScanConfig implements Serializable { private Integer osaHighThreshold; private Integer osaMediumThreshold; private Integer osaLowThreshold; - private Properties osaFsaConfig; //for MAVEN + private FSAConfigProperties osaFsaConfig; //for MAVEN private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; @@ -427,11 +427,11 @@ public void setOsaLowThreshold(Integer osaLowThreshold) { this.osaLowThreshold = osaLowThreshold; } - public Properties getOsaFsaConfig() { + public FSAConfigProperties getOsaFsaConfig() { return osaFsaConfig; } - public void setOsaFsaConfig(Properties osaFsaConfig) { + public void setOsaFsaConfig(FSAConfigProperties osaFsaConfig) { this.osaFsaConfig = osaFsaConfig; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 80801e74..92d87ef8 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.whitesource.fs.FSAConfigProperties; + import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; @@ -49,8 +51,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { - Properties ret = new Properties(); + public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + FSAConfigProperties ret = new FSAConfigProperties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); From d8a3aa4363daf3a8cd019005373ef6b00b501b1c Mon Sep 17 00:00:00 2001 From: Margarital Date: Thu, 7 Feb 2019 10:42:58 +0200 Subject: [PATCH 043/473] Add Junit dependency to pom file. Create unit test --- pom.xml | 7 ++ .../configuration/CxScanConfigTest.java | 105 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java diff --git a/pom.xml b/pom.xml index 69a5dc72..474d12f5 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,13 @@ 1.7.5 provided + + + junit + junit + 4.8.1 + test + diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java new file mode 100644 index 00000000..807e99f9 --- /dev/null +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -0,0 +1,105 @@ +package com.cx.restclient.configuration; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; + +public class CxScanConfigTest { + private Logger logUnitTests = LoggerFactory.getLogger("testush"); + private CxScanConfig cxScanConfig = new CxScanConfig(); + + String url; + String username; + String password; + String cxOrigin; + boolean disableCertificateValidation = false; + private CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); + + @Test + public void CxScanConfig() { + String url = "http://XX.XX.XX.XX"; + String username = "userName"; + String password = "password"; + String cxOrigin = "cxOrigin"; + boolean disableCertificateValidation = false; + CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); + + assertEquals("Incorrect URL", cxScanConfigWithParameters.getUrl(), url); + assertEquals("Incorrect userName", cxScanConfigWithParameters.getUsername(), username); + assertEquals("Incorrect password", cxScanConfigWithParameters.getPassword(), password); + assertEquals("Incorrect CxOrigin", cxScanConfigWithParameters.getCxOrigin(), cxOrigin); + assertEquals("Incorrect disableCertificateValidation", cxScanConfigWithParameters.isDisableCertificateValidation(), disableCertificateValidation); + } + + + @Test + public void getSetSastEnabled() { + + logUnitTests.info("Current test validate that we get correct values from getSastEnabled method\n"); + + logUnitTests.info("1 test --> Set 'setSastEnabled' to value 'false' and validate that get 'false' value\n"); + cxScanConfig.setSastEnabled(false); + assertEquals("I expected to get 'false' value but got different value", cxScanConfig.getSastEnabled(), false); + + + logUnitTests.info("2 test --> Set 'setSastEnabled' to value 'true' and validate that get 'true' value\n"); + cxScanConfig.setSastEnabled(true); + assertEquals("I expected to get 'true' value but got different value", cxScanConfig.getSastEnabled(), true); + + + logUnitTests.info("3 test --> Negative test - Set 'setSastEnabled' to value 'true' and validate that get 'false' value\n"); + cxScanConfig.setSastEnabled(true); + assertEquals("Negative test - expected to see different values", !(cxScanConfig.getSastEnabled()), false); + + + logUnitTests.info("4 test --> Negative test - Set 'setSastEnabled' to value 'false' and validate that get 'true' value\n"); + cxScanConfig.setSastEnabled(false); + assertEquals("Negative test - expected to see different values", !(cxScanConfig.getSastEnabled()), true); + + } + + @Test + public void getSetOsaEnabled() { + logUnitTests.info("Current test validate that we get correct values from getOsaEnabled method\n"); + + logUnitTests.info("1 test --> Set 'setOsaEnabled' to value 'false' and validate that get 'false' value\n"); + cxScanConfig.setOsaEnabled(false); + assertEquals("I expected to get 'false' value but got different value", cxScanConfig.getOsaEnabled(), false); + + logUnitTests.info("2 test --> Set 'setOsaEnabled' to value 'true' and validate that get 'true' value\n"); + cxScanConfig.setOsaEnabled(true); + assertEquals("I expected to get 'true' value but got different value", cxScanConfig.getOsaEnabled(), true); + + logUnitTests.info("3 test --> Negative test - Set 'setOsaEnabled' to value 'true' and validate that get 'false' value\n"); + cxScanConfig.setOsaEnabled(true); + assertEquals("Negative test - expected to see different values", !(cxScanConfig.getOsaEnabled()), false); + + logUnitTests.info("4 test --> Negative test - Set 'setOsaEnabled' to value 'false' and validate that get 'true' value\n"); + cxScanConfig.setOsaEnabled(false); + assertEquals("Negative test - expected to see different values", !(cxScanConfig.getOsaEnabled()), true); + } + + @Test + public void getSetCxOrigin() { + + logUnitTests.info("Current test validate that we get correct values from getCxOrigin method\n"); + + logUnitTests.info("1 test --> Set 'setCxOrigin' to value ANY String value and validate that getCxOrigin method will return correct value\n"); + String cxOriginValue = "SetCxOriginValueUnitTest"; + cxScanConfig.setCxOrigin(cxOriginValue); + assertEquals("I expected to get" + cxOriginValue + " value but got different value", cxScanConfig.getCxOrigin(), cxOriginValue); + + //TODO current test failed because we allow to set empty string + logUnitTests.info("2 test --> Set 'setCxOrigin' to value to EMPTY String and validate that getCxOrigin method will return correct value\n"); + cxScanConfig.setCxOrigin(" "); + assertEquals("I expected to get ' ' value but got different value", cxScanConfig.getCxOrigin().trim().isEmpty(), true); + + logUnitTests.info("3 test --> Set 'setCxOrigin' to value to NOT STRING and validate that getCxOrigin method throw error\n"); + cxScanConfig.setCxOrigin(null); + assertNull("Did not Get NULL value", cxScanConfig.getCxOrigin()); + + } + +} \ No newline at end of file From 728372c3419a216a4dfaaf8547a9ca26cb1c63cd Mon Sep 17 00:00:00 2001 From: Margarital Date: Tue, 12 Feb 2019 14:59:53 +0200 Subject: [PATCH 044/473] I add dependency org.slf4j slf4j-simple 1.6.1 --- pom.xml | 6 ++++++ .../com/cx/restclient/configuration/CxScanConfigTest.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 474d12f5..57a4e131 100644 --- a/pom.xml +++ b/pom.xml @@ -114,6 +114,12 @@ 4.8.1 test + + org.slf4j + slf4j-simple + 1.6.1 + test + diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java index 807e99f9..41819691 100644 --- a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -7,7 +7,7 @@ import static org.junit.Assert.*; public class CxScanConfigTest { - private Logger logUnitTests = LoggerFactory.getLogger("testush"); + private Logger logUnitTests = LoggerFactory.getLogger("CxCommonClient Unit tests "); private CxScanConfig cxScanConfig = new CxScanConfig(); String url; From bd5d6db8df9d4fc0231b46140a4aedfd5ee06295 Mon Sep 17 00:00:00 2001 From: Checkmarx Admin Date: Tue, 19 Feb 2019 12:03:30 +0200 Subject: [PATCH 045/473] Create Jenkinsfile --- Jenkinsfile | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..955f1ff8 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,92 @@ +Skip to content + +Search or jump to… + +Pull requests +Issues +Marketplace +Explore + @cxadmin Sign out +We are having a problem billing your account. Please enter a new payment method or check with your payment provider for details on why the transaction failed. You can downgrade to the Free plan in your Billing settings. +You can always contact support with any questions. +5 +1 4 CxRepositories/Cx-Client-Common + Code Issues 0 Pull requests 0 Projects 0 Wiki Insights Settings +Cx-Client-Common/Jenkinsfile +78f3672 13 days ago +@cxadmin cxadmin Update Jenkinsfile +We found potential security vulnerabilities in your dependencies. +You can see this message because you have been granted access to vulnerability alerts for this repository. +Manage your notification settings or learn more about vulnerability alerts. + + +57 lines (53 sloc) 1.75 KB +pipeline { + parameters { + booleanParam(name: 'IsReleaseBuild', description: 'Check the box if you want to create a release build') + } + agent { + node { + label 'Plugins' + } + } + tools { + jdk 'JDK_WINDOWS_1.8.0_92' + } + + stages { + stage('Remove Snapshot') { + steps { + + powershell '''#------------------------------------------------------------------------------------------------------------ +# REMOVE THE WORD SNAPSHOT (ONLY FOR RELEASE BUILDS) +#------------------------------------------------------------------------------------------------------------ +[string]$IsReleaseBuild = $ENV:IsReleaseBuild +[string]$RootPath = "C:\\CI-Slave\\workspace\\$ENV:JOB_NAME" +If($IsReleaseBuild -eq "true") +{ + Write-Host " ----------------------------------------------------- " + Write-Host "| SNAPSHOT DISABLED: Removing Snapshot before build |" + Write-Host " ----------------------------------------------------- " + $XmlPath = $RootPath + "\\pom.xml" + If(Test-Path "$XmlPath") + { + [xml]$XmlDocument = Get-Content -Path $XmlPath + $XmlDocument.project.version = $XmlDocument.project.version.Replace("-SNAPSHOT", "") + $XmlDocument.Save($XmlPath) + } +} +Else +{ + Write-Host " ----------------------------------------------------- " + Write-Host "| SNAPSHOT ENABLED: Run Build without modifying |" + Write-Host " ----------------------------------------------------- " +}''' + } + } + + stage('Build') { + steps { + bat """mvn clean install -Dorg.apache.maven.user-settings=C:\\Jenkins\\workspace\\settings.xml -Dbuild.number=${BUILD_NUMBER}""" + } + } + stage('Archive Artifacts') { + steps { + archiveArtifacts 'target/*.jar' + } + } + } +} +© 2019 GitHub, Inc. +Terms +Privacy +Security +Status +Help +Contact GitHub +Pricing +API +Training +Blog +About +Press h to open a hovercard with more details. From 5d692a9afdb2e7d16ef476a823733437de2ae5a8 Mon Sep 17 00:00:00 2001 From: Checkmarx Admin Date: Tue, 19 Feb 2019 13:57:09 +0200 Subject: [PATCH 046/473] Update Jenkinsfile --- Jenkinsfile | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 955f1ff8..79f5073f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,26 +1,3 @@ -Skip to content - -Search or jump to… - -Pull requests -Issues -Marketplace -Explore - @cxadmin Sign out -We are having a problem billing your account. Please enter a new payment method or check with your payment provider for details on why the transaction failed. You can downgrade to the Free plan in your Billing settings. -You can always contact support with any questions. -5 -1 4 CxRepositories/Cx-Client-Common - Code Issues 0 Pull requests 0 Projects 0 Wiki Insights Settings -Cx-Client-Common/Jenkinsfile -78f3672 13 days ago -@cxadmin cxadmin Update Jenkinsfile -We found potential security vulnerabilities in your dependencies. -You can see this message because you have been granted access to vulnerability alerts for this repository. -Manage your notification settings or learn more about vulnerability alerts. - - -57 lines (53 sloc) 1.75 KB pipeline { parameters { booleanParam(name: 'IsReleaseBuild', description: 'Check the box if you want to create a release build') @@ -77,16 +54,3 @@ Else } } } -© 2019 GitHub, Inc. -Terms -Privacy -Security -Status -Help -Contact GitHub -Pricing -API -Training -Blog -About -Press h to open a hovercard with more details. From 099f371781c115bd1442828bad774d20d1951482 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Thu, 21 Feb 2019 12:16:14 +0200 Subject: [PATCH 047/473] 1. merger the genrateXmlReport 2. upgrade jackson-databind to 2.9.8 --- pom.xml | 19 +++++++------------ .../java/com/cx/restclient/CxSASTClient.java | 13 ++++++++----- .../configuration/CxScanConfig.java | 9 +++++++++ src/main/resources/com/cx/report/report.ftl | 2 +- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 69a5dc72..4ba78bc1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.1-SNAPSHOT + 9.00.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -62,6 +62,11 @@ + + org.slf4j + slf4j-simple + 1.6.1 + org.freemarker freemarker @@ -80,7 +85,7 @@ com.fasterxml.jackson.core jackson-databind - 2.8.9 + 2.9.8 org.whitesource @@ -95,18 +100,8 @@ ch.qos.logback logback-classic - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-api - 1.7.5 - provided - diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 03201658..6e70398c 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -116,7 +116,7 @@ long createSASTScan(long projectId) throws IOException, CxClientException { } if (config.getRemoteType() == null) { //scan is local return createLocalSASTScan(projectId); - }else{ + } else { return createRemoteSourceScan(projectId); } } @@ -255,10 +255,12 @@ private SASTResults retrieveSASTResults(long scanId, long projectId) throws CxCl sastResults.setResults(scanId, statisticsResults, config.getUrl(), projectId); //SAST detailed report - byte[] cxReport = getScanReport(sastResults.getScanId(), ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); - CxXMLResults reportObj = convertToXMLResult(cxReport); - sastResults.setScanDetailedReport(reportObj); - sastResults.setRawXMLReport(cxReport); + if (config.getGenerateXmlReport() == null || config.getGenerateXmlReport()) { + byte[] cxReport = getScanReport(sastResults.getScanId(), ReportType.XML, CONTENT_TYPE_APPLICATION_XML_V1); + CxXMLResults reportObj = convertToXMLResult(cxReport); + sastResults.setScanDetailedReport(reportObj); + sastResults.setRawXMLReport(cxReport); + } sastResults.setSastResultsReady(true); return sastResults; } @@ -282,6 +284,7 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { httpClient.patchRequest(SAST_QUEUE_SCAN_STATUS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, entity, 200, "cancel SAST scan"); log.info("SAST Scan canceled. (scanId: " + scanId + ")"); } + //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { List queuedScans = getQueueScans(projectId); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index fcba41b7..2646b965 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -65,6 +65,7 @@ public class CxScanConfig implements Serializable { private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; + private Boolean generateXmlReport = true; private String cxARMUrl; private String[] paths; @@ -575,4 +576,12 @@ public int getJenkinsJob() { public void setJenkinsJob(int jenkinsJob) { this.jenkinsJob = jenkinsJob; } + + public Boolean getGenerateXmlReport() { + return generateXmlReport; + } + + public void setGenerateXmlReport(Boolean generateXmlReport) { + this.generateXmlReport = generateXmlReport; + } } diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index f9b32bff..c2ed822f 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -1579,7 +1579,7 @@ - <#if config.sastEnabled && sast.sastResultsReady> + <#if config.sastEnabled && config.generateXmlReport &&sast.sastResultsReady> <#if sast.high gt 0 || sast.medium gt 0 || sast.low gt 0>
    From ba99c1ce9b5211b6f32fa695a5b99b46be3ae626 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Thu, 21 Feb 2019 18:13:57 +0200 Subject: [PATCH 048/473] add jUnit dependency for unitTest --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4ba78bc1..68b917d3 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,13 @@ - + + junit + junit + 4.12 + test + + org.slf4j slf4j-simple 1.6.1 From 7b772058f0663f9b2129aa8aad7076a4e06a180a Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 13:10:52 +0200 Subject: [PATCH 049/473] test --- src/main/java/com/cx/restclient/CxSASTClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 6e70398c..e10d8080 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -334,7 +334,6 @@ private CxID createScan(CreateScanRequest request) throws CxClientException, IOE return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } - private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); } From a10eceeafc2bca92bac6ad7d5270d26f2bcce759 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 14:42:14 +0200 Subject: [PATCH 050/473] update fsa- to 18.12.2 --- pom.xml | 515 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 258 insertions(+), 257 deletions(-) diff --git a/pom.xml b/pom.xml index 68b917d3..35c27034 100644 --- a/pom.xml +++ b/pom.xml @@ -1,261 +1,262 @@  - - 4.0.0 - com.checkmarx - cx-client-common - 9.00.0-SNAPSHOT - jar - Checkmarx Client - Enables web services access Checkmarx SAST and OSA scan. - https://www.checkmarx.com - - scm:git:git://github.com/CxRepositories/Cx-Client-Common.git - scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git - https://github.com/CxRepositories/Cx-Client-Common/tree/master - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - UTF-8 - - - Checkmarx - https://www.checkmarx.com/ - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.3 - - - default-descriptor - - descriptor - report - - process-classes - - - help-descriptor - - helpmojo - - process-classes - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.8 - 1.8 - - - - - - - junit - junit - 4.12 - test - - - org.slf4j - slf4j-simple - 1.6.1 - - - org.freemarker - freemarker - 2.3.23 - - - org.apache.httpcomponents - httpmime - 4.4.1 - - - org.apache.httpcomponents - httpcore - 4.4 - - - com.fasterxml.jackson.core - jackson-databind - 2.9.8 - - - org.whitesource - whitesource-fs-agent - 18.10.2 - - - javax.xml.bind - jaxb-api - - - ch.qos.logback - logback-classic - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.3 - - - - - - release - + + 4.0.0 + com.checkmarx + cx-client-common + 9.00.0-SNAPSHOT + jar + Checkmarx Client + Enables web services access Checkmarx SAST and OSA scan. + https://www.checkmarx.com + + scm:git:git://github.com/CxRepositories/Cx-Client-Common.git + scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git + https://github.com/CxRepositories/Cx-Client-Common/tree/master + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + UTF-8 + + + Checkmarx + https://www.checkmarx.com/ + + - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar-no-fork - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - C:\Program Files (x86)\GNU\GnuPG\gpg2.exe - Checkmarx123456 - - - - sign-artifacts - verify - - sign - - - - + + org.apache.maven.plugins + maven-plugin-plugin + 3.3 + + + default-descriptor + + descriptor + report + + process-classes + + + help-descriptor + + helpmojo + + process-classes + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + - - - - - - Gal Or Nussbaum - gal.nussbaum@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - https://www.linkedin.com/in/gal-nussbaum-68b3a76a/de - - - - Dor Golan - dor.golan@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Shaul Valero - shaul.valero@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Dev TL - Developer - - - http://i.imgur.com/44Iil53.png - - - - Eyal Amor - eyal.amor@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Yair David - Yair.David@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - QA Manager - - - http://i.imgur.com/EVIS8LO.jpg - - - + + + + junit + junit + 4.12 + test + + + org.slf4j + slf4j-simple + 1.6.1 + + + org.freemarker + freemarker + 2.3.23 + + + org.apache.httpcomponents + httpmime + 4.4.1 + + + org.apache.httpcomponents + httpcore + 4.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.8 + + + org.whitesource + wss-unified-agent-utils + 18.12.2 + + + javax.xml.bind + jaxb-api + + + ch.qos.logback + logback-classic + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.3 + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar-no-fork + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + C:\Program Files (x86)\GNU\GnuPG\gpg2.exe + Checkmarx123456 + + + + sign-artifacts + verify + + sign + + + + + + + + + + + Gal Or Nussbaum + gal.nussbaum@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + https://www.linkedin.com/in/gal-nussbaum-68b3a76a/de + + + + Dor Golan + dor.golan@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + http://i.imgur.com/44Iil53.png + + + + Shaul Valero + shaul.valero@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Dev TL + Developer + + + http://i.imgur.com/44Iil53.png + + + + Eyal Amor + eyal.amor@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + Architect + Developer + + + http://i.imgur.com/44Iil53.png + + + + Yair David + Yair.David@checkmarx.com + Checkmarx + https://www.checkmarx.com/ + + QA Manager + + + http://i.imgur.com/EVIS8LO.jpg + + + \ No newline at end of file From 775d37fc5eb81c1ba19bc2336d2d45e676f042f3 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 15:07:49 +0200 Subject: [PATCH 051/473] add generalException to scanResults --- src/main/java/com/cx/restclient/dto/ScanResults.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index a0c5b63c..5d0357c9 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -14,6 +14,7 @@ public class ScanResults implements Serializable { private Exception sastWaitException = null; private Exception osaCreateException = null; private Exception osaWaitException = null; + private Exception generalException = null; public ScanResults() { } @@ -65,4 +66,12 @@ public Exception getOsaWaitException() { public void setOsaWaitException(Exception osaWaitException) { this.osaWaitException = osaWaitException; } + + public Exception getGeneralException() { + return generalException; + } + + public void setGeneralException(Exception generalException) { + this.generalException = generalException; + } } From adbf1fc434d874e961ae965193201457122638dd Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 15:39:53 +0200 Subject: [PATCH 052/473] update fsa 18.12.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 35c27034..9deed35b 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ org.whitesource - wss-unified-agent-utils + wss-unified-agent-main 18.12.2 From 40ce585df150f3b67d45586723d174b2c9306388 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 16:15:34 +0200 Subject: [PATCH 053/473] add generalException --- src/main/java/com/cx/restclient/dto/ScanResults.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 5d0357c9..9e116934 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -19,6 +19,16 @@ public class ScanResults implements Serializable { public ScanResults() { } + public boolean isSASTCreated() { + return this.sastCreateException != null; + } + public boolean isOSACreated() { + return this.osaCreateException != null; + } + public boolean isShragaCreated() { + return this.generalException != null; + } + public SASTResults getSastResults() { return sastResults; } From ab4ff0dcc7e1b6db6d99e3fb671a67630f096354 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 16:19:32 +0200 Subject: [PATCH 054/473] add generalException --- src/main/java/com/cx/restclient/dto/ScanResults.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 9e116934..9278de57 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -28,6 +28,12 @@ public boolean isOSACreated() { public boolean isShragaCreated() { return this.generalException != null; } + public boolean isSASTDone() { + return this.sastWaitException != null; + } + public boolean isOSADone() { + return this.osaWaitException != null; + } public SASTResults getSastResults() { return sastResults; From 579fa5868797e90e779e92ced250179a206eba72 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 16:29:15 +0200 Subject: [PATCH 055/473] add generalException --- src/main/java/com/cx/restclient/dto/ScanResults.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 9278de57..3e919e90 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -25,9 +25,7 @@ public boolean isSASTCreated() { public boolean isOSACreated() { return this.osaCreateException != null; } - public boolean isShragaCreated() { - return this.generalException != null; - } + public boolean isSASTDone() { return this.sastWaitException != null; } From 829a94fdec2acd4864212e70b7f74526e00f8ea8 Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 16:39:43 +0200 Subject: [PATCH 056/473] add generalException --- src/main/java/com/cx/restclient/dto/ScanResults.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 3e919e90..4798db01 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -20,17 +20,17 @@ public ScanResults() { } public boolean isSASTCreated() { - return this.sastCreateException != null; + return this.sastCreateException == null; } public boolean isOSACreated() { - return this.osaCreateException != null; + return this.osaCreateException == null; } public boolean isSASTDone() { - return this.sastWaitException != null; + return this.sastWaitException == null; } public boolean isOSADone() { - return this.osaWaitException != null; + return this.osaWaitException == null; } public SASTResults getSastResults() { From 9c586fca8ec57ff4d29c078bceccff1a6c90e3fc Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Sun, 24 Feb 2019 16:46:46 +0200 Subject: [PATCH 057/473] add generalException --- .../java/com/cx/restclient/dto/ScanResults.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 4798db01..5d0357c9 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -19,20 +19,6 @@ public class ScanResults implements Serializable { public ScanResults() { } - public boolean isSASTCreated() { - return this.sastCreateException == null; - } - public boolean isOSACreated() { - return this.osaCreateException == null; - } - - public boolean isSASTDone() { - return this.sastWaitException == null; - } - public boolean isOSADone() { - return this.osaWaitException == null; - } - public SASTResults getSastResults() { return sastResults; } From 014b7cf0f400f2930d1b558abb272eaf570315ab Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Mon, 25 Feb 2019 15:03:41 +0200 Subject: [PATCH 058/473] add common version to the log --- pom.xml | 6 ++++ .../com/cx/restclient/CxShragaClient.java | 18 +++++++++- .../com/cx/restclient/common/ErrorUtil.java | 35 ------------------- .../java/com/cx/restclient/common/Waiter.java | 2 +- .../httpClient/utils/HttpClientHelper.java | 4 --- 5 files changed, 24 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/com/cx/restclient/common/ErrorUtil.java diff --git a/pom.xml b/pom.xml index 9deed35b..2e0ecf33 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ + + + src/main/resources + true + + diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index f197cd3d..306d688f 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -18,6 +18,7 @@ import java.net.MalformedURLException; import java.net.URLEncoder; import java.util.List; +import java.util.Properties; import org.whitesource.fs.FSAConfigProperties; @@ -65,8 +66,23 @@ public CxShragaClient(String serverUrl, String username, String password, String } //API Scans methods + public String getClientVersion() { + String version = ""; + try { + Properties properties = new Properties(); + java.io.InputStream is = getClass().getClassLoader().getResourceAsStream("common.properties"); + if (is != null) { + properties.load(is); + version = properties.getProperty("version"); + } + } catch (Exception e) { + + } + return version; + } public void init() throws CxClientException, IOException { - log.info("Initializing Cx client"); + + log.info("Initializing Cx client ["+ getClientVersion()+"]"); login(); resolveTeam(); if (config.getSastEnabled()) { diff --git a/src/main/java/com/cx/restclient/common/ErrorUtil.java b/src/main/java/com/cx/restclient/common/ErrorUtil.java deleted file mode 100644 index 208b69e8..00000000 --- a/src/main/java/com/cx/restclient/common/ErrorUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.cx.restclient.common; - -import org.apache.http.HttpStatus; - -import java.util.HashSet; -import java.util.Set; - -/** - * Created by shaulv on 6/20/2018. - */ -public class ErrorUtil { - - private ErrorUtil() { - - } - - private static Set serverErrorCodes = new HashSet<>(); - - static { - serverErrorCodes.add(HttpStatus.SC_INTERNAL_SERVER_ERROR); - serverErrorCodes.add(HttpStatus.SC_NOT_IMPLEMENTED); - serverErrorCodes.add(HttpStatus.SC_BAD_GATEWAY); - serverErrorCodes.add(HttpStatus.SC_SERVICE_UNAVAILABLE); - serverErrorCodes.add(HttpStatus.SC_GATEWAY_TIMEOUT); - serverErrorCodes.add(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED); - serverErrorCodes.add(HttpStatus.SC_INSUFFICIENT_STORAGE); - } - - public static boolean isServerErrorCodes(int errorCodes) { - if(serverErrorCodes.contains(errorCodes)) { - return true; - } - return false; - } -} diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index 18fdf4bf..5d87a7ed 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -41,7 +41,7 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) try { obj = getStatus(taskId); status = ((BaseStatus) obj).getBaseStatus(); - log.info(status.value()); + log.debug(status.value()); } catch (Exception e) { log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); retry--; diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index dcda4b49..55227448 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -2,7 +2,6 @@ import com.cx.restclient.common.ErrorMessage; -import com.cx.restclient.common.ErrorUtil; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.fasterxml.jackson.databind.JavaType; @@ -71,9 +70,6 @@ private static T convertToCollectionObject(HttpResponse response, JavaType j public static void validateResponse(HttpResponse response, int status, String message) throws CxClientException { if (response.getStatusLine().getStatusCode() != status) { - if (ErrorUtil.isServerErrorCodes(response.getStatusLine().getStatusCode())) { - throw new CxClientException(ErrorMessage.SERVICE_UNAVAILABLE.getErrorMessage()); - } String responseBody = extractResponseBody(response); responseBody = responseBody.replace("{", "").replace("}", "").replace(System.getProperty("line.separator"), " ").replace(" ", ""); throw new CxHTTPClientException(response.getStatusLine().getStatusCode(), message + ": " + responseBody); From 822aaf6d74a2445669cf890135658f74b2c1035e Mon Sep 17 00:00:00 2001 From: Gal Nussbaum Date: Mon, 25 Feb 2019 15:04:00 +0200 Subject: [PATCH 059/473] add common version to the log --- src/main/resources/common.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/resources/common.properties diff --git a/src/main/resources/common.properties b/src/main/resources/common.properties new file mode 100644 index 00000000..713c9158 --- /dev/null +++ b/src/main/resources/common.properties @@ -0,0 +1 @@ +version = ${project.version} \ No newline at end of file From 54d6f986666aab80f1cbe43b0aae45b85a3df368 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 24 Mar 2019 14:55:17 +0200 Subject: [PATCH 060/473] OSA security fix --- pom.xml | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pom.xml b/pom.xml index 2e0ecf33..f2e029d3 100644 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,32 @@ jackson-databind 2.9.8 + + org.bouncycastle + bcprov-jdk15on + 1.60 + + + org.codehaus.plexus + plexus-archiver + 3.6.0 + + + commons-compress + org.apache.commons + + + + + commons-collections + commons-collections + 3.2.2 + + + com.google.guava + guava + 27.0-jre + org.whitesource wss-unified-agent-main @@ -113,6 +139,22 @@ ch.qos.logback logback-classic + + bcpg-jdk15on + org.bouncycastle + + + plexus-archiver + org.codehaus.plexus + + + commons-collections + commons-collections + + + guava + com.google.guava + From 03dc9eba52298f609b043bb8ae147a0e713805d0 Mon Sep 17 00:00:00 2001 From: Checkmarx Admin Date: Mon, 1 Apr 2019 14:09:56 +0300 Subject: [PATCH 061/473] Update Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 79f5073f..8e66aad2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,6 @@ pipeline { parameters { - booleanParam(name: 'IsReleaseBuild', description: 'Check the box if you want to create a release build') + booleanParam(name: 'IsReleaseBuild', description: 'Check the box if you want to create a release build!') } agent { node { From 6324288f32ed3b33ddc36969133edd2ad1632dd9 Mon Sep 17 00:00:00 2001 From: Checkmarx Admin Date: Mon, 1 Apr 2019 14:16:02 +0300 Subject: [PATCH 062/473] Update Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8e66aad2..9654b554 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ pipeline { stage('Remove Snapshot') { steps { - powershell '''#------------------------------------------------------------------------------------------------------------ + powershell '''#------------------------------------------------------------------------------------------------------------- # REMOVE THE WORD SNAPSHOT (ONLY FOR RELEASE BUILDS) #------------------------------------------------------------------------------------------------------------ [string]$IsReleaseBuild = $ENV:IsReleaseBuild From ef53e49437a472eba804f6d495c5a1b9279d9e32 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Mon, 1 Apr 2019 14:50:23 +0300 Subject: [PATCH 063/473] get and print Cx Version --- .../com/cx/restclient/CxShragaClient.java | 42 ++++++++++++------- .../cx/restclient/common/CxGlobalMessage.java | 25 ----------- .../com/cx/restclient/common/CxPARAM.java | 14 +++++-- .../com/cx/restclient/common/ShragaUtils.java | 4 +- .../configuration/CxScanConfig.java | 12 +++++- .../java/com/cx/restclient/dto/CxVersion.java | 28 +++++++++++++ .../com/cx/restclient/osa/utils/OSAUtils.java | 1 - 7 files changed, 79 insertions(+), 47 deletions(-) delete mode 100644 src/main/java/com/cx/restclient/common/CxGlobalMessage.java create mode 100644 src/main/java/com/cx/restclient/dto/CxVersion.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 306d688f..afacbbdb 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,9 +1,9 @@ package com.cx.restclient; -import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; +import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; @@ -56,7 +56,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(),config.isUseSSOLogin(), log); + config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } @@ -80,15 +80,17 @@ public String getClientVersion() { } return version; } + public void init() throws CxClientException, IOException { - log.info("Initializing Cx client ["+ getClientVersion()+"]"); + log.info("Initializing Cx client [" + getClientVersion() + "]"); + getCxVersion(); login(); resolveTeam(); if (config.getSastEnabled()) { resolvePreset(); } - if (config.getEnablePolicyViolations()){ + if (config.getEnablePolicyViolations()) { resolveCxARMUrl(); } resolveProject(); @@ -130,16 +132,16 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc return osaResults; } - public void printIsProjectViolated(){ - log.info("-----------------------------------------------------------------------------------------"); - log.info("Policy Management: "); - log.info("--------------------"); + public void printIsProjectViolated() { if (config.getEnablePolicyViolations()) { - if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()){ - log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); + log.info("-----------------------------------------------------------------------------------------"); + log.info("Policy Management: "); + log.info("--------------------"); + if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()) { + log.info(PROJECT_POLICY_COMPLAINT_STATUS); log.info("-----------------------------------------------------------------------------------------"); - }else { - log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); + } else { + log.info(PROJECT_POLICY_VIOLATED_STATUS); if (!sastResults.getSastPolicies().isEmpty()) { log.info("SAST violated policies names: " + getPoliciesNames(sastResults.getSastPolicies())); } @@ -186,11 +188,21 @@ public void login() throws IOException, CxClientException { httpClient.login(); } + public void getCxVersion() throws IOException, CxClientException { + try { + config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); + log.info("Checkmarx Server version [" + config.getCxVersion().getVersion() + "]. Hotfix [" + config.getCxVersion().getHotFix() + "]."); + + } catch (Exception ex) { + log.debug("Checkmarx Server version [lower than 9.0]"); + } + } + public String getTeamIdByName(String teamName) throws CxClientException, IOException { - teamName = teamName.replace("\\","/"); + teamName = teamName.replace("\\", "/"); List allTeams = getTeamList(); for (Team team : allTeams) { - if (team.getFullName().replace("\\","/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive + if (team.getFullName().replace("\\", "/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } @@ -287,7 +299,7 @@ private void resolveProject() throws IOException, CxClientException { List projects = getProjectByName(config.getProjectName(), config.getTeamId()); if (projects == null || projects.isEmpty()) { // Project is new if (config.getDenyProject()) { - throw new CxClientException(DENY_NEW_PROJECT_ERROR.replace("{projectName}" , config.getProjectName())); + throw new CxClientException(DENY_NEW_PROJECT_ERROR.replace("{projectName}", config.getProjectName())); } //Create newProject CreateProjectRequest request = new CreateProjectRequest(config.getProjectName(), config.getTeamId(), config.getPublic()); diff --git a/src/main/java/com/cx/restclient/common/CxGlobalMessage.java b/src/main/java/com/cx/restclient/common/CxGlobalMessage.java deleted file mode 100644 index d0602b60..00000000 --- a/src/main/java/com/cx/restclient/common/CxGlobalMessage.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cx.restclient.common; - -/** - * Created by shaulv on 8/9/2018. - */ -public enum CxGlobalMessage { - - PROJECT_POLICY_STATUS("Project policy status : %s"), - PROJECT_POLICY_VIOLATED_STATUS("Project policy status : violated"), - PROJECT_POLICY_COMPLAINT_STATUS("Project policy status : compliant"); - - private String message; - - private CxGlobalMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public String formatMessage(Object... args) { - return String.format(message, args); - } -} diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index b9e3343d..730a447b 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -8,17 +8,23 @@ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; public static final String SSO_AUTHENTICATION = "auth/ssologin"; - public static final String ORIGIN_HEADER = "cxOrigin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; public static final String CREATE_PROJECT = "projects";//Create new project (default preset and configuration) - public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; - - public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; + public static final String CX_VERSION = "system/version"; public static final String CX_ARM_URL = "/Configurations/Portal"; public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + + + public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; + + public static final String ORIGIN_HEADER = "cxOrigin"; + public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; + public static final String PROJECT_POLICY_VIOLATED_STATUS = "Project policy status : violated"; + public static final String PROJECT_POLICY_COMPLAINT_STATUS = "Project policy status : compliant"; + public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index 966c89ca..bcccc413 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; +import static com.cx.restclient.common.CxPARAM.PROJECT_POLICY_VIOLATED_STATUS; + /** * Created by: dorg. * Date: 4/12/2018. @@ -30,7 +32,7 @@ public static String getBuildFailureResult(CxScanConfig config, SASTResults sast public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { boolean isPolicyViolated = config.getEnablePolicyViolations() && ((osaResults!=null && osaResults.getOsaPolicies().size() > 0) || (sastResults != null && sastResults.getSastPolicies().size() > 0)); if(isPolicyViolated) { - res.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); + res.append(PROJECT_POLICY_VIOLATED_STATUS).append("\n"); } return isPolicyViolated; } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 2646b965..9897fb33 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,6 +1,7 @@ package com.cx.restclient.configuration; +import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; @@ -17,6 +18,7 @@ public class CxScanConfig implements Serializable { private Boolean osaEnabled = false; private String cxOrigin; + private CxVersion cxVersion; private boolean disableCertificateValidation = false; private boolean useSSOLogin = false; @@ -70,7 +72,7 @@ public class CxScanConfig implements Serializable { private String cxARMUrl; private String[] paths; //remote source control - RemoteSourceTypes remoteType = null; + private RemoteSourceTypes remoteType = null; private String remoteSrcUser; private String remoteSrcPass; private String remoteSrcUrl; @@ -584,4 +586,12 @@ public Boolean getGenerateXmlReport() { public void setGenerateXmlReport(Boolean generateXmlReport) { this.generateXmlReport = generateXmlReport; } + + public CxVersion getCxVersion() { + return cxVersion; + } + + public void setCxVersion(CxVersion cxVersion) { + this.cxVersion = cxVersion; + } } diff --git a/src/main/java/com/cx/restclient/dto/CxVersion.java b/src/main/java/com/cx/restclient/dto/CxVersion.java new file mode 100644 index 00000000..6d1d2d6e --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/CxVersion.java @@ -0,0 +1,28 @@ +package com.cx.restclient.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by Galn on 4/1/2019. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CxVersion { + private String version; + private String hotFix; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHotFix() { + return hotFix; + } + + public void setHotFix(String hotFix) { + this.hotFix = hotFix; + } +} diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 92d87ef8..c7246594 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -1,6 +1,5 @@ package com.cx.restclient.osa.utils; -import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; From d4e9b3aee8c2c05132127fdfc3dc2c784fe07e05 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 2 Apr 2019 15:35:30 +0300 Subject: [PATCH 064/473] adding Proxy --- .../com/cx/restclient/CxShragaClient.java | 8 +- .../configuration/CxScanConfig.java | 13 ++- .../java/com/cx/restclient/dto/CxProxy.java | 80 +++++++++++++++++++ .../restclient/httpClient/CxHttpClient.java | 30 ++++++- .../configuration/CxScanConfigTest.java | 4 +- 5 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/CxProxy.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index afacbbdb..b05c5b3b 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -3,6 +3,7 @@ import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; +import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; import com.cx.restclient.exception.CxClientException; @@ -56,13 +57,14 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); + config.isDisableCertificateValidation(), config.isUseSSOLogin(),config.getProxy(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } - public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { - this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); + //For Test Connection + public CxShragaClient(String serverUrl, String username, String password, CxProxy proxy, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { + this(new CxScanConfig(serverUrl, username, password, origin, proxy, disableCertificateValidation), log); } //API Scans methods diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 9897fb33..d0b855e6 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,6 +1,7 @@ package com.cx.restclient.configuration; +import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; @@ -28,6 +29,7 @@ public class CxScanConfig implements Serializable { private String username; private String password; private String url; + private CxProxy proxy; private String projectName; private String teamPath; private String teamId; @@ -87,12 +89,13 @@ public class CxScanConfig implements Serializable { public CxScanConfig() { } - public CxScanConfig(String url, String username, String password, String cxOrigin, boolean disableCertificateValidation) { + public CxScanConfig(String url, String username, String password, String cxOrigin, CxProxy proxy, boolean disableCertificateValidation) { this.url = url; this.username = username; this.password = password; this.cxOrigin = cxOrigin; this.disableCertificateValidation = disableCertificateValidation; + this.proxy = proxy; } public Boolean getSastEnabled() { @@ -179,6 +182,14 @@ public void setUrl(String url) { this.url = url; } + public CxProxy getProxy() { + return proxy; + } + + public void setProxy(CxProxy proxy) { + this.proxy = proxy; + } + public String getProjectName() { return projectName; } diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java new file mode 100644 index 00000000..6b99c275 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/CxProxy.java @@ -0,0 +1,80 @@ +package com.cx.restclient.dto; + +/** + * Created by Galn on 4/2/2019. + */ +public class CxProxy { + private Boolean useProxy; + private String proxyHost; + private Integer proxyPort; + private String scheme; + private String proxyUser; + private String proxyPass; + + + public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme, String proxyUser, String proxyPass) { + this.useProxy = useProxy; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.scheme = scheme; + this.proxyUser = proxyUser; + this.proxyPass = proxyPass; + } + + public CxProxy() { + } + + public Boolean getUseProxy() { + return useProxy; + } + + public void setUseProxy(Boolean useProxy) { + this.useProxy = useProxy; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public Integer getProxyPort() { + return proxyPort; + } + + public void setProxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + } + + public void setProxyPort(String proxyPort) { + try { + this.proxyPort = Integer.parseInt(proxyPort); + }catch (Exception ex){} + } + + public String getScheme() { + return scheme; + } + + public void setScheme(String scheme) { + this.scheme = scheme; + } + + public String getProxyUser() { + return proxyUser; + } + + public void setProxyUser(String proxyUser) { + this.proxyUser = proxyUser; + } + + public String getProxyPass() { + return proxyPass; + } + + public void setProxyPass(String proxyPass) { + this.proxyPass = proxyPass; + } +} diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 45ed0604..9ee3d81e 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -2,12 +2,16 @@ import com.cx.restclient.common.ErrorMessage; import com.cx.restclient.common.UrlUtils; +import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import org.apache.http.*; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; @@ -16,7 +20,9 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; @@ -53,6 +59,7 @@ public class CxHttpClient { private final String username; private final String password; private String cxOrigin; + private CxProxy proxy; private CookieStore cookieStore; private String cookies; @@ -95,12 +102,13 @@ public void process(HttpResponse httpResponse, HttpContext httpContext) throws H }; - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, CxProxy proxy, Logger logi) throws MalformedURLException { this.logi = logi; this.username = username; this.password = password; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; + this.proxy = proxy; //create httpclient HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); if (isSSO) { @@ -112,6 +120,25 @@ public CxHttpClient(String hostname, String username, String password, String or if (disableSSLValidation) { builder = disableCertificateValidation(builder, logi); } + + if (proxy != null && proxy.getUseProxy()) { + HttpHost proxyHostObject; + if (proxy.getScheme() != null) { + proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort(), proxy.getScheme()); + } else { + proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort()); + } + builder.setProxy(proxyHostObject); + + if (proxy.getProxyUser() != null && proxy.getProxyPass() != null) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(proxy.getProxyHost(), proxy.getProxyPort()), + new UsernamePasswordCredentials(proxy.getProxyUser(), proxy.getProxyPass())); + builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()).setDefaultCredentialsProvider(credsProvider); + } + // httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); + } + builder.useSystemProperties(); apacheClient = builder.build(); } @@ -125,7 +152,6 @@ public void login() throws IOException, CxClientException { UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); HttpPost post = new HttpPost(rootUri + AUTHENTICATION); token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); - } } diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java index 41819691..860b4d87 100644 --- a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -15,7 +15,7 @@ public class CxScanConfigTest { String password; String cxOrigin; boolean disableCertificateValidation = false; - private CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); + private CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, null, disableCertificateValidation); @Test public void CxScanConfig() { @@ -24,7 +24,7 @@ public void CxScanConfig() { String password = "password"; String cxOrigin = "cxOrigin"; boolean disableCertificateValidation = false; - CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); + CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, null,disableCertificateValidation); assertEquals("Incorrect URL", cxScanConfigWithParameters.getUrl(), url); assertEquals("Incorrect userName", cxScanConfigWithParameters.getUsername(), username); From e8910908bc4ef1a9de98494dcbd7cc52afaa03c9 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 2 Apr 2019 17:29:18 +0300 Subject: [PATCH 065/473] change scheme to proxyScheme --- src/main/java/com/cx/restclient/dto/CxProxy.java | 12 ++++++------ .../com/cx/restclient/httpClient/CxHttpClient.java | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java index 6b99c275..3e631834 100644 --- a/src/main/java/com/cx/restclient/dto/CxProxy.java +++ b/src/main/java/com/cx/restclient/dto/CxProxy.java @@ -7,7 +7,7 @@ public class CxProxy { private Boolean useProxy; private String proxyHost; private Integer proxyPort; - private String scheme; + private String proxyScheme; private String proxyUser; private String proxyPass; @@ -16,7 +16,7 @@ public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String sch this.useProxy = useProxy; this.proxyHost = proxyHost; this.proxyPort = proxyPort; - this.scheme = scheme; + this.proxyScheme = scheme; this.proxyUser = proxyUser; this.proxyPass = proxyPass; } @@ -54,12 +54,12 @@ public void setProxyPort(String proxyPort) { }catch (Exception ex){} } - public String getScheme() { - return scheme; + public String getProxyScheme() { + return proxyScheme; } - public void setScheme(String scheme) { - this.scheme = scheme; + public void setProxyScheme(String proxyScheme) { + this.proxyScheme = proxyScheme; } public String getProxyUser() { diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 9ee3d81e..b310568e 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -123,8 +123,8 @@ public CxHttpClient(String hostname, String username, String password, String or if (proxy != null && proxy.getUseProxy()) { HttpHost proxyHostObject; - if (proxy.getScheme() != null) { - proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort(), proxy.getScheme()); + if (proxy.getProxyScheme() != null) { + proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort(), proxy.getProxyScheme()); } else { proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort()); } From eed2936898c04e8027e4c27ce15245712f99db91 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 2 Apr 2019 18:06:10 +0300 Subject: [PATCH 066/473] change scheme to proxyScheme --- .../java/com/cx/restclient/dto/CxProxy.java | 20 ------------------- .../restclient/httpClient/CxHttpClient.java | 8 -------- 2 files changed, 28 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java index 3e631834..dd92b1d8 100644 --- a/src/main/java/com/cx/restclient/dto/CxProxy.java +++ b/src/main/java/com/cx/restclient/dto/CxProxy.java @@ -8,8 +8,6 @@ public class CxProxy { private String proxyHost; private Integer proxyPort; private String proxyScheme; - private String proxyUser; - private String proxyPass; public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme, String proxyUser, String proxyPass) { @@ -17,8 +15,6 @@ public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String sch this.proxyHost = proxyHost; this.proxyPort = proxyPort; this.proxyScheme = scheme; - this.proxyUser = proxyUser; - this.proxyPass = proxyPass; } public CxProxy() { @@ -61,20 +57,4 @@ public String getProxyScheme() { public void setProxyScheme(String proxyScheme) { this.proxyScheme = proxyScheme; } - - public String getProxyUser() { - return proxyUser; - } - - public void setProxyUser(String proxyUser) { - this.proxyUser = proxyUser; - } - - public String getProxyPass() { - return proxyPass; - } - - public void setProxyPass(String proxyPass) { - this.proxyPass = proxyPass; - } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index b310568e..7c9236b3 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -129,14 +129,6 @@ public CxHttpClient(String hostname, String username, String password, String or proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort()); } builder.setProxy(proxyHostObject); - - if (proxy.getProxyUser() != null && proxy.getProxyPass() != null) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope(proxy.getProxyHost(), proxy.getProxyPort()), - new UsernamePasswordCredentials(proxy.getProxyUser(), proxy.getProxyPass())); - builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()).setDefaultCredentialsProvider(credsProvider); - } - // httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } builder.useSystemProperties(); From dc297fe2195aa568868af4f99e657109994a4b61 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 2 Apr 2019 18:07:31 +0300 Subject: [PATCH 067/473] remove proxy user and pass --- src/main/java/com/cx/restclient/dto/CxProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java index dd92b1d8..59084b84 100644 --- a/src/main/java/com/cx/restclient/dto/CxProxy.java +++ b/src/main/java/com/cx/restclient/dto/CxProxy.java @@ -10,7 +10,7 @@ public class CxProxy { private String proxyScheme; - public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme, String proxyUser, String proxyPass) { + public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme) { this.useProxy = useProxy; this.proxyHost = proxyHost; this.proxyPort = proxyPort; From 1077a53b652ca2289716243765e95bdc87da9d5d Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 7 Apr 2019 16:05:28 +0300 Subject: [PATCH 068/473] remove proxy --- .../com/cx/restclient/CxShragaClient.java | 7 +++---- .../configuration/CxScanConfig.java | 14 +------------ .../restclient/httpClient/CxHttpClient.java | 20 +------------------ .../configuration/CxScanConfigTest.java | 4 ++-- 4 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index b05c5b3b..163196fc 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -3,7 +3,6 @@ import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; -import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; import com.cx.restclient.exception.CxClientException; @@ -57,14 +56,14 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(),config.getProxy(), log); + config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } //For Test Connection - public CxShragaClient(String serverUrl, String username, String password, CxProxy proxy, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { - this(new CxScanConfig(serverUrl, username, password, origin, proxy, disableCertificateValidation), log); + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { + this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } //API Scans methods diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index d0b855e6..e3011fbf 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,7 +1,5 @@ package com.cx.restclient.configuration; - -import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; @@ -29,7 +27,6 @@ public class CxScanConfig implements Serializable { private String username; private String password; private String url; - private CxProxy proxy; private String projectName; private String teamPath; private String teamId; @@ -89,13 +86,12 @@ public class CxScanConfig implements Serializable { public CxScanConfig() { } - public CxScanConfig(String url, String username, String password, String cxOrigin, CxProxy proxy, boolean disableCertificateValidation) { + public CxScanConfig(String url, String username, String password, String cxOrigin, boolean disableCertificateValidation) { this.url = url; this.username = username; this.password = password; this.cxOrigin = cxOrigin; this.disableCertificateValidation = disableCertificateValidation; - this.proxy = proxy; } public Boolean getSastEnabled() { @@ -182,14 +178,6 @@ public void setUrl(String url) { this.url = url; } - public CxProxy getProxy() { - return proxy; - } - - public void setProxy(CxProxy proxy) { - this.proxy = proxy; - } - public String getProjectName() { return projectName; } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 7c9236b3..669af29b 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -2,16 +2,12 @@ import com.cx.restclient.common.ErrorMessage; import com.cx.restclient.common.UrlUtils; -import com.cx.restclient.dto.CxProxy; import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import org.apache.http.*; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CookieStore; -import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; @@ -20,9 +16,7 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; @@ -59,7 +53,6 @@ public class CxHttpClient { private final String username; private final String password; private String cxOrigin; - private CxProxy proxy; private CookieStore cookieStore; private String cookies; @@ -102,13 +95,12 @@ public void process(HttpResponse httpResponse, HttpContext httpContext) throws H }; - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, CxProxy proxy, Logger logi) throws MalformedURLException { + public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { this.logi = logi; this.username = username; this.password = password; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; - this.proxy = proxy; //create httpclient HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); if (isSSO) { @@ -121,16 +113,6 @@ public CxHttpClient(String hostname, String username, String password, String or builder = disableCertificateValidation(builder, logi); } - if (proxy != null && proxy.getUseProxy()) { - HttpHost proxyHostObject; - if (proxy.getProxyScheme() != null) { - proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort(), proxy.getProxyScheme()); - } else { - proxyHostObject = new HttpHost(proxy.getProxyHost(), proxy.getProxyPort() == null ? 80 : proxy.getProxyPort()); - } - builder.setProxy(proxyHostObject); - } - builder.useSystemProperties(); apacheClient = builder.build(); } diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java index 860b4d87..7e109b0a 100644 --- a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -15,7 +15,7 @@ public class CxScanConfigTest { String password; String cxOrigin; boolean disableCertificateValidation = false; - private CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, null, disableCertificateValidation); + private CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); @Test public void CxScanConfig() { @@ -24,7 +24,7 @@ public void CxScanConfig() { String password = "password"; String cxOrigin = "cxOrigin"; boolean disableCertificateValidation = false; - CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, null,disableCertificateValidation); + CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin,disableCertificateValidation); assertEquals("Incorrect URL", cxScanConfigWithParameters.getUrl(), url); assertEquals("Incorrect userName", cxScanConfigWithParameters.getUsername(), username); From d8e4429003bc066db6408860d7f2d73db276a8a9 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Tue, 23 Apr 2019 16:17:22 +0300 Subject: [PATCH 069/473] log the hotfix only if greater than 0 --- src/main/java/com/cx/restclient/CxShragaClient.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 163196fc..b239fae1 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -192,10 +192,17 @@ public void login() throws IOException, CxClientException { public void getCxVersion() throws IOException, CxClientException { try { config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); - log.info("Checkmarx Server version [" + config.getCxVersion().getVersion() + "]. Hotfix [" + config.getCxVersion().getHotFix() + "]."); + String hotfix = ""; + try { + if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { + hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; + } + } catch (Exception ex){} + + log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); } catch (Exception ex) { - log.debug("Checkmarx Server version [lower than 9.0]"); + log.debug("Checkmarx server version [lower than 9.0]"); } } From e987a259a73bc6d2bc857d7f5fa602949244984b Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 28 Apr 2019 15:09:37 +0300 Subject: [PATCH 070/473] log the hotfix only if greater than 0 --- src/main/java/com/cx/restclient/common/ShragaUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index bcccc413..b47afd06 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -88,7 +88,7 @@ public static boolean isThresholdForNewResultExceeded(CxScanConfig config, SASTR private static boolean isSeverityExceeded(int result, Integer threshold, StringBuilder res, String severity, String severityType) { boolean fail = false; if (threshold != null && result > threshold) { - res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append("\n"); + res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append(". \n"); fail = true; } return fail; From b1ed467dffd90288ac600d343f635bbb59f5db8a Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 12 May 2019 14:48:27 +0300 Subject: [PATCH 071/473] add testi --- src/main/java/testi.java | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/main/java/testi.java diff --git a/src/main/java/testi.java b/src/main/java/testi.java new file mode 100644 index 00000000..94b85aba --- /dev/null +++ b/src/main/java/testi.java @@ -0,0 +1,148 @@ +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.common.ShragaUtils; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.sast.dto.SASTResults; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + + +/** + * Created by Galn on 04/03/2018. + */ +public class testi { + private static String DEFAULT_FILTER_PATTERNS = "!**/_cvs/**/*, !**/.svn/**/*, !**/.hg/**/*, !**/.git/**/*, !**/.bzr/**/*, !**/bin/**/*," + + "!**/obj/**/*, !**/backup/**/*, !**/.idea/**/*, !**/*.DS_Store, !**/*.ipr, !**/*.iws, " + + "!**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, " + + "!**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, " + + "!**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, " + + "!**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.swf, " + + "!**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, " + + "!**/*.tar.gz, !**/*.tar, !**/*.gz, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, " + + "!**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.war, !**/*.ear, " + + "!**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, " + + "!**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, !**/node_modules/**/*"; + + private static String DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS = "*.zip, *.tgz, *.war, *.ear"; + + + public static void main(String[] args) throws Exception { + + + + SASTResults sastResults = null; + // SASTResults lastSastResults = null; + OSAResults osaResults = null; + // OSAResults lastOsaResults = null; + Logger logi = LoggerFactory.getLogger("testush"); + + + CxScanConfig config = setConfigi(); + + + CxShragaClient shraga = new CxShragaClient(config, logi); + // shraga.getClientVersion(); + shraga.init(); + + try { + if (config.getOsaEnabled()) { + shraga.createOSAScan(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getSastEnabled()) { + shraga.createSASTScan(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getSastEnabled()) { + sastResults = shraga.waitForSASTResults(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getOsaEnabled()) { + osaResults = shraga.waitForOSAResults(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + //lastSastResults = shraga.getLatestSASTResults(); + // lastOsaResults = shraga.getLatestOSAResults(); + if (config.getEnablePolicyViolations()) { + shraga.printIsProjectViolated(); + } + //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); + String s = shraga.generateHTMLSummary(); + File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); + FileUtils.writeStringToFile(file, s); + + shraga.close(); + } + + + private static CxScanConfig setConfigi() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); + //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); + config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); + //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); + config.setUsername("adi.gido@checkmarx.com"); + //config.setPassword("Cx123456!"); + config.setPassword("Cx@123456"); + config.setAvoidDuplicateProjectScans(false); + + // config.setUrl("http://10.32.1.91"); + config.setUrl("https://sast.checkmarx.net"); + config.setCxOrigin("common"); + config.setProjectName("Bamboo Plugin - Main"); + //config.setProjectName("OSAPROJ"); + config.setPresetName("Default"); + //config.setPresetId(7); + config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); + //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); + config.setSastFolderExclusions(""); + config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); + config.setSastScanTimeoutInMinutes(null); + config.setScanComment(""); + config.setIncremental(false); + config.setSynchronous(true); + config.setSastThresholdsEnabled(false); + config.setSastHighThreshold(1); + config.setSastMediumThreshold(1); + config.setSastLowThreshold(1); + config.setGeneratePDFReport(true); + config.setOsaEnabled(false); + + + config.setOsaFilterPattern("");//TODO check + config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); + config.setOsaRunInstall(true); + config.setOsaThresholdsEnabled(true); + config.setOsaHighThreshold(10); + config.setOsaMediumThreshold(0); + config.setOsaLowThreshold(0); + config.setDenyProject(false); + config.setPublic(true); + //config.setUseSSOLogin(false); + //config.setZipFile(); + //config.setOsaDependenciesJson(); + config.setEnablePolicyViolations(true); + + return config; + } + +} From 49031079157639ab8eeddd0a72a19558bf610806 Mon Sep 17 00:00:00 2001 From: idana Date: Tue, 14 May 2019 11:37:53 +0300 Subject: [PATCH 072/473] added osa scan timeout --- src/main/java/com/cx/restclient/CxOSAClient.java | 3 ++- .../cx/restclient/configuration/CxScanConfig.java | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 387b7d35..10586091 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -95,7 +95,7 @@ private String resolveOSADependencies() throws JsonProcessingException { public OSAResults getOSAResults(String scanId, long projectId) throws CxClientException, InterruptedException, IOException { log.info("-------------------------------------Get CxOSA Results:-----------------------------------"); log.info("Waiting for OSA scan to finish"); - OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, -1, log); + OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId,this.config.getOsaScanTimeoutInMinutes(), log); log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); @@ -116,6 +116,7 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx return osaResults; } + private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus, long projectId) throws CxClientException, IOException { OSASummaryResults osaSummaryResults = getOSAScanSummaryResults(scanId); List osaLibraries = getOSALibraries(scanId); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index e3011fbf..81c9efcf 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.Serializable; + import org.whitesource.fs.FSAConfigProperties; /** @@ -39,6 +40,7 @@ public class CxScanConfig implements Serializable { private String sastFolderExclusions; private String sastFilterPattern; private Integer sastScanTimeoutInMinutes; + private Integer sastOsaScanTimeoutInMinutes; private String scanComment; private Boolean isIncremental = false; private Boolean isSynchronous = false; @@ -191,7 +193,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if(!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")&& !teamPath.startsWith(("/"))){ + if (!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\") && !teamPath.startsWith(("/"))) { teamPath = "\\" + teamPath; } this.teamPath = teamPath; @@ -269,6 +271,14 @@ public void setSastScanTimeoutInMinutes(Integer sastScanTimeoutInMinutes) { this.sastScanTimeoutInMinutes = sastScanTimeoutInMinutes; } + public Integer getOsaScanTimeoutInMinutes() { + return sastOsaScanTimeoutInMinutes == null ? -1 : sastOsaScanTimeoutInMinutes; + } + + public void setOsaScanTimeoutInMinutes(Integer sastOsaScanTimeoutInMinutes) { + this.sastOsaScanTimeoutInMinutes = sastOsaScanTimeoutInMinutes; + } + public String getScanComment() { return scanComment; } From 4231714431d1b77810831527bb49c76458854b4f Mon Sep 17 00:00:00 2001 From: idana Date: Sun, 19 May 2019 09:41:57 +0300 Subject: [PATCH 073/473] change osa time out variable name --- .../java/com/cx/restclient/configuration/CxScanConfig.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 81c9efcf..dd590f4b 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -40,7 +40,7 @@ public class CxScanConfig implements Serializable { private String sastFolderExclusions; private String sastFilterPattern; private Integer sastScanTimeoutInMinutes; - private Integer sastOsaScanTimeoutInMinutes; + private Integer osaScanTimeoutInMinutes; private String scanComment; private Boolean isIncremental = false; private Boolean isSynchronous = false; @@ -272,11 +272,11 @@ public void setSastScanTimeoutInMinutes(Integer sastScanTimeoutInMinutes) { } public Integer getOsaScanTimeoutInMinutes() { - return sastOsaScanTimeoutInMinutes == null ? -1 : sastOsaScanTimeoutInMinutes; + return osaScanTimeoutInMinutes == null ? -1 : osaScanTimeoutInMinutes; } public void setOsaScanTimeoutInMinutes(Integer sastOsaScanTimeoutInMinutes) { - this.sastOsaScanTimeoutInMinutes = sastOsaScanTimeoutInMinutes; + this.osaScanTimeoutInMinutes = sastOsaScanTimeoutInMinutes; } public String getScanComment() { From 3ad084d38e42b65e87a207837f3ace07f88f47a1 Mon Sep 17 00:00:00 2001 From: iland Date: Wed, 22 May 2019 11:52:28 +0300 Subject: [PATCH 074/473] bugid: removed unnecessary plugins CR_by: Adi --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f2e029d3..c8df945c 100644 --- a/pom.xml +++ b/pom.xml @@ -208,7 +208,7 @@ - + From c7acd2ba51df086a373e53852464a38d8935e3d3 Mon Sep 17 00:00:00 2001 From: CxGalnus Date: Sun, 26 May 2019 14:26:17 +0300 Subject: [PATCH 075/473] osa fixes --- pom.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c8df945c..049bc31c 100644 --- a/pom.xml +++ b/pom.xml @@ -98,13 +98,18 @@ com.fasterxml.jackson.core jackson-databind - 2.9.8 + 2.9.9 org.bouncycastle bcprov-jdk15on 1.60 + + plexus-utils + org.codehaus.plexus + 3.2.0 + org.codehaus.plexus plexus-archiver @@ -114,6 +119,10 @@ commons-compress org.apache.commons + + plexus-utils + org.codehaus.plexus + @@ -155,6 +164,10 @@ guava com.google.guava + + plexus-utils + org.codehaus.plexus + From 1e889bc89e270bc126d94490029c2fb710ba0fbe Mon Sep 17 00:00:00 2001 From: "DM\\orlyk" Date: Thu, 6 Jun 2019 10:45:59 +0300 Subject: [PATCH 076/473] Changing version to 9.00.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 049bc31c..f692740a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.0-SNAPSHOT + 9.00.0 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 9bdfe66e79eda9becc907d9e1ea6bb355f40b40c Mon Sep 17 00:00:00 2001 From: "DM\\orlyk" Date: Thu, 6 Jun 2019 15:27:30 +0300 Subject: [PATCH 077/473] Fix path replace separator - instead of replacing only one separator occurrence we should replace all separator occurrences --- src/main/java/com/cx/restclient/CxShragaClient.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index b239fae1..22599b0e 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -207,16 +207,24 @@ public void getCxVersion() throws IOException, CxClientException { } public String getTeamIdByName(String teamName) throws CxClientException, IOException { - teamName = teamName.replace("\\", "/"); + teamName = replaceDelimiters(teamName); List allTeams = getTeamList(); for (Team team : allTeams) { - if (team.getFullName().replace("\\", "/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive + String fullName = replaceDelimiters(team.getFullName()); + if (fullName.equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } throw new CxClientException("Could not resolve team ID from team name: " + teamName); } + private String replaceDelimiters(String teamName) { + while(teamName.contains("\\")) { + teamName = teamName.replace("\\", "/"); + } + return teamName; + } + public String getTeamNameById(String teamId) throws CxClientException, IOException { List allTeams = getTeamList(); for (Team team : allTeams) { From 43303413ab0864016d9578a11aacd7175f383572 Mon Sep 17 00:00:00 2001 From: "DM\\orlyk" Date: Thu, 6 Jun 2019 15:27:30 +0300 Subject: [PATCH 078/473] Fix path replace separator - instead of replacing only one separator occurrence we should replace all separator occurrences (cherry picked from commit 9bdfe66) --- src/main/java/com/cx/restclient/CxShragaClient.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index b239fae1..22599b0e 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -207,16 +207,24 @@ public void getCxVersion() throws IOException, CxClientException { } public String getTeamIdByName(String teamName) throws CxClientException, IOException { - teamName = teamName.replace("\\", "/"); + teamName = replaceDelimiters(teamName); List allTeams = getTeamList(); for (Team team : allTeams) { - if (team.getFullName().replace("\\", "/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive + String fullName = replaceDelimiters(team.getFullName()); + if (fullName.equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } throw new CxClientException("Could not resolve team ID from team name: " + teamName); } + private String replaceDelimiters(String teamName) { + while(teamName.contains("\\")) { + teamName = teamName.replace("\\", "/"); + } + return teamName; + } + public String getTeamNameById(String teamId) throws CxClientException, IOException { List allTeams = getTeamList(); for (Team team : allTeams) { From 231b53fc3686312d32dcfb30c9b63fe0cedb71f0 Mon Sep 17 00:00:00 2001 From: "DM\\orlyk" Date: Mon, 10 Jun 2019 11:39:48 +0300 Subject: [PATCH 079/473] Fix path replace separator - instead of replacing only one separator occurrence we should replace all separator occurrences (cherry picked from commit 9bdfe66) --- src/main/java/com/cx/restclient/CxShragaClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 22599b0e..370d887a 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -219,8 +219,9 @@ public String getTeamIdByName(String teamName) throws CxClientException, IOExcep } private String replaceDelimiters(String teamName) { - while(teamName.contains("\\")) { + while(teamName.contains("\\") || teamName.contains("//")) { teamName = teamName.replace("\\", "/"); + teamName = teamName.replace("//", "/"); } return teamName; } From e3b220f3c341932051dbfd4474457cf72430bc1f Mon Sep 17 00:00:00 2001 From: "DM\\orlyk" Date: Mon, 10 Jun 2019 11:39:48 +0300 Subject: [PATCH 080/473] Fix path replace separator - instead of replacing only one separator occurrence we should replace all separator occurrences (cherry picked from commit 9bdfe66) (cherry picked from commit 231b53f) --- src/main/java/com/cx/restclient/CxShragaClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 22599b0e..370d887a 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -219,8 +219,9 @@ public String getTeamIdByName(String teamName) throws CxClientException, IOExcep } private String replaceDelimiters(String teamName) { - while(teamName.contains("\\")) { + while(teamName.contains("\\") || teamName.contains("//")) { teamName = teamName.replace("\\", "/"); + teamName = teamName.replace("//", "/"); } return teamName; } From f7860a7ed6ba280cb77b57482021f993b73f2090 Mon Sep 17 00:00:00 2001 From: idanA Date: Tue, 18 Jun 2019 13:50:52 +0300 Subject: [PATCH 081/473] Fix for date parsing issue when creating report from a non english sast server --- .../cx/restclient/sast/dto/SASTResults.java | 30 ++++++++++------ .../sast/dto/SupportedLanguage.java | 36 +++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 955592ac..6b04f25f 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,6 +1,5 @@ package com.cx.restclient.sast.dto; -import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.cxArm.dto.Violation; import java.io.Serializable; @@ -37,10 +36,10 @@ public class SASTResults implements Serializable { private String sastProjectLink; private String sastPDFLink; - private String scanStart; - private String scanTime; - private String scanStartTime; - private String scanEndTime; + private String scanStart = ""; + private String scanTime = ""; + private String scanStartTime = ""; + private String scanEndTime = ""; private String filesScanned; private String LOC; @@ -336,14 +335,23 @@ private String formatToDisplayDate(Date date) { return new SimpleDateFormat(displayDatePattern, locale).format(date); } - private Date createStartDate(String scanStart) throws ParseException { - //"Sunday, February 26, 2017 12:17:09 PM" - String oldPattern = "EEEE, MMMM dd, yyyy hh:mm:ss a"; - Locale locale = Locale.ENGLISH; + private Date createStartDate(String scanStart) throws Exception { + DateFormat formatter; + Date formattedDate = null; - DateFormat oldDateFormat = new SimpleDateFormat(oldPattern, locale); + for (SupportedLanguage lang : SupportedLanguage.values()) { + try { + formatter = new SimpleDateFormat(lang.getFormat(), lang.getLocale()); + formattedDate = formatter.parse(scanStart); + break; + } catch (Exception ignored) { + } + } - return oldDateFormat.parse(scanStart); + if(formattedDate == null){ + throw new Exception(String.format("Failed parsing date [%s]", scanStart)); + } + return formattedDate; } private Date createTimeDate(String scanTime) throws ParseException { diff --git a/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java b/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java new file mode 100644 index 00000000..0d8e70d2 --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java @@ -0,0 +1,36 @@ +package com.cx.restclient.sast.dto; + +import java.util.Locale; + +public enum SupportedLanguage { + + ENGLISH_US(Locale.US, "EEEE, MMMM dd, yyyy hh:mm:ss a"), + ENGLISH_GB(Locale.ENGLISH, "dd MMMM yyyy hh:mm"), + FRENCH_FR(Locale.FRENCH, "EEEE dd MMMM yyyy hh:mm"), + PORTUGUESE_PT(new Locale("pt"), "dd 'de' MMMM 'de' yyyy hh:mm"), + SPANISH(new Locale("es"), "EEEE, dd 'de' MMMM 'de' YYYY hh:mm"), + RUSSIAN(new Locale("ru"), "dd MMMM yyyy 'г.' hh:mm"); + + //TODO: Add fitting format +// JAPANESE(new Locale("ja-JP"), "ss"), +// KOREAN(new Locale("ko-KR"), "ss"), +// PORTUGUESE_BR(new Locale("pt-BR"), "ss"), +// CHINESE_CN(new Locale("zn-CN"), "ss"), +// CHINESE_TW(new Locale("zn-TW"), "ss"); + + private final Locale locale; + private final String format; + + SupportedLanguage(Locale locale, String format) { + this.format = format; + this.locale = locale; + } + + public Locale getLocale() { + return locale; + } + + public String getFormat() { + return format; + } +} From cdd6d41efa57f6e005cc2281ba9006b12b102427 Mon Sep 17 00:00:00 2001 From: idana Date: Thu, 27 Jun 2019 08:52:21 +0300 Subject: [PATCH 082/473] added missing import --- src/main/java/com/cx/restclient/sast/dto/SASTResults.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 6b04f25f..0e9c2977 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,5 +1,6 @@ package com.cx.restclient.sast.dto; +import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.cxArm.dto.Violation; import java.io.Serializable; From 583ff96f6f1be097aebb7de36cc9d3de065c8e2e Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 3 Jul 2019 11:38:05 +0300 Subject: [PATCH 083/473] Violation DTO now implements Serializable --- src/main/java/com/cx/restclient/cxArm/dto/Violation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java index 5d214660..4e18a82a 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serializable; import java.util.Date; import static com.cx.restclient.common.ShragaUtils.formatDate; @@ -10,7 +11,7 @@ * Created by Galn on 7/5/2018. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Violation { +public class Violation implements Serializable { private String ruleId; private String ruleName; From 0c53225923202dfc04d0dfcb0859129131761e9a Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 3 Jul 2019 11:38:05 +0300 Subject: [PATCH 084/473] Violation DTO now implements Serializable --- src/main/java/com/cx/restclient/cxArm/dto/Violation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java index 5d214660..4e18a82a 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serializable; import java.util.Date; import static com.cx.restclient.common.ShragaUtils.formatDate; @@ -10,7 +11,7 @@ * Created by Galn on 7/5/2018. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Violation { +public class Violation implements Serializable { private String ruleId; private String ruleName; From a02d202de323b8250e57be1b9469d0d257f0aaad Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 3 Jul 2019 11:51:48 +0300 Subject: [PATCH 085/473] Violation DTO now implements Serializable updated common version to 9.20 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f692740a..09facca4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.0 + 9.20.0 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From ae9e166c3406d74c72ec5138be0461edf408f928 Mon Sep 17 00:00:00 2001 From: Alexey Kononov Date: Mon, 8 Jul 2019 10:38:22 +0300 Subject: [PATCH 086/473] Part of the fix for #178942 (PDF Link in report summary incorrect if SAST project name not same as Jenkins project name). The rest of the fix is in Jenkins client. --- src/main/java/com/cx/restclient/CxSASTClient.java | 3 --- .../com/cx/restclient/configuration/CxScanConfig.java | 11 ----------- .../java/com/cx/restclient/sast/dto/SASTResults.java | 6 ------ .../java/com/cx/restclient/sast/utils/SASTParam.java | 1 - 4 files changed, 21 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index e10d8080..18aadcf4 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -231,9 +231,6 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); - if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { - sastResults.setSastPDFLink(config.getProjectName(), Integer.toString(config.getJenkinsJob())); - } } } return sastResults; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index dd590f4b..1766b50f 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -82,9 +82,6 @@ public class CxScanConfig implements Serializable { private String remoteSrcBranch; private String preforceMode; - //for Jenkins PDF rport - private int jenkinsJob; - public CxScanConfig() { } @@ -580,14 +577,6 @@ public void setPreforceMode(String preforceMode) { this.preforceMode = preforceMode; } - public int getJenkinsJob() { - return jenkinsJob; - } - - public void setJenkinsJob(int jenkinsJob) { - this.jenkinsJob = jenkinsJob; - } - public Boolean getGenerateXmlReport() { return generateXmlReport; } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 0e9c2977..69d83fcf 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,7 +1,6 @@ package com.cx.restclient.sast.dto; import com.cx.restclient.cxArm.dto.Policy; -import com.cx.restclient.cxArm.dto.Violation; import java.io.Serializable; import java.text.DateFormat; @@ -10,7 +9,6 @@ import java.util.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; -import static com.cx.restclient.sast.utils.SASTParam.PDF_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -210,10 +208,6 @@ public void setSastPDFLink(String sastPDFLink) { this.sastPDFLink = sastPDFLink; } - public void setSastPDFLink(String projectName, String buildId) { - this.sastPDFLink = String.format(PDF_LINK_FORMAT, projectName, buildId); - } - public String getScanStart() { return scanStart; } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 1b9d0924..da289114 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -38,7 +38,6 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "/job/%s/%s/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; From cc51c1e501648dc1e252a7fc872fcadf74c7c1a6 Mon Sep 17 00:00:00 2001 From: Margarita <45617576+margaritalm@users.noreply.github.com> Date: Thu, 11 Jul 2019 10:55:56 +0300 Subject: [PATCH 087/473] Test margarita --- src/main/java/com/cx/restclient/CxShragaClient.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 370d887a..c7fd0686 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -20,6 +20,13 @@ import java.util.List; import java.util.Properties; + + + //Tests - Margarita + // ONLY MARGARITA tests public static final String SAST_SCAN_RESULTS_STATISTICS = "sast/scans/{scanId}/resultsStatistics"; + + + import org.whitesource.fs.FSAConfigProperties; import static com.cx.restclient.common.CxPARAM.*; @@ -346,4 +353,4 @@ private Project createNewProject(CreateProjectRequest request) throws CxClientEx StringEntity entity = new StringEntity(json); return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } -} \ No newline at end of file +} From f27d6ee85df256c699388e91506e37030b8a3db2 Mon Sep 17 00:00:00 2001 From: Margarita <45617576+margaritalm@users.noreply.github.com> Date: Thu, 11 Jul 2019 11:01:22 +0300 Subject: [PATCH 088/473] Update CxShragaClient.java --- src/main/java/com/cx/restclient/CxShragaClient.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index c7fd0686..fb36d4eb 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -20,13 +20,6 @@ import java.util.List; import java.util.Properties; - - - //Tests - Margarita - // ONLY MARGARITA tests public static final String SAST_SCAN_RESULTS_STATISTICS = "sast/scans/{scanId}/resultsStatistics"; - - - import org.whitesource.fs.FSAConfigProperties; import static com.cx.restclient.common.CxPARAM.*; From cd39a5f93a768729047e4424a764664383c0750a Mon Sep 17 00:00:00 2001 From: idanA Date: Wed, 24 Jul 2019 16:40:44 +0300 Subject: [PATCH 089/473] mvn path to FSA --- src/main/java/com/cx/restclient/CxOSAClient.java | 1 + .../cx/restclient/configuration/CxScanConfig.java | 11 +++++++++++ .../java/com/cx/restclient/osa/utils/OSAUtils.java | 13 +++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 10586091..2d8b5b4e 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -82,6 +82,7 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaArchiveIncludePatterns(), config.getSourceDir(), config.getOsaRunInstall(), + config.getMvnPath(), log); } ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index dd590f4b..a2c011c2 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -23,6 +23,8 @@ public class CxScanConfig implements Serializable { private boolean disableCertificateValidation = false; private boolean useSSOLogin = false; + private String mvnPath = ""; + private String sourceDir; private File reportsDir; private String username; @@ -603,4 +605,13 @@ public CxVersion getCxVersion() { public void setCxVersion(CxVersion cxVersion) { this.cxVersion = cxVersion; } + + public String getMvnPath() { + return mvnPath; + } + + public void setMvnPath(String mvnPath) { + this.mvnPath = mvnPath; + } + } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index c7246594..e7d85745 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; + import org.whitesource.fs.FSAConfigProperties; @@ -50,7 +51,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String mvnPath, Logger log) { FSAConfigProperties ret = new FSAConfigProperties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -69,6 +70,10 @@ public static FSAConfigProperties generateOSAScanConfiguration(String folderExcl ret.put("includes", INCLUDE_ALL_EXTENSIONS); } + if (StringUtils.isNotEmpty(mvnPath)) { + ret.put("maven.environmentPath", mvnPath); + } + if (StringUtils.isNotEmpty(excludesString)) { ret.put("excludes", excludesString); } @@ -94,9 +99,9 @@ public static FSAConfigProperties generateOSAScanConfiguration(String folderExcl ret.put("npm.runPreStep", "true"); ret.put("bower.runPreStep", "false"); ret.put("npm.ignoreScripts", "true"); - setResolveDependencies(ret,"true"); - }else { - setResolveDependencies(ret,"false"); + setResolveDependencies(ret, "true"); + } else { + setResolveDependencies(ret, "false"); } ret.put("d", scanFolder); From 767af58b4b6d8c474b18c25e5a99ab805f627ed5 Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 5 Aug 2019 14:00:02 +0300 Subject: [PATCH 090/473] version update --- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index e7d85745..37b1db36 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -25,7 +25,6 @@ * Created by Galn on 07/02/2018. */ public abstract class OSAUtils { - private static final String[] SUPPORTED_EXTENSIONS = {"jar", "war", "ear", "aar", "dll", "exe", "msi", "nupkg", "egg", "whl", "tar.gz", "gem", "deb", "udeb", "dmg", "drpm", "rpm", "pkg.tar.xz", "swf", "swc", "air", "apk", "zip", "gzip", "tar.bz2", "tgz", "c", "cc", "cp", "cpp", "css", "c++", "h", "hh", "hpp", "hxx", "h++", "m", "mm", "pch", "java", "c#", "cs", "csharp", "go", "goc", "js", "plx", "pm", "ph", "cgi", "fcgi", "psgi", "al", "perl", "t", "p6m", "p6l", "nqp,6pl", "6pm", From e9d9b980af1e6140228625d83197534ed2ee5f56 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 7 Aug 2019 19:17:29 +0300 Subject: [PATCH 091/473] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..937914ad --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,21 @@ +# Maven +# Build your Java project and run tests with Apache Maven. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/java + +trigger: +- master + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: Maven@3 + inputs: + mavenPomFile: 'pom.xml' + mavenOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: false + goals: 'package' From 36b211c837f954635eeb6474dd21fde0934ecb94 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 7 Aug 2019 19:21:24 +0300 Subject: [PATCH 092/473] PathToPublish target/cx-client-common-9.20.0.jar --- azure-pipelines.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 937914ad..2a30baff 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,3 +19,8 @@ steps: jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'package' +- task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: 'target/cx-client-common-9.20.0.jar' + ArtifactName: 'drop' + publishLocation: 'Container' \ No newline at end of file From 2f613e6ce4a1d3f8a186e25885db078e8ecf29fe Mon Sep 17 00:00:00 2001 From: OlegCx Date: Sun, 11 Aug 2019 16:02:43 +0300 Subject: [PATCH 093/473] Add condition step AB#47 --- azure-pipelines.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2a30baff..dbfdf11a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,4 +23,10 @@ steps: inputs: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' - publishLocation: 'Container' \ No newline at end of file + publishLocation: 'Container' +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + echo 'Hello world' + condition: succeeded() \ No newline at end of file From 30c96f091bdfd0394727f71f6f0a6c8b6707454f Mon Sep 17 00:00:00 2001 From: OlegCx Date: Sun, 11 Aug 2019 18:14:02 +0300 Subject: [PATCH 094/473] Remove condition #47 --- azure-pipelines.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dbfdf11a..1f956a80 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,9 +24,4 @@ steps: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' publishLocation: 'Container' -- task: Bash@3 - inputs: - targetType: 'inline' - script: | - echo 'Hello world' - condition: succeeded() \ No newline at end of file +- task: Bash@3 \ No newline at end of file From 6590fa9afef0b0a3927fedd6e39b075b2cd3022b Mon Sep 17 00:00:00 2001 From: OlegCx Date: Sun, 11 Aug 2019 18:16:20 +0300 Subject: [PATCH 095/473] Remove condition #47 --- azure-pipelines.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1f956a80..2a30baff 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,5 +23,4 @@ steps: inputs: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' - publishLocation: 'Container' -- task: Bash@3 \ No newline at end of file + publishLocation: 'Container' \ No newline at end of file From 004d0ed89e254f4edaedbf92508f7121daf7f326 Mon Sep 17 00:00:00 2001 From: idanA Date: Sun, 18 Aug 2019 11:21:36 +0300 Subject: [PATCH 096/473] reverted FSA to 18.7.2 --- pom.xml | 4 ++-- src/main/java/com/cx/restclient/CxOSAClient.java | 5 ++--- src/main/java/com/cx/restclient/CxShragaClient.java | 9 ++++----- .../com/cx/restclient/configuration/CxScanConfig.java | 9 ++++----- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 6 ++---- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index f692740a..ecb8198e 100644 --- a/pom.xml +++ b/pom.xml @@ -137,8 +137,8 @@ org.whitesource - wss-unified-agent-main - 18.12.2 + whitesource-fs-agent + 18.7.2 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 10586091..0f175d36 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -13,11 +13,10 @@ import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; -import org.whitesource.fs.FSAConfigProperties; - import java.io.IOException; import java.util.List; +import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -74,7 +73,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); - FSAConfigProperties scannerProperties = config.getOsaFsaConfig(); + Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { scannerProperties = OSAUtils.generateOSAScanConfiguration( config.getOsaFolderExclusions(), diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index fb36d4eb..4662f8a9 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -20,8 +20,6 @@ import java.util.List; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; @@ -197,7 +195,8 @@ public void getCxVersion() throws IOException, CxClientException { if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; } - } catch (Exception ex){} + } catch (Exception ex) { + } log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); @@ -219,7 +218,7 @@ public String getTeamIdByName(String teamName) throws CxClientException, IOExcep } private String replaceDelimiters(String teamName) { - while(teamName.contains("\\") || teamName.contains("//")) { + while (teamName.contains("\\") || teamName.contains("//")) { teamName = teamName.replace("\\", "/"); teamName = teamName.replace("//", "/"); } @@ -263,7 +262,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 1766b50f..07a42048 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -6,8 +6,7 @@ import java.io.File; import java.io.Serializable; - -import org.whitesource.fs.FSAConfigProperties; +import java.util.Properties; /** * Created by galn on 21/12/2016. @@ -64,7 +63,7 @@ public class CxScanConfig implements Serializable { private Integer osaHighThreshold; private Integer osaMediumThreshold; private Integer osaLowThreshold; - private FSAConfigProperties osaFsaConfig; //for MAVEN + private Properties osaFsaConfig; //for MAVEN private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; @@ -436,11 +435,11 @@ public void setOsaLowThreshold(Integer osaLowThreshold) { this.osaLowThreshold = osaLowThreshold; } - public FSAConfigProperties getOsaFsaConfig() { + public Properties getOsaFsaConfig() { return osaFsaConfig; } - public void setOsaFsaConfig(FSAConfigProperties osaFsaConfig) { + public void setOsaFsaConfig(Properties osaFsaConfig) { this.osaFsaConfig = osaFsaConfig; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index c7246594..3e85ed6d 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -15,8 +15,6 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; @@ -50,8 +48,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { - FSAConfigProperties ret = new FSAConfigProperties(); + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); From 4279f1440c6ad4e7edd1177775e142e2cfd53bf3 Mon Sep 17 00:00:00 2001 From: idana Date: Sun, 18 Aug 2019 11:21:36 +0300 Subject: [PATCH 097/473] reverted FSA to 18.7.2 --- pom.xml | 4 ++-- src/main/java/com/cx/restclient/CxOSAClient.java | 5 ++--- src/main/java/com/cx/restclient/CxShragaClient.java | 9 ++++----- .../cx/restclient/configuration/CxScanConfig.java | 9 ++++----- .../java/com/cx/restclient/osa/utils/OSAUtils.java | 12 +++--------- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 09facca4..6921b3ac 100644 --- a/pom.xml +++ b/pom.xml @@ -137,8 +137,8 @@ org.whitesource - wss-unified-agent-main - 18.12.2 + whitesource-fs-agent + 18.7.2 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 2d8b5b4e..22e47b8a 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -13,11 +13,10 @@ import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; -import org.whitesource.fs.FSAConfigProperties; - import java.io.IOException; import java.util.List; +import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; @@ -74,7 +73,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); - FSAConfigProperties scannerProperties = config.getOsaFsaConfig(); + Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { scannerProperties = OSAUtils.generateOSAScanConfiguration( config.getOsaFolderExclusions(), diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 370d887a..d2611504 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -20,8 +20,6 @@ import java.util.List; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; @@ -197,7 +195,8 @@ public void getCxVersion() throws IOException, CxClientException { if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; } - } catch (Exception ex){} + } catch (Exception ex) { + } log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); @@ -219,7 +218,7 @@ public String getTeamIdByName(String teamName) throws CxClientException, IOExcep } private String replaceDelimiters(String teamName) { - while(teamName.contains("\\") || teamName.contains("//")) { + while (teamName.contains("\\") || teamName.contains("//")) { teamName = teamName.replace("\\", "/"); teamName = teamName.replace("//", "/"); } @@ -263,7 +262,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index a2c011c2..e5beca4a 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -6,8 +6,7 @@ import java.io.File; import java.io.Serializable; - -import org.whitesource.fs.FSAConfigProperties; +import java.util.Properties; /** * Created by galn on 21/12/2016. @@ -66,7 +65,7 @@ public class CxScanConfig implements Serializable { private Integer osaHighThreshold; private Integer osaMediumThreshold; private Integer osaLowThreshold; - private FSAConfigProperties osaFsaConfig; //for MAVEN + private Properties osaFsaConfig; //for MAVEN private String osaDependenciesJson; private Boolean avoidDuplicateProjectScans = false; private boolean enablePolicyViolations = false; @@ -441,11 +440,11 @@ public void setOsaLowThreshold(Integer osaLowThreshold) { this.osaLowThreshold = osaLowThreshold; } - public FSAConfigProperties getOsaFsaConfig() { + public Properties getOsaFsaConfig() { return osaFsaConfig; } - public void setOsaFsaConfig(FSAConfigProperties osaFsaConfig) { + public void setOsaFsaConfig(Properties osaFsaConfig) { this.osaFsaConfig = osaFsaConfig; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 37b1db36..516e01bd 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -16,15 +16,13 @@ import java.util.Map; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - - import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; /** * Created by Galn on 07/02/2018. */ public abstract class OSAUtils { + private static final String[] SUPPORTED_EXTENSIONS = {"jar", "war", "ear", "aar", "dll", "exe", "msi", "nupkg", "egg", "whl", "tar.gz", "gem", "deb", "udeb", "dmg", "drpm", "rpm", "pkg.tar.xz", "swf", "swc", "air", "apk", "zip", "gzip", "tar.bz2", "tgz", "c", "cc", "cp", "cpp", "css", "c++", "h", "hh", "hpp", "hxx", "h++", "m", "mm", "pch", "java", "c#", "cs", "csharp", "go", "goc", "js", "plx", "pm", "ph", "cgi", "fcgi", "psgi", "al", "perl", "t", "p6m", "p6l", "nqp,6pl", "6pm", @@ -50,8 +48,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String mvnPath, Logger log) { - FSAConfigProperties ret = new FSAConfigProperties(); + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -69,10 +67,6 @@ public static FSAConfigProperties generateOSAScanConfiguration(String folderExcl ret.put("includes", INCLUDE_ALL_EXTENSIONS); } - if (StringUtils.isNotEmpty(mvnPath)) { - ret.put("maven.environmentPath", mvnPath); - } - if (StringUtils.isNotEmpty(excludesString)) { ret.put("excludes", excludesString); } From b7efadba0aefd5fa257a026cba42e43325ec31ac Mon Sep 17 00:00:00 2001 From: Alexey Kononov Date: Mon, 8 Jul 2019 10:38:22 +0300 Subject: [PATCH 098/473] Part of the fix for #178942 (PDF Link in report summary incorrect if SAST project name not same as Jenkins project name). The rest of the fix is in Jenkins client. --- src/main/java/com/cx/restclient/CxSASTClient.java | 3 --- .../com/cx/restclient/configuration/CxScanConfig.java | 11 ----------- .../java/com/cx/restclient/sast/dto/SASTResults.java | 6 ------ .../java/com/cx/restclient/sast/utils/SASTParam.java | 1 - 4 files changed, 21 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index e10d8080..18aadcf4 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -231,9 +231,6 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); - if (JENKINS.equalsIgnoreCase(config.getCxOrigin())) { - sastResults.setSastPDFLink(config.getProjectName(), Integer.toString(config.getJenkinsJob())); - } } } return sastResults; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index e5beca4a..56cdd75a 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -83,9 +83,6 @@ public class CxScanConfig implements Serializable { private String remoteSrcBranch; private String preforceMode; - //for Jenkins PDF rport - private int jenkinsJob; - public CxScanConfig() { } @@ -581,14 +578,6 @@ public void setPreforceMode(String preforceMode) { this.preforceMode = preforceMode; } - public int getJenkinsJob() { - return jenkinsJob; - } - - public void setJenkinsJob(int jenkinsJob) { - this.jenkinsJob = jenkinsJob; - } - public Boolean getGenerateXmlReport() { return generateXmlReport; } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 955592ac..d20d1739 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,7 +1,6 @@ package com.cx.restclient.sast.dto; import com.cx.restclient.cxArm.dto.Policy; -import com.cx.restclient.cxArm.dto.Violation; import java.io.Serializable; import java.text.DateFormat; @@ -10,7 +9,6 @@ import java.util.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; -import static com.cx.restclient.sast.utils.SASTParam.PDF_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -210,10 +208,6 @@ public void setSastPDFLink(String sastPDFLink) { this.sastPDFLink = sastPDFLink; } - public void setSastPDFLink(String projectName, String buildId) { - this.sastPDFLink = String.format(PDF_LINK_FORMAT, projectName, buildId); - } - public String getScanStart() { return scanStart; } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 1b9d0924..da289114 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -38,7 +38,6 @@ public class SASTParam { public static final String LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; public static final String SCAN_LINK_FORMAT = "/CxWebClient/ViewerMain.aspx?scanId=%s&ProjectID=%s"; public static final String PROJECT_LINK_FORMAT = "/CxWebClient/portal#/projectState/%d/Summary"; - public static final String PDF_LINK_FORMAT = "/job/%s/%s/checkmarx/pdfReport"; //REPORT PARAMS public static final String PDF_REPORT_NAME = "CxSASTReport"; From 0c43f6b959afd27c1e994a90d6560f77045eeb7e Mon Sep 17 00:00:00 2001 From: idana Date: Thu, 27 Jun 2019 08:52:21 +0300 Subject: [PATCH 099/473] added missing import --- .../cx/restclient/sast/dto/SASTResults.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index d20d1739..69d83fcf 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -35,10 +35,10 @@ public class SASTResults implements Serializable { private String sastProjectLink; private String sastPDFLink; - private String scanStart; - private String scanTime; - private String scanStartTime; - private String scanEndTime; + private String scanStart = ""; + private String scanTime = ""; + private String scanStartTime = ""; + private String scanEndTime = ""; private String filesScanned; private String LOC; @@ -330,14 +330,23 @@ private String formatToDisplayDate(Date date) { return new SimpleDateFormat(displayDatePattern, locale).format(date); } - private Date createStartDate(String scanStart) throws ParseException { - //"Sunday, February 26, 2017 12:17:09 PM" - String oldPattern = "EEEE, MMMM dd, yyyy hh:mm:ss a"; - Locale locale = Locale.ENGLISH; + private Date createStartDate(String scanStart) throws Exception { + DateFormat formatter; + Date formattedDate = null; - DateFormat oldDateFormat = new SimpleDateFormat(oldPattern, locale); + for (SupportedLanguage lang : SupportedLanguage.values()) { + try { + formatter = new SimpleDateFormat(lang.getFormat(), lang.getLocale()); + formattedDate = formatter.parse(scanStart); + break; + } catch (Exception ignored) { + } + } - return oldDateFormat.parse(scanStart); + if(formattedDate == null){ + throw new Exception(String.format("Failed parsing date [%s]", scanStart)); + } + return formattedDate; } private Date createTimeDate(String scanTime) throws ParseException { From 4505733cfabe92fb9f8272c48630c356857f5a4d Mon Sep 17 00:00:00 2001 From: idana Date: Tue, 18 Jun 2019 13:50:52 +0300 Subject: [PATCH 100/473] Fix for date parsing issue when creating report from a non english sast server --- .../cx/restclient/sast/dto/SASTResults.java | 1 + .../sast/dto/SupportedLanguage.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 69d83fcf..2b68faf7 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,6 +1,7 @@ package com.cx.restclient.sast.dto; import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.cxArm.dto.Violation; import java.io.Serializable; import java.text.DateFormat; diff --git a/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java b/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java new file mode 100644 index 00000000..0d8e70d2 --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java @@ -0,0 +1,36 @@ +package com.cx.restclient.sast.dto; + +import java.util.Locale; + +public enum SupportedLanguage { + + ENGLISH_US(Locale.US, "EEEE, MMMM dd, yyyy hh:mm:ss a"), + ENGLISH_GB(Locale.ENGLISH, "dd MMMM yyyy hh:mm"), + FRENCH_FR(Locale.FRENCH, "EEEE dd MMMM yyyy hh:mm"), + PORTUGUESE_PT(new Locale("pt"), "dd 'de' MMMM 'de' yyyy hh:mm"), + SPANISH(new Locale("es"), "EEEE, dd 'de' MMMM 'de' YYYY hh:mm"), + RUSSIAN(new Locale("ru"), "dd MMMM yyyy 'г.' hh:mm"); + + //TODO: Add fitting format +// JAPANESE(new Locale("ja-JP"), "ss"), +// KOREAN(new Locale("ko-KR"), "ss"), +// PORTUGUESE_BR(new Locale("pt-BR"), "ss"), +// CHINESE_CN(new Locale("zn-CN"), "ss"), +// CHINESE_TW(new Locale("zn-TW"), "ss"); + + private final Locale locale; + private final String format; + + SupportedLanguage(Locale locale, String format) { + this.format = format; + this.locale = locale; + } + + public Locale getLocale() { + return locale; + } + + public String getFormat() { + return format; + } +} From ed8b3217f96ed71bf0d6596aa34201080a894ba7 Mon Sep 17 00:00:00 2001 From: idana Date: Tue, 20 Aug 2019 14:19:41 +0300 Subject: [PATCH 101/473] merge from 9.0 --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxOSAClient.java | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 6921b3ac..2843fdea 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0 + 9.20.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 22e47b8a..081fbc8c 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -81,7 +81,6 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaArchiveIncludePatterns(), config.getSourceDir(), config.getOsaRunInstall(), - config.getMvnPath(), log); } ObjectMapper mapper = new ObjectMapper(); @@ -95,7 +94,7 @@ private String resolveOSADependencies() throws JsonProcessingException { public OSAResults getOSAResults(String scanId, long projectId) throws CxClientException, InterruptedException, IOException { log.info("-------------------------------------Get CxOSA Results:-----------------------------------"); log.info("Waiting for OSA scan to finish"); - OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId,this.config.getOsaScanTimeoutInMinutes(), log); + OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); @@ -105,7 +104,7 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx resolveOSAViolation(osaResults, projectId); } - OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); + OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); if (config.getReportsDir() != null) { writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), log); @@ -127,11 +126,11 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus return results; } - private void resolveOSAViolation(OSAResults osaResults, long projectId){ + private void resolveOSAViolation(OSAResults osaResults, long projectId) { try { getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) .forEach(osaResults::addPolicy); - }catch (Exception ex) { + } catch (Exception ex) { log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } } @@ -212,7 +211,7 @@ private void printOSAProgress(OSAScanStatus scanStatus, long startTime) { } private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClientException { - if (scanStatus ==null || Status.FAILED == scanStatus.getBaseStatus()) { + if (scanStatus == null || Status.FAILED == scanStatus.getBaseStatus()) { String failedMsg = scanStatus.getState() == null ? "" : "status [" + scanStatus.getState().getName() + "]. Reason: " + scanStatus.getState().getFailureReason(); throw new CxClientException("OSA scan cannot be completed. " + failedMsg); } From 10bcc3e51713cd27953d5c423bb2c80fdb3c268c Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 21 Aug 2019 14:14:00 +0300 Subject: [PATCH 102/473] 9.00.1 version, rolled back osa --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ecb8198e..4120d7fa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.0 + 9.00.1 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 977b9f204308184978d01c3360ffdc0316552ae6 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 21 Aug 2019 18:19:45 +0300 Subject: [PATCH 103/473] Upload build artifact to Github release #AB47 --- azure-pipelines.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2a30baff..ea03f3de 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,4 +23,12 @@ steps: inputs: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' - publishLocation: 'Container' \ No newline at end of file + publishLocation: 'Container' +- task: GitHubRelease@0 + inputs: + gitHubConnection: 'GithubService' + repositoryName: '$(Build.Repository.Name)' + action: 'create' + target: '$(Build.SourceVersion)' + tagSource: 'auto' + compareWith: 'lastFullRelease' \ No newline at end of file From 82ae90b2b6cefa9e27e58f821866c2fab730be22 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 21 Aug 2019 18:26:17 +0300 Subject: [PATCH 104/473] Upload build artifact to Github release AB#47 Add tag --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea03f3de..6290e3a6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,4 +31,5 @@ steps: action: 'create' target: '$(Build.SourceVersion)' tagSource: 'auto' + tagPattern: '$(MajorVersion).$(MinorVersion).$(PatchVersion)' compareWith: 'lastFullRelease' \ No newline at end of file From 5cc384dcb8804cff0b2542297fa0d3e3a984a84c Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 21 Aug 2019 18:33:29 +0300 Subject: [PATCH 105/473] Upload build artifact to Github release AB#47 Add tag Release_9.00.0 --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6290e3a6..25392c5e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,6 +30,6 @@ steps: repositoryName: '$(Build.Repository.Name)' action: 'create' target: '$(Build.SourceVersion)' - tagSource: 'auto' - tagPattern: '$(MajorVersion).$(MinorVersion).$(PatchVersion)' + tagSource: 'manual' + tag: 'Release_9.00.0' compareWith: 'lastFullRelease' \ No newline at end of file From ff0525e185838100896857b5f432aa2f86d24055 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 21 Aug 2019 18:48:09 +0300 Subject: [PATCH 106/473] Upload build artifact to Github release AB#47 Publish jar --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 25392c5e..798d08cc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,7 +29,7 @@ steps: gitHubConnection: 'GithubService' repositoryName: '$(Build.Repository.Name)' action: 'create' - target: '$(Build.SourceVersion)' + target: 'target/cx-client-common-9.20.0.jar' tagSource: 'manual' tag: 'Release_9.00.0' compareWith: 'lastFullRelease' \ No newline at end of file From dbadc3768f96b30df2d2e81c8170dbebfe6a8f8e Mon Sep 17 00:00:00 2001 From: OlegCx Date: Wed, 21 Aug 2019 18:51:21 +0300 Subject: [PATCH 107/473] Removethe upload build artifact to Github release AB#47 --- azure-pipelines.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 798d08cc..2a30baff 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,13 +23,4 @@ steps: inputs: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' - publishLocation: 'Container' -- task: GitHubRelease@0 - inputs: - gitHubConnection: 'GithubService' - repositoryName: '$(Build.Repository.Name)' - action: 'create' - target: 'target/cx-client-common-9.20.0.jar' - tagSource: 'manual' - tag: 'Release_9.00.0' - compareWith: 'lastFullRelease' \ No newline at end of file + publishLocation: 'Container' \ No newline at end of file From 1d260b4da1ac68fc1709d64ef01772c9300a7875 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Thu, 22 Aug 2019 17:33:11 +0300 Subject: [PATCH 108/473] Upload to S3 AB#47 --- azure-pipelines.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2a30baff..2330bf7e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,4 +23,12 @@ steps: inputs: PathtoPublish: 'target/cx-client-common-9.20.0.jar' ArtifactName: 'drop' - publishLocation: 'Container' \ No newline at end of file + publishLocation: 'Container' +- task: S3Upload@1 + inputs: + awsCredentials: 'CxSLDC bucket' + regionName: 'eu-west-1' + bucketName: 'cxsdlc' + sourceFolder: 'target/cx-client-common-9.20.0.jar' + globExpressions: '**' + filesAcl: 'bucket-owner-full-control' From df6e44782da8fd937347cde7cfa174297e70631f Mon Sep 17 00:00:00 2001 From: OlegCx Date: Thu, 22 Aug 2019 17:35:59 +0300 Subject: [PATCH 109/473] Upload to S3 AB#47 --- azure-pipelines.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2330bf7e..fa1284b2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,11 +19,6 @@ steps: jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'package' -- task: PublishBuildArtifacts@1 - inputs: - PathtoPublish: 'target/cx-client-common-9.20.0.jar' - ArtifactName: 'drop' - publishLocation: 'Container' - task: S3Upload@1 inputs: awsCredentials: 'CxSLDC bucket' From 6b1ddc108ac54bb9a0fdf20dc1f8d66735c73936 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Thu, 22 Aug 2019 17:37:52 +0300 Subject: [PATCH 110/473] Upload to S3 AB#47 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fa1284b2..a922c9f6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,6 +24,6 @@ steps: awsCredentials: 'CxSLDC bucket' regionName: 'eu-west-1' bucketName: 'cxsdlc' - sourceFolder: 'target/cx-client-common-9.20.0.jar' + sourceFolder: 'target/' globExpressions: '**' filesAcl: 'bucket-owner-full-control' From 673dc9e267622150c5a872d4747318130139276f Mon Sep 17 00:00:00 2001 From: OlegCx Date: Thu, 22 Aug 2019 17:46:28 +0300 Subject: [PATCH 111/473] Upload to S3 AB#47 --- azure-pipelines.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a922c9f6..34ce0b82 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,6 +24,9 @@ steps: awsCredentials: 'CxSLDC bucket' regionName: 'eu-west-1' bucketName: 'cxsdlc' - sourceFolder: 'target/' - globExpressions: '**' + sourceFolder: 'target' + globExpressions: 'cx-client-common-*.jar' + targetFolder: '$(Build.BuildID)' filesAcl: 'bucket-owner-full-control' + createBucket: true + From f5d3889e26de07ce2b892f02198683b500627b61 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Sun, 25 Aug 2019 13:37:14 +0300 Subject: [PATCH 112/473] AB#47 jar in build folder and also in bucket root --- azure-pipelines.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 34ce0b82..b539a6eb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,4 +29,13 @@ steps: targetFolder: '$(Build.BuildID)' filesAcl: 'bucket-owner-full-control' createBucket: true +- task: S3Upload@1 + inputs: + awsCredentials: 'CxSLDC bucket' + regionName: 'eu-west-1' + bucketName: 'cxsdlc' + sourceFolder: 'target' + globExpressions: 'cx-client-common-*.jar' + filesAcl: 'bucket-owner-full-control' + createBucket: true From ca0173fe905d3e076977b35fab5becde02d93ac2 Mon Sep 17 00:00:00 2001 From: OlegM Date: Tue, 3 Sep 2019 14:01:38 +0300 Subject: [PATCH 113/473] Enter for test new repo --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 4120d7fa..3841eaf5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ Enables web services access Checkmarx SAST and OSA scan. https://www.checkmarx.com + scm:git:git://github.com/CxRepositories/Cx-Client-Common.git scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git https://github.com/CxRepositories/Cx-Client-Common/tree/master From 37fe1d8332b5ec11581fd3015e5cf7234b324e8c Mon Sep 17 00:00:00 2001 From: OlegCx Date: Tue, 3 Sep 2019 14:16:57 +0300 Subject: [PATCH 114/473] AB#47 add build for Version_9.0 --- azure-pipelines.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..7ad3bb1f --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,40 @@ +# Maven +# Build your Java project and run tests with Apache Maven. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/java + +trigger: +- Version_9.00 + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: Maven@3 + inputs: + mavenPomFile: 'pom.xml' + mavenOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: false + goals: 'package' +- task: S3Upload@1 + inputs: + awsCredentials: 'CxSLDC bucket' + regionName: 'eu-west-1' + bucketName: 'cxsdlc' + sourceFolder: 'target' + globExpressions: 'cx-client-common-*.jar' + targetFolder: '$(Build.BuildID)' + filesAcl: 'bucket-owner-full-control' + createBucket: true +- task: S3Upload@1 + inputs: + awsCredentials: 'CxSLDC bucket' + regionName: 'eu-west-1' + bucketName: 'cxsdlc' + sourceFolder: 'target' + globExpressions: 'cx-client-common-*.jar' + filesAcl: 'bucket-owner-full-control' + createBucket: true From 9ef19125b6e70a6a5d233244a6e0242264c31ffe Mon Sep 17 00:00:00 2001 From: OlegM Date: Tue, 3 Sep 2019 14:51:09 +0300 Subject: [PATCH 115/473] merged with 9.00 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3841eaf5..0a2dc319 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.1 + 9.20.0 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 84d454bd9f2cc1457ca82fc70860408bf279e0b0 Mon Sep 17 00:00:00 2001 From: OlegM Date: Tue, 3 Sep 2019 14:56:06 +0300 Subject: [PATCH 116/473] reverted to properties --- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 37b1db36..c9c36616 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -16,9 +16,6 @@ import java.util.Map; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - - import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; /** @@ -50,8 +47,8 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static FSAConfigProperties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String mvnPath, Logger log) { - FSAConfigProperties ret = new FSAConfigProperties(); + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String mvnPath, Logger log) { + Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); From 1d8902b7e39eaf88c439d70f31ef8212e2afd260 Mon Sep 17 00:00:00 2001 From: OlegM Date: Tue, 3 Sep 2019 15:02:42 +0300 Subject: [PATCH 117/473] reverted to properties --- src/main/java/com/cx/restclient/CxShragaClient.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 370d887a..6ef7c742 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -20,8 +20,6 @@ import java.util.List; import java.util.Properties; -import org.whitesource.fs.FSAConfigProperties; - import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; @@ -263,7 +261,7 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } - public void setOsaFSAProperties(FSAConfigProperties fsaConfig) { //For CxMaven plugin + public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } From 5b96dc10f081e0cf3e4238eb0bf1fd268bc85882 Mon Sep 17 00:00:00 2001 From: OlegCx Date: Tue, 3 Sep 2019 16:38:35 +0300 Subject: [PATCH 118/473] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b539a6eb..b28d2a16 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,6 +19,10 @@ steps: jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'package' +- task: Bash@3 + inputs: + targetType: 'inline' + script: 'ls' - task: S3Upload@1 inputs: awsCredentials: 'CxSLDC bucket' From c5d367a10f1131c178d87b5189a0df4aeb5efe2f Mon Sep 17 00:00:00 2001 From: OlegCx Date: Tue, 3 Sep 2019 16:40:36 +0300 Subject: [PATCH 119/473] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b28d2a16..b539a6eb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,10 +19,6 @@ steps: jdkArchitectureOption: 'x64' publishJUnitResults: false goals: 'package' -- task: Bash@3 - inputs: - targetType: 'inline' - script: 'ls' - task: S3Upload@1 inputs: awsCredentials: 'CxSLDC bucket' From 252f3ecfa51900adc02fe94a13f05a8962705261 Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 4 Sep 2019 16:46:30 +0300 Subject: [PATCH 120/473] removed maven path references --- src/main/java/com/cx/restclient/CxOSAClient.java | 1 - .../com/cx/restclient/configuration/CxScanConfig.java | 10 ---------- .../java/com/cx/restclient/osa/utils/OSAUtils.java | 6 +----- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 22e47b8a..0f175d36 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -81,7 +81,6 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaArchiveIncludePatterns(), config.getSourceDir(), config.getOsaRunInstall(), - config.getMvnPath(), log); } ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 56cdd75a..71d5303d 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -22,8 +22,6 @@ public class CxScanConfig implements Serializable { private boolean disableCertificateValidation = false; private boolean useSSOLogin = false; - private String mvnPath = ""; - private String sourceDir; private File reportsDir; private String username; @@ -594,12 +592,4 @@ public void setCxVersion(CxVersion cxVersion) { this.cxVersion = cxVersion; } - public String getMvnPath() { - return mvnPath; - } - - public void setMvnPath(String mvnPath) { - this.mvnPath = mvnPath; - } - } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index c9c36616..6e341cf9 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -47,7 +47,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String mvnPath, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -66,10 +66,6 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("includes", INCLUDE_ALL_EXTENSIONS); } - if (StringUtils.isNotEmpty(mvnPath)) { - ret.put("maven.environmentPath", mvnPath); - } - if (StringUtils.isNotEmpty(excludesString)) { ret.put("excludes", excludesString); } From de6ad68dbecdb20a230becfda2116410aef538c1 Mon Sep 17 00:00:00 2001 From: idana Date: Sun, 8 Sep 2019 10:56:50 +0300 Subject: [PATCH 121/473] generate token --- pom.xml | 1 - .../com/cx/restclient/httpClient/CxHttpClient.java | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0a2dc319..6921b3ac 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,6 @@ Enables web services access Checkmarx SAST and OSA scan. https://www.checkmarx.com - scm:git:git://github.com/CxRepositories/Cx-Client-Common.git scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git https://github.com/CxRepositories/Cx-Client-Common/tree/master diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 669af29b..70dae443 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -78,7 +78,6 @@ public void process(HttpRequest httpRequest, HttpContext httpContext) throws Htt private final HttpResponseInterceptor responseFilter = new HttpResponseInterceptor() { - public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { for (org.apache.http.cookie.Cookie c : cookieStore.getCookies()) { if (CSRF_TOKEN_HEADER.equals(c.getName())) { @@ -123,12 +122,17 @@ public void login() throws IOException, CxClientException { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } else { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + token = generateToken(); } } + public TokenLoginResponse generateToken() throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } + private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { List parameters = new ArrayList(); parameters.add(new BasicNameValuePair("username", username)); From 571b69034b31a3a46db69e8e41bd94f7b79a0817 Mon Sep 17 00:00:00 2001 From: OlegM Date: Sun, 8 Sep 2019 12:18:23 +0300 Subject: [PATCH 122/473] fixed fsa 18.7.2-fix --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 3841eaf5..dfa4cfbf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.1 + 9.00.2 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -99,7 +99,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.9.9.3 org.bouncycastle @@ -139,7 +139,7 @@ org.whitesource whitesource-fs-agent - 18.7.2 + 18.7.2-fix javax.xml.bind From c37433e4577947e0243e5f3f8b3a9edb55ef23ac Mon Sep 17 00:00:00 2001 From: idanA Date: Mon, 9 Sep 2019 15:22:14 +0300 Subject: [PATCH 123/473] retrieve/revoke refresh token --- pom.xml | 2 +- .../com/cx/restclient/CxShragaClient.java | 17 +++- .../com/cx/restclient/common/CxPARAM.java | 1 + .../configuration/CxScanConfig.java | 16 ++++ .../cx/restclient/dto/TokenLoginResponse.java | 9 ++ .../restclient/httpClient/CxHttpClient.java | 86 +++++++++++++++---- .../com/cx/restclient/osa/dto/ClientType.java | 31 +++++++ 7 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/cx/restclient/osa/dto/ClientType.java diff --git a/pom.xml b/pom.xml index 6921b3ac..2843fdea 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0 + 9.20.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 6ef7c742..5ae54845 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -5,9 +5,11 @@ import com.cx.restclient.cxArm.dto.CxArmConfig; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; +import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; +import com.cx.restclient.osa.dto.ClientType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; import org.apache.http.client.HttpResponseException; @@ -54,6 +56,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), + config.getRefreshToken(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); @@ -187,6 +190,15 @@ public void login() throws IOException, CxClientException { httpClient.login(); } + public String getToken() throws IOException, CxClientException { + final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(ClientType.CLI); + return tokenLoginResponse.getRefresh_token(); + } + + public void revokeToken(String token) throws IOException, CxClientException { + httpClient.revokeToken(token); + } + public void getCxVersion() throws IOException, CxClientException { try { config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); @@ -195,7 +207,8 @@ public void getCxVersion() throws IOException, CxClientException { if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; } - } catch (Exception ex){} + } catch (Exception ex) { + } log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); @@ -217,7 +230,7 @@ public String getTeamIdByName(String teamName) throws CxClientException, IOExcep } private String replaceDelimiters(String teamName) { - while(teamName.contains("\\") || teamName.contains("//")) { + while (teamName.contains("\\") || teamName.contains("//")) { teamName = teamName.replace("\\", "/"); teamName = teamName.replace("//", "/"); } diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index 730a447b..dcca603b 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -7,6 +7,7 @@ */ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; + public static final String REVOCATION = "auth/identity/connect/revocation"; public static final String SSO_AUTHENTICATION = "auth/ssologin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 71d5303d..56e8440d 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -26,6 +26,7 @@ public class CxScanConfig implements Serializable { private File reportsDir; private String username; private String password; + private String refreshToken; private String url; private String projectName; private String teamPath; @@ -92,6 +93,13 @@ public CxScanConfig(String url, String username, String password, String cxOrigi this.disableCertificateValidation = disableCertificateValidation; } + public CxScanConfig(String url, String refreshToken, String cxOrigin, boolean disableCertificateValidation) { + this.url = url; + this.refreshToken = refreshToken; + this.cxOrigin = cxOrigin; + this.disableCertificateValidation = disableCertificateValidation; + } + public Boolean getSastEnabled() { return sastEnabled; } @@ -160,6 +168,14 @@ public void setUsername(String username) { this.username = username; } + public void setRefreshToken(String token) { + this.refreshToken = token; + } + + public String getRefreshToken() { + return refreshToken; + } + public String getPassword() { return password; } diff --git a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java index a7e0f39f..948aa620 100644 --- a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java +++ b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java @@ -10,6 +10,7 @@ public class TokenLoginResponse { private String access_token; private long expires_in; private String token_type; + private String refresh_token; public String getAccess_token() { return access_token; @@ -34,4 +35,12 @@ public String getToken_type() { public void setToken_type(String token_type) { this.token_type = token_type; } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 70dae443..f55b1b91 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -6,6 +6,7 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; +import com.cx.restclient.osa.dto.ClientType; import org.apache.http.*; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; @@ -29,6 +30,7 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -48,10 +50,11 @@ public class CxHttpClient { private Logger logi; private HttpClient apacheClient; - private TokenLoginResponse token; + private TokenLoginResponse tokenLoginResponse; private String rootUri; private final String username; private final String password; + private final String refreshtoken; private String cxOrigin; private CookieStore cookieStore; @@ -64,8 +67,8 @@ public class CxHttpClient { private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { httpRequest.addHeader(ORIGIN_HEADER, cxOrigin); - if (token != null) { - httpRequest.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); + if (tokenLoginResponse != null) { + httpRequest.addHeader(HttpHeaders.AUTHORIZATION, tokenLoginResponse.getToken_type() + " " + tokenLoginResponse.getAccess_token()); } if (csrfToken != null) { httpRequest.addHeader(CSRF_TOKEN_HEADER, csrfToken); @@ -94,10 +97,11 @@ public void process(HttpResponse httpResponse, HttpContext httpContext) throws H }; - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + public CxHttpClient(String hostname, String username, String password, String origin, String refreshToken, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { this.logi = logi; this.username = username; this.password = password; + this.refreshtoken = refreshToken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; //create httpclient @@ -117,34 +121,86 @@ public CxHttpClient(String hostname, String username, String password, String or } public void login() throws IOException, CxClientException { - - if (useSSo) { + if (refreshtoken != null) { + tokenLoginResponse = getAccessTokenFromRefreshToken(); + } else if (useSSo) { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } else { - token = generateToken(); + tokenLoginResponse = generateToken(); } } public TokenLoginResponse generateToken() throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + return generateToken(ClientType.RESOURCE_OWNER); + } + + public TokenLoginResponse generateToken(ClientType clientType) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(clientType); HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, - TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + try { + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + } + } + + private TokenLoginResponse getAccessTokenFromRefreshToken() throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateTokenFromRefreshEntity(ClientType.CLI); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + try { + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Failed to generate access token from refresh token failure error was: %s", e.getMessage()), e); + } + } + + public void revokeToken(String token) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateRevocationEntity(ClientType.CLI, token); + HttpPost post = new HttpPost(rootUri + REVOCATION); + try { + request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + String.class, HttpStatus.SC_OK, "revocation", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Token revocation failure error was: %s", e.getMessage()), e); + } } - private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { - List parameters = new ArrayList(); + private UrlEncodedFormEntity generateRevocationEntity(ClientType clientType, String token) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("token_type_hint", "refresh_token")); + parameters.add(new BasicNameValuePair("token", token)); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); + + return new UrlEncodedFormEntity(parameters, "utf-8"); + + } + + private UrlEncodedFormEntity generateUrlEncodedFormEntity(ClientType clientType) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("username", username)); parameters.add(new BasicNameValuePair("password", password)); parameters.add(new BasicNameValuePair("grant_type", "password")); - parameters.add(new BasicNameValuePair("scope", "sast_rest_api cxarm_api")); - parameters.add(new BasicNameValuePair("client_id", "resource_owner_client")); - parameters.add(new BasicNameValuePair("client_secret", "014DF517-39D1-4453-B7B3-9930C563627C")); + parameters.add(new BasicNameValuePair("scope", clientType.getScopes())); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); return new UrlEncodedFormEntity(parameters, "utf-8"); } + private UrlEncodedFormEntity generateTokenFromRefreshEntity(ClientType clientType) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); + parameters.add(new BasicNameValuePair("refresh_token", refreshtoken)); + + return new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8.name()); + } + //GET REQUEST public T getRequest(String relPath, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); diff --git a/src/main/java/com/cx/restclient/osa/dto/ClientType.java b/src/main/java/com/cx/restclient/osa/dto/ClientType.java new file mode 100644 index 00000000..8843318c --- /dev/null +++ b/src/main/java/com/cx/restclient/osa/dto/ClientType.java @@ -0,0 +1,31 @@ +package com.cx.restclient.osa.dto; + +public enum ClientType { + + RESOURCE_OWNER("resource_owner_client", "sast_rest_api cxarm_api", + "014DF517-39D1-4453-B7B3-9930C563627C"), + CLI("cli_client", "sast_rest_api offline_access", + "B9D84EA8-E476-4E83-A628-8A342D74D3BD"); + + private String clientId; + private String scopes; + private String clientSecret; + + ClientType(String clientId, String scopes, String clientSecret) { + this.clientId = clientId; + this.scopes = scopes; + this.clientSecret = clientSecret; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getScopes() { + return scopes; + } + + public String getClientId() { + return clientId; + } +} From 9d1e27852f97a5d33e441b5b449a8a6d7f6f48b5 Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 11 Sep 2019 11:43:49 +0300 Subject: [PATCH 124/473] added missing dependencies for JDK11 --- pom.xml | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2843fdea..4efe2989 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,9 @@ UTF-8 + 1.2.0 + 2.3.0 + 1.18.6 Checkmarx @@ -78,7 +81,7 @@ org.slf4j slf4j-simple - 1.6.1 + 1.6.2 org.freemarker @@ -170,6 +173,41 @@ + + + + com.sun.activation + javax.activation + ${javax.activation.version} + + + + javax.xml.bind + jaxb-api + ${jaxb.api.version} + + + + com.sun.xml.bind + jaxb-core + ${jaxb.api.version} + + + + com.sun.xml.bind + jaxb-impl + ${jaxb.api.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + From 3a14b40bd56a4850b1cdda23c6f81607dcbb51ab Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 16 Sep 2019 17:00:20 +0300 Subject: [PATCH 125/473] added remote scan --- pom.xml | 2 +- .../java/com/cx/restclient/CxSASTClient.java | 22 ++++++++++++++----- .../restclient/dto/RemoteSourceRequest.java | 4 ++-- .../cx/restclient/sast/utils/SASTParam.java | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4efe2989..d047626b 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ org.apache.httpcomponents httpcore - 4.4 + 4.4.12 com.fasterxml.jackson.core diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 18aadcf4..f7b38cef 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -159,6 +159,7 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient HttpEntity entity; RemoteSourceRequest req = new RemoteSourceRequest(config); RemoteSourceTypes type = req.getType(); + boolean isSSH = false; switch (type) { case SVN: @@ -182,16 +183,17 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity(new Gson().toJson(req), ContentType.APPLICATION_JSON); break; case GIT: - if (req.getPrivateKey().length < 1) { + if (req.getPrivateKey().length == 0) { Map content = new HashMap<>(); content.put("url", config.getRemoteSrcUrl()); content.put("branch", config.getRemoteSrcBranch()); entity = new StringEntity(new JSONObject(content).toString(), ContentType.APPLICATION_JSON); } else { + isSSH = true; builder = MultipartEntityBuilder.create(); builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); + builder.addBinaryBody("privateKey", new byte[0], ContentType.MULTIPART_FORM_DATA, null); entity = builder.build(); } break; @@ -200,7 +202,14 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity(""); } - return createRemoteSourceScan(projectId, entity, type.value()).getId(); + createRemoteSourceRequest(projectId, entity, type.value(), isSSH); + + CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); + log.info("Sending SAST scan request"); + CxID createScanResponse = createScan(scanRequest); + log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); + + return createScanResponse.getId(); } @@ -331,8 +340,11 @@ private CxID createScan(CreateScanRequest request) throws CxClientException, IOE return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } - private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { - return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { + final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), CONTENT_TYPE_APPLICATION_JSON_V1, + entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + + return cxID; } private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientException, IOException { diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java index 095b5f60..06ed5316 100644 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -13,7 +13,7 @@ public class RemoteSourceRequest { private String userName; private String password; private RemoteSourceTypes type; - private transient String browseMode; + private transient String browseMode; ; public RemoteSourceRequest() { @@ -24,7 +24,7 @@ public RemoteSourceRequest(CxScanConfig config) { this.password = config.getRemoteSrcPass(); this.url = config.getRemoteSrcUrl(); this.port = config.getRemoteSrcPort(); - this.privateKey = config.getRemoteSrcKeyFile(); + this.privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); this.paths = config.getPaths(); this.type = config.getRemoteType(); } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index da289114..2ccf5a4e 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -18,7 +18,7 @@ public class SASTParam { public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; - public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/{projectId}/sourceCode/remoteSettings/{sourceType}";//todo maybe with ssh? + public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/%s/sourceCode/remoteSettings/%s/%s"; From 295b361bfb4b24ed5553b0cfe74e31b92c132c6b Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 23 Sep 2019 17:11:20 +0300 Subject: [PATCH 126/473] removed double space --- src/main/java/com/cx/restclient/common/ShragaUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index b47afd06..c599d30c 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -45,9 +45,9 @@ public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastR thresholdExceeded |= isSeverityExceeded(sastResults.getLow(), config.getSastLowThreshold(), res, "low", "CxSAST "); } if (config.isOSAThresholdEffectivelyEnabled() && osaResults != null && osaResults.isOsaResultsReady()) { - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalHighVulnerabilities(), config.getOsaHighThreshold(), res, "high", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalMediumVulnerabilities(), config.getOsaMediumThreshold(), res, "medium", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalLowVulnerabilities(), config.getOsaLowThreshold(), res, "low", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalHighVulnerabilities(), config.getOsaHighThreshold(), res, "high", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalMediumVulnerabilities(), config.getOsaMediumThreshold(), res, "medium", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalLowVulnerabilities(), config.getOsaLowThreshold(), res, "low", "CxOSA "); } return thresholdExceeded; } From b6859d29f19e968cec54b148be18bba06c013841 Mon Sep 17 00:00:00 2001 From: idana Date: Thu, 3 Oct 2019 14:14:30 +0300 Subject: [PATCH 127/473] added option to specify separate location for osa source --- src/main/java/com/cx/restclient/CxOSAClient.java | 1 + .../com/cx/restclient/configuration/CxScanConfig.java | 9 +++++++++ src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 5 ++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 0f175d36..d15458dc 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -81,6 +81,7 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaArchiveIncludePatterns(), config.getSourceDir(), config.getOsaRunInstall(), + config.getOsaLocationPath(), log); } ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 56e8440d..0fc88952 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -23,6 +23,7 @@ public class CxScanConfig implements Serializable { private boolean useSSOLogin = false; private String sourceDir; + private String osaLocationPath; private File reportsDir; private String username; private String password; @@ -152,6 +153,14 @@ public void setSourceDir(String sourceDir) { this.sourceDir = sourceDir; } + public String getOsaLocationPath() { + return osaLocationPath; + } + + public void setOsaLocationPath(String osaLocationPath) { + this.osaLocationPath = osaLocationPath; + } + public File getReportsDir() { return reportsDir; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 6e341cf9..649971d3 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -47,7 +47,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -96,8 +96,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S setResolveDependencies(ret, "false"); } - ret.put("d", scanFolder); - + ret.put("d", osaLocationPath == null ? scanFolder : osaLocationPath); return ret; } From 46ff6d0348b8964317e960a486f2b0115508df23 Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Mon, 14 Oct 2019 15:29:49 +0300 Subject: [PATCH 128/473] Update pom.xml --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2843fdea..900efb62 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.9.10 org.bouncycastle @@ -320,4 +320,4 @@ - \ No newline at end of file + From bacf93674fcbf0536d8936a636364be2d792c847 Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Mon, 14 Oct 2019 15:34:48 +0300 Subject: [PATCH 129/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 900efb62..b101f307 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10 + 2.9.9 org.bouncycastle From a023b2f13f5bcabf140f821237d974cd46b68f1c Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Tue, 5 Nov 2019 15:19:58 +0200 Subject: [PATCH 130/473] Implemented missing common features used in CLI 9.2 Plugin --- pom.xml | 6 +- .../java/com/cx/restclient/CxOSAClient.java | 44 +++---- .../java/com/cx/restclient/CxSASTClient.java | 88 +++++++++----- .../java/com/cx/restclient/common/Waiter.java | 5 +- .../configuration/CxScanConfig.java | 90 +++++++++++++- .../restclient/dto/RemoteSourceRequest.java | 111 ++++++++++++------ .../com/cx/restclient/osa/utils/OSAUtils.java | 53 +++++++-- .../cx/restclient/sast/utils/SASTUtils.java | 19 +++ 8 files changed, 308 insertions(+), 108 deletions(-) diff --git a/pom.xml b/pom.xml index d047626b..0ef2dfea 100644 --- a/pom.xml +++ b/pom.xml @@ -139,9 +139,9 @@ 27.0-jre - org.whitesource - whitesource-fs-agent - 18.7.2 + com.checkmarx + cx-ws-fs-agent + 18.7.2.3 javax.xml.bind diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index d15458dc..e442977f 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -33,27 +33,30 @@ class CxOSAClient { private CxHttpClient httpClient; private Logger log; private CxScanConfig config; - private Waiter osaWaiter = new Waiter("CxOSA scan", 20) { - @Override - public OSAScanStatus getStatus(String id) throws CxClientException, IOException { - return getOSAScanStatus(id); - } - - @Override - public void printProgress(OSAScanStatus scanStatus) { - printOSAProgress(scanStatus, getStartTimeSec()); - } - - @Override - public OSAScanStatus resolveStatus(OSAScanStatus scanStatus) throws CxClientException { - return resolveOSAStatus(scanStatus); - } - }; + private Waiter osaWaiter; public CxOSAClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; this.httpClient = client; this.config = config; + int interval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; + int retry = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + osaWaiter = new Waiter("CxOSA scan", interval, retry) { + @Override + public OSAScanStatus getStatus(String id) throws CxClientException, IOException { + return getOSAScanStatus(id); + } + + @Override + public void printProgress(OSAScanStatus scanStatus) { + printOSAProgress(scanStatus, getStartTimeSec()); + } + + @Override + public OSAScanStatus resolveStatus(OSAScanStatus scanStatus) throws CxClientException { + return resolveOSAStatus(scanStatus); + } + }; } //API @@ -82,13 +85,14 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getSourceDir(), config.getOsaRunInstall(), config.getOsaLocationPath(), + config.getOsaScanDepth(), log); } ObjectMapper mapper = new ObjectMapper(); log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); - OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); + OSAUtils.writeToOsaListToFile(OSAUtils.getWorkDirectory(config.getReportsDir(), config.getOsaGenerateJsonReport()), osaDependenciesJson, log); return osaDependenciesJson; } @@ -108,9 +112,9 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); if (config.getReportsDir() != null) { - writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), log); - writeJsonToFile(OSA_LIBRARIES_NAME, osaResults.getOsaLibraries(), config.getReportsDir(), log); - writeJsonToFile(OSA_VULNERABILITIES_NAME, osaResults.getOsaVulnerabilities(), config.getReportsDir(), log); + writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); + writeJsonToFile(OSA_LIBRARIES_NAME, osaResults.getOsaLibraries(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); + writeJsonToFile(OSA_VULNERABILITIES_NAME, osaResults.getOsaVulnerabilities(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); } return osaResults; diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index f7b38cef..c882966d 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -11,7 +11,6 @@ import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.google.gson.Gson; -import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; @@ -49,24 +48,9 @@ class CxSASTClient { private CxScanConfig config; private int reportTimeoutSec = 5000; private int cxARMTimeoutSec = 1000; - private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { - @Override - public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { - return getSASTScanStatus(id); - } - - @Override - public void printProgress(ResponseQueueScanStatus scanStatus) { - printSASTProgress(scanStatus, getStartTimeSec()); - } - - @Override - public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { - return resolveSASTStatus(scanStatus); - } - }; + private Waiter sastWaiter; - private Waiter reportWaiter = new Waiter("Scan report", 10) { + private Waiter reportWaiter = new Waiter("Scan report", 10, 3) { @Override public ReportStatus getStatus(String id) throws CxClientException, IOException { return getReportStatus(id); @@ -83,7 +67,7 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; - private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20) { + private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20, 3) { @Override public CxARMStatus getStatus(String id) throws CxClientException, IOException { return getCxARMStatus(id); @@ -104,6 +88,24 @@ public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientExcepti this.log = log; this.httpClient = client; this.config = config; + int interval = config.getProgressInterval() != null ? config.getProgressInterval() : 20; + int retry = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + sastWaiter = new Waiter("CxSAST scan", interval, retry) { + @Override + public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { + return getSASTScanStatus(id); + } + + @Override + public void printProgress(ResponseQueueScanStatus scanStatus) { + printSASTProgress(scanStatus, getStartTimeSec()); + } + + @Override + public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { + return resolveSASTStatus(scanStatus); + } + }; } //**------ API ------**// @@ -137,7 +139,8 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc //prepare sources for scan if (config.getZipFile() == null) { log.info("Zipping sources"); - File zipTempFile = CxZipUtils.zipWorkspaceFolder(config, MAX_ZIP_SIZE_BYTES, log); + Long maxZipSize = config.getMaxZipSize() != null ? config.getMaxZipSize() * 1024 * 1024 : MAX_ZIP_SIZE_BYTES; + File zipTempFile = CxZipUtils.zipWorkspaceFolder(config, maxZipSize, log); //Upload zipped source file uploadZipFile(zipTempFile, projectId); deleteTempZipFile(zipTempFile, log); @@ -163,16 +166,23 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient switch (type) { case SVN: + if (req.getPrivateKey() != null && req.getPrivateKey().length > 1) { + isSSH = true; + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null) + .addTextBody("absoluteUrl", req.getUri().getAbsoluteUrl()) + .addTextBody("port", String.valueOf(req.getUri().getPort())) + .addTextBody("paths", config.getSourceDir()); //todo add paths to req OR using without + entity = builder.build(); + } else { + entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); + } + break; case TFS: - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null); - builder.addTextBody("absoluteUrl", req.getUrl(), ContentType.APPLICATION_JSON); - builder.addTextBody("port", String.valueOf(req.getPort()), ContentType.APPLICATION_JSON); - builder.addTextBody("paths", StringUtils.join(req.getPaths(), ";"), ContentType.APPLICATION_JSON); - entity = builder.build(); + entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); break; case PERFORCE: - if (config.getPreforceMode() != null) { + if (config.getPerforceMode() != null) { req.setBrowseMode("Workspace"); } else { req.setBrowseMode("Depot"); @@ -183,17 +193,17 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity(new Gson().toJson(req), ContentType.APPLICATION_JSON); break; case GIT: - if (req.getPrivateKey().length == 0) { + if (req.getPrivateKey() == null || req.getPrivateKey().length < 1) { Map content = new HashMap<>(); - content.put("url", config.getRemoteSrcUrl()); + content.put("url", req.getUri().getAbsoluteUrl()); content.put("branch", config.getRemoteSrcBranch()); entity = new StringEntity(new JSONObject(content).toString(), ContentType.APPLICATION_JSON); } else { isSSH = true; - builder = MultipartEntityBuilder.create(); - builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("url", req.getUri().getAbsoluteUrl(), ContentType.APPLICATION_JSON); builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? - builder.addBinaryBody("privateKey", new byte[0], ContentType.MULTIPART_FORM_DATA, null); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); entity = builder.build(); } break; @@ -242,6 +252,20 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr sastResults.setPdfFileName(pdfFileName); } } + // CLI report/s + else if (!config.getReports().isEmpty()) { + for (Map.Entry report : config.getReports().entrySet()) { + if (report != null) { + log.info("Generating " + report.getKey().value() + " report"); + byte[] scanReport = getScanReport(sastResults.getScanId(), report.getKey(), CONTENT_TYPE_APPLICATION_PDF_V1); + writeReport(scanReport, report.getValue(), log); + if (report.getKey().value().equals("PDF")) { + sastResults.setPDFReport(scanReport); + sastResults.setPdfFileName(report.getValue()); + } + } + } + } return sastResults; } diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index 5d87a7ed..5a912db5 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -13,13 +13,14 @@ */ public abstract class Waiter { - private int retry = 3; + private int retry; private String scanType; private int sleepIntervalSec; - public Waiter(String scanType, int interval) { + public Waiter(String scanType, int interval, int retry) { this.scanType = scanType; this.sleepIntervalSec = interval; + this.retry = retry; } private long startTimeSec; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 0fc88952..a3a7e207 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,10 +2,13 @@ import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; +import com.cx.restclient.sast.dto.ReportType; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; /** @@ -25,6 +28,8 @@ public class CxScanConfig implements Serializable { private String sourceDir; private String osaLocationPath; private File reportsDir; + // Map / (e.g. PDF to its file path) + private Map reports = new HashMap<>(); private String username; private String password; private String refreshToken; @@ -81,7 +86,15 @@ public class CxScanConfig implements Serializable { private int remoteSrcPort; private byte[] remoteSrcKeyFile; private String remoteSrcBranch; - private String preforceMode; + private String perforceMode; + + // CLI config properties + private Integer progressInterval; + private Integer osaProgressInterval; + private Integer connectionRetries; + private String osaScanDepth; + private Integer maxZipSize; + private String defaultProjectName; public CxScanConfig() { } @@ -593,12 +606,12 @@ public void setRemoteSrcBranch(String remoteSrcBranch) { this.remoteSrcBranch = remoteSrcBranch; } - public String getPreforceMode() { - return preforceMode; + public String getPerforceMode() { + return perforceMode; } - public void setPreforceMode(String preforceMode) { - this.preforceMode = preforceMode; + public void setPerforceMode(String perforceMode) { + this.perforceMode = perforceMode; } public Boolean getGenerateXmlReport() { @@ -617,4 +630,71 @@ public void setCxVersion(CxVersion cxVersion) { this.cxVersion = cxVersion; } + public Integer getProgressInterval() { + return progressInterval; + } + + public void setProgressInterval(Integer progressInterval) { + this.progressInterval = progressInterval; + } + + public Integer getOsaProgressInterval() { + return osaProgressInterval; + } + + public void setOsaProgressInterval(Integer osaProgressInterval) { + this.osaProgressInterval = osaProgressInterval; + } + + public Integer getConnectionRetries() { + return connectionRetries; + } + + public void setConnectionRetries(Integer connectionRetries) { + this.connectionRetries = connectionRetries; + } + + public String getOsaScanDepth() { + return osaScanDepth; + } + + public void setOsaScanDepth(String osaScanDepth) { + this.osaScanDepth = osaScanDepth; + } + + public Integer getMaxZipSize() { + return maxZipSize; + } + + public void setMaxZipSize(Integer maxZipSize) { + this.maxZipSize = maxZipSize; + } + + public String getDefaultProjectName() { + return defaultProjectName; + } + + public void setDefaultProjectName(String defaultProjectName) { + this.defaultProjectName = defaultProjectName; + } + + public Map getReports() { + return reports; + } + + public void addPDFReport(String pdfReportPath) { + reports.put(ReportType.PDF, pdfReportPath); + } + + public void addXMLReport(String xmlReportPath) { + reports.put(ReportType.XML, xmlReportPath); + } + + public void addCSVReport(String csvReportPath) { + reports.put(ReportType.CSV, csvReportPath); + } + + public void addRTFReport(String rtfReportPath) { + reports.put(ReportType.RTF, rtfReportPath); + } } diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java index 06ed5316..23f755b9 100644 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -1,48 +1,100 @@ package com.cx.restclient.dto; import com.cx.restclient.configuration.CxScanConfig; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; /** * Created by Galn on 11/25/2018. */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties({ "type" }) public class RemoteSourceRequest { - String url; - int port; + + public class Credentials { + private String userName; + private String password; + + public Credentials(String userName, String password) { + this.userName = userName; + this.password = password; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } + + public class Uri { + private String absoluteUrl; + private int port; + + public Uri(String absoluteUrl, int port) { + this.absoluteUrl = absoluteUrl; + this.port = port; + } + + public String getAbsoluteUrl() { + return absoluteUrl; + } + + public void setAbsoluteUrl(String absoluteUrl) { + this.absoluteUrl = absoluteUrl; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + } + + private Credentials credentials; + private Uri uri; private byte[] privateKey; private String[] paths; - private String userName; - private String password; private RemoteSourceTypes type; - private transient String browseMode; - ; + private String browseMode; public RemoteSourceRequest() { } public RemoteSourceRequest(CxScanConfig config) { - this.userName = config.getRemoteSrcUser(); - this.password = config.getRemoteSrcPass(); - this.url = config.getRemoteSrcUrl(); - this.port = config.getRemoteSrcPort(); - this.privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); - this.paths = config.getPaths(); - this.type = config.getRemoteType(); + credentials = new Credentials(config.getRemoteSrcUser(), config.getRemoteSrcPass()); + uri = new Uri(config.getRemoteSrcUrl(), config.getRemoteSrcPort()); + privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); + paths = config.getPaths(); + type = config.getRemoteType(); } - public String getUrl() { - return url; + public Credentials getCredentials() { + return credentials; } - public void setUrl(String absoluteUrl) { - this.url = absoluteUrl; + public void setCredentials(Credentials credentials) { + this.credentials = credentials; } - public int getPort() { - return port; + public Uri getUri() { + return uri; } - public void setPort(int port) { - this.port = port; + public void setUri(Uri uri) { + this.uri = uri; } public byte[] getPrivateKey() { @@ -61,22 +113,6 @@ public void setPaths(String[] paths) { this.paths = paths; } - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - public RemoteSourceTypes getType() { return type; } @@ -92,5 +128,4 @@ public String getBrowseMode() { public void setBrowseMode(String browseMode) { this.browseMode = browseMode; } - } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 649971d3..82fca684 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -47,7 +47,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, String osaScanDepth, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -85,7 +85,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("archiveIncludes", DEFAULT_ARCHIVE_INCLUDES); } - ret.put("archiveExtractionDepth", "4"); + ret.put("archiveExtractionDepth", StringUtils.isNotEmpty(osaScanDepth) ? osaScanDepth : "4"); if (installBeforeScan) { ret.put("npm.runPreStep", "true"); @@ -131,15 +131,52 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info("-----------------------------------------------------------------------------------------"); } - public static void writeJsonToFile(String name, Object jsonObj, File workDirectory, Logger log) { + public static File getWorkDirectory(File filePath, Boolean osaGenerateJsonReport) { + if (filePath == null) { + return null; + } + + if (!osaGenerateJsonReport) { + return filePath; + } + + File workDirectory; + if (!filePath.isAbsolute()) { + workDirectory = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION); + } + else { + workDirectory = filePath.getParentFile(); + } + if (!workDirectory.exists()) { + workDirectory.mkdirs(); + } + + return workDirectory; + } + + public static void writeJsonToFile(String name, Object jsonObj, File workDirectory, Boolean cliOsaGenerateJsonReport, Logger log) { try { ObjectMapper objectMapper = new ObjectMapper(); - String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); - String fileName = name + "_" + now + ".json"; - File jsonFile = new File(workDirectory + CX_REPORT_LOCATION, fileName); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj); - FileUtils.writeStringToFile(jsonFile, json); - log.info(name + " json location: " + workDirectory + CX_REPORT_LOCATION + File.separator + fileName); + + if(cliOsaGenerateJsonReport) { + workDirectory = new File(workDirectory.getPath().replace(".json", "_" + name + ".json")); + if (!workDirectory.isAbsolute()) { + workDirectory = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION + File.separator + workDirectory); + } + if (!workDirectory.getParentFile().exists()) { + workDirectory.getParentFile().mkdirs(); + } + FileUtils.writeStringToFile(workDirectory, json); + log.info(name + " json location: " + workDirectory); + } + else { + String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); + String fileName = name + "_" + now + ".json"; + File jsonFile = new File(workDirectory + CX_REPORT_LOCATION, fileName); + FileUtils.writeStringToFile(jsonFile, json); + log.info(name + " json location: " + workDirectory + CX_REPORT_LOCATION + File.separator + fileName); + } } catch (Exception ex) { log.warn("Failed to write OSA JSON report (" + name + ") to file: " + ex.getMessage()); } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 7eb55c5c..18c73505 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -77,4 +77,23 @@ public static String writePDFReport(byte[] scanReport, File workspace, String pd } return pdfFileName; } + + // CLI Report/s + public static void writeReport(byte[] scanReport, String reportName, Logger log) { + try { + File reportFile = new File(reportName); + if (!reportFile.isAbsolute()) { + reportFile = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION + File.separator + reportFile); + } + + if (!reportFile.getParentFile().exists()) { + reportFile.getParentFile().mkdirs(); + } + + FileUtils.writeByteArrayToFile(reportFile, scanReport); + log.info("report location: " + reportFile.getAbsolutePath()); + } catch (Exception e) { + log.error("Failed to write report: ", e.getMessage()); + } + } } From 46f7d9c12e62c879faaf8f6faebba342ed6c78aa Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Thu, 7 Nov 2019 13:41:19 +0200 Subject: [PATCH 131/473] jdk11 support --- pom.xml | 41 ++++++------------- .../com/cx/restclient/osa/dto/OSAResults.java | 2 + .../cx/restclient/sast/utils/SASTUtils.java | 8 +++- .../com/cx/restclient/sast/dto/jaxb.index | 1 + .../cx/restclient/sast/dto/jaxb.properties | 2 + 5 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 src/main/resources/com/cx/restclient/sast/dto/jaxb.index create mode 100644 src/main/resources/com/cx/restclient/sast/dto/jaxb.properties diff --git a/pom.xml b/pom.xml index 0ef2dfea..5f243906 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0-SNAPSHOT + 9.20.1-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -174,40 +174,25 @@ - - - com.sun.activation - javax.activation - ${javax.activation.version} - - - - javax.xml.bind - jaxb-api - ${jaxb.api.version} - - - - com.sun.xml.bind - jaxb-core - ${jaxb.api.version} - - - - com.sun.xml.bind - jaxb-impl - ${jaxb.api.version} - - org.projectlombok lombok ${lombok.version} provided + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + - - + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java index 18934abc..fbceb19f 100644 --- a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java +++ b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java @@ -1,6 +1,7 @@ package com.cx.restclient.osa.dto; import com.cx.restclient.cxArm.dto.Policy; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.util.ArrayList; @@ -14,6 +15,7 @@ /** * Created by Galn on 07/02/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class OSAResults implements Serializable { private String osaScanId; private OSASummaryResults results; diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 18c73505..57dd9292 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -3,11 +3,12 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.CxXMLResults; import com.cx.restclient.sast.dto.SASTResults; +import com.sun.xml.bind.v2.JAXBContextFactory; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; - +import java.util.Collections; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -36,7 +37,10 @@ public static CxXMLResults convertToXMLResult(byte[] cxReport) throws CxClientEx CxXMLResults reportObj = null; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cxReport); try { - JAXBContext jaxbContext = JAXBContext.newInstance(CxXMLResults.class); + + JAXBContextFactory jaxbContextFactory = new JAXBContextFactory(); + JAXBContext jaxbContext = jaxbContextFactory.createContext(CxXMLResults.class.getPackage().getName(), + CxXMLResults.class.getClassLoader(),Collections.emptyMap()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); reportObj = (CxXMLResults) unmarshaller.unmarshal(byteArrayInputStream); diff --git a/src/main/resources/com/cx/restclient/sast/dto/jaxb.index b/src/main/resources/com/cx/restclient/sast/dto/jaxb.index new file mode 100644 index 00000000..7436e11f --- /dev/null +++ b/src/main/resources/com/cx/restclient/sast/dto/jaxb.index @@ -0,0 +1 @@ +CxXMLResults \ No newline at end of file diff --git a/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties b/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties new file mode 100644 index 00000000..47e77158 --- /dev/null +++ b/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties @@ -0,0 +1,2 @@ +javax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.JAXBContextFactory +javax.xml.bind.context.factory=com.sun.xml.bind.v2.JAXBContextFactory \ No newline at end of file From 446408d7caa506a6141c1f6122889f47161cec19 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Sun, 10 Nov 2019 11:47:38 +0200 Subject: [PATCH 132/473] fix common version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f243906..d972d663 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.1-SNAPSHOT + 9.20.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From b9928700648ee9c14f1b48003053306becdbc7e7 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Wed, 13 Nov 2019 13:52:32 +0200 Subject: [PATCH 133/473] fix vulnerabilities : upgrade jackson-databind to 2.9.10.1 & bcprov-jdk15on to 1.64 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d972d663..163e4508 100644 --- a/pom.xml +++ b/pom.xml @@ -101,12 +101,12 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9 + 2.9.10.1 org.bouncycastle bcprov-jdk15on - 1.60 + 1.64 plexus-utils From 5841b429bde0a9d1126942f8edd0b637068aed62 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 13 Nov 2019 16:19:09 +0200 Subject: [PATCH 134/473] AB#282: Implemented SCA authentication. --- .../com/cx/restclient/CxShragaClient.java | 48 ++++++++++--- .../java/com/cx/restclient/SCAClient.java | 45 ++++++++++++ .../com/cx/restclient/common/CxPARAM.java | 2 +- .../configuration/CxScanConfig.java | 20 ++++++ .../com/cx/restclient/dto/LoginSettings.java | 71 +++++++++++++++++++ .../java/com/cx/restclient/dto/SCAConfig.java | 51 +++++++++++++ .../restclient/httpClient/CxHttpClient.java | 59 +++++++-------- .../com/cx/restclient/osa/dto/ClientType.java | 5 +- src/main/java/testi.java | 49 ++++++++----- 9 files changed, 292 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/cx/restclient/SCAClient.java create mode 100644 src/main/java/com/cx/restclient/dto/LoginSettings.java create mode 100644 src/main/java/com/cx/restclient/dto/SCAConfig.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 5ae54845..8a76d2a5 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,9 +1,11 @@ package com.cx.restclient; +import com.cx.restclient.common.UrlUtils; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; import com.cx.restclient.dto.CxVersion; +import com.cx.restclient.dto.LoginSettings; import com.cx.restclient.dto.Team; import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; @@ -34,6 +36,7 @@ //SHRAGA //System Holistic Rest Api Generic Application public class CxShragaClient { + private static final String DEFAULT_AUTH_API_PATH = "CxRestApi/auth/"; private CxHttpClient httpClient; private Logger log; @@ -42,6 +45,8 @@ public class CxShragaClient { private CxSASTClient sastClient; private CxOSAClient osaClient; + private final SCAClient scaClient; + private long sastScanId; private String osaScanId; private SASTResults sastResults = new SASTResults(); @@ -53,13 +58,13 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept this.log = log; this.httpClient = new CxHttpClient( config.getUrl(), - config.getUsername(), - config.getPassword(), config.getCxOrigin(), - config.getRefreshToken(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); + scaClient = new SCAClient(log, config); } //For Test Connection @@ -96,6 +101,10 @@ public void init() throws CxClientException, IOException { resolveCxARMUrl(); } resolveProject(); + + if (config.getScaEnabled()) { + scaClient.login(); + } } public long createSASTScan() throws IOException, CxClientException { @@ -110,6 +119,10 @@ public String createOSAScan() throws IOException, CxClientException { return osaScanId; } + public void createSCAScan() { + // TODO + } + public void cancelSASTScan() throws IOException, CxClientException { sastClient.cancelSASTScan(sastScanId); } @@ -182,16 +195,20 @@ public List getAllProjects() throws IOException, CxClientException { public void close() { httpClient.close(); } - //HELP config Methods + public void login() throws IOException, CxClientException { // perform login to server log.info("Logging into the Checkmarx service."); - httpClient.login(); + + LoginSettings settings = getDefaultLoginSettings(); + settings.setRefreshToken(config.getRefreshToken()); + httpClient.login(settings); } public String getToken() throws IOException, CxClientException { - final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(ClientType.CLI); + LoginSettings settings = getDefaultLoginSettings(); + final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(settings); return tokenLoginResponse.getRefresh_token(); } @@ -277,8 +294,8 @@ public List getConfigurationSetList() throws IOException, CxClientExc public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } - //Private methods + private void resolveTeam() throws CxClientException, IOException { if (config.getTeamId() == null) { config.setTeamId(getTeamIdByName(config.getTeamPath())); @@ -357,4 +374,19 @@ private Project createNewProject(CreateProjectRequest request) throws CxClientEx StringEntity entity = new StringEntity(json); return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } + + private LoginSettings getDefaultLoginSettings() throws MalformedURLException { + LoginSettings result = new LoginSettings(); + + String baseUrl = UrlUtils.parseURLToString(config.getUrl(), DEFAULT_AUTH_API_PATH); + result.setAccessControlBaseUrl(baseUrl); + + result.setUsername(config.getUsername()); + result.setPassword(config.getPassword()); + + result.setClientTypeForPasswordAuth(ClientType.RESOURCE_OWNER); + result.setClientTypeForRefreshToken(ClientType.CLI); + + return result; + } } \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java new file mode 100644 index 00000000..1a26a9f9 --- /dev/null +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -0,0 +1,45 @@ +package com.cx.restclient; + +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.LoginSettings; +import com.cx.restclient.dto.SCAConfig; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.httpClient.CxHttpClient; +import com.cx.restclient.osa.dto.ClientType; +import org.slf4j.Logger; + +import java.io.IOException; +import java.net.MalformedURLException; + +/** + * SCA - Software Composition Analysis - is the successor of OSA. + */ +class SCAClient { + private final Logger log; + private final SCAConfig scaConfig; + + // This class uses its own instance of CxHttpClient, because SCA has a different base URL and Access Control server. + private final CxHttpClient httpClient; + + SCAClient(Logger log, CxScanConfig scanConfig) throws MalformedURLException { + this.log = log; + + scaConfig = scanConfig.getScaConfig(); + httpClient = new CxHttpClient(scaConfig.getApiUrl(), + scanConfig.getCxOrigin(), + scanConfig.isDisableCertificateValidation(), + scanConfig.isUseSSOLogin(), + log); + } + + void login() throws IOException, CxClientException { + log.info("Logging into SCA."); + LoginSettings settings = new LoginSettings(); + settings.setAccessControlBaseUrl(scaConfig.getAccessControlUrl()); + settings.setUsername(scaConfig.getUsername()); + settings.setPassword(scaConfig.getPassword()); + settings.setTenant(scaConfig.getTenant()); + settings.setClientTypeForPasswordAuth(ClientType.SCA_CLI); + httpClient.login(settings); + } +} diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index dcca603b..f73e6dd2 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -6,7 +6,7 @@ * Created by Galn on 14/02/2018. */ public abstract class CxPARAM { - public static final String AUTHENTICATION = "auth/identity/connect/token"; + public static final String AUTHENTICATION = "identity/connect/token"; public static final String REVOCATION = "auth/identity/connect/revocation"; public static final String SSO_AUTHENTICATION = "auth/ssologin"; public static final String CXPRESETS = "sast/presets"; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index a3a7e207..b9bc0756 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,6 +2,7 @@ import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; +import com.cx.restclient.dto.SCAConfig; import com.cx.restclient.sast.dto.ReportType; import org.apache.commons.lang3.StringUtils; @@ -96,6 +97,9 @@ public class CxScanConfig implements Serializable { private Integer maxZipSize; private String defaultProjectName; + private boolean scaEnabled; + private SCAConfig scaConfig; + public CxScanConfig() { } @@ -697,4 +701,20 @@ public void addCSVReport(String csvReportPath) { public void addRTFReport(String rtfReportPath) { reports.put(ReportType.RTF, rtfReportPath); } + + public void setScaEnabled(boolean scaEnabled) { + this.scaEnabled = scaEnabled; + } + + public boolean getScaEnabled() { + return scaEnabled; + } + + public SCAConfig getScaConfig() { + return scaConfig; + } + + public void setScaConfig(SCAConfig scaConfig) { + this.scaConfig = scaConfig; + } } diff --git a/src/main/java/com/cx/restclient/dto/LoginSettings.java b/src/main/java/com/cx/restclient/dto/LoginSettings.java new file mode 100644 index 00000000..21e0648e --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/LoginSettings.java @@ -0,0 +1,71 @@ +package com.cx.restclient.dto; + +import com.cx.restclient.osa.dto.ClientType; + +public class LoginSettings { + private String accessControlBaseUrl; + private String username; + private String password; + private CharSequence tenant; + private String refreshToken; + + // TODO: find a way to use a single client type here. + private ClientType clientTypeForRefreshToken; + private ClientType clientTypeForPasswordAuth; + + public void setAccessControlBaseUrl(String accessControlBaseUrl) { + this.accessControlBaseUrl = accessControlBaseUrl; + } + + public String getAccessControlBaseUrl() { + return accessControlBaseUrl; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } + + public void setClientTypeForRefreshToken(ClientType clientType) { + this.clientTypeForRefreshToken = clientType; + } + + public ClientType getClientTypeForRefreshToken() { + return clientTypeForRefreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setClientTypeForPasswordAuth(ClientType clientType) { + this.clientTypeForPasswordAuth = clientType; + } + + public ClientType getClientTypeForPasswordAuth() { + return clientTypeForPasswordAuth; + } + + public CharSequence getTenant() { + return tenant; + } + + public void setTenant(CharSequence tenant) { + this.tenant = tenant; + } +} diff --git a/src/main/java/com/cx/restclient/dto/SCAConfig.java b/src/main/java/com/cx/restclient/dto/SCAConfig.java new file mode 100644 index 00000000..8a0a02d7 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/SCAConfig.java @@ -0,0 +1,51 @@ +package com.cx.restclient.dto; + +import java.io.Serializable; + +public class SCAConfig implements Serializable { + private String apiUrl; + private String accessControlUrl; + private String username; + private String password; + private String tenant; + + public String getApiUrl() { + return apiUrl; + } + + public void setApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + } + + public void setAccessControlUrl(String accessControlUrl) { + this.accessControlUrl = accessControlUrl; + } + + public String getAccessControlUrl() { + return accessControlUrl; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getTenant() { + return tenant; + } +} diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index f55b1b91..e4334234 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -2,11 +2,13 @@ import com.cx.restclient.common.ErrorMessage; import com.cx.restclient.common.UrlUtils; +import com.cx.restclient.dto.LoginSettings; import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import com.cx.restclient.osa.dto.ClientType; +import org.apache.commons.lang3.StringUtils; import org.apache.http.*; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpClient; @@ -52,9 +54,6 @@ public class CxHttpClient { private HttpClient apacheClient; private TokenLoginResponse tokenLoginResponse; private String rootUri; - private final String username; - private final String password; - private final String refreshtoken; private String cxOrigin; private CookieStore cookieStore; @@ -63,6 +62,7 @@ public class CxHttpClient { private Boolean useSSo = false; + private LoginSettings lastLoginSettings; private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { @@ -96,12 +96,8 @@ public void process(HttpResponse httpResponse, HttpContext httpContext) throws H } }; - - public CxHttpClient(String hostname, String username, String password, String origin, String refreshToken, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + public CxHttpClient(String hostname, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { this.logi = logi; - this.username = username; - this.password = password; - this.refreshtoken = refreshToken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; //create httpclient @@ -120,24 +116,22 @@ public CxHttpClient(String hostname, String username, String password, String or apacheClient = builder.build(); } - public void login() throws IOException, CxClientException { - if (refreshtoken != null) { - tokenLoginResponse = getAccessTokenFromRefreshToken(); + public void login(LoginSettings settings) throws IOException, CxClientException { + lastLoginSettings = settings; + if (settings.getRefreshToken() != null) { + tokenLoginResponse = getAccessTokenFromRefreshToken(settings); } else if (useSSo) { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } else { - tokenLoginResponse = generateToken(); + tokenLoginResponse = generateToken(settings); } } - public TokenLoginResponse generateToken() throws IOException, CxClientException { - return generateToken(ClientType.RESOURCE_OWNER); - } - - public TokenLoginResponse generateToken(ClientType clientType) throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(clientType); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + public TokenLoginResponse generateToken(LoginSettings settings) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(settings); + String fullUrl = UrlUtils.parseURLToString(settings.getAccessControlBaseUrl(), AUTHENTICATION); + HttpPost post = new HttpPost(fullUrl); try { return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); @@ -146,9 +140,10 @@ public TokenLoginResponse generateToken(ClientType clientType) throws IOExceptio } } - private TokenLoginResponse getAccessTokenFromRefreshToken() throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateTokenFromRefreshEntity(ClientType.CLI); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + private TokenLoginResponse getAccessTokenFromRefreshToken(LoginSettings settings) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateTokenFromRefreshEntity(settings); + String fullUrl = UrlUtils.parseURLToString(settings.getAccessControlBaseUrl(), AUTHENTICATION); + HttpPost post = new HttpPost(fullUrl); try { return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); @@ -179,24 +174,31 @@ private UrlEncodedFormEntity generateRevocationEntity(ClientType clientType, Str } - private UrlEncodedFormEntity generateUrlEncodedFormEntity(ClientType clientType) throws UnsupportedEncodingException { + private UrlEncodedFormEntity generateUrlEncodedFormEntity(LoginSettings settings) throws UnsupportedEncodingException { + ClientType clientType = settings.getClientTypeForPasswordAuth(); List parameters = new ArrayList<>(); - parameters.add(new BasicNameValuePair("username", username)); - parameters.add(new BasicNameValuePair("password", password)); + parameters.add(new BasicNameValuePair("username", settings.getUsername())); + parameters.add(new BasicNameValuePair("password", settings.getPassword())); parameters.add(new BasicNameValuePair("grant_type", "password")); parameters.add(new BasicNameValuePair("scope", clientType.getScopes())); parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); + if (!StringUtils.isEmpty(settings.getTenant())) { + String authContext = String.format("Tenant:%s", settings.getTenant()); + parameters.add(new BasicNameValuePair("acr_values", authContext)); + } + return new UrlEncodedFormEntity(parameters, "utf-8"); } - private UrlEncodedFormEntity generateTokenFromRefreshEntity(ClientType clientType) throws UnsupportedEncodingException { + private UrlEncodedFormEntity generateTokenFromRefreshEntity(LoginSettings settings) throws UnsupportedEncodingException { + ClientType clientType = settings.getClientTypeForRefreshToken(); List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); - parameters.add(new BasicNameValuePair("refresh_token", refreshtoken)); + parameters.add(new BasicNameValuePair("refresh_token", settings.getRefreshToken())); return new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8.name()); } @@ -254,7 +256,7 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity } catch (CxTokenExpiredException ex) { if (retry) { logi.warn("Access token expired, requesting a new token"); - login(); + login(lastLoginSettings); return request(httpMethod, contentType, entity, responseType, expectStatus, failedMsg, isCollection, false); } throw ex; @@ -293,5 +295,4 @@ private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { log.warn("Failed to set SSL TLS : " + e.getMessage()); } } - } diff --git a/src/main/java/com/cx/restclient/osa/dto/ClientType.java b/src/main/java/com/cx/restclient/osa/dto/ClientType.java index 8843318c..5e8d2214 100644 --- a/src/main/java/com/cx/restclient/osa/dto/ClientType.java +++ b/src/main/java/com/cx/restclient/osa/dto/ClientType.java @@ -4,8 +4,11 @@ public enum ClientType { RESOURCE_OWNER("resource_owner_client", "sast_rest_api cxarm_api", "014DF517-39D1-4453-B7B3-9930C563627C"), + CLI("cli_client", "sast_rest_api offline_access", - "B9D84EA8-E476-4E83-A628-8A342D74D3BD"); + "B9D84EA8-E476-4E83-A628-8A342D74D3BD"), + + SCA_CLI("sca_resource_owner_client", "sca_api offline_access", ""); private String clientId; private String scopes; diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 94b85aba..f8fa34ca 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -1,6 +1,6 @@ import com.cx.restclient.CxShragaClient; -import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.SCAConfig; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; import org.apache.commons.io.FileUtils; @@ -30,9 +30,6 @@ public class testi { public static void main(String[] args) throws Exception { - - - SASTResults sastResults = null; // SASTResults lastSastResults = null; OSAResults osaResults = null; @@ -50,6 +47,8 @@ public static void main(String[] args) throws Exception { try { if (config.getOsaEnabled()) { shraga.createOSAScan(); + } else if (config.getScaEnabled()) { + shraga.createSCAScan(); } } catch (Exception ex) { logi.error(ex.getMessage()); @@ -86,7 +85,7 @@ public static void main(String[] args) throws Exception { } //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); String s = shraga.generateHTMLSummary(); - File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); + File file = new File("c:\\cxdev\\reports\\report.html"); FileUtils.writeStringToFile(file, s); shraga.close(); @@ -95,25 +94,26 @@ public static void main(String[] args) throws Exception { private static CxScanConfig setConfigi() { CxScanConfig config = new CxScanConfig(); + + configureSca(config); + config.setSastEnabled(true); - config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); - //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); - config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); - //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); - config.setUsername("adi.gido@checkmarx.com"); - //config.setPassword("Cx123456!"); - config.setPassword("Cx@123456"); + + config.setSourceDir("c:\\cxdev\\projectsToScan\\BookStore_Small_CLI\\"); + config.setReportsDir(new File("c:\\cxdev\\reports\\")); + + config.setUrl("http://10.32.1.57"); + config.setUsername("myusername"); + config.setPassword("mypassword"); + config.setAvoidDuplicateProjectScans(false); - // config.setUrl("http://10.32.1.91"); - config.setUrl("https://sast.checkmarx.net"); config.setCxOrigin("common"); - config.setProjectName("Bamboo Plugin - Main"); - //config.setProjectName("OSAPROJ"); + config.setProjectName("CommonClientTest1"); config.setPresetName("Default"); - //config.setPresetId(7); - config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); - //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); + + config.setTeamPath("\\CxServer"); + config.setSastFolderExclusions(""); config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); config.setSastScanTimeoutInMinutes(null); @@ -145,4 +145,15 @@ private static CxScanConfig setConfigi() { return config; } + private static void configureSca(CxScanConfig parentConfig) { + parentConfig.setScaEnabled(false); + + SCAConfig config = new SCAConfig(); + config.setApiUrl("http://scaapp.lumodev.com"); + config.setAccessControlUrl("http://upgrade.dev-ac-checkmarx.com"); + config.setUsername("myusername"); + config.setPassword("mypassword"); + config.setTenant("Checkmarx"); + parentConfig.setScaConfig(config); + } } From 6dddb2b3842ebdd53849fb3bb8bf2c0886ad067b Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 14 Nov 2019 13:03:16 +0200 Subject: [PATCH 135/473] AB#281: Updated the version of slf4j-simple, because the previous version didn't support 'debug' log level. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d972d663..515fc8b5 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ org.slf4j slf4j-simple - 1.6.2 + 1.7.29 org.freemarker From 29873efedf97cf0e1a565fa36955b7ba59da3723 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 14 Nov 2019 13:41:48 +0200 Subject: [PATCH 136/473] AB#283: Implemented getting project ID and project creation. --- .../com/cx/restclient/CxShragaClient.java | 4 +- .../java/com/cx/restclient/SCAClient.java | 67 ++++++++++++++++++- .../configuration/CxScanConfig.java | 2 +- .../restclient/httpClient/CxHttpClient.java | 4 +- .../httpClient/utils/HttpClientHelper.java | 7 +- .../sca/dto/CreateProjectRequest.java | 13 ++++ .../com/cx/restclient/sca/dto/Project.java | 22 ++++++ .../restclient/{ => sca}/dto/SCAConfig.java | 11 ++- src/main/java/testi.java | 7 +- 9 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/CreateProjectRequest.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/Project.java rename src/main/java/com/cx/restclient/{ => sca}/dto/SCAConfig.java (81%) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 8a76d2a5..246c9192 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -57,7 +57,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept this.config = config; this.log = log; this.httpClient = new CxHttpClient( - config.getUrl(), + UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), @@ -103,7 +103,7 @@ public void init() throws CxClientException, IOException { resolveProject(); if (config.getScaEnabled()) { - scaClient.login(); + scaClient.init(); } } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 1a26a9f9..4b23371f 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -1,37 +1,65 @@ package com.cx.restclient; +import com.cx.restclient.common.UrlUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.LoginSettings; -import com.cx.restclient.dto.SCAConfig; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; +import com.cx.restclient.httpClient.utils.ContentType; +import com.cx.restclient.httpClient.utils.HttpClientHelper; import com.cx.restclient.osa.dto.ClientType; +import com.cx.restclient.sca.dto.CreateProjectRequest; +import com.cx.restclient.sca.dto.Project; +import com.cx.restclient.sca.dto.SCAConfig; +import org.apache.http.HttpStatus; +import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import java.io.IOException; import java.net.MalformedURLException; +import java.util.List; /** * SCA - Software Composition Analysis - is the successor of OSA. */ class SCAClient { + private static final String API_PATH = "api/"; + private final Logger log; private final SCAConfig scaConfig; // This class uses its own instance of CxHttpClient, because SCA has a different base URL and Access Control server. private final CxHttpClient httpClient; + private String projectId; + SCAClient(Logger log, CxScanConfig scanConfig) throws MalformedURLException { this.log = log; scaConfig = scanConfig.getScaConfig(); - httpClient = new CxHttpClient(scaConfig.getApiUrl(), + String apiBaseUrl = UrlUtils.parseURLToString(scaConfig.getApiUrl(), API_PATH); + + httpClient = new CxHttpClient(apiBaseUrl, scanConfig.getCxOrigin(), scanConfig.isDisableCertificateValidation(), scanConfig.isUseSSOLogin(), log); } + void init() throws IOException, CxClientException { + login(); + resolveProject(); + } + + private void resolveProject() throws IOException, CxClientException { + projectId = getProjectIdByName(scaConfig.getProjectName()); + if (projectId == null) { + log.debug("Project not found, creating a new one."); + projectId = createProject(scaConfig.getProjectName()); + } + log.debug("Using project ID: " + projectId); + } + void login() throws IOException, CxClientException { log.info("Logging into SCA."); LoginSettings settings = new LoginSettings(); @@ -42,4 +70,39 @@ void login() throws IOException, CxClientException { settings.setClientTypeForPasswordAuth(ClientType.SCA_CLI); httpClient.login(settings); } + + private String getProjectIdByName(String name) throws IOException, CxClientException { + log.debug("Getting project by name: " + name); + + List allProjects = (List) httpClient.getRequest("projects", + ContentType.CONTENT_TYPE_APPLICATION_JSON, + Project.class, + HttpStatus.SC_OK, + "SCA projects", + true); + + String result = allProjects.stream() + .filter((Project project) -> name.equals(project.getName())) + .map(project -> project.getId()) + .findFirst() + .orElse(null); + + return result; + } + + private String createProject(String name) throws CxClientException, IOException { + CreateProjectRequest request = new CreateProjectRequest(); + request.setName(name); + + StringEntity entity = HttpClientHelper.convertToStringEntity(request); + + Project newProject = httpClient.postRequest("projects", + ContentType.CONTENT_TYPE_APPLICATION_JSON, + entity, + Project.class, + HttpStatus.SC_CREATED, + "create a project"); + + return newProject.getId(); + } } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index b9bc0756..26ec1956 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,7 +2,7 @@ import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; -import com.cx.restclient.dto.SCAConfig; +import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sast.dto.ReportType; import org.apache.commons.lang3.StringUtils; diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index e4334234..93fec937 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -96,9 +96,9 @@ public void process(HttpResponse httpResponse, HttpContext httpContext) throws H } }; - public CxHttpClient(String hostname, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { this.logi = logi; - this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); + this.rootUri = rootUri; this.cxOrigin = origin; //create httpclient HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 55227448..8c0d038c 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -1,7 +1,6 @@ package com.cx.restclient.httpClient.utils; -import com.cx.restclient.common.ErrorMessage; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.fasterxml.jackson.databind.JavaType; @@ -9,8 +8,10 @@ import com.fasterxml.jackson.databind.type.TypeFactory; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; +import org.apache.http.entity.StringEntity; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.List; @@ -58,6 +59,10 @@ public static String convertToJson(Object o) throws CxClientException { } } + public static StringEntity convertToStringEntity(Object o) throws CxClientException, UnsupportedEncodingException { + return new StringEntity(convertToJson(o)); + } + private static T convertToCollectionObject(HttpResponse response, JavaType javaType) throws CxClientException { ObjectMapper mapper = new ObjectMapper(); try { diff --git a/src/main/java/com/cx/restclient/sca/dto/CreateProjectRequest.java b/src/main/java/com/cx/restclient/sca/dto/CreateProjectRequest.java new file mode 100644 index 00000000..30e8c6bc --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/CreateProjectRequest.java @@ -0,0 +1,13 @@ +package com.cx.restclient.sca.dto; + +public class CreateProjectRequest { + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/cx/restclient/sca/dto/Project.java b/src/main/java/com/cx/restclient/sca/dto/Project.java new file mode 100644 index 00000000..71f4a58a --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/Project.java @@ -0,0 +1,22 @@ +package com.cx.restclient.sca.dto; + +public class Project { + private String name; + private String id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/src/main/java/com/cx/restclient/dto/SCAConfig.java b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java similarity index 81% rename from src/main/java/com/cx/restclient/dto/SCAConfig.java rename to src/main/java/com/cx/restclient/sca/dto/SCAConfig.java index 8a0a02d7..afcb5617 100644 --- a/src/main/java/com/cx/restclient/dto/SCAConfig.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java @@ -1,4 +1,4 @@ -package com.cx.restclient.dto; +package com.cx.restclient.sca.dto; import java.io.Serializable; @@ -8,6 +8,7 @@ public class SCAConfig implements Serializable { private String username; private String password; private String tenant; + private String projectName; public String getApiUrl() { return apiUrl; @@ -48,4 +49,12 @@ public void setTenant(String tenant) { public String getTenant() { return tenant; } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } } diff --git a/src/main/java/testi.java b/src/main/java/testi.java index f8fa34ca..990ff58a 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -1,6 +1,6 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.SCAConfig; +import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; import org.apache.commons.io.FileUtils; @@ -36,10 +36,8 @@ public static void main(String[] args) throws Exception { // OSAResults lastOsaResults = null; Logger logi = LoggerFactory.getLogger("testush"); - CxScanConfig config = setConfigi(); - CxShragaClient shraga = new CxShragaClient(config, logi); // shraga.getClientVersion(); shraga.init(); @@ -146,7 +144,7 @@ private static CxScanConfig setConfigi() { } private static void configureSca(CxScanConfig parentConfig) { - parentConfig.setScaEnabled(false); + parentConfig.setScaEnabled(true); SCAConfig config = new SCAConfig(); config.setApiUrl("http://scaapp.lumodev.com"); @@ -154,6 +152,7 @@ private static void configureSca(CxScanConfig parentConfig) { config.setUsername("myusername"); config.setPassword("mypassword"); config.setTenant("Checkmarx"); + config.setProjectName("CommonClientScaTest3"); parentConfig.setScaConfig(config); } } From 815bdce34ecd4df605982f13a07de051808a2330 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 17 Nov 2019 10:33:31 +0200 Subject: [PATCH 137/473] AB#284: Implemented starting a scan (by uploading zipped sources). --- .../java/com/cx/restclient/CxSASTClient.java | 42 +++++------ .../com/cx/restclient/CxShragaClient.java | 4 +- .../java/com/cx/restclient/SCAClient.java | 72 +++++++++++++++---- .../com/cx/restclient/dto/PathFilter.java | 30 ++++++++ .../cx/restclient/sast/utils/SASTUtils.java | 15 +--- .../cx/restclient/sast/utils/zip/CxZip.java | 5 +- .../restclient/sast/utils/zip/CxZipUtils.java | 37 ++++++---- 7 files changed, 134 insertions(+), 71 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/PathFilter.java diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index c882966d..7fbecd2d 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -2,6 +2,7 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.PathFilter; import com.cx.restclient.dto.RemoteSourceRequest; import com.cx.restclient.dto.RemoteSourceTypes; import com.cx.restclient.dto.Status; @@ -137,25 +138,14 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc defineScanSetting(scanSettingRequest); //prepare sources for scan - if (config.getZipFile() == null) { - log.info("Zipping sources"); - Long maxZipSize = config.getMaxZipSize() != null ? config.getMaxZipSize() * 1024 * 1024 : MAX_ZIP_SIZE_BYTES; - File zipTempFile = CxZipUtils.zipWorkspaceFolder(config, maxZipSize, log); - //Upload zipped source file - uploadZipFile(zipTempFile, projectId); - deleteTempZipFile(zipTempFile, log); - } else { - uploadZipFile(config.getZipFile(), projectId); - } + PathFilter filter = new PathFilter(config.getSastFolderExclusions(), config.getSastFilterPattern(), log); + File zipFile = CxZipUtils.getZippedSources(config, filter, log); + uploadZipFile(zipFile, projectId); + CxZipUtils.deleteZippedSources(zipFile, config, log); //Start a new createSASTScan log.info("Uploading zip file"); - CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); - log.info("Sending SAST scan request"); - CxID createScanResponse = createScan(scanRequest); - log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); - - return createScanResponse.getId(); + return createScan(projectId); } private long createRemoteSourceScan(long projectId) throws IOException, CxClientException { @@ -214,15 +204,9 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient } createRemoteSourceRequest(projectId, entity, type.value(), isSSH); - CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); - log.info("Sending SAST scan request"); - CxID createScanResponse = createScan(scanRequest); - log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); - - return createScanResponse.getId(); + return createScan(projectId); } - //GET SAST results + reports public SASTResults waitForSASTResults(long scanId, long projectId) throws InterruptedException, IOException, CxClientException { SASTResults sastResults; @@ -359,9 +343,15 @@ private void uploadZipFile(File zipFile, long projectId) throws CxClientExceptio httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, entity, null, 204, "upload ZIP file"); } - private CxID createScan(CreateScanRequest request) throws CxClientException, IOException { - StringEntity entity = new StringEntity(convertToJson(request)); - return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); + private long createScan(long projectId) throws CxClientException, IOException { + CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); + + log.info("Sending SAST scan request"); + StringEntity entity = new StringEntity(convertToJson(scanRequest)); + CxID createScanResponse = httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); + log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); + + return createScanResponse.getId(); } private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 246c9192..8bab2e8b 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -119,8 +119,8 @@ public String createOSAScan() throws IOException, CxClientException { return osaScanId; } - public void createSCAScan() { - // TODO + public void createSCAScan() throws IOException, CxClientException { + scaClient.createScan(); } public void cancelSASTScan() throws IOException, CxClientException { diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 4b23371f..d2667003 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -2,20 +2,28 @@ import com.cx.restclient.common.UrlUtils; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.PathFilter; import com.cx.restclient.dto.LoginSettings; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.httpClient.utils.ContentType; import com.cx.restclient.httpClient.utils.HttpClientHelper; import com.cx.restclient.osa.dto.ClientType; +import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.dto.CreateProjectRequest; import com.cx.restclient.sca.dto.Project; import com.cx.restclient.sca.dto.SCAConfig; +import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.ContentBody; +import org.apache.http.entity.mime.content.InputStreamBody; +import org.apache.http.entity.mime.content.StringBody; import org.slf4j.Logger; -import java.io.IOException; +import java.io.*; import java.net.MalformedURLException; import java.util.List; @@ -26,23 +34,24 @@ class SCAClient { private static final String API_PATH = "api/"; private final Logger log; - private final SCAConfig scaConfig; + private final CxScanConfig config; // This class uses its own instance of CxHttpClient, because SCA has a different base URL and Access Control server. private final CxHttpClient httpClient; private String projectId; - SCAClient(Logger log, CxScanConfig scanConfig) throws MalformedURLException { + SCAClient(Logger log, CxScanConfig config) throws MalformedURLException { this.log = log; + this.config = config; - scaConfig = scanConfig.getScaConfig(); + SCAConfig scaConfig = config.getScaConfig(); String apiBaseUrl = UrlUtils.parseURLToString(scaConfig.getApiUrl(), API_PATH); httpClient = new CxHttpClient(apiBaseUrl, - scanConfig.getCxOrigin(), - scanConfig.isDisableCertificateValidation(), - scanConfig.isUseSSOLogin(), + config.getCxOrigin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), log); } @@ -51,26 +60,42 @@ void init() throws IOException, CxClientException { resolveProject(); } - private void resolveProject() throws IOException, CxClientException { - projectId = getProjectIdByName(scaConfig.getProjectName()); - if (projectId == null) { - log.debug("Project not found, creating a new one."); - projectId = createProject(scaConfig.getProjectName()); - } - log.debug("Using project ID: " + projectId); + void createScan() throws IOException, CxClientException { + log.info("----------------------------------- Create SCA Scan:------------------------------------"); + log.info("Creating SCA scan"); + + PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); + File zipFile = CxZipUtils.getZippedSources(config, filter, log); + + uploadZipFile(zipFile); + + CxZipUtils.deleteZippedSources(zipFile, config, log); } void login() throws IOException, CxClientException { log.info("Logging into SCA."); + SCAConfig scaConfig = config.getScaConfig(); + LoginSettings settings = new LoginSettings(); settings.setAccessControlBaseUrl(scaConfig.getAccessControlUrl()); settings.setUsername(scaConfig.getUsername()); settings.setPassword(scaConfig.getPassword()); settings.setTenant(scaConfig.getTenant()); settings.setClientTypeForPasswordAuth(ClientType.SCA_CLI); + httpClient.login(settings); } + private void resolveProject() throws IOException, CxClientException { + String projectName = config.getScaConfig().getProjectName(); + projectId = getProjectIdByName(projectName); + if (projectId == null) { + log.debug("Project not found, creating a new one."); + projectId = createProject(projectName); + } + log.debug("Using project ID: " + projectId); + } + private String getProjectIdByName(String name) throws IOException, CxClientException { log.debug("Getting project by name: " + name); @@ -105,4 +130,23 @@ private String createProject(String name) throws CxClientException, IOException return newProject.getId(); } + + private void uploadZipFile(File zipFile) throws IOException, CxClientException { + log.info("Uploading zipped sources."); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + InputStream input = new FileInputStream(zipFile.getAbsoluteFile()); + InputStreamBody fileBody = new InputStreamBody(input, org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM, "zippedSource"); + builder.addPart("zipFile", fileBody); + + ContentBody projectIdBody = new StringBody(projectId, org.apache.http.entity.ContentType.APPLICATION_FORM_URLENCODED); + builder.addPart("projectId", projectIdBody); + + HttpEntity entity = builder.build(); + + String scanId = httpClient.postRequest("scans/zip", null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); + log.debug("Scan ID: " + scanId); + } } diff --git a/src/main/java/com/cx/restclient/dto/PathFilter.java b/src/main/java/com/cx/restclient/dto/PathFilter.java new file mode 100644 index 00000000..15932566 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/PathFilter.java @@ -0,0 +1,30 @@ +package com.cx.restclient.dto; + +import com.cx.restclient.common.ShragaUtils; +import org.slf4j.Logger; + +import java.util.List; +import java.util.Map; + +public class PathFilter { + private String[] includes; + private String[] excludes; + + public PathFilter(String folderExclusions, String filterPattern, Logger log) { + Map> stringListMap = ShragaUtils.generateIncludesExcludesPatternLists(folderExclusions, filterPattern, log); + includes = getArray(stringListMap, ShragaUtils.INCLUDES_LIST); + excludes = getArray(stringListMap, ShragaUtils.EXCLUDES_LIST); + } + + public String[] getIncludes() { + return includes; + } + + public String[] getExcludes() { + return excludes; + } + + private static String[] getArray(Map> map, String key){ + return map.get(key).toArray(new String[0]); + } +} diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 57dd9292..fe512370 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -6,33 +6,22 @@ import com.sun.xml.bind.v2.JAXBContextFactory; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; -import java.util.Collections; + import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.ByteArrayInputStream; import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.util.Collections; import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; -import static com.cx.restclient.sast.utils.SASTParam.PDF_REPORT_NAME; /** * Created by Galn on 07/02/2018. */ public abstract class SASTUtils { - public static void deleteTempZipFile(File zipTempFile, Logger log) { - if (zipTempFile.exists() && !zipTempFile.delete()) { - log.warn("Failed to delete temporary zip file: " + zipTempFile.getAbsolutePath()); - } else { - log.info("Temporary file deleted"); - } - } - public static CxXMLResults convertToXMLResult(byte[] cxReport) throws CxClientException { CxXMLResults reportObj = null; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cxReport); diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java b/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java index aed2a75e..c08d2538 100644 --- a/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java +++ b/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java @@ -1,6 +1,7 @@ package com.cx.restclient.sast.utils.zip; +import com.cx.restclient.dto.PathFilter; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; @@ -23,7 +24,7 @@ public CxZip(String tempFileName, long maxZipSizeInBytes, Logger log) { this.maxZipSizeInBytes = maxZipSizeInBytes; } - public File zipWorkspaceFolder(File baseDir, String[] includes, String[] excludes) + public File zipWorkspaceFolder(File baseDir, PathFilter filter) throws IOException { log.info("Zipping workspace: '" + baseDir + "'"); @@ -38,7 +39,7 @@ public void updateProgress(String fileName, long size) { OutputStream fileOutputStream = new FileOutputStream(tempFile); try { - new Zipper(log).zip(baseDir, includes, excludes, fileOutputStream, maxZipSizeInBytes, zipListener); + new Zipper(log).zip(baseDir, filter.getIncludes(), filter.getExcludes(), fileOutputStream, maxZipSizeInBytes, zipListener); } catch (Zipper.MaxZipSizeReached e) { tempFile.delete(); throw new IOException("Reached maximum upload size limit of " + FileUtils.byteCountToDisplaySize(maxZipSizeInBytes)); diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java b/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java index d47d9e9e..51cad1d8 100644 --- a/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java @@ -1,34 +1,43 @@ package com.cx.restclient.sast.utils.zip; -import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.PathFilter; import org.slf4j.Logger; import java.io.File; import java.io.IOException; -import java.util.List; -import java.util.Map; +import static com.cx.restclient.sast.utils.SASTParam.MAX_ZIP_SIZE_BYTES; import static com.cx.restclient.sast.utils.SASTParam.TEMP_FILE_NAME_TO_ZIP; /** * CxZipUtils generates the patterns used for zipping the workspace folder */ - - public abstract class CxZipUtils { + public static File getZippedSources(CxScanConfig config, PathFilter filter, Logger log) throws IOException { + File result = config.getZipFile(); + if (result == null) { + log.info("Zipping sources"); + Long maxZipSize = config.getMaxZipSize() != null ? config.getMaxZipSize() * 1024 * 1024 : MAX_ZIP_SIZE_BYTES; + + CxZip cxZip = new CxZip(TEMP_FILE_NAME_TO_ZIP, maxZipSize, log); + result = cxZip.zipWorkspaceFolder(new File(config.getSourceDir()), filter); + log.debug("The sources were zipped to " + result.getAbsolutePath()); + } + return result; + } - public static File zipWorkspaceFolder(CxScanConfig config, long maxZipBytes, Logger log) throws IOException { - Map> stringListMap = ShragaUtils.generateIncludesExcludesPatternLists(config.getSastFolderExclusions(), config.getSastFilterPattern(), log); - List includes = stringListMap.get(ShragaUtils.INCLUDES_LIST); - List excludes = stringListMap.get(ShragaUtils.EXCLUDES_LIST); - - CxZip cxZip = new CxZip(TEMP_FILE_NAME_TO_ZIP, maxZipBytes, log); - - return cxZip.zipWorkspaceFolder(new File(config.getSourceDir()), includes.toArray(new String[includes.size()]), excludes.toArray(new String[excludes.size()])); - + public static void deleteZippedSources(File file, CxScanConfig config, Logger log) { + boolean isZipFileProvidedExternally = (config.getZipFile() != null); + if (!isZipFileProvidedExternally) { + if (file.exists() && !file.delete()) { + log.warn("Failed to delete temporary zip file: " + file.getAbsolutePath()); + } else { + log.info("Temporary file deleted"); + } + } } } From d1614f75918fb4f7cb40360270c21fdc3ab9324e Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 17 Nov 2019 13:04:34 +0200 Subject: [PATCH 138/473] AB#307: Introduced DependencyScanner interface to generalize OSA and SCA functionality. --- .../java/com/cx/restclient/CxOSAClient.java | 77 +++++++++++++++---- .../com/cx/restclient/CxShragaClient.java | 65 ++++++++++------ .../java/com/cx/restclient/SCAClient.java | 52 ++++++++++--- .../restclient/common/DependencyScanner.java | 17 ++++ .../common/summary/SummaryUtils.java | 12 +-- .../configuration/CxScanConfig.java | 33 ++++---- .../restclient/dto/DependencyScannerType.java | 11 +++ src/main/java/testi.java | 42 +++++----- src/main/resources/com/cx/report/report.ftl | 14 ++-- .../configuration/CxScanConfigTest.java | 22 ------ 10 files changed, 221 insertions(+), 124 deletions(-) create mode 100644 src/main/java/com/cx/restclient/common/DependencyScanner.java create mode 100644 src/main/java/com/cx/restclient/dto/DependencyScannerType.java diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index e442977f..bdb55da7 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -1,8 +1,8 @@ package com.cx.restclient; +import com.cx.restclient.common.DependencyScanner; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -28,12 +28,18 @@ /** * Created by Galn on 05/02/2018. */ -class CxOSAClient { +class CxOSAClient implements DependencyScanner { private CxHttpClient httpClient; private Logger log; private CxScanConfig config; private Waiter osaWaiter; + private long projectId; + private String scanId; + + public void setProjectId(long projectId) { + this.projectId = projectId; + } public CxOSAClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; @@ -59,8 +65,15 @@ public OSAScanStatus resolveStatus(OSAScanStatus scanStatus) throws CxClientExce }; } - //API - public String createOSAScan(long projectId) throws IOException, CxClientException { + @Override + public void init() { + // Empty for now + } + + @Override + public String createScan() throws CxClientException { + ensureProjectIdSpecified(); + log.info("----------------------------------- Create CxOSA Scan:------------------------------------"); log.info("Creating OSA scan"); String osaDependenciesJson = config.getOsaDependenciesJson(); @@ -71,7 +84,14 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio throw new CxClientException("Failed to resolve dependencies for OSA scan: " + e.getMessage(), e); } } - return sendOSAScan(osaDependenciesJson, projectId); + + try { + scanId = sendOSAScan(osaDependenciesJson, projectId); + } catch (IOException e) { + scanId = null; + throw new CxClientException("Error sending OSA scan request.", e); + } + return scanId; } private String resolveOSADependencies() throws JsonProcessingException { @@ -96,14 +116,32 @@ private String resolveOSADependencies() throws JsonProcessingException { return osaDependenciesJson; } - public OSAResults getOSAResults(String scanId, long projectId) throws CxClientException, InterruptedException, IOException { + @Override + public OSAResults waitForScanResults() throws CxClientException { + ensureProjectIdSpecified(); + + if (scanId == null) { + throw new CxClientException("Scan was not created."); + } + log.info("-------------------------------------Get CxOSA Results:-----------------------------------"); log.info("Waiting for OSA scan to finish"); - OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId,this.config.getOsaScanTimeoutInMinutes(), log); + + OSAScanStatus osaScanStatus; + try { + osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); + } catch (InterruptedException e) { + throw new CxClientException("Error while waiting for OSA scan to finish.", e); + } log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); - OSAResults osaResults = retrieveOSAResults(scanId, osaScanStatus, projectId); + OSAResults osaResults; + try { + osaResults = retrieveOSAResults(scanId, osaScanStatus, projectId); + } catch (IOException e) { + throw new CxClientException("Failed to retrieve OSA results.", e); + } if (config.getEnablePolicyViolations()) { resolveOSAViolation(osaResults, projectId); @@ -120,7 +158,6 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx return osaResults; } - private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus, long projectId) throws CxClientException, IOException { OSASummaryResults osaSummaryResults = getOSAScanSummaryResults(scanId); List osaLibraries = getOSALibraries(scanId); @@ -140,14 +177,20 @@ private void resolveOSAViolation(OSAResults osaResults, long projectId){ } } + @Override + public OSAResults getLatestScanResults() throws CxClientException { + ensureProjectIdSpecified(); - public OSAResults getLatestOSAResults(long projectId) throws CxClientException, IOException, InterruptedException { log.info("----------------------------------Get CxOSA Last Results:--------------------------------"); - List scanList = getOSALastOSAStatus(projectId); - for (OSAScanStatus s : scanList) { - if (Status.SUCCEEDED.value().equals(s.getState().getName())) { - return retrieveOSAResults(s.getId(), s, projectId); + try { + List scanList = getOSALastOSAStatus(projectId); + for (OSAScanStatus s : scanList) { + if (Status.SUCCEEDED.value().equals(s.getState().getName())) { + return retrieveOSAResults(s.getId(), s, projectId); + } } + } catch (IOException e) { + throw new CxClientException("Error getting last scan results."); } return new OSAResults(); } @@ -226,5 +269,11 @@ private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClient } return scanStatus; } + + private void ensureProjectIdSpecified() throws CxClientException { + if (projectId == 0) { + throw new CxClientException("projectId must be set before executing this method."); + } + } } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 8bab2e8b..12a41a1e 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,13 +1,11 @@ package com.cx.restclient; +import com.cx.restclient.common.DependencyScanner; import com.cx.restclient.common.UrlUtils; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; -import com.cx.restclient.dto.CxVersion; -import com.cx.restclient.dto.LoginSettings; -import com.cx.restclient.dto.Team; -import com.cx.restclient.dto.TokenLoginResponse; +import com.cx.restclient.dto.*; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -44,14 +42,15 @@ public class CxShragaClient { private long projectId; private CxSASTClient sastClient; - private CxOSAClient osaClient; - private final SCAClient scaClient; +// private CxOSAClient osaClient; +// private final SCAClient scaClient; private long sastScanId; private String osaScanId; private SASTResults sastResults = new SASTResults(); private OSAResults osaResults = new OSAResults(); + private DependencyScanner dependencyScanner; public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException { this.config = config; @@ -63,8 +62,15 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); - osaClient = new CxOSAClient(httpClient, log, config); - scaClient = new SCAClient(log, config); + + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { + dependencyScanner = new CxOSAClient(httpClient, log, config); + } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { + dependencyScanner = new SCAClient(log, config); + } + +// osaClient = new CxOSAClient(httpClient, log, config); +// scaClient = new SCAClient(log, config); } //For Test Connection @@ -89,7 +95,6 @@ public String getClientVersion() { } public void init() throws CxClientException, IOException { - log.info("Initializing Cx client [" + getClientVersion() + "]"); getCxVersion(); login(); @@ -102,8 +107,8 @@ public void init() throws CxClientException, IOException { } resolveProject(); - if (config.getScaEnabled()) { - scaClient.init(); + if (dependencyScanner != null) { + dependencyScanner.init(); } } @@ -113,16 +118,27 @@ public long createSASTScan() throws IOException, CxClientException { return sastScanId; } - public String createOSAScan() throws IOException, CxClientException { - osaScanId = osaClient.createOSAScan(projectId); - osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); +// public String createOSAScan() throws IOException, CxClientException { +// osaScanId = osaClient.createOSAScan(projectId); +// osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); +// return osaScanId; +// } +// +// public void createSCAScan() throws IOException, CxClientException { +// scaClient.createScan(); +// } + + public String createDependencyScan() throws CxClientException { + if (dependencyScanner != null) { + osaScanId = dependencyScanner.createScan(); + osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); + } + else { + osaScanId = null; + } return osaScanId; } - public void createSCAScan() throws IOException, CxClientException { - scaClient.createScan(); - } - public void cancelSASTScan() throws IOException, CxClientException { sastClient.cancelSASTScan(sastScanId); } @@ -137,13 +153,13 @@ public SASTResults getLatestSASTResults() throws InterruptedException, CxClientE return sastResults; } - public OSAResults waitForOSAResults() throws InterruptedException, CxClientException, IOException { - osaResults = osaClient.getOSAResults(osaScanId, projectId); + public OSAResults waitForDependencyScanResults() throws CxClientException { + osaResults = dependencyScanner.waitForScanResults(); return osaResults; } - public OSAResults getLatestOSAResults() throws InterruptedException, CxClientException, IOException { - osaResults = osaClient.getLatestOSAResults(projectId); + public OSAResults getLatestDependencyScanResults() throws CxClientException { + osaResults = dependencyScanner.getLatestScanResults(); return osaResults; } @@ -353,6 +369,11 @@ private void resolveProject() throws IOException, CxClientException { } else { projectId = projects.get(0).getId(); } + + // SAST and OSA share the same project ID. + if (dependencyScanner instanceof CxOSAClient) { + ((CxOSAClient) dependencyScanner).setProjectId(projectId); + } } private List getProjectByName(String projectName, String teamId) throws IOException, CxClientException { diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index d2667003..933ddd07 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -1,14 +1,16 @@ package com.cx.restclient; +import com.cx.restclient.common.DependencyScanner; import com.cx.restclient.common.UrlUtils; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.PathFilter; import com.cx.restclient.dto.LoginSettings; +import com.cx.restclient.dto.PathFilter; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.httpClient.utils.ContentType; import com.cx.restclient.httpClient.utils.HttpClientHelper; import com.cx.restclient.osa.dto.ClientType; +import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.dto.CreateProjectRequest; import com.cx.restclient.sca.dto.Project; @@ -23,14 +25,17 @@ import org.apache.http.entity.mime.content.StringBody; import org.slf4j.Logger; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.util.List; /** * SCA - Software Composition Analysis - is the successor of OSA. */ -class SCAClient { +class SCAClient implements DependencyScanner { private static final String API_PATH = "api/"; private final Logger log; @@ -55,24 +60,45 @@ class SCAClient { log); } - void init() throws IOException, CxClientException { - login(); - resolveProject(); + @Override + public void init() throws CxClientException { + try { + login(); + resolveProject(); + } catch (IOException e) { + throw new CxClientException("Failed to init SCA Client.", e); + } } - void createScan() throws IOException, CxClientException { + @Override + public String createScan() throws CxClientException { log.info("----------------------------------- Create SCA Scan:------------------------------------"); log.info("Creating SCA scan"); PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); - File zipFile = CxZipUtils.getZippedSources(config, filter, log); + String scanId; + try { + File zipFile = CxZipUtils.getZippedSources(config, filter, log); + scanId = uploadZipFile(zipFile); + CxZipUtils.deleteZippedSources(zipFile, config, log); + } catch (IOException e) { + throw new CxClientException("Error creating SCA scan.", e); + } - uploadZipFile(zipFile); + return scanId; + } + + @Override + public OSAResults waitForScanResults() { + return new OSAResults(); + } - CxZipUtils.deleteZippedSources(zipFile, config, log); + @Override + public OSAResults getLatestScanResults() throws CxClientException { + return null; } - void login() throws IOException, CxClientException { + private void login() throws IOException, CxClientException { log.info("Logging into SCA."); SCAConfig scaConfig = config.getScaConfig(); @@ -131,7 +157,7 @@ private String createProject(String name) throws CxClientException, IOException return newProject.getId(); } - private void uploadZipFile(File zipFile) throws IOException, CxClientException { + private String uploadZipFile(File zipFile) throws IOException, CxClientException { log.info("Uploading zipped sources."); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -148,5 +174,7 @@ private void uploadZipFile(File zipFile) throws IOException, CxClientException { String scanId = httpClient.postRequest("scans/zip", null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); log.debug("Scan ID: " + scanId); + + return scanId; } } diff --git a/src/main/java/com/cx/restclient/common/DependencyScanner.java b/src/main/java/com/cx/restclient/common/DependencyScanner.java new file mode 100644 index 00000000..78d03839 --- /dev/null +++ b/src/main/java/com/cx/restclient/common/DependencyScanner.java @@ -0,0 +1,17 @@ +package com.cx.restclient.common; + +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.osa.dto.OSAResults; + +/** + * Dependency Scanner is an umbrella term for OSA and SCA. + */ +public interface DependencyScanner { + void init() throws CxClientException; + + String createScan() throws CxClientException; + + OSAResults waitForScanResults() throws CxClientException; + + OSAResults getLatestScanResults() throws CxClientException; +} diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index af4a55b0..c7148ba0 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -3,6 +3,7 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; import com.cx.restclient.sast.dto.SASTResults; @@ -76,7 +77,7 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu } //osa: - if (config.getOsaEnabled()) { + if (config.getDependencyScannerType() != DependencyScannerType.NONE) { if (osaResults.isOsaResultsReady()) { boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); templateData.put("osaThresholdExceeded", osaThresholdExceeded); @@ -117,13 +118,14 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu )); } - if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { + if (config.getDependencyScannerType() != DependencyScannerType.NONE && + osaResults.getOsaPolicies().size() > 0) { policyViolated = true; policies.putAll(osaResults.getOsaPolicies().stream().collect( Collectors.toMap(Policy::getPolicyName, Policy::getRuleName, - (left, right) -> { - return left; - }))); + (left, right) -> { + return left; + }))); } policyViolatedCount = policies.size(); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 26ec1956..0f0f2b84 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,6 +1,7 @@ package com.cx.restclient.configuration; import com.cx.restclient.dto.CxVersion; +import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.dto.RemoteSourceTypes; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sast.dto.ReportType; @@ -18,7 +19,6 @@ public class CxScanConfig implements Serializable { private Boolean sastEnabled = false; - private Boolean osaEnabled = false; private String cxOrigin; private CxVersion cxVersion; @@ -97,8 +97,9 @@ public class CxScanConfig implements Serializable { private Integer maxZipSize; private String defaultProjectName; - private boolean scaEnabled; + // private boolean scaEnabled; private SCAConfig scaConfig; + private DependencyScannerType dependencyScannerType; public CxScanConfig() { } @@ -126,14 +127,6 @@ public void setSastEnabled(Boolean sastEnabled) { this.sastEnabled = sastEnabled; } - public Boolean getOsaEnabled() { - return osaEnabled; - } - - public void setOsaEnabled(Boolean osaEnabled) { - this.osaEnabled = osaEnabled; - } - public String getCxOrigin() { return cxOrigin; } @@ -494,7 +487,9 @@ public boolean isSASTThresholdEffectivelyEnabled() { } public boolean isOSAThresholdEffectivelyEnabled() { - return getOsaEnabled() && getOsaThresholdsEnabled() && (getOsaHighThreshold() != null || getOsaMediumThreshold() != null || getOsaLowThreshold() != null); + return getDependencyScannerType() != DependencyScannerType.NONE && + getOsaThresholdsEnabled() && + (getOsaHighThreshold() != null || getOsaMediumThreshold() != null || getOsaLowThreshold() != null); } public void setOsaDependenciesJson(String osaDependenciesJson) { @@ -702,14 +697,6 @@ public void addRTFReport(String rtfReportPath) { reports.put(ReportType.RTF, rtfReportPath); } - public void setScaEnabled(boolean scaEnabled) { - this.scaEnabled = scaEnabled; - } - - public boolean getScaEnabled() { - return scaEnabled; - } - public SCAConfig getScaConfig() { return scaConfig; } @@ -717,4 +704,12 @@ public SCAConfig getScaConfig() { public void setScaConfig(SCAConfig scaConfig) { this.scaConfig = scaConfig; } + + public DependencyScannerType getDependencyScannerType() { + return dependencyScannerType; + } + + public void setDependencyScannerType(DependencyScannerType scannerType) { + this.dependencyScannerType = scannerType; + } } diff --git a/src/main/java/com/cx/restclient/dto/DependencyScannerType.java b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java new file mode 100644 index 00000000..8e572dd3 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java @@ -0,0 +1,11 @@ +package com.cx.restclient.dto; + +public enum DependencyScannerType { + /** + * Indicates that dependency scan should not be performed. + */ + NONE, + + OSA, + SCA +} diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 990ff58a..5f47ae2f 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -1,5 +1,6 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; @@ -39,15 +40,11 @@ public static void main(String[] args) throws Exception { CxScanConfig config = setConfigi(); CxShragaClient shraga = new CxShragaClient(config, logi); - // shraga.getClientVersion(); + // shraga.getClientVersion(); shraga.init(); try { - if (config.getOsaEnabled()) { - shraga.createOSAScan(); - } else if (config.getScaEnabled()) { - shraga.createSCAScan(); - } + shraga.createDependencyScan(); } catch (Exception ex) { logi.error(ex.getMessage()); } @@ -69,16 +66,14 @@ public static void main(String[] args) throws Exception { } try { - if (config.getOsaEnabled()) { - osaResults = shraga.waitForOSAResults(); - } + osaResults = shraga.waitForDependencyScanResults(); } catch (Exception ex) { logi.error(ex.getMessage()); } //lastSastResults = shraga.getLatestSASTResults(); - // lastOsaResults = shraga.getLatestOSAResults(); - if (config.getEnablePolicyViolations()) { + //lastOsaResults = shraga.getLatestDependencyScanResults(); + if (config.getEnablePolicyViolations()) { shraga.printIsProjectViolated(); } //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); @@ -93,11 +88,13 @@ public static void main(String[] args) throws Exception { private static CxScanConfig setConfigi() { CxScanConfig config = new CxScanConfig(); + config.setDependencyScannerType(DependencyScannerType.SCA); configureSca(config); + configureOsa(config); config.setSastEnabled(true); - config.setSourceDir("c:\\cxdev\\projectsToScan\\BookStore_Small_CLI\\"); + config.setSourceDir("c:\\cxdev\\projectsToScan\\SastAndOsaSource\\"); config.setReportsDir(new File("c:\\cxdev\\reports\\")); config.setUrl("http://10.32.1.57"); @@ -123,16 +120,7 @@ private static CxScanConfig setConfigi() { config.setSastMediumThreshold(1); config.setSastLowThreshold(1); config.setGeneratePDFReport(true); - config.setOsaEnabled(false); - - config.setOsaFilterPattern("");//TODO check - config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); - config.setOsaRunInstall(true); - config.setOsaThresholdsEnabled(true); - config.setOsaHighThreshold(10); - config.setOsaMediumThreshold(0); - config.setOsaLowThreshold(0); config.setDenyProject(false); config.setPublic(true); //config.setUseSSOLogin(false); @@ -143,9 +131,17 @@ private static CxScanConfig setConfigi() { return config; } - private static void configureSca(CxScanConfig parentConfig) { - parentConfig.setScaEnabled(true); + private static void configureOsa(CxScanConfig config) { + config.setOsaFilterPattern("");//TODO check + config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); + config.setOsaRunInstall(true); + config.setOsaThresholdsEnabled(true); + config.setOsaHighThreshold(10); + config.setOsaMediumThreshold(0); + config.setOsaLowThreshold(0); + } + private static void configureSca(CxScanConfig parentConfig) { SCAConfig config = new SCAConfig(); config.setApiUrl("http://scaapp.lumodev.com"); config.setAccessControlUrl("http://upgrade.dev-ac-checkmarx.com"); diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index c2ed822f..ac76ac42 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -947,17 +947,17 @@ <#if config.sastEnabled && !sast.sastResultsReady>
  • SAST Scan Failed
  • - <#if config.osaEnabled && !osa.osaResultsReady> + <#if config.dependencyScannerType != "NONE" && !osa.osaResultsReady>
  • OSA Scan Failed
  • <#if policyViolated>
  • ${policyViolatedCount} ${policyLabel} Violated
  • - <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.dependencyScannerType != "NONE" && osa.osaResultsReady && osaThresholdExceeded>
  • Exceeded CxSAST and CxOSA Vulnerability Thresholds
  • <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)>
  • Exceeded CxSAST Vulnerability Threshold
  • - <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> + <#elseif config.dependencyScannerType != "NONE" && osa.osaResultsReady && osaThresholdExceeded>
  • Exceeded CxOSA Vulnerability Threshold
  • @@ -1011,7 +1011,7 @@
    <#if config.sastEnabled> -
    +
    chart-large" id="sast-summary">
    CxSAST Vulnerabilities Status
    <#if sast.sastResultsReady> @@ -1307,7 +1307,7 @@
    - <#if config.osaEnabled> + <#if config.dependencyScannerType != "NONE">
    CxOSA Vulnerabilities & Libraries
    @@ -1963,7 +1963,7 @@ - <#if config.osaEnabled && osa.osaResultsReady> + <#if config.dependencyScannerType != "NONE" && osa.osaResultsReady> <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0>
    @@ -2305,7 +2305,7 @@ - <#if (config.osaEnabled || config.sastEnabled) && policyViolated> + <#if (config.dependencyScannerType != "NONE" || config.sastEnabled) && policyViolated> <#if policyViolatedCount gt 0>
    diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java index 7e109b0a..3dc6c8fd 100644 --- a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -60,27 +60,6 @@ public void getSetSastEnabled() { } - @Test - public void getSetOsaEnabled() { - logUnitTests.info("Current test validate that we get correct values from getOsaEnabled method\n"); - - logUnitTests.info("1 test --> Set 'setOsaEnabled' to value 'false' and validate that get 'false' value\n"); - cxScanConfig.setOsaEnabled(false); - assertEquals("I expected to get 'false' value but got different value", cxScanConfig.getOsaEnabled(), false); - - logUnitTests.info("2 test --> Set 'setOsaEnabled' to value 'true' and validate that get 'true' value\n"); - cxScanConfig.setOsaEnabled(true); - assertEquals("I expected to get 'true' value but got different value", cxScanConfig.getOsaEnabled(), true); - - logUnitTests.info("3 test --> Negative test - Set 'setOsaEnabled' to value 'true' and validate that get 'false' value\n"); - cxScanConfig.setOsaEnabled(true); - assertEquals("Negative test - expected to see different values", !(cxScanConfig.getOsaEnabled()), false); - - logUnitTests.info("4 test --> Negative test - Set 'setOsaEnabled' to value 'false' and validate that get 'true' value\n"); - cxScanConfig.setOsaEnabled(false); - assertEquals("Negative test - expected to see different values", !(cxScanConfig.getOsaEnabled()), true); - } - @Test public void getSetCxOrigin() { @@ -101,5 +80,4 @@ public void getSetCxOrigin() { assertNull("Did not Get NULL value", cxScanConfig.getCxOrigin()); } - } \ No newline at end of file From 3e5d0cdeb1e7f2ac1c26c96e0be371467d4f11bc Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 17 Nov 2019 15:26:05 +0200 Subject: [PATCH 139/473] AB#285: Waiting for SCA scan to finish. Waiter class improvements. --- .../java/com/cx/restclient/CxOSAClient.java | 26 +++---- .../java/com/cx/restclient/CxSASTClient.java | 11 +-- .../java/com/cx/restclient/SCAClient.java | 19 +++++- .../com/cx/restclient/common/ShragaUtils.java | 11 +++ .../java/com/cx/restclient/common/Waiter.java | 31 +++------ .../java/com/cx/restclient/sca/SCAWaiter.java | 67 +++++++++++++++++++ .../sca/dto/ScanStatusResponse.java | 18 +++++ .../com/cx/restclient/sca/dto/StatusName.java | 20 ++++++ 8 files changed, 156 insertions(+), 47 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/SCAWaiter.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/StatusName.java diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index bdb55da7..0fdd7034 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -1,6 +1,7 @@ package com.cx.restclient; import com.cx.restclient.common.DependencyScanner; +import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.Status; @@ -128,11 +129,7 @@ public OSAResults waitForScanResults() throws CxClientException { log.info("Waiting for OSA scan to finish"); OSAScanStatus osaScanStatus; - try { - osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); - } catch (InterruptedException e) { - throw new CxClientException("Error while waiting for OSA scan to finish.", e); - } + osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); @@ -147,7 +144,7 @@ public OSAResults waitForScanResults() throws CxClientException { resolveOSAViolation(osaResults, projectId); } - OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); + OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); if (config.getReportsDir() != null) { writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); @@ -168,11 +165,11 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus return results; } - private void resolveOSAViolation(OSAResults osaResults, long projectId){ + private void resolveOSAViolation(OSAResults osaResults, long projectId) { try { getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) .forEach(osaResults::addPolicy); - }catch (Exception ex) { + } catch (Exception ex) { log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } } @@ -247,19 +244,14 @@ private OSAScanStatus getOSAScanStatus(String scanId) throws CxClientException, } private void printOSAProgress(OSAScanStatus scanStatus, long startTime) { - long elapsedSec = System.currentTimeMillis() / 1000 - startTime; - long hours = elapsedSec / 3600; - long minutes = elapsedSec % 3600 / 60; - long seconds = elapsedSec % 60; - String hoursStr = (hours < 10) ? ("0" + Long.toString(hours)) : (Long.toString(hours)); - String minutesStr = (minutes < 10) ? ("0" + Long.toString(minutes)) : (Long.toString(minutes)); - String secondsStr = (seconds < 10) ? ("0" + Long.toString(seconds)) : (Long.toString(seconds)); - log.info("Waiting for OSA scan results. Elapsed time: " + hoursStr + ":" + minutesStr + ":" + secondsStr + ". " + + String timestamp = ShragaUtils.getTimestampSince(startTime); + + log.info("Waiting for OSA scan results. Elapsed time: " + timestamp + ". " + "Status: " + scanStatus.getState().getName()); } private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClientException { - if (scanStatus ==null || Status.FAILED == scanStatus.getBaseStatus()) { + if (scanStatus == null || Status.FAILED == scanStatus.getBaseStatus()) { String failedMsg = scanStatus.getState() == null ? "" : "status [" + scanStatus.getState().getName() + "]. Reason: " + scanStatus.getState().getFailureReason(); throw new CxClientException("OSA scan cannot be completed. " + failedMsg); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 7fbecd2d..f0c3b18b 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -1,5 +1,6 @@ package com.cx.restclient; +import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.PathFilter; @@ -409,16 +410,10 @@ private ResponseQueueScanStatus getSASTScanStatus(String scanId) throws CxClient } private void printSASTProgress(ResponseQueueScanStatus scanStatus, long startTime) { - long elapsedSec = System.currentTimeMillis() / 1000 - startTime; - long hours = elapsedSec / 3600; - long minutes = elapsedSec % 3600 / 60; - long seconds = elapsedSec % 60; - String hoursStr = (hours < 10) ? ("0" + Long.toString(hours)) : (Long.toString(hours)); - String minutesStr = (minutes < 10) ? ("0" + Long.toString(minutes)) : (Long.toString(minutes)); - String secondsStr = (seconds < 10) ? ("0" + Long.toString(seconds)) : (Long.toString(seconds)); + String timestamp = ShragaUtils.getTimestampSince(startTime); String prefix = (scanStatus.getTotalPercent() < 10) ? " " : ""; - log.info("Waiting for SAST scan results. Elapsed time: " + hoursStr + ":" + minutesStr + ":" + secondsStr + ". " + prefix + + log.info("Waiting for SAST scan results. Elapsed time: " + timestamp + ". " + prefix + scanStatus.getTotalPercent() + "% processed. Status: " + scanStatus.getStage().getValue() + "."); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 933ddd07..fea2a25d 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -2,6 +2,7 @@ import com.cx.restclient.common.DependencyScanner; import com.cx.restclient.common.UrlUtils; +import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.LoginSettings; import com.cx.restclient.dto.PathFilter; @@ -12,9 +13,11 @@ import com.cx.restclient.osa.dto.ClientType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.utils.zip.CxZipUtils; +import com.cx.restclient.sca.SCAWaiter; import com.cx.restclient.sca.dto.CreateProjectRequest; import com.cx.restclient.sca.dto.Project; import com.cx.restclient.sca.dto.SCAConfig; +import com.cx.restclient.sca.dto.ScanStatusResponse; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; @@ -45,11 +48,16 @@ class SCAClient implements DependencyScanner { private final CxHttpClient httpClient; private String projectId; + private final Waiter waiter; + private String scanId; SCAClient(Logger log, CxScanConfig config) throws MalformedURLException { this.log = log; this.config = config; + int pollInterval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; + int marRetries = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + SCAConfig scaConfig = config.getScaConfig(); String apiBaseUrl = UrlUtils.parseURLToString(scaConfig.getApiUrl(), API_PATH); @@ -58,6 +66,8 @@ class SCAClient implements DependencyScanner { config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); + + waiter = new SCAWaiter("SCA scan", pollInterval, marRetries, httpClient, log); } @Override @@ -76,7 +86,7 @@ public String createScan() throws CxClientException { log.info("Creating SCA scan"); PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); - String scanId; + scanId = null; try { File zipFile = CxZipUtils.getZippedSources(config, filter, log); scanId = uploadZipFile(zipFile); @@ -89,7 +99,12 @@ public String createScan() throws CxClientException { } @Override - public OSAResults waitForScanResults() { + public OSAResults waitForScanResults() throws CxClientException { + log.info("------------------------------------Get SCA Results:-----------------------------------"); + log.info("Waiting for SCA scan to finish"); + + waiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); + return new OSAResults(); } diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index c599d30c..556f60b5 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -167,4 +167,15 @@ public static String formatDate(String date, String fromFormat, String toFormat) } return ret; } + + public static String getTimestampSince(long startTimeSec) { + long elapsedSec = System.currentTimeMillis() / 1000 - startTimeSec; + long hours = elapsedSec / 3600; + long minutes = elapsedSec % 3600 / 60; + long seconds = elapsedSec % 60; + String hoursStr = (hours < 10) ? ("0" + hours) : (Long.toString(hours)); + String minutesStr = (minutes < 10) ? ("0" + minutes) : (Long.toString(minutes)); + String secondsStr = (seconds < 10) ? ("0" + seconds) : (Long.toString(seconds)); + return String.format("%s:%s:%s", hoursStr, minutesStr, secondsStr); + } } diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index 5a912db5..a2f120a2 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -25,24 +25,18 @@ public Waiter(String scanType, int interval, int retry) { private long startTimeSec; - protected Status status = null; - - public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { + public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException { startTimeSec = System.currentTimeMillis() / 1000; long elapsedTimeSec = 0L; - T obj; + T statusResponse; try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - + statusResponse = getStatus(taskId); - while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { + while (isTaskInProgress(statusResponse) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { Thread.sleep(sleepIntervalSec * 1000); try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - log.debug(status.value()); + statusResponse = getStatus(taskId); } catch (Exception e) { log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); retry--; @@ -52,16 +46,16 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) continue; } elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; - printProgress(obj); - + printProgress(statusResponse); } + if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { throw new CxClientException("Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); } } catch (Exception e) { throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); } - return resolveStatus(obj); + return resolveStatus(statusResponse); } public abstract T getStatus(String id) throws CxClientException, IOException; @@ -70,12 +64,9 @@ public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) public abstract T resolveStatus(T status) throws CxClientException; - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; + public boolean isTaskInProgress(T statusResponse) { + Status status = ((BaseStatus) statusResponse).getBaseStatus(); + return status.equals(Status.IN_PROGRESS); } public long getStartTimeSec() { diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java new file mode 100644 index 00000000..8c51c2f4 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -0,0 +1,67 @@ +package com.cx.restclient.sca; + +import com.cx.restclient.common.ShragaUtils; +import com.cx.restclient.common.Waiter; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.httpClient.CxHttpClient; +import com.cx.restclient.httpClient.utils.ContentType; +import com.cx.restclient.sca.dto.ScanStatusResponse; +import com.cx.restclient.sca.dto.StatusName; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; + +import java.io.IOException; + +public class SCAWaiter extends Waiter { + private final CxHttpClient httpClient; + private final Logger log; + + public SCAWaiter(String scanType, int interval, int retry, CxHttpClient httpClient, Logger log) { + super(scanType, interval, retry); + this.httpClient = httpClient; + this.log = log; + } + + @Override + public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOException { + String path = String.format("scans/%s/status", scanId); + + ScanStatusResponse response = httpClient.getRequest(path, + ContentType.CONTENT_TYPE_APPLICATION_JSON, + ScanStatusResponse.class, HttpStatus.SC_OK, + "SCA scan status", + false); + + return response; + } + + @Override + public void printProgress(ScanStatusResponse statusResponse) { + log.info(String.format("Waiting for SCA scan results. Elapsed time: %s. Status: %s.", + ShragaUtils.getTimestampSince(getStartTimeSec()), + statusResponse.getName().getValue())); + } + + @Override + public ScanStatusResponse resolveStatus(ScanStatusResponse lastStatusResponse) throws CxClientException { + if (lastStatusResponse == null || lastStatusResponse.getName() == StatusName.FAILED) { + String details = null; + if (lastStatusResponse != null) { + details = String.format("Status: %s, message: \'%s\'", + lastStatusResponse.getName(), + lastStatusResponse.getMessage()); + } + throw new CxClientException("SCA scan cannot be completed. " + details); + } + + if (lastStatusResponse.getName() == StatusName.DONE) { + log.info("SCA scan finished."); + } + return lastStatusResponse; + } + + @Override + public boolean isTaskInProgress(ScanStatusResponse statusResponse) { + return statusResponse != null && statusResponse.getName() == StatusName.SCANNING; + } +} diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java b/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java new file mode 100644 index 00000000..01bc8391 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java @@ -0,0 +1,18 @@ +package com.cx.restclient.sca.dto; + +public class ScanStatusResponse { + private StatusName name; + private String message; + + public StatusName getName() { + return name; + } + + public void setName(StatusName name) { + this.name = name; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/cx/restclient/sca/dto/StatusName.java b/src/main/java/com/cx/restclient/sca/dto/StatusName.java new file mode 100644 index 00000000..2ac058dc --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/StatusName.java @@ -0,0 +1,20 @@ +package com.cx.restclient.sca.dto; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum StatusName { + FAILED("Failed"), + DONE("Done"), + SCANNING("Scanning"); + + private final String value; + + @JsonValue + public String getValue() { + return value; + } + + StatusName(String value) { + this.value = value; + } +} From c8b1dd1b83db5ea541714dc7f798181ed297d49d Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Sun, 17 Nov 2019 21:41:16 +0200 Subject: [PATCH 140/473] update fsa after fixing by tomer --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 163e4508..57f2ebf2 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ com.checkmarx cx-ws-fs-agent - 18.7.2.3 + 18.7.2.4 javax.xml.bind @@ -343,4 +343,4 @@ - \ No newline at end of file + From 33e2bc4b47789fbf771618254739981e17df9a13 Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Sun, 17 Nov 2019 22:13:37 +0200 Subject: [PATCH 141/473] update fsa --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0ef2dfea..81b98dc6 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ com.checkmarx cx-ws-fs-agent - 18.7.2.3 + 18.7.2.4 javax.xml.bind @@ -358,4 +358,4 @@ - \ No newline at end of file + From 3461c6dd184afaf83607c6d27aa2248eca5f2252 Mon Sep 17 00:00:00 2001 From: alexeyk Date: Mon, 18 Nov 2019 14:20:20 +0200 Subject: [PATCH 142/473] AB#307, AB#286: added DependencyScanResults that wraps OSAResults and SCAResults. Getting risk report summary. --- .../java/com/cx/restclient/CxOSAClient.java | 23 +++-- .../com/cx/restclient/CxShragaClient.java | 72 +++++++-------- .../java/com/cx/restclient/SCAClient.java | 64 +++++++++++--- .../com/cx/restclient/common/CxPARAM.java | 2 +- .../restclient/common/DependencyScanner.java | 8 +- .../com/cx/restclient/common/ShragaUtils.java | 72 +++++++++++---- .../common/summary/SummaryUtils.java | 6 +- .../restclient/dto/DependencyScanResults.java | 27 ++++++ .../com/cx/restclient/dto/ScanResults.java | 10 +-- .../java/com/cx/restclient/sca/SCAWaiter.java | 3 +- .../com/cx/restclient/sca/dto/SCAResults.java | 22 +++++ .../restclient/sca/dto/SCASummaryResults.java | 87 +++++++++++++++++++ src/main/java/testi.java | 8 +- 13 files changed, 318 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/DependencyScanResults.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/SCAResults.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 0fdd7034..875e97ae 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -4,6 +4,7 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -72,7 +73,7 @@ public void init() { } @Override - public String createScan() throws CxClientException { + public String createScan(DependencyScanResults target) throws CxClientException { ensureProjectIdSpecified(); log.info("----------------------------------- Create CxOSA Scan:------------------------------------"); @@ -92,6 +93,11 @@ public String createScan() throws CxClientException { scanId = null; throw new CxClientException("Error sending OSA scan request.", e); } + + OSAResults osaResults = new OSAResults(); + osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); + target.setOsaResults(osaResults); + return scanId; } @@ -118,7 +124,7 @@ private String resolveOSADependencies() throws JsonProcessingException { } @Override - public OSAResults waitForScanResults() throws CxClientException { + public void waitForScanResults(DependencyScanResults target) throws CxClientException { ensureProjectIdSpecified(); if (scanId == null) { @@ -152,7 +158,7 @@ public OSAResults waitForScanResults() throws CxClientException { writeJsonToFile(OSA_VULNERABILITIES_NAME, osaResults.getOsaVulnerabilities(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); } - return osaResults; + target.setOsaResults(osaResults); } private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus, long projectId) throws CxClientException, IOException { @@ -175,21 +181,26 @@ private void resolveOSAViolation(OSAResults osaResults, long projectId) { } @Override - public OSAResults getLatestScanResults() throws CxClientException { + public DependencyScanResults getLatestScanResults() throws CxClientException { ensureProjectIdSpecified(); log.info("----------------------------------Get CxOSA Last Results:--------------------------------"); + OSAResults osaResults = null; try { List scanList = getOSALastOSAStatus(projectId); for (OSAScanStatus s : scanList) { if (Status.SUCCEEDED.value().equals(s.getState().getName())) { - return retrieveOSAResults(s.getId(), s, projectId); + osaResults = retrieveOSAResults(s.getId(), s, projectId); + break; } } } catch (IOException e) { throw new CxClientException("Error getting last scan results."); } - return new OSAResults(); + + DependencyScanResults result = new DependencyScanResults(); + result.setOsaResults(osaResults != null ? osaResults : new OSAResults()); + return result; } //Private Methods diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 12a41a1e..0616f134 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -10,7 +10,6 @@ import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.ClientType; -import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; @@ -42,13 +41,10 @@ public class CxShragaClient { private long projectId; private CxSASTClient sastClient; -// private CxOSAClient osaClient; -// private final SCAClient scaClient; private long sastScanId; - private String osaScanId; private SASTResults sastResults = new SASTResults(); - private OSAResults osaResults = new OSAResults(); + private DependencyScanResults dependencyScanResults = new DependencyScanResults(); private DependencyScanner dependencyScanner; @@ -68,9 +64,6 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { dependencyScanner = new SCAClient(log, config); } - -// osaClient = new CxOSAClient(httpClient, log, config); -// scaClient = new SCAClient(log, config); } //For Test Connection @@ -118,25 +111,10 @@ public long createSASTScan() throws IOException, CxClientException { return sastScanId; } -// public String createOSAScan() throws IOException, CxClientException { -// osaScanId = osaClient.createOSAScan(projectId); -// osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); -// return osaScanId; -// } -// -// public void createSCAScan() throws IOException, CxClientException { -// scaClient.createScan(); -// } - public String createDependencyScan() throws CxClientException { - if (dependencyScanner != null) { - osaScanId = dependencyScanner.createScan(); - osaResults.setOsaProjectSummaryLink(config.getUrl(), projectId); - } - else { - osaScanId = null; - } - return osaScanId; + ensureDependencyScannerExists(); + String scanId = dependencyScanner.createScan(dependencyScanResults); + return scanId; } public void cancelSASTScan() throws IOException, CxClientException { @@ -153,14 +131,16 @@ public SASTResults getLatestSASTResults() throws InterruptedException, CxClientE return sastResults; } - public OSAResults waitForDependencyScanResults() throws CxClientException { - osaResults = dependencyScanner.waitForScanResults(); - return osaResults; + public DependencyScanResults waitForDependencyScanResults() throws CxClientException { + ensureDependencyScannerExists(); + dependencyScanner.waitForScanResults(dependencyScanResults); + return dependencyScanResults; } - public OSAResults getLatestDependencyScanResults() throws CxClientException { - osaResults = dependencyScanner.getLatestScanResults(); - return osaResults; + public DependencyScanResults getLatestDependencyScanResults() throws CxClientException { + ensureDependencyScannerExists(); + dependencyScanResults = dependencyScanner.getLatestScanResults(); + return dependencyScanResults; } public void printIsProjectViolated() { @@ -168,16 +148,22 @@ public void printIsProjectViolated() { log.info("-----------------------------------------------------------------------------------------"); log.info("Policy Management: "); log.info("--------------------"); - if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()) { - log.info(PROJECT_POLICY_COMPLAINT_STATUS); + + boolean hasOsaViolations = dependencyScanResults != null && + dependencyScanResults.getOsaResults() != null && + dependencyScanResults.getOsaResults().getOsaPolicies() != null && + !dependencyScanResults.getOsaResults().getOsaPolicies().isEmpty(); + + if (sastResults.getSastPolicies().isEmpty() && !hasOsaViolations) { + log.info(PROJECT_POLICY_COMPLIANT_STATUS); log.info("-----------------------------------------------------------------------------------------"); } else { log.info(PROJECT_POLICY_VIOLATED_STATUS); if (!sastResults.getSastPolicies().isEmpty()) { log.info("SAST violated policies names: " + getPoliciesNames(sastResults.getSastPolicies())); } - if (!osaResults.getOsaPolicies().isEmpty()) { - log.info("OSA violated policies names: " + getPoliciesNames(osaResults.getOsaPolicies())); + if (hasOsaViolations) { + log.info("OSA violated policies names: " + getPoliciesNames(dependencyScanResults.getOsaResults().getOsaPolicies())); } log.info("-----------------------------------------------------------------------------------------"); } @@ -189,11 +175,11 @@ private CxArmConfig getCxARMConfig() throws IOException, CxClientException { } public String generateHTMLSummary() throws Exception { - return SummaryUtils.generateSummary(sastResults, osaResults, config); + return SummaryUtils.generateSummary(sastResults, dependencyScanResults, config); } - public String generateHTMLSummary(SASTResults sastResults, OSAResults osaResults) throws Exception { - return SummaryUtils.generateSummary(sastResults, osaResults, config); + public String generateHTMLSummary(SASTResults sastResults, DependencyScanResults dependencyScanResults) throws Exception { + return SummaryUtils.generateSummary(sastResults, dependencyScanResults, config); } public List getAllProjects() throws IOException, CxClientException { @@ -410,4 +396,12 @@ private LoginSettings getDefaultLoginSettings() throws MalformedURLException { return result; } + + private void ensureDependencyScannerExists() throws CxClientException { + if (dependencyScanner == null) { + throw new CxClientException( + String.format("Unable to continue: dependency scanner type was set to %s in config.", + DependencyScannerType.NONE)); + } + } } \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index fea2a25d..0b84bcf7 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -4,6 +4,7 @@ import com.cx.restclient.common.UrlUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.LoginSettings; import com.cx.restclient.dto.PathFilter; import com.cx.restclient.exception.CxClientException; @@ -11,13 +12,9 @@ import com.cx.restclient.httpClient.utils.ContentType; import com.cx.restclient.httpClient.utils.HttpClientHelper; import com.cx.restclient.osa.dto.ClientType; -import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.SCAWaiter; -import com.cx.restclient.sca.dto.CreateProjectRequest; -import com.cx.restclient.sca.dto.Project; -import com.cx.restclient.sca.dto.SCAConfig; -import com.cx.restclient.sca.dto.ScanStatusResponse; +import com.cx.restclient.sca.dto.*; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; @@ -81,7 +78,7 @@ public void init() throws CxClientException { } @Override - public String createScan() throws CxClientException { + public String createScan(DependencyScanResults target) throws CxClientException { log.info("----------------------------------- Create SCA Scan:------------------------------------"); log.info("Creating SCA scan"); @@ -99,17 +96,26 @@ public String createScan() throws CxClientException { } @Override - public OSAResults waitForScanResults() throws CxClientException { + public void waitForScanResults(DependencyScanResults target) throws CxClientException { log.info("------------------------------------Get SCA Results:-----------------------------------"); - log.info("Waiting for SCA scan to finish"); + log.info("Waiting for SCA scan to finish"); waiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); - return new OSAResults(); + log.info("SCA scan finished successfully. Retrieving SCA scan results."); + SCAResults scaResult; + try { + scaResult = retrieveScanResults(); + } catch (IOException e) { + throw new CxClientException("Error retrieving SCA scan results.", e); + } + + target.setScaResults(scaResult); } @Override - public OSAResults getLatestScanResults() throws CxClientException { + public DependencyScanResults getLatestScanResults() { + // TODO return null; } @@ -192,4 +198,42 @@ private String uploadZipFile(File zipFile) throws IOException, CxClientException return scanId; } + + private SCAResults retrieveScanResults() throws IOException, CxClientException { + String reportId = getReportId(); + SCASummaryResults scanSummary = getSummaryReport(reportId); + + SCAResults result = new SCAResults(); + result.setScanId(scanId); + result.setSummary(scanSummary); + return result; + } + + private String getReportId() throws IOException, CxClientException { + log.debug("Getting report ID by scan ID: " + scanId); + String path = String.format("scans/%s/riskReportId", scanId); + String reportId = httpClient.getRequest(path, + ContentType.CONTENT_TYPE_APPLICATION_JSON, + String.class, + HttpStatus.SC_OK, + "Risk report ID", + false); + log.debug("Found report ID: " + reportId); + return reportId; + } + + private SCASummaryResults getSummaryReport(String reportId) throws IOException, CxClientException { + log.debug("Getting summary report."); + + String path = String.format("riskReports/%s/summary", reportId); + + SCASummaryResults result = httpClient.getRequest(path, + ContentType.CONTENT_TYPE_APPLICATION_JSON, + SCASummaryResults.class, + HttpStatus.SC_OK, + "SCA report summary", + false); + + return result; + } } diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index f73e6dd2..69fdae55 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -24,7 +24,7 @@ public abstract class CxPARAM { public static final String ORIGIN_HEADER = "cxOrigin"; public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; public static final String PROJECT_POLICY_VIOLATED_STATUS = "Project policy status : violated"; - public static final String PROJECT_POLICY_COMPLAINT_STATUS = "Project policy status : compliant"; + public static final String PROJECT_POLICY_COMPLIANT_STATUS = "Project policy status : compliant"; public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + diff --git a/src/main/java/com/cx/restclient/common/DependencyScanner.java b/src/main/java/com/cx/restclient/common/DependencyScanner.java index 78d03839..752077f3 100644 --- a/src/main/java/com/cx/restclient/common/DependencyScanner.java +++ b/src/main/java/com/cx/restclient/common/DependencyScanner.java @@ -1,7 +1,7 @@ package com.cx.restclient.common; +import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.osa.dto.OSAResults; /** * Dependency Scanner is an umbrella term for OSA and SCA. @@ -9,9 +9,9 @@ public interface DependencyScanner { void init() throws CxClientException; - String createScan() throws CxClientException; + String createScan(DependencyScanResults target) throws CxClientException; - OSAResults waitForScanResults() throws CxClientException; + void waitForScanResults(DependencyScanResults target) throws CxClientException; - OSAResults getLatestScanResults() throws CxClientException; + DependencyScanResults getLatestScanResults() throws CxClientException; } diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index 556f60b5..eb6c3cb6 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -1,8 +1,12 @@ package com.cx.restclient.common; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.osa.dto.OSASummaryResults; import com.cx.restclient.sast.dto.SASTResults; +import com.cx.restclient.sca.dto.SCAResults; +import com.cx.restclient.sca.dto.SCASummaryResults; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -20,34 +24,67 @@ */ public abstract class ShragaUtils { //Util methods - public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, OSAResults osaResults) { - StringBuilder res = new StringBuilder(""); - isThresholdExceeded(config, sastResults, osaResults, res); + public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults) { + StringBuilder res = new StringBuilder(); + isThresholdExceeded(config, sastResults, dependencyScanResults, res); isThresholdForNewResultExceeded(config, sastResults, res); - isPolicyViolated(config, sastResults, osaResults, res); + isPolicyViolated(config, sastResults, dependencyScanResults, res); return res.toString(); } - public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && ((osaResults!=null && osaResults.getOsaPolicies().size() > 0) || (sastResults != null && sastResults.getSastPolicies().size() > 0)); - if(isPolicyViolated) { + private static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults, StringBuilder res) { + boolean isPolicyViolated = config.getEnablePolicyViolations() && + ((dependencyScanResults != null && + dependencyScanResults.getOsaResults() != null && + dependencyScanResults.getOsaResults().getOsaPolicies() != null && + dependencyScanResults.getOsaResults().getOsaPolicies().size() > 0) || + (sastResults != null && sastResults.getSastPolicies().size() > 0)); + + if (isPolicyViolated) { res.append(PROJECT_POLICY_VIOLATED_STATUS).append("\n"); } return isPolicyViolated; } - public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { + public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults, StringBuilder res) { boolean thresholdExceeded = false; if (config.isSASTThresholdEffectivelyEnabled() && sastResults != null && sastResults.isSastResultsReady()) { - thresholdExceeded = isSeverityExceeded(sastResults.getHigh(), config.getSastHighThreshold(), res, "high", "CxSAST "); - thresholdExceeded |= isSeverityExceeded(sastResults.getMedium(), config.getSastMediumThreshold(), res, "medium", "CxSAST "); - thresholdExceeded |= isSeverityExceeded(sastResults.getLow(), config.getSastLowThreshold(), res, "low", "CxSAST "); + final String SEVERITY_TYPE = "CxSAST"; + thresholdExceeded = isSeverityExceeded(sastResults.getHigh(), config.getSastHighThreshold(), res, "high", SEVERITY_TYPE); + thresholdExceeded |= isSeverityExceeded(sastResults.getMedium(), config.getSastMediumThreshold(), res, "medium", SEVERITY_TYPE); + thresholdExceeded |= isSeverityExceeded(sastResults.getLow(), config.getSastLowThreshold(), res, "low", SEVERITY_TYPE); } - if (config.isOSAThresholdEffectivelyEnabled() && osaResults != null && osaResults.isOsaResultsReady()) { - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalHighVulnerabilities(), config.getOsaHighThreshold(), res, "high", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalMediumVulnerabilities(), config.getOsaMediumThreshold(), res, "medium", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalLowVulnerabilities(), config.getOsaLowThreshold(), res, "low", "CxOSA "); + + if (config.isOSAThresholdEffectivelyEnabled() && dependencyScanResults != null) { + SCAResults scaResults = dependencyScanResults.getScaResults(); + OSAResults osaResults = dependencyScanResults.getOsaResults(); + int totalHigh = 0, totalMedium = 0, totalLow = 0; + String severityType = null; + + if (scaResults != null) { + SCASummaryResults summary = scaResults.getSummary(); + if (summary != null) { + severityType = "SCA"; + totalHigh = summary.getHighVulnerabilitiesCount(); + totalMedium = summary.getMediumVulnerabilitiesCount(); + totalLow = summary.getLowVulnerabilitiesCount(); + } + } else if (osaResults != null && osaResults.isOsaResultsReady()) { + OSASummaryResults summary = osaResults.getResults(); + if (summary != null) { + severityType = "CxOSA"; + totalHigh = summary.getTotalHighVulnerabilities(); + totalMedium = summary.getTotalMediumVulnerabilities(); + totalLow = summary.getTotalLowVulnerabilities(); + } + } + + if (severityType != null) { + thresholdExceeded |= isSeverityExceeded(totalHigh, config.getOsaHighThreshold(), res, "high", severityType); + thresholdExceeded |= isSeverityExceeded(totalMedium, config.getOsaMediumThreshold(), res, "medium", severityType); + thresholdExceeded |= isSeverityExceeded(totalLow, config.getOsaLowThreshold(), res, "low", severityType); + } } return thresholdExceeded; } @@ -88,7 +125,8 @@ public static boolean isThresholdForNewResultExceeded(CxScanConfig config, SASTR private static boolean isSeverityExceeded(int result, Integer threshold, StringBuilder res, String severity, String severityType) { boolean fail = false; if (threshold != null && result > threshold) { - res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append(". \n"); + res.append(String.format("%s %s severity results are above threshold. Results: %d. Threshold: %d.\n", + severityType, severity, result, threshold)); fail = true; } return fail; @@ -131,8 +169,10 @@ public static String processExcludeFolders(String folderExclusions, Logger log) log.info("Exclude folders converted to: '" + result.toString() + "'"); return result.toString(); } + public static final String INCLUDES_LIST = "includes"; public static final String EXCLUDES_LIST = "excludes"; + public static Map> convertPatternsToLists(String filterPatterns) { filterPatterns = StringUtils.defaultString(filterPatterns); List inclusions = new ArrayList(); diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index c7148ba0..93a3564e 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -3,6 +3,7 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; @@ -20,7 +21,8 @@ public abstract class SummaryUtils { - public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { + public static String generateSummary(SASTResults sastResults, DependencyScanResults dependencyScanResults, CxScanConfig config) throws IOException, TemplateException { + OSAResults osaResults = dependencyScanResults.getOsaResults(); Configuration cfg = new Configuration(new Version("2.3.23")); cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); @@ -79,7 +81,7 @@ public static String generateSummary(SASTResults sastResults, OSAResults osaResu //osa: if (config.getDependencyScannerType() != DependencyScannerType.NONE) { if (osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); + boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, dependencyScanResults, new StringBuilder()); templateData.put("osaThresholdExceeded", osaThresholdExceeded); buildFailed |= osaThresholdExceeded; diff --git a/src/main/java/com/cx/restclient/dto/DependencyScanResults.java b/src/main/java/com/cx/restclient/dto/DependencyScanResults.java new file mode 100644 index 00000000..1e50f7e3 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/DependencyScanResults.java @@ -0,0 +1,27 @@ +package com.cx.restclient.dto; + +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.sca.dto.SCAResults; + +import java.io.Serializable; + +public class DependencyScanResults implements Serializable { + private OSAResults osaResults; + private SCAResults scaResults; + + public void setOsaResults(OSAResults osaResults) { + this.osaResults = osaResults; + } + + public OSAResults getOsaResults() { + return osaResults; + } + + public void setScaResults(SCAResults scaResults) { + this.scaResults = scaResults; + } + + public SCAResults getScaResults() { + return scaResults; + } +} diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java index 5d0357c9..d8eb8d07 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -8,7 +8,7 @@ public class ScanResults implements Serializable { private SASTResults sastResults = new SASTResults(); - private OSAResults osaResults = new OSAResults(); + private DependencyScanResults dependencyScanResults = new DependencyScanResults(); private Exception sastCreateException = null; private Exception sastWaitException = null; @@ -27,12 +27,12 @@ public void setSastResults(SASTResults sastResults) { this.sastResults = sastResults; } - public OSAResults getOsaResults() { - return osaResults; + public DependencyScanResults getDependencyScanResults() { + return dependencyScanResults; } - public void setOsaResults(OSAResults osaResults) { - this.osaResults = osaResults; + public void setDependencyScanResults(DependencyScanResults dependencyScanResults) { + this.dependencyScanResults = dependencyScanResults; } public Exception getSastCreateException() { diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index 8c51c2f4..f23c4c5a 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -28,7 +28,8 @@ public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOE ScanStatusResponse response = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, - ScanStatusResponse.class, HttpStatus.SC_OK, + ScanStatusResponse.class, + HttpStatus.SC_OK, "SCA scan status", false); diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAResults.java b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java new file mode 100644 index 00000000..261ee2ef --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java @@ -0,0 +1,22 @@ +package com.cx.restclient.sca.dto; + +public class SCAResults { + private String scanId; + private SCASummaryResults summary; + + public void setScanId(String scanId) { + this.scanId = scanId; + } + + public String getScanId() { + return scanId; + } + + public void setSummary(SCASummaryResults summary) { + this.summary = summary; + } + + public SCASummaryResults getSummary() { + return summary; + } +} diff --git a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java new file mode 100644 index 00000000..38124457 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java @@ -0,0 +1,87 @@ +package com.cx.restclient.sca.dto; + +import java.time.OffsetDateTime; + +public class SCASummaryResults { + private String riskReportId; + private int highVulnerabilitiesCount; + private int mediumVulnerabilitiesCount; + private int lowVulnerabilitiesCount; + private int totalPackages; + private int directPackages; + private OffsetDateTime createdOn; + private double riskScore; + private int totalOutdatedPackages; + + public String getRiskReportId() { + return riskReportId; + } + + public void setRiskReportId(String riskReportId) { + this.riskReportId = riskReportId; + } + + public int getHighVulnerabilitiesCount() { + return highVulnerabilitiesCount; + } + + public void setHighVulnerabilitiesCount(int highVulnerabilitiesCount) { + this.highVulnerabilitiesCount = highVulnerabilitiesCount; + } + + public int getMediumVulnerabilitiesCount() { + return mediumVulnerabilitiesCount; + } + + public void setMediumVulnerabilitiesCount(int mediumVulnerabilitiesCount) { + this.mediumVulnerabilitiesCount = mediumVulnerabilitiesCount; + } + + public int getLowVulnerabilitiesCount() { + return lowVulnerabilitiesCount; + } + + public void setLowVulnerabilitiesCount(int lowVulnerabilitiesCount) { + this.lowVulnerabilitiesCount = lowVulnerabilitiesCount; + } + + public int getTotalPackages() { + return totalPackages; + } + + public void setTotalPackages(int totalPackages) { + this.totalPackages = totalPackages; + } + + public int getDirectPackages() { + return directPackages; + } + + public void setDirectPackages(int directPackages) { + this.directPackages = directPackages; + } + + public OffsetDateTime getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(OffsetDateTime createdOn) { + this.createdOn = createdOn; + } + + public double getRiskScore() { + return riskScore; + } + + public void setRiskScore(double riskScore) { + this.riskScore = riskScore; + } + + public int getTotalOutdatedPackages() { + return totalOutdatedPackages; + } + + public void setTotalOutdatedPackages(int totalOutdatedPackages) { + this.totalOutdatedPackages = totalOutdatedPackages; + } +} diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 5f47ae2f..925777ba 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -44,7 +44,9 @@ public static void main(String[] args) throws Exception { shraga.init(); try { - shraga.createDependencyScan(); + if (config.getDependencyScannerType() != DependencyScannerType.NONE) { + shraga.createDependencyScan(); + } } catch (Exception ex) { logi.error(ex.getMessage()); } @@ -66,7 +68,9 @@ public static void main(String[] args) throws Exception { } try { - osaResults = shraga.waitForDependencyScanResults(); + if (config.getDependencyScannerType() != DependencyScannerType.NONE) { + shraga.waitForDependencyScanResults(); + } } catch (Exception ex) { logi.error(ex.getMessage()); } From 511ff9f94c8a0bd7d247ec9d909ffc7339673706 Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 18 Nov 2019 15:28:42 +0200 Subject: [PATCH 143/473] replaced old 9.0 with more updated version --- .../java/com/cx/restclient/common/Waiter.java | 83 ---- .../common/summary/SummaryUtils.java | 161 ------- .../com/cx/restclient/cxArm/dto/Rule.java | 19 - .../cx/restclient/cxArm/utils/CxARMUtils.java | 75 --- .../java/com/cx/restclient/dto/CxProxy.java | 60 --- .../java/com/cx/restclient/dto/CxVersion.java | 28 -- .../restclient/dto/RemoteSourceRequest.java | 96 ---- .../cx/restclient/dto/RemoteSourceTypes.java | 25 - .../restclient/httpClient/CxHttpClient.java | 237 --------- .../cx/restclient/sast/dto/CxARMStatus.java | 48 -- .../restclient/sast/dto/CxARMStatusEnum.java | 25 - src/main/java/testi.java | 148 ------ src/main/resources/common.properties | 1 - .../java/com/cx/restclient/CxOSAClient.java | 30 +- .../java/com/cx/restclient/CxSASTClient.java | 171 +------ .../com/cx/restclient/CxShragaClient.java | 113 ++--- .../cx/restclient/common/CxGlobalMessage.java | 25 + .../com/cx/restclient/common/CxPARAM.java | 16 +- .../cx/restclient/common/ErrorMessage.java | 0 .../com/cx/restclient/common/ErrorUtil.java | 35 ++ .../com/cx/restclient/common/ShragaUtils.java | 25 +- .../com/cx/restclient/common/UrlUtils.java | 0 .../java/com/cx/restclient/common/Waiter.java | 75 +++ .../common/summary/SummaryUtils.java | 127 +++++ .../configuration/CxScanConfig.java | 129 +---- .../cx/restclient/cxArm/dto/CxArmConfig.java | 0 .../cx/restclient/cxArm/dto/CxProviders.java | 3 + .../com/cx/restclient/cxArm/dto/Policy.java | 35 +- .../cx/restclient/cxArm/dto/Violation.java | 15 + .../cx/restclient/cxArm/utils/CxARMUtils.java | 21 + .../com/cx/restclient/dto/BaseStatus.java | 1 + .../com/cx/restclient/dto/LoginRequest.java | 0 .../com/cx/restclient/dto/ScanResults.java | 17 +- .../java/com/cx/restclient/dto/Status.java | 0 .../main/java/com/cx/restclient/dto/Team.java | 1 - .../cx/restclient/dto/ThresholdResult.java | 33 ++ .../cx/restclient/dto/TokenLoginResponse.java | 0 .../exception/CxClientException.java | 0 .../exception/CxHTTPClientException.java | 0 .../exception/CxTokenExpiredException.java | 0 .../restclient/httpClient/CxHttpClient.java | 309 ++++++++++++ .../httpClient/utils/ContentType.java | 0 .../httpClient/utils/HttpClientHelper.java | 5 + .../java/com/cx/restclient/osa/dto/CVE.java | 0 .../restclient/osa/dto/CVEReportTableRow.java | 0 .../com/cx/restclient/osa/dto/Content.java | 0 .../osa/dto/CreateOSAScanRequest.java | 0 .../osa/dto/CreateOSAScanResponse.java | 0 .../com/cx/restclient/osa/dto/Library.java | 0 .../cx/restclient/osa/dto/LoginRequest.java | 0 .../com/cx/restclient/osa/dto/OSAResults.java | 25 +- .../cx/restclient/osa/dto/OSAScanState.java | 0 .../cx/restclient/osa/dto/OSAScanStatus.java | 0 .../restclient/osa/dto/OSAScanStatusEnum.java | 0 .../restclient/osa/dto/OSASummaryResults.java | 0 .../restclient/osa/dto/ScanConfiguration.java | 0 .../com/cx/restclient/osa/dto/Severity.java | 0 .../java/com/cx/restclient/osa/dto/State.java | 0 .../com/cx/restclient/osa/utils/OSAParam.java | 0 .../com/cx/restclient/osa/utils/OSAUtils.java | 9 + .../com/cx/restclient/sast/dto/Comment.java | 0 .../sast/dto/CreateProjectRequest.java | 0 .../sast/dto/CreateReportRequest.java | 0 .../sast/dto/CreateReportResponse.java | 0 .../sast/dto/CreateScanRequest.java | 0 .../cx/restclient/sast/dto/CurrentStatus.java | 0 .../java/com/cx/restclient/sast/dto/CxID.java | 0 .../com/cx/restclient/sast/dto/CxNameObj.java | 0 .../cx/restclient/sast/dto/CxValueObj.java | 0 .../cx/restclient/sast/dto/CxXMLResults.java | 1 - .../sast/dto/EmailNotifications.java | 0 .../restclient/sast/dto/LastScanResponse.java | 0 .../com/cx/restclient/sast/dto/Preset.java | 0 .../com/cx/restclient/sast/dto/Project.java | 0 .../cx/restclient/sast/dto/QueueStatus.java | 0 .../cx/restclient/sast/dto/ReportStatus.java | 0 .../restclient/sast/dto/ReportStatusEnum.java | 0 .../cx/restclient/sast/dto/ReportType.java | 0 .../sast/dto/ResponseQueueScanStatus.java | 0 .../cx/restclient/sast/dto/SASTResults.java | 31 +- .../sast/dto/SASTStatisticsResponse.java | 0 .../sast/dto/ScanSettingRequest.java | 0 .../sast/dto/ScanSettingResponse.java | 0 .../sast/dto/SupportedLanguage.java | 0 .../sast/dto/UpdateScanStatusRequest.java | 0 .../cx/restclient/sast/utils/SASTParam.java | 6 - .../cx/restclient/sast/utils/SASTUtils.java | 8 +- .../cx/restclient/sast/utils/zip/CxZip.java | 0 .../restclient/sast/utils/zip/CxZipUtils.java | 0 .../sast/utils/zip/ZipListener.java | 0 .../cx/restclient/sast/utils/zip/Zipper.java | 0 .../images/no-policy-violation-found.png | Bin .../report/images/policy-violation-found.png | Bin .../main/resources/com/cx/report/report.ftl | 453 +++++++++--------- .../configuration/ConnectionTest.java | 49 ++ 95 files changed, 1073 insertions(+), 1701 deletions(-) delete mode 100644 src/main/java/com/cx/restclient/common/Waiter.java delete mode 100644 src/main/java/com/cx/restclient/common/summary/SummaryUtils.java delete mode 100644 src/main/java/com/cx/restclient/cxArm/dto/Rule.java delete mode 100644 src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java delete mode 100644 src/main/java/com/cx/restclient/dto/CxProxy.java delete mode 100644 src/main/java/com/cx/restclient/dto/CxVersion.java delete mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java delete mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java delete mode 100644 src/main/java/com/cx/restclient/httpClient/CxHttpClient.java delete mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java delete mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java delete mode 100644 src/main/java/testi.java delete mode 100644 src/main/resources/common.properties rename src/{ => src}/main/java/com/cx/restclient/CxOSAClient.java (93%) rename src/{ => src}/main/java/com/cx/restclient/CxSASTClient.java (69%) rename src/{ => src}/main/java/com/cx/restclient/CxShragaClient.java (76%) create mode 100644 src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java rename src/{ => src}/main/java/com/cx/restclient/common/CxPARAM.java (61%) rename src/{ => src}/main/java/com/cx/restclient/common/ErrorMessage.java (100%) create mode 100644 src/src/main/java/com/cx/restclient/common/ErrorUtil.java rename src/{ => src}/main/java/com/cx/restclient/common/ShragaUtils.java (84%) rename src/{ => src}/main/java/com/cx/restclient/common/UrlUtils.java (100%) create mode 100644 src/src/main/java/com/cx/restclient/common/Waiter.java create mode 100644 src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java rename src/{ => src}/main/java/com/cx/restclient/configuration/CxScanConfig.java (79%) rename src/{ => src}/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java (100%) rename src/{ => src}/main/java/com/cx/restclient/cxArm/dto/CxProviders.java (74%) rename src/{ => src}/main/java/com/cx/restclient/cxArm/dto/Policy.java (54%) rename src/{ => src}/main/java/com/cx/restclient/cxArm/dto/Violation.java (89%) create mode 100644 src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java rename src/{ => src}/main/java/com/cx/restclient/dto/BaseStatus.java (99%) rename src/{ => src}/main/java/com/cx/restclient/dto/LoginRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/dto/ScanResults.java (81%) rename src/{ => src}/main/java/com/cx/restclient/dto/Status.java (100%) rename src/{ => src}/main/java/com/cx/restclient/dto/Team.java (99%) create mode 100644 src/src/main/java/com/cx/restclient/dto/ThresholdResult.java rename src/{ => src}/main/java/com/cx/restclient/dto/TokenLoginResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/exception/CxClientException.java (100%) rename src/{ => src}/main/java/com/cx/restclient/exception/CxHTTPClientException.java (100%) rename src/{ => src}/main/java/com/cx/restclient/exception/CxTokenExpiredException.java (100%) create mode 100644 src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java rename src/{ => src}/main/java/com/cx/restclient/httpClient/utils/ContentType.java (100%) rename src/{ => src}/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java (92%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/CVE.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/Content.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/Library.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/LoginRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/OSAResults.java (89%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/OSAScanState.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/Severity.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/dto/State.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/utils/OSAParam.java (100%) rename src/{ => src}/main/java/com/cx/restclient/osa/utils/OSAUtils.java (93%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/Comment.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CurrentStatus.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CxID.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CxNameObj.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CxValueObj.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/CxXMLResults.java (99%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/EmailNotifications.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/LastScanResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/Preset.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/Project.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/QueueStatus.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ReportStatus.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ReportType.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/SASTResults.java (92%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/SASTParam.java (88%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/SASTUtils.java (90%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/zip/CxZip.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java (100%) rename src/{ => src}/main/java/com/cx/restclient/sast/utils/zip/Zipper.java (100%) rename src/{ => src}/main/resources/com/cx/report/images/no-policy-violation-found.png (100%) rename src/{ => src}/main/resources/com/cx/report/images/policy-violation-found.png (100%) rename src/{ => src}/main/resources/com/cx/report/report.ftl (89%) create mode 100644 src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java deleted file mode 100644 index 5d87a7ed..00000000 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.cx.restclient.common; - -import com.cx.restclient.dto.BaseStatus; -import com.cx.restclient.dto.Status; -import com.cx.restclient.exception.CxClientException; -import org.slf4j.Logger; - -import java.io.IOException; -import java.util.Date; - -/** - * Created by Galn on 13/02/2018. - */ -public abstract class Waiter { - - private int retry = 3; - private String scanType; - private int sleepIntervalSec; - - public Waiter(String scanType, int interval) { - this.scanType = scanType; - this.sleepIntervalSec = interval; - } - - private long startTimeSec; - - protected Status status = null; - - public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { - startTimeSec = System.currentTimeMillis() / 1000; - long elapsedTimeSec = 0L; - T obj; - - try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - - - while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { - Thread.sleep(sleepIntervalSec * 1000); - try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - log.debug(status.value()); - } catch (Exception e) { - log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); - retry--; - if (retry <= 0) { - throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); - } - continue; - } - elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; - printProgress(obj); - - } - if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { - throw new CxClientException("Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); - } - } catch (Exception e) { - throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); - } - return resolveStatus(obj); - } - - public abstract T getStatus(String id) throws CxClientException, IOException; - - public abstract void printProgress(T status); - - public abstract T resolveStatus(T status) throws CxClientException; - - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; - } - - public long getStartTimeSec() { - return startTimeSec; - } -} diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java deleted file mode 100644 index af4a55b0..00000000 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.cx.restclient.common.summary; - -import com.cx.restclient.common.ShragaUtils; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.restclient.osa.dto.OSASummaryResults; -import com.cx.restclient.sast.dto.SASTResults; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import freemarker.template.Version; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -public abstract class SummaryUtils { - - public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { - - Configuration cfg = new Configuration(new Version("2.3.23")); - cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); - Template template = cfg.getTemplate("report.ftl"); - - Map templateData = new HashMap(); - templateData.put("config", config); - templateData.put("sast", sastResults); - templateData.put("osa", osaResults); - - //calculated params: - - boolean buildFailed = false; - boolean policyViolated = false; - int policyViolatedCount = 0; - //sast: - if (config.getSastEnabled()) { - if (sastResults.isSastResultsReady()) { - boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); - boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); - templateData.put("sastThresholdExceeded", sastThresholdExceeded); - templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); - buildFailed = sastThresholdExceeded || sastNewResultsExceeded; - //calculate sast bars: - float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); - float sastBarNorm = maxCount * 10f / 9f; - - //sast high bars - float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; - float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); - float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; - templateData.put("sastHighTotalHeight", sastHighTotalHeight); - templateData.put("sastHighNewHeight", sastHighNewHeight); - templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); - - //sast medium bars - float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; - float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); - float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; - templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); - templateData.put("sastMediumNewHeight", sastMediumNewHeight); - templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); - - //sast low bars - float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; - float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); - float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; - templateData.put("sastLowTotalHeight", sastLowTotalHeight); - templateData.put("sastLowNewHeight", sastLowNewHeight); - templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); - } else { - buildFailed = true; - } - } - - //osa: - if (config.getOsaEnabled()) { - if (osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); - templateData.put("osaThresholdExceeded", osaThresholdExceeded); - buildFailed |= osaThresholdExceeded; - - //calculate osa bars: - OSASummaryResults osaSummaryResults = osaResults.getResults(); - int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); - int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); - int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); - float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); - float osaBarNorm = osaMaxCount * 10f / 9f; - - float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; - float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; - float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; - - templateData.put("osaHighTotalHeight", osaHighTotalHeight); - templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); - templateData.put("osaLowTotalHeight", osaLowTotalHeight); - } else { - buildFailed = true; - } - } - - - if (config.getEnablePolicyViolations()) { - Map policies = new HashMap(); - - if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { - policyViolated = true; - policies = sastResults.getSastPolicies().stream().collect( - Collectors.toMap(Policy::getPolicyName, - Policy::getRuleName, - (left, right) -> { - return left; - } - )); - } - - if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { - policyViolated = true; - policies.putAll(osaResults.getOsaPolicies().stream().collect( - Collectors.toMap(Policy::getPolicyName, Policy::getRuleName, - (left, right) -> { - return left; - }))); - } - - policyViolatedCount = policies.size(); - String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; - templateData.put("policyLabel", policyLabel); - templateData.put("policyViolatedCount", policyViolatedCount); - } - - - templateData.put("policyViolated", policyViolated); - buildFailed |= policyViolated; - templateData.put("buildFailed", buildFailed); - - //generate the report: - StringWriter writer = new StringWriter(); - template.process(templateData, writer); - return writer.toString(); - } - - private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { - int minimalVisibilityHeight = 5; - //new high - float highNewHeightPx = (float) newCount / (float) count * totalHeight; - //if new height is between 1 and 9 - give it a minimum height and if theres enough spce in total height - if (isNewNeedChange(totalHeight, highNewHeightPx, minimalVisibilityHeight)) { - highNewHeightPx = minimalVisibilityHeight; - } - - return highNewHeightPx; - } - - private static boolean isNewNeedChange(float highTotalHeightPx, float highNewHeightPx, int minimalVisibilityHeight) { - return highNewHeightPx > 0 && highNewHeightPx < minimalVisibilityHeight && highTotalHeightPx > minimalVisibilityHeight * 2; - } -} \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Rule.java b/src/main/java/com/cx/restclient/cxArm/dto/Rule.java deleted file mode 100644 index d0028a8c..00000000 --- a/src/main/java/com/cx/restclient/cxArm/dto/Rule.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cx.restclient.cxArm.dto; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Galn on 11/11/2018. - */ -public class Rule { - List violations = new ArrayList(); - - public List getViolations() { - return violations; - } - - public void setViolations(List violations) { - this.violations = violations; - } -} diff --git a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java deleted file mode 100644 index fa416ba4..00000000 --- a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.cx.restclient.cxArm.utils; - -import com.cx.restclient.cxArm.dto.Policy; -import com.cx.restclient.cxArm.dto.Violation; -import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.httpClient.CxHttpClient; - -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -import static com.cx.restclient.common.CxPARAM.CX_ARM_VIOLATION; -import static com.cx.restclient.common.ShragaUtils.formatDate; -import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; - -/** - * Created by Galn on 7/30/2018. - */ -public abstract class CxARMUtils { - public static List getProjectViolatedPolicies(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { - String relativePath = CX_ARM_VIOLATION.replace("{projectId}", Long.toString(projectId)).replace("{provider}", provider); - return (List) httpClient.getRequest(cxARMUrl, relativePath, CONTENT_TYPE_APPLICATION_JSON_V1, null, Policy.class, 200, "CxARM violations", true); - } - - public static List getPolicyList(Policy policy) { - List policies = new ArrayList(); - Map> rules = resolveRules(policy.getViolations()); - Iterator it = rules.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = (Map.Entry) it.next(); - List violations = (List) pair.getValue(); - String firstDate = resolveFirstDate(violations); - policies.add(new Policy(policy.getPolicyId(), policy.getPolicyName(), pair.getKey().toString(), violations, firstDate)); - it.remove(); // avoids a ConcurrentModificationException - } - - return policies; - } - - private static Map> resolveRules(List violations) { - Map> rules = violations.stream().collect( - Collectors.toMap(Violation::getRuleName, e -> { - List ary = new ArrayList(); - ary.add(e); - return ary; - }, - (left, right) -> { - left.addAll(right); - return left; - })); - - return rules; - } - - private static String resolveFirstDate(List violations) { - Date firstDetectionDate = new Date(violations.get(0).getFirstDetectionDateByArm()); - for (Violation violation : violations) { - Date date = new Date(violation.getFirstDetectionDateByArm()); - if (date.before(firstDetectionDate)) { - firstDetectionDate = date; - } - } - String firstDate = formatDate(firstDetectionDate.toString(), "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); - return firstDate; - } - - public static String getPoliciesNames(List policies) { - String str =""; - for (Policy policy : policies){ - str += ", " + policy.getPolicyName(); - } - str = str.substring(1, str.length()); - return str; - } -} diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java deleted file mode 100644 index 59084b84..00000000 --- a/src/main/java/com/cx/restclient/dto/CxProxy.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.cx.restclient.dto; - -/** - * Created by Galn on 4/2/2019. - */ -public class CxProxy { - private Boolean useProxy; - private String proxyHost; - private Integer proxyPort; - private String proxyScheme; - - - public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme) { - this.useProxy = useProxy; - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - this.proxyScheme = scheme; - } - - public CxProxy() { - } - - public Boolean getUseProxy() { - return useProxy; - } - - public void setUseProxy(Boolean useProxy) { - this.useProxy = useProxy; - } - - public String getProxyHost() { - return proxyHost; - } - - public void setProxyHost(String proxyHost) { - this.proxyHost = proxyHost; - } - - public Integer getProxyPort() { - return proxyPort; - } - - public void setProxyPort(Integer proxyPort) { - this.proxyPort = proxyPort; - } - - public void setProxyPort(String proxyPort) { - try { - this.proxyPort = Integer.parseInt(proxyPort); - }catch (Exception ex){} - } - - public String getProxyScheme() { - return proxyScheme; - } - - public void setProxyScheme(String proxyScheme) { - this.proxyScheme = proxyScheme; - } -} diff --git a/src/main/java/com/cx/restclient/dto/CxVersion.java b/src/main/java/com/cx/restclient/dto/CxVersion.java deleted file mode 100644 index 6d1d2d6e..00000000 --- a/src/main/java/com/cx/restclient/dto/CxVersion.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cx.restclient.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -/** - * Created by Galn on 4/1/2019. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class CxVersion { - private String version; - private String hotFix; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getHotFix() { - return hotFix; - } - - public void setHotFix(String hotFix) { - this.hotFix = hotFix; - } -} diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java deleted file mode 100644 index 095b5f60..00000000 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.cx.restclient.dto; - -import com.cx.restclient.configuration.CxScanConfig; - -/** - * Created by Galn on 11/25/2018. - */ -public class RemoteSourceRequest { - String url; - int port; - private byte[] privateKey; - private String[] paths; - private String userName; - private String password; - private RemoteSourceTypes type; - private transient String browseMode; - ; - - public RemoteSourceRequest() { - } - - public RemoteSourceRequest(CxScanConfig config) { - this.userName = config.getRemoteSrcUser(); - this.password = config.getRemoteSrcPass(); - this.url = config.getRemoteSrcUrl(); - this.port = config.getRemoteSrcPort(); - this.privateKey = config.getRemoteSrcKeyFile(); - this.paths = config.getPaths(); - this.type = config.getRemoteType(); - } - - public String getUrl() { - return url; - } - - public void setUrl(String absoluteUrl) { - this.url = absoluteUrl; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public byte[] getPrivateKey() { - return privateKey; - } - - public void setPrivateKey(byte[] privateKey) { - this.privateKey = privateKey; - } - - public String[] getPaths() { - return paths; - } - - public void setPaths(String[] paths) { - this.paths = paths; - } - - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public RemoteSourceTypes getType() { - return type; - } - - public void setType(RemoteSourceTypes type) { - this.type = type; - } - - public String getBrowseMode() { - return browseMode; - } - - public void setBrowseMode(String browseMode) { - this.browseMode = browseMode; - } - -} diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java b/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java deleted file mode 100644 index 02f07277..00000000 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cx.restclient.dto; - -/** - * Created by: Galn. - * Date: 25/11/2016. - */ -public enum RemoteSourceTypes { - - SHARED("shared"), - SVN("svn"), - GIT("git"), - TFS("tfs"), - PERFORCE("perforce"); - - private String value; - - RemoteSourceTypes(String value) { - this.value = value; - } - - public String value() { - return value; - } - -} \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java deleted file mode 100644 index 669af29b..00000000 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.cx.restclient.httpClient; - -import com.cx.restclient.common.ErrorMessage; -import com.cx.restclient.common.UrlUtils; -import com.cx.restclient.dto.TokenLoginResponse; -import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.exception.CxHTTPClientException; -import com.cx.restclient.exception.CxTokenExpiredException; -import org.apache.http.*; -import org.apache.http.client.CookieStore; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.*; -import org.apache.http.client.utils.HttpClientUtils; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HttpContext; -import org.apache.http.ssl.SSLContexts; -import org.apache.http.ssl.TrustStrategy; -import org.slf4j.Logger; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; - -import static com.cx.restclient.common.CxPARAM.*; -import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; -import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; - -/** - * Created by Galn on 05/02/2018. - */ -public class CxHttpClient { - - private Logger logi; - private HttpClient apacheClient; - private TokenLoginResponse token; - private String rootUri; - private final String username; - private final String password; - private String cxOrigin; - - private CookieStore cookieStore; - private String cookies; - private String csrfToken; - - private Boolean useSSo = false; - - - private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { - public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { - httpRequest.addHeader(ORIGIN_HEADER, cxOrigin); - if (token != null) { - httpRequest.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); - } - if (csrfToken != null) { - httpRequest.addHeader(CSRF_TOKEN_HEADER, csrfToken); - } - if (cookies != null) { - httpRequest.addHeader("cookie", cookies); - } - } - }; - - - private final HttpResponseInterceptor responseFilter = new HttpResponseInterceptor() { - - public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { - for (org.apache.http.cookie.Cookie c : cookieStore.getCookies()) { - if (CSRF_TOKEN_HEADER.equals(c.getName())) { - csrfToken = c.getValue(); - } - } - Header[] setCookies = httpResponse.getHeaders("Set-Cookie"); - StringBuilder sb = new StringBuilder(); - for (Header h : setCookies) { - sb.append(h.getValue()).append(";"); - } - cookies = (cookies == null ? "" : cookies) + sb.toString(); - } - }; - - - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { - this.logi = logi; - this.username = username; - this.password = password; - this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); - this.cxOrigin = origin; - //create httpclient - HttpClientBuilder builder = HttpClientBuilder.create().addInterceptorFirst(requestFilter); - if (isSSO) { - this.useSSo = true; - cookieStore = new BasicCookieStore(); - builder.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); - } - setSSLTls(builder, "TLSv1.2", logi); - if (disableSSLValidation) { - builder = disableCertificateValidation(builder, logi); - } - - builder.useSystemProperties(); - apacheClient = builder.build(); - } - - public void login() throws IOException, CxClientException { - - if (useSSo) { - HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); - request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); - } else { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); - } - } - - private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { - List parameters = new ArrayList(); - parameters.add(new BasicNameValuePair("username", username)); - parameters.add(new BasicNameValuePair("password", password)); - parameters.add(new BasicNameValuePair("grant_type", "password")); - parameters.add(new BasicNameValuePair("scope", "sast_rest_api cxarm_api")); - parameters.add(new BasicNameValuePair("client_id", "resource_owner_client")); - parameters.add(new BasicNameValuePair("client_secret", "014DF517-39D1-4453-B7B3-9930C563627C")); - - return new UrlEncodedFormEntity(parameters, "utf-8"); - } - - //GET REQUEST - public T getRequest(String relPath, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { - return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); - } - - public T getRequest(String rootURL, String relPath, String acceptHeader, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { - HttpGet get = new HttpGet(rootURL + relPath); - get.addHeader(HttpHeaders.ACCEPT, acceptHeader); - return request(get, contentType, null, responseType, expectStatus, "get " + failedMsg, isCollection, true); - } - - //POST REQUEST - public T postRequest(String relPath, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg) throws IOException, CxClientException { - HttpPost post = new HttpPost(rootUri + relPath); - return request(post, contentType, entity, responseType, expectStatus, failedMsg, false, true); - } - - //PUT REQUEST - public T putRequest(String relPath, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg) throws IOException, CxClientException { - HttpPut put = new HttpPut(rootUri + relPath); - return request(put, contentType, entity, responseType, expectStatus, failedMsg, false, true); - } - - //PATCH REQUEST - public void patchRequest(String relPath, String contentType, HttpEntity entity, int expectStatus, String failedMsg) throws IOException, CxClientException { - HttpPatch patch = new HttpPatch(rootUri + relPath); - request(patch, contentType, entity, null, expectStatus, failedMsg, false, true); - } - - private T request(HttpRequestBase httpMethod, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg, boolean isCollection, boolean retry) throws IOException, CxClientException { - if (contentType != null) { - httpMethod.addHeader("Content-type", contentType); - } - if (entity != null && httpMethod instanceof HttpEntityEnclosingRequestBase) { //Entity for Post methods - ((HttpEntityEnclosingRequestBase) httpMethod).setEntity(entity); - } - HttpResponse response = null; - - try { - response = apacheClient.execute(httpMethod); - - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { //Token expired - throw new CxTokenExpiredException(extractResponseBody(response)); - } - validateResponse(response, expectStatus, "Failed to " + failedMsg); - - //extract response as object and return the link - return convertToObject(response, responseType, isCollection); - } catch (UnknownHostException e) { - throw new CxHTTPClientException(ErrorMessage.CHECKMARX_SERVER_CONNECTION_FAILED.getErrorMessage()); - } catch (CxTokenExpiredException ex) { - if (retry) { - logi.warn("Access token expired, requesting a new token"); - login(); - return request(httpMethod, contentType, entity, responseType, expectStatus, failedMsg, isCollection, false); - } - throw ex; - } finally { - httpMethod.releaseConnection(); - HttpClientUtils.closeQuietly(response); - } - } - - public void close() { - HttpClientUtils.closeQuietly(apacheClient); - } - - private HttpClientBuilder disableCertificateValidation(HttpClientBuilder builder, Logger logi) { - try { - SSLContext disabledSSLContext = SSLContexts.custom().loadTrustMaterial(new TrustStrategy() { - public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - return true; - } - }).build(); - builder.setSslcontext(disabledSSLContext); - builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { - logi.warn("Failed to disable certificate verification: " + e.getMessage()); - } - - return builder; - } - - private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { - try { - final SSLContext sslContext = SSLContext.getInstance(protocol); - sslContext.init(null, null, null); - HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - log.warn("Failed to set SSL TLS : " + e.getMessage()); - } - } - -} diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java deleted file mode 100644 index 691ee0d3..00000000 --- a/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.cx.restclient.sast.dto; - - -import com.cx.restclient.dto.BaseStatus; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -/** - * Created by Galn on 07/03/2018. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class CxARMStatus extends BaseStatus { - private CxID project; - private CxID scan; - String status; - String lastSync; - - public CxID getProject() { - return project; - } - - public void setProject(CxID project) { - this.project = project; - } - - public CxID getScan() { - return scan; - } - - public void setScan(CxID scan) { - this.scan = scan; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getLastSync() { - return lastSync; - } - - public void setLastSync(String lastSync) { - this.lastSync = lastSync; - } -} diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java deleted file mode 100644 index caf256aa..00000000 --- a/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cx.restclient.sast.dto; - -/** - * Created by Galn on 07/03/2018. - */ -public enum CxARMStatusEnum { - - IN_PROGRESS("InProgress"), - FINISHED("Finished"), - FAILED("Failed"), - NONE("None"); - - private final String value; - - CxARMStatusEnum(String value) { - this.value = value; - } - - public String value() { - return this.value; - } - - -} - diff --git a/src/main/java/testi.java b/src/main/java/testi.java deleted file mode 100644 index 94b85aba..00000000 --- a/src/main/java/testi.java +++ /dev/null @@ -1,148 +0,0 @@ -import com.cx.restclient.CxShragaClient; -import com.cx.restclient.common.ShragaUtils; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.restclient.sast.dto.SASTResults; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - - -/** - * Created by Galn on 04/03/2018. - */ -public class testi { - private static String DEFAULT_FILTER_PATTERNS = "!**/_cvs/**/*, !**/.svn/**/*, !**/.hg/**/*, !**/.git/**/*, !**/.bzr/**/*, !**/bin/**/*," + - "!**/obj/**/*, !**/backup/**/*, !**/.idea/**/*, !**/*.DS_Store, !**/*.ipr, !**/*.iws, " + - "!**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, " + - "!**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, " + - "!**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, " + - "!**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.swf, " + - "!**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, " + - "!**/*.tar.gz, !**/*.tar, !**/*.gz, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, " + - "!**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.war, !**/*.ear, " + - "!**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, " + - "!**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, !**/node_modules/**/*"; - - private static String DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS = "*.zip, *.tgz, *.war, *.ear"; - - - public static void main(String[] args) throws Exception { - - - - SASTResults sastResults = null; - // SASTResults lastSastResults = null; - OSAResults osaResults = null; - // OSAResults lastOsaResults = null; - Logger logi = LoggerFactory.getLogger("testush"); - - - CxScanConfig config = setConfigi(); - - - CxShragaClient shraga = new CxShragaClient(config, logi); - // shraga.getClientVersion(); - shraga.init(); - - try { - if (config.getOsaEnabled()) { - shraga.createOSAScan(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getSastEnabled()) { - shraga.createSASTScan(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getSastEnabled()) { - sastResults = shraga.waitForSASTResults(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getOsaEnabled()) { - osaResults = shraga.waitForOSAResults(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - //lastSastResults = shraga.getLatestSASTResults(); - // lastOsaResults = shraga.getLatestOSAResults(); - if (config.getEnablePolicyViolations()) { - shraga.printIsProjectViolated(); - } - //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); - String s = shraga.generateHTMLSummary(); - File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); - FileUtils.writeStringToFile(file, s); - - shraga.close(); - } - - - private static CxScanConfig setConfigi() { - CxScanConfig config = new CxScanConfig(); - config.setSastEnabled(true); - config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); - //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); - config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); - //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); - config.setUsername("adi.gido@checkmarx.com"); - //config.setPassword("Cx123456!"); - config.setPassword("Cx@123456"); - config.setAvoidDuplicateProjectScans(false); - - // config.setUrl("http://10.32.1.91"); - config.setUrl("https://sast.checkmarx.net"); - config.setCxOrigin("common"); - config.setProjectName("Bamboo Plugin - Main"); - //config.setProjectName("OSAPROJ"); - config.setPresetName("Default"); - //config.setPresetId(7); - config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); - //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); - config.setSastFolderExclusions(""); - config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); - config.setSastScanTimeoutInMinutes(null); - config.setScanComment(""); - config.setIncremental(false); - config.setSynchronous(true); - config.setSastThresholdsEnabled(false); - config.setSastHighThreshold(1); - config.setSastMediumThreshold(1); - config.setSastLowThreshold(1); - config.setGeneratePDFReport(true); - config.setOsaEnabled(false); - - - config.setOsaFilterPattern("");//TODO check - config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); - config.setOsaRunInstall(true); - config.setOsaThresholdsEnabled(true); - config.setOsaHighThreshold(10); - config.setOsaMediumThreshold(0); - config.setOsaLowThreshold(0); - config.setDenyProject(false); - config.setPublic(true); - //config.setUseSSOLogin(false); - //config.setZipFile(); - //config.setOsaDependenciesJson(); - config.setEnablePolicyViolations(true); - - return config; - } - -} diff --git a/src/main/resources/common.properties b/src/main/resources/common.properties deleted file mode 100644 index 713c9158..00000000 --- a/src/main/resources/common.properties +++ /dev/null @@ -1 +0,0 @@ -version = ${project.version} \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/src/main/java/com/cx/restclient/CxOSAClient.java similarity index 93% rename from src/main/java/com/cx/restclient/CxOSAClient.java rename to src/src/main/java/com/cx/restclient/CxOSAClient.java index 0f175d36..92f48c1d 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/src/main/java/com/cx/restclient/CxOSAClient.java @@ -8,18 +8,17 @@ import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.*; import com.cx.restclient.osa.utils.OSAUtils; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.osa.utils.OSAParam.*; @@ -33,6 +32,7 @@ class CxOSAClient { private CxHttpClient httpClient; private Logger log; private CxScanConfig config; + private Waiter osaWaiter = new Waiter("CxOSA scan", 20) { @Override public OSAScanStatus getStatus(String id) throws CxClientException, IOException { @@ -71,7 +71,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio return sendOSAScan(osaDependenciesJson, projectId); } - private String resolveOSADependencies() throws JsonProcessingException { + private String resolveOSADependencies() { log.info("Scanning for CxOSA compatible files"); Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { @@ -83,18 +83,17 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaRunInstall(), log); } - ObjectMapper mapper = new ObjectMapper(); - log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); + return osaDependenciesJson; } public OSAResults getOSAResults(String scanId, long projectId) throws CxClientException, InterruptedException, IOException { log.info("-------------------------------------Get CxOSA Results:-----------------------------------"); log.info("Waiting for OSA scan to finish"); - OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId,this.config.getOsaScanTimeoutInMinutes(), log); + OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, -1, log); log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); @@ -104,7 +103,7 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx resolveOSAViolation(osaResults, projectId); } - OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); + OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); if (config.getReportsDir() != null) { writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), log); @@ -115,7 +114,6 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx return osaResults; } - private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus, long projectId) throws CxClientException, IOException { OSASummaryResults osaSummaryResults = getOSAScanSummaryResults(scanId); List osaLibraries = getOSALibraries(scanId); @@ -126,11 +124,13 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus return results; } - private void resolveOSAViolation(OSAResults osaResults, long projectId){ + private void resolveOSAViolation(OSAResults osaResults, long projectId) { try { - getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) - .forEach(osaResults::addPolicy); - }catch (Exception ex) { + for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value())) { + osaResults.getOsaPolicies().add(policy.getPolicyName()); + osaResults.addAllViolations(policy.getViolations()); + } + } catch (Exception ex) { log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } } @@ -159,7 +159,7 @@ private String sendOSAScan(String osaDependenciesJson, long projectId) throws Cx private CreateOSAScanResponse sendOSARequest(long projectId, String osaDependenciesJson) throws IOException, CxClientException { CreateOSAScanRequest req = new CreateOSAScanRequest(projectId, osaDependenciesJson); - StringEntity entity = new StringEntity(convertToJson(req)); + StringEntity entity = new StringEntity(convertToJson(req), StandardCharsets.UTF_8); return httpClient.postRequest(OSA_SCAN_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CreateOSAScanResponse.class, 201, "create OSA scan"); } @@ -211,7 +211,7 @@ private void printOSAProgress(OSAScanStatus scanStatus, long startTime) { } private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClientException { - if (scanStatus ==null || Status.FAILED == scanStatus.getBaseStatus()) { + if (Status.FAILED == scanStatus.getBaseStatus()) { String failedMsg = scanStatus.getState() == null ? "" : "status [" + scanStatus.getState().getName() + "]. Reason: " + scanStatus.getState().getFailureReason(); throw new CxClientException("OSA scan cannot be completed. " + failedMsg); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/src/main/java/com/cx/restclient/CxSASTClient.java similarity index 69% rename from src/main/java/com/cx/restclient/CxSASTClient.java rename to src/src/main/java/com/cx/restclient/CxSASTClient.java index 18aadcf4..9a18af77 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/src/main/java/com/cx/restclient/CxSASTClient.java @@ -2,36 +2,26 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.RemoteSourceRequest; -import com.cx.restclient.dto.RemoteSourceTypes; +import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.sast.dto.*; import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; -import com.google.gson.Gson; -import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; -import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.InputStreamBody; -import org.json.JSONObject; import org.slf4j.Logger; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; +import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; import static com.cx.restclient.cxArm.dto.CxProviders.SAST; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; import static com.cx.restclient.httpClient.utils.ContentType.*; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -41,14 +31,12 @@ /** * Created by Galn on 05/02/2018. */ - class CxSASTClient { - public static final String JENKINS = "jenkins"; + private Logger log; private CxHttpClient httpClient; private CxScanConfig config; - private int reportTimeoutSec = 5000; - private int cxARMTimeoutSec = 1000; + private int reportTimeoutSec = 500; private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { @@ -66,7 +54,7 @@ public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) } }; - private Waiter reportWaiter = new Waiter("Scan report", 10) { + private Waiter reportWaiter = new Waiter("Scan report", 5) { @Override public ReportStatus getStatus(String id) throws CxClientException, IOException { return getReportStatus(id); @@ -83,23 +71,6 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; - private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20) { - @Override - public CxARMStatus getStatus(String id) throws CxClientException, IOException { - return getCxARMStatus(id); - } - - @Override - public void printProgress(CxARMStatus cxARMStatus) { - printCxARMProgress(cxARMStatus, getStartTimeSec()); - } - - @Override - public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientException { - return resolveCxARMStatus(cxARMStatus); - } - }; - CxSASTClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; this.httpClient = client; @@ -111,17 +82,10 @@ public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientExcepti //CREATE SAST scan long createSASTScan(long projectId) throws IOException, CxClientException { log.info("-----------------------------------Create CxSAST Scan:------------------------------------"); + if (config.isAvoidDuplicateProjectScans() != null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { throw new CxClientException("\nAvoid duplicate project scans in queue\n"); } - if (config.getRemoteType() == null) { //scan is local - return createLocalSASTScan(projectId); - } else { - return createRemoteSourceScan(projectId); - } - } - - private long createLocalSASTScan(long projectId) throws IOException, CxClientException { ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); @@ -155,55 +119,6 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc return createScanResponse.getId(); } - private long createRemoteSourceScan(long projectId) throws IOException, CxClientException { - HttpEntity entity; - RemoteSourceRequest req = new RemoteSourceRequest(config); - RemoteSourceTypes type = req.getType(); - - switch (type) { - case SVN: - case TFS: - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null); - builder.addTextBody("absoluteUrl", req.getUrl(), ContentType.APPLICATION_JSON); - builder.addTextBody("port", String.valueOf(req.getPort()), ContentType.APPLICATION_JSON); - builder.addTextBody("paths", StringUtils.join(req.getPaths(), ";"), ContentType.APPLICATION_JSON); - entity = builder.build(); - break; - case PERFORCE: - if (config.getPreforceMode() != null) { - req.setBrowseMode("Workspace"); - } else { - req.setBrowseMode("Depot"); - } - entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); - break; - case SHARED: - entity = new StringEntity(new Gson().toJson(req), ContentType.APPLICATION_JSON); - break; - case GIT: - if (req.getPrivateKey().length < 1) { - Map content = new HashMap<>(); - content.put("url", config.getRemoteSrcUrl()); - content.put("branch", config.getRemoteSrcBranch()); - entity = new StringEntity(new JSONObject(content).toString(), ContentType.APPLICATION_JSON); - } else { - builder = MultipartEntityBuilder.create(); - builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); - builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); - entity = builder.build(); - } - break; - default: - log.error("todo"); - entity = new StringEntity(""); - - } - return createRemoteSourceScan(projectId, entity, type.value()).getId(); - } - - //GET SAST results + reports public SASTResults waitForSASTResults(long scanId, long projectId) throws InterruptedException, IOException, CxClientException { SASTResults sastResults; @@ -216,9 +131,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr //retrieve SAST scan results sastResults = retrieveSASTResults(scanId, projectId); - if (config.getEnablePolicyViolations()) { + /* if (config.getEnablePolicyViolations()) { resolveSASTViolation(sastResults, projectId); - } + }*/ SASTUtils.printSASTResultsToConsole(sastResults, config.getEnablePolicyViolations(), log); //PDF report @@ -227,9 +142,7 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr byte[] pdfReport = getScanReport(sastResults.getScanId(), ReportType.PDF, CONTENT_TYPE_APPLICATION_PDF_V1); sastResults.setPDFReport(pdfReport); if (config.getReportsDir() != null) { - String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); - String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; - pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); + String pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), log); sastResults.setPdfFileName(pdfFileName); } } @@ -238,9 +151,10 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr private void resolveSASTViolation(SASTResults sastResults, long projectId) { try { - cxARMWaiter.waitForTaskToFinish(Long.toString(projectId), cxARMTimeoutSec, log); - getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, SAST.value()) - .forEach(sastResults::addPolicy); + for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, SAST.value())) { + sastResults.getSastPolicies().add(policy.getPolicyName()); + sastResults.addAllViolations(policy.getViolations()); + } } catch (Exception ex) { log.error("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); } @@ -277,11 +191,12 @@ SASTResults getLatestSASTResults(long projectId) throws IOException, CxClientExc public void cancelSASTScan(long scanId) throws IOException, CxClientException { UpdateScanStatusRequest request = new UpdateScanStatusRequest(CurrentStatus.CANCELED); String json = convertToJson(request); - StringEntity entity = new StringEntity(json); + StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8); httpClient.patchRequest(SAST_QUEUE_SCAN_STATUS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, entity, 200, "cancel SAST scan"); log.info("SAST Scan canceled. (scanId: " + scanId + ")"); } + //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { List queuedScans = getQueueScans(projectId); @@ -313,28 +228,23 @@ private ScanSettingResponse getScanSetting(long projectId) throws IOException, C } private void defineScanSetting(ScanSettingRequest scanSetting) throws IOException, CxClientException { - StringEntity entity = new StringEntity(convertToJson(scanSetting)); + StringEntity entity = new StringEntity(convertToJson(scanSetting), StandardCharsets.UTF_8); httpClient.putRequest(SAST_UPDATE_SCAN_SETTINGS, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 200, "define scan setting"); } private void uploadZipFile(File zipFile, long projectId) throws CxClientException, IOException { - InputStreamBody streamBody = new InputStreamBody(new FileInputStream(zipFile.getAbsoluteFile()), ContentType.APPLICATION_OCTET_STREAM, "zippedSource"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - builder.addPart("zippedSource", streamBody); + builder.addBinaryBody("zippedSource", zipFile); HttpEntity entity = builder.build(); httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, entity, null, 204, "upload ZIP file"); } private CxID createScan(CreateScanRequest request) throws CxClientException, IOException { - StringEntity entity = new StringEntity(convertToJson(request)); + StringEntity entity = new StringEntity(convertToJson(request), StandardCharsets.UTF_8); return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } - private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { - return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); - } - private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientException, IOException { return httpClient.getRequest(SAST_SCAN_RESULTS_STATISTICS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, SASTStatisticsResponse.class, 200, "SAST scan statistics", false); } @@ -348,7 +258,7 @@ private List getQueueScans(long projectId) throws IOExc } private CreateReportResponse createScanReport(CreateReportRequest reportRequest) throws CxClientException, IOException { - StringEntity entity = new StringEntity(convertToJson(reportRequest)); + StringEntity entity = new StringEntity(convertToJson(reportRequest), StandardCharsets.UTF_8); return httpClient.postRequest(SAST_CREATE_REPORT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CreateReportResponse.class, 202, "to create " + reportRequest.getReportType() + " scan report"); } @@ -397,7 +307,7 @@ private void printSASTProgress(ResponseQueueScanStatus scanStatus, long startTim } private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { - if (scanStatus != null && Status.SUCCEEDED == scanStatus.getBaseStatus()) { + if (Status.SUCCEEDED == scanStatus.getBaseStatus()) { log.info("SAST scan finished successfully."); return scanStatus; } else { @@ -408,14 +318,14 @@ private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanSt //Report Waiter - overload methods private ReportStatus getReportStatus(String reportId) throws CxClientException, IOException { ReportStatus reportStatus = httpClient.getRequest(SAST_GET_REPORT_STATUS.replace("{reportId}", reportId), CONTENT_TYPE_APPLICATION_JSON_V1, ReportStatus.class, 200, " report status", false); - reportStatus.setBaseId(reportId); + String currentStatus = reportStatus.getStatus().getValue(); if (currentStatus.equals(ReportStatusEnum.INPROCESS.value())) { reportStatus.setBaseStatus(Status.IN_PROGRESS); } else if (currentStatus.equals(ReportStatusEnum.FAILED.value())) { reportStatus.setBaseStatus(Status.FAILED); } else { - reportStatus.setBaseStatus(Status.SUCCEEDED); //todo fix it!! + reportStatus.setBaseStatus(Status.SUCCEEDED); } return reportStatus; @@ -427,43 +337,10 @@ private void printReportProgress(ReportStatus reportStatus, long startTime) { } private ReportStatus resolveReportStatus(ReportStatus reportStatus) throws CxClientException { - if (reportStatus != null && Status.SUCCEEDED == reportStatus.getBaseStatus()) { + if (Status.SUCCEEDED == reportStatus.getBaseStatus()) { return reportStatus; } else { throw new CxClientException("Generation of scan report [id=" + reportStatus.getBaseId() + "] failed."); } } - - - //CxARM Waiter - overload methods - private CxARMStatus getCxARMStatus(String projectId) throws CxClientException, IOException { - CxARMStatus cxARMStatus = httpClient.getRequest(SAST_GET_CXARM_STATUS.replace("{projectId}", projectId), CONTENT_TYPE_APPLICATION_JSON_V1, CxARMStatus.class, 200, " cxARM status", false); - cxARMStatus.setBaseId(projectId); - - String currentStatus = cxARMStatus.getStatus(); - if (currentStatus.equals(CxARMStatusEnum.IN_PROGRESS.value())) { - cxARMStatus.setBaseStatus(Status.IN_PROGRESS); - } else if (currentStatus.equals(CxARMStatusEnum.FAILED.value())) { - cxARMStatus.setBaseStatus(Status.FAILED); - } else if (currentStatus.equals(CxARMStatusEnum.FINISHED.value())) { - cxARMStatus.setBaseStatus(Status.SUCCEEDED); - } else { - cxARMStatus.setBaseStatus(Status.FAILED); - } - - return cxARMStatus; - } - - private void printCxARMProgress(CxARMStatus cxARMStatus, long startTime) { - log.info("Waiting for server to retrieve policy violations. " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran - } - - private CxARMStatus resolveCxARMStatus(CxARMStatus cxARMStatus) throws CxClientException { - if (cxARMStatus != null && Status.SUCCEEDED == cxARMStatus.getBaseStatus()) { - return cxARMStatus; - } else { - throw new CxClientException("Getting policy violations of project [id=" + cxARMStatus.getBaseId() + "] failed."); //todo Liran - } - } - } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/src/main/java/com/cx/restclient/CxShragaClient.java similarity index 76% rename from src/main/java/com/cx/restclient/CxShragaClient.java rename to src/src/main/java/com/cx/restclient/CxShragaClient.java index 4662f8a9..baf5fc41 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,10 +1,11 @@ package com.cx.restclient; +import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; -import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; +import com.cx.restclient.dto.ThresholdResult; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -17,11 +18,12 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Properties; import static com.cx.restclient.common.CxPARAM.*; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; +import static com.cx.restclient.common.ShragaUtils.isThresholdExceeded; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -45,6 +47,20 @@ public class CxShragaClient { private SASTResults sastResults = new SASTResults(); private OSAResults osaResults = new OSAResults(); + public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int proxyPort, + String proxyUser, String proxyPassword) throws MalformedURLException { + this.config = config; + this.log = log; + this.httpClient = new CxHttpClient( + config.getUrl(), + config.getUsername(), + config.getPassword(), + config.getCxOrigin(), + config.isDisableCertificateValidation(), log, + proxyHost, proxyPort, proxyUser, proxyPassword); + sastClient = new CxSASTClient(httpClient, log, config); + osaClient = new CxOSAClient(httpClient, log, config); + } public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException { this.config = config; @@ -54,36 +70,23 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); + config.isDisableCertificateValidation(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } - //For Test Connection + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, + Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log, proxyHost, proxyPort, proxyUser, proxyPassword); + } + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } //API Scans methods - public String getClientVersion() { - String version = ""; - try { - Properties properties = new Properties(); - java.io.InputStream is = getClass().getClassLoader().getResourceAsStream("common.properties"); - if (is != null) { - properties.load(is); - version = properties.getProperty("version"); - } - } catch (Exception e) { - - } - return version; - } - public void init() throws CxClientException, IOException { - - log.info("Initializing Cx client [" + getClientVersion() + "]"); - getCxVersion(); + log.info("Initializing Cx client"); login(); resolveTeam(); if (config.getSastEnabled()) { @@ -131,27 +134,21 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc return osaResults; } - public void printIsProjectViolated() { - if (config.getEnablePolicyViolations()) { - log.info("-----------------------------------------------------------------------------------------"); - log.info("Policy Management: "); - log.info("--------------------"); - if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()) { - log.info(PROJECT_POLICY_COMPLAINT_STATUS); - log.info("-----------------------------------------------------------------------------------------"); - } else { - log.info(PROJECT_POLICY_VIOLATED_STATUS); - if (!sastResults.getSastPolicies().isEmpty()) { - log.info("SAST violated policies names: " + getPoliciesNames(sastResults.getSastPolicies())); - } - if (!osaResults.getOsaPolicies().isEmpty()) { - log.info("OSA violated policies names: " + getPoliciesNames(osaResults.getOsaPolicies())); - } - log.info("-----------------------------------------------------------------------------------------"); - } + public ThresholdResult getThresholdResult() { + StringBuilder res = new StringBuilder(""); + boolean isFail = isThresholdExceeded(config, sastResults, osaResults, res); + return new ThresholdResult(isFail, res.toString()); + } + + public boolean isPolicyViolated(StringBuilder failDescription) { + boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaViolations().size() > 0; + if (isPolicyViolated) { + failDescription.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); } + return isPolicyViolated; } + private CxArmConfig getCxARMConfig() throws IOException, CxClientException { return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); } @@ -187,44 +184,17 @@ public void login() throws IOException, CxClientException { httpClient.login(); } - public void getCxVersion() throws IOException, CxClientException { - try { - config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); - String hotfix = ""; - try { - if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { - hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; - } - } catch (Exception ex) { - } - - log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); - - } catch (Exception ex) { - log.debug("Checkmarx server version [lower than 9.0]"); - } - } - public String getTeamIdByName(String teamName) throws CxClientException, IOException { - teamName = replaceDelimiters(teamName); + teamName = teamName.replace("\\", "/"); List allTeams = getTeamList(); for (Team team : allTeams) { - String fullName = replaceDelimiters(team.getFullName()); - if (fullName.equalsIgnoreCase(teamName)) { //TODO caseSenesitive + if (team.getFullName().replace("\\", "/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } throw new CxClientException("Could not resolve team ID from team name: " + teamName); } - private String replaceDelimiters(String teamName) { - while (teamName.contains("\\") || teamName.contains("//")) { - teamName = teamName.replace("\\", "/"); - teamName = teamName.replace("//", "/"); - } - return teamName; - } - public String getTeamNameById(String teamId) throws CxClientException, IOException { List allTeams = getTeamList(); for (Team team : allTeams) { @@ -342,7 +312,8 @@ private List getProjectByName(String projectName, String teamId) throws private Project createNewProject(CreateProjectRequest request) throws CxClientException, IOException { String json = convertToJson(request); - StringEntity entity = new StringEntity(json); + StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8); return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } -} + +} \ No newline at end of file diff --git a/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java b/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java new file mode 100644 index 00000000..d0602b60 --- /dev/null +++ b/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java @@ -0,0 +1,25 @@ +package com.cx.restclient.common; + +/** + * Created by shaulv on 8/9/2018. + */ +public enum CxGlobalMessage { + + PROJECT_POLICY_STATUS("Project policy status : %s"), + PROJECT_POLICY_VIOLATED_STATUS("Project policy status : violated"), + PROJECT_POLICY_COMPLAINT_STATUS("Project policy status : compliant"); + + private String message; + + private CxGlobalMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public String formatMessage(Object... args) { + return String.format(message, args); + } +} diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/src/main/java/com/cx/restclient/common/CxPARAM.java similarity index 61% rename from src/main/java/com/cx/restclient/common/CxPARAM.java rename to src/src/main/java/com/cx/restclient/common/CxPARAM.java index 730a447b..96c456f1 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -7,25 +7,17 @@ */ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; - public static final String SSO_AUTHENTICATION = "auth/ssologin"; + public static final String ORIGIN_HEADER = "cxOrigin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; public static final String CREATE_PROJECT = "projects";//Create new project (default preset and configuration) - public static final String CX_VERSION = "system/version"; - - public static final String CX_ARM_URL = "/Configurations/Portal"; - public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; - - public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; - public static final String ORIGIN_HEADER = "cxOrigin"; - public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; - public static final String PROJECT_POLICY_VIOLATED_STATUS = "Project policy status : violated"; - public static final String PROJECT_POLICY_COMPLAINT_STATUS = "Project policy status : compliant"; - + public static final String CX_ARM_URL ="/Configurations/Portal"; + public static final String CX_ARM_VIOLATION ="/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; + } diff --git a/src/main/java/com/cx/restclient/common/ErrorMessage.java b/src/src/main/java/com/cx/restclient/common/ErrorMessage.java similarity index 100% rename from src/main/java/com/cx/restclient/common/ErrorMessage.java rename to src/src/main/java/com/cx/restclient/common/ErrorMessage.java diff --git a/src/src/main/java/com/cx/restclient/common/ErrorUtil.java b/src/src/main/java/com/cx/restclient/common/ErrorUtil.java new file mode 100644 index 00000000..208b69e8 --- /dev/null +++ b/src/src/main/java/com/cx/restclient/common/ErrorUtil.java @@ -0,0 +1,35 @@ +package com.cx.restclient.common; + +import org.apache.http.HttpStatus; + +import java.util.HashSet; +import java.util.Set; + +/** + * Created by shaulv on 6/20/2018. + */ +public class ErrorUtil { + + private ErrorUtil() { + + } + + private static Set serverErrorCodes = new HashSet<>(); + + static { + serverErrorCodes.add(HttpStatus.SC_INTERNAL_SERVER_ERROR); + serverErrorCodes.add(HttpStatus.SC_NOT_IMPLEMENTED); + serverErrorCodes.add(HttpStatus.SC_BAD_GATEWAY); + serverErrorCodes.add(HttpStatus.SC_SERVICE_UNAVAILABLE); + serverErrorCodes.add(HttpStatus.SC_GATEWAY_TIMEOUT); + serverErrorCodes.add(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED); + serverErrorCodes.add(HttpStatus.SC_INSUFFICIENT_STORAGE); + } + + public static boolean isServerErrorCodes(int errorCodes) { + if(serverErrorCodes.contains(errorCodes)) { + return true; + } + return false; + } +} diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/src/main/java/com/cx/restclient/common/ShragaUtils.java similarity index 84% rename from src/main/java/com/cx/restclient/common/ShragaUtils.java rename to src/src/main/java/com/cx/restclient/common/ShragaUtils.java index b47afd06..a3a37e9c 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -12,31 +12,12 @@ import java.util.List; import java.util.Map; -import static com.cx.restclient.common.CxPARAM.PROJECT_POLICY_VIOLATED_STATUS; - /** * Created by: dorg. * Date: 4/12/2018. */ public abstract class ShragaUtils { //Util methods - public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, OSAResults osaResults) { - StringBuilder res = new StringBuilder(""); - isThresholdExceeded(config, sastResults, osaResults, res); - isThresholdForNewResultExceeded(config, sastResults, res); - isPolicyViolated(config, sastResults, osaResults, res); - - return res.toString(); - } - - public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && ((osaResults!=null && osaResults.getOsaPolicies().size() > 0) || (sastResults != null && sastResults.getSastPolicies().size() > 0)); - if(isPolicyViolated) { - res.append(PROJECT_POLICY_VIOLATED_STATUS).append("\n"); - } - return isPolicyViolated; - } - public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { boolean thresholdExceeded = false; if (config.isSASTThresholdEffectivelyEnabled() && sastResults != null && sastResults.isSastResultsReady()) { @@ -88,7 +69,7 @@ public static boolean isThresholdForNewResultExceeded(CxScanConfig config, SASTR private static boolean isSeverityExceeded(int result, Integer threshold, StringBuilder res, String severity, String severityType) { boolean fail = false; if (threshold != null && result > threshold) { - res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append(". \n"); + res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append("\n"); fail = true; } return fail; @@ -141,10 +122,10 @@ public static Map> convertPatternsToLists(String filterPatt for (String filter : filters) { if (StringUtils.isNotEmpty(filter)) { if (!filter.startsWith("!")) { - inclusions.add(filter.trim()); + inclusions.add(filter); } else if (filter.length() > 1) { filter = filter.substring(1); // Trim the "!" - exclusions.add(filter.trim()); + exclusions.add(filter); } } } diff --git a/src/main/java/com/cx/restclient/common/UrlUtils.java b/src/src/main/java/com/cx/restclient/common/UrlUtils.java similarity index 100% rename from src/main/java/com/cx/restclient/common/UrlUtils.java rename to src/src/main/java/com/cx/restclient/common/UrlUtils.java diff --git a/src/src/main/java/com/cx/restclient/common/Waiter.java b/src/src/main/java/com/cx/restclient/common/Waiter.java new file mode 100644 index 00000000..e852695f --- /dev/null +++ b/src/src/main/java/com/cx/restclient/common/Waiter.java @@ -0,0 +1,75 @@ +package com.cx.restclient.common; + +import com.cx.restclient.dto.BaseStatus; +import com.cx.restclient.dto.Status; +import com.cx.restclient.exception.CxClientException; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Date; + +/** + * Created by Galn on 13/02/2018. + */ +public abstract class Waiter { + + private int retry = 5; + private String scanType; + private int sleepIntervalSec; + + public Waiter(String scanType, int interval) { + this.scanType = scanType; + this.sleepIntervalSec = interval; + } + + private long startTimeSec; + + protected Status status = null; + + public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { + startTimeSec = System.currentTimeMillis() / 1000; + long elapsedTimeSec = 0L; + status = Status.IN_PROGRESS; + T obj = null; + + while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { + Thread.sleep(sleepIntervalSec * 1000); + try { + obj = getStatus(taskId); + status = ((BaseStatus) obj).getBaseStatus(); + } catch (Exception e) { + log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); + if (retry <= 0) { + throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + } + retry--; + continue; + } + elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; + printProgress(obj); + + } + if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { + throw new CxClientException( "Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); + } + return resolveStatus(obj); + } + + public abstract T getStatus(String id) throws CxClientException, IOException; + + public abstract void printProgress(T status); + + public abstract T resolveStatus(T status) throws CxClientException; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public long getStartTimeSec() { + return startTimeSec; + } +} diff --git a/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java new file mode 100644 index 00000000..6ba09564 --- /dev/null +++ b/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -0,0 +1,127 @@ +package com.cx.restclient.common.summary; + +import com.cx.restclient.common.ShragaUtils; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.osa.dto.OSASummaryResults; +import com.cx.restclient.sast.dto.SASTResults; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.Version; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +public abstract class SummaryUtils { + + public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { + + Configuration cfg = new Configuration(new Version("2.3.23")); + cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); + Template template = cfg.getTemplate("report.ftl"); + + Map templateData = new HashMap(); + templateData.put("config", config); + templateData.put("sast", sastResults); + templateData.put("osa", osaResults); + + //calculated params: + + boolean buildFailed = false; + boolean policyViolated = false; + //sast: + if (config.getSastEnabled() && sastResults.isSastResultsReady()) { + boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); + boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); + templateData.put("sastThresholdExceeded", sastThresholdExceeded); + templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); + buildFailed = sastThresholdExceeded || sastNewResultsExceeded; + //calculate sast bars: + float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); + float sastBarNorm = maxCount * 10f / 9f; + + //sast high bars + float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; + float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); + float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; + templateData.put("sastHighTotalHeight", sastHighTotalHeight); + templateData.put("sastHighNewHeight", sastHighNewHeight); + templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); + /*if (config.getEnablePolicyViolations() && !sastResults.getSastViolations().isEmpty()){ + policyViolated = true; + }*/ + + //sast medium bars + float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; + float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); + float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; + templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); + templateData.put("sastMediumNewHeight", sastMediumNewHeight); + templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); + + //sast low bars + float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; + float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); + float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; + templateData.put("sastLowTotalHeight", sastLowTotalHeight); + templateData.put("sastLowNewHeight", sastLowNewHeight); + templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); + } + + //osa: + if (config.getOsaEnabled() && osaResults.isOsaResultsReady()) { + boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); + templateData.put("osaThresholdExceeded", osaThresholdExceeded); + buildFailed |= osaThresholdExceeded; + if (config.getEnablePolicyViolations() && !osaResults.getOsaViolations().isEmpty()){ + policyViolated = true; + } + //calculate osa bars: + OSASummaryResults osaSummaryResults = osaResults.getResults(); + int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); + int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); + int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); + float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); + float osaBarNorm = osaMaxCount * 10f / 9f; + + float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; + float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; + float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; + + templateData.put("osaHighTotalHeight", osaHighTotalHeight); + templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); + templateData.put("osaLowTotalHeight", osaLowTotalHeight); + } + + String policyLabel = osaResults.getOsaPolicies().size() == 1? "Policy": "Policies"; + templateData.put("policyLabel", policyLabel); + + templateData.put("policyViolated", policyViolated); + buildFailed |= policyViolated; + templateData.put("buildFailed", buildFailed); + + //generate the report: + StringWriter writer = new StringWriter(); + template.process(templateData, writer); + return writer.toString(); + } + + private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { + int minimalVisibilityHeight = 5; + //new high + float highNewHeightPx = (float) newCount / (float) count * totalHeight; + //if new height is between 1 and 9 - give it a minimum height and if theres enough spce in total height + if (isNewNeedChange(totalHeight, highNewHeightPx, minimalVisibilityHeight)) { + highNewHeightPx = minimalVisibilityHeight; + } + + return highNewHeightPx; + } + + private static boolean isNewNeedChange(float highTotalHeightPx, float highNewHeightPx, int minimalVisibilityHeight) { + return highNewHeightPx > 0 && highNewHeightPx < minimalVisibilityHeight && highTotalHeightPx > minimalVisibilityHeight * 2; + } +} \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java similarity index 79% rename from src/main/java/com/cx/restclient/configuration/CxScanConfig.java rename to src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 07a42048..c7c79637 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,7 +1,6 @@ package com.cx.restclient.configuration; -import com.cx.restclient.dto.CxVersion; -import com.cx.restclient.dto.RemoteSourceTypes; + import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -17,11 +16,7 @@ public class CxScanConfig implements Serializable { private Boolean osaEnabled = false; private String cxOrigin; - private CxVersion cxVersion; - private boolean disableCertificateValidation = false; - private boolean useSSOLogin = false; - private String sourceDir; private File reportsDir; private String username; @@ -31,7 +26,6 @@ public class CxScanConfig implements Serializable { private String teamPath; private String teamId; private Boolean denyProject = false; - private Boolean hideResults = false; private Boolean isPublic = true; private Boolean forceScan = false; private String presetName; @@ -39,7 +33,6 @@ public class CxScanConfig implements Serializable { private String sastFolderExclusions; private String sastFilterPattern; private Integer sastScanTimeoutInMinutes; - private Integer osaScanTimeoutInMinutes; private String scanComment; private Boolean isIncremental = false; private Boolean isSynchronous = false; @@ -70,16 +63,6 @@ public class CxScanConfig implements Serializable { private Boolean generateXmlReport = true; private String cxARMUrl; - private String[] paths; - //remote source control - private RemoteSourceTypes remoteType = null; - private String remoteSrcUser; - private String remoteSrcPass; - private String remoteSrcUrl; - private int remoteSrcPort; - private byte[] remoteSrcKeyFile; - private String remoteSrcBranch; - private String preforceMode; public CxScanConfig() { } @@ -124,18 +107,6 @@ public void setDisableCertificateValidation(boolean disableCertificateValidation this.disableCertificateValidation = disableCertificateValidation; } - public boolean isUseSSOLogin() { - return useSSOLogin; - } - - public void setUseSSOLogin(boolean useSSOLogin) { - this.useSSOLogin = useSSOLogin; - } - - public Boolean getAvoidDuplicateProjectScans() { - return avoidDuplicateProjectScans; - } - public String getSourceDir() { return sourceDir; } @@ -189,7 +160,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if (!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\") && !teamPath.startsWith(("/"))) { + if(!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")&& !teamPath.startsWith(("/"))){ teamPath = "\\" + teamPath; } this.teamPath = teamPath; @@ -267,14 +238,6 @@ public void setSastScanTimeoutInMinutes(Integer sastScanTimeoutInMinutes) { this.sastScanTimeoutInMinutes = sastScanTimeoutInMinutes; } - public Integer getOsaScanTimeoutInMinutes() { - return osaScanTimeoutInMinutes == null ? -1 : osaScanTimeoutInMinutes; - } - - public void setOsaScanTimeoutInMinutes(Integer sastOsaScanTimeoutInMinutes) { - this.osaScanTimeoutInMinutes = sastOsaScanTimeoutInMinutes; - } - public String getScanComment() { return scanComment; } @@ -487,14 +450,6 @@ public void setCxARMUrl(String cxARMUrl) { this.cxARMUrl = cxARMUrl; } - public Boolean getHideResults() { - return hideResults; - } - - public void setHideResults(Boolean hideResults) { - this.hideResults = hideResults; - } - public Boolean isAvoidDuplicateProjectScans() { return avoidDuplicateProjectScans; @@ -504,78 +459,6 @@ public void setAvoidDuplicateProjectScans(Boolean avoidDuplicateProjectScans) { this.avoidDuplicateProjectScans = avoidDuplicateProjectScans; } - public String getRemoteSrcUser() { - return remoteSrcUser; - } - - public void setRemoteSrcUser(String remoteSrcUser) { - this.remoteSrcUser = remoteSrcUser; - } - - public String getRemoteSrcPass() { - return remoteSrcPass; - } - - public void setRemoteSrcPass(String remoteSrcPass) { - this.remoteSrcPass = remoteSrcPass; - } - - public String getRemoteSrcUrl() { - return remoteSrcUrl; - } - - public void setRemoteSrcUrl(String remoteSrcUrl) { - this.remoteSrcUrl = remoteSrcUrl; - } - - public int getRemoteSrcPort() { - return remoteSrcPort; - } - - public void setRemoteSrcPort(int remoteSrcPort) { - this.remoteSrcPort = remoteSrcPort; - } - - public byte[] getRemoteSrcKeyFile() { - return remoteSrcKeyFile; - } - - public void setRemoteSrcKeyFile(byte[] remoteSrcKeyFile) { - this.remoteSrcKeyFile = remoteSrcKeyFile; - } - - public RemoteSourceTypes getRemoteType() { - return remoteType; - } - - public void setRemoteType(RemoteSourceTypes remoteType) { - this.remoteType = remoteType; - } - - public String[] getPaths() { - return paths; - } - - public void setPaths(String[] paths) { - this.paths = paths; - } - - public String getRemoteSrcBranch() { - return remoteSrcBranch; - } - - public void setRemoteSrcBranch(String remoteSrcBranch) { - this.remoteSrcBranch = remoteSrcBranch; - } - - public String getPreforceMode() { - return preforceMode; - } - - public void setPreforceMode(String preforceMode) { - this.preforceMode = preforceMode; - } - public Boolean getGenerateXmlReport() { return generateXmlReport; } @@ -583,12 +466,4 @@ public Boolean getGenerateXmlReport() { public void setGenerateXmlReport(Boolean generateXmlReport) { this.generateXmlReport = generateXmlReport; } - - public CxVersion getCxVersion() { - return cxVersion; - } - - public void setCxVersion(CxVersion cxVersion) { - this.cxVersion = cxVersion; - } } diff --git a/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java b/src/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java similarity index 100% rename from src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java rename to src/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java diff --git a/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java b/src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java similarity index 74% rename from src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java rename to src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java index 237fdb7c..2315dfab 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java +++ b/src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java @@ -1,9 +1,12 @@ package com.cx.restclient.cxArm.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * Created by Galn on 7/8/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public enum CxProviders { OPEN_SOURCE("open_source"), SAST("sast"); diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Policy.java b/src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java similarity index 54% rename from src/main/java/com/cx/restclient/cxArm/dto/Policy.java rename to src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java index 2696232d..3b73a1f0 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Policy.java +++ b/src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java @@ -13,22 +13,8 @@ public class Policy implements Serializable { long policyId; String policyName; - String ruleName; - String firstDetectionDate; List violations = new ArrayList(); - public Policy() { - } - - - public Policy(long policyId, String policyName, String ruleName, List violations, String firstDate) { - this.policyId = policyId; - this.policyName = policyName; - this.ruleName = ruleName; - this.violations = violations; - this.firstDetectionDate = firstDate; - } - public long getPolicyId() { return policyId; } @@ -50,22 +36,11 @@ public List getViolations() { } public void setViolations(List violations) { - this.violations = violations; - } - - public String getRuleName() { - return ruleName; - } - - public void setRuleName(String ruleName) { - this.ruleName = ruleName; - } - - public String getFirstDetectionDate() { - return firstDetectionDate; - } + //Add the policy name to the violations for the report + for(Violation violation: violations){ + violation.setPolicyName(policyName); + } - public void setFirstDetectionDate(String firstDetectionDate) { - this.firstDetectionDate = firstDetectionDate; + this.violations = violations; } } diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java b/src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java similarity index 89% rename from src/main/java/com/cx/restclient/cxArm/dto/Violation.java rename to src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java index 4e18a82a..8445ab3c 100644 --- a/src/main/java/com/cx/restclient/cxArm/dto/Violation.java +++ b/src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java @@ -48,6 +48,9 @@ public class Violation implements Serializable { private String policyName; + private String detectionDate; + + public String getRuleId() { return ruleId; } @@ -191,4 +194,16 @@ public String getPolicyName() { public void setPolicyName(String policyName) { this.policyName = policyName; } + + public String getDetectionDate() { + if (detectionDate == null){ + String date = new Date(firstDetectionDateByArm).toString(); + detectionDate = formatDate(date, "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); + } + return detectionDate; + } + + public void setDetectionDate(String detectionDate) { + this.detectionDate = detectionDate; + } } diff --git a/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java new file mode 100644 index 00000000..569814f4 --- /dev/null +++ b/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java @@ -0,0 +1,21 @@ +package com.cx.restclient.cxArm.utils; + +import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.httpClient.CxHttpClient; + +import java.io.IOException; +import java.util.List; + +import static com.cx.restclient.common.CxPARAM.CX_ARM_VIOLATION; +import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; + +/** + * Created by Galn on 7/30/2018. + */ +public abstract class CxARMUtils { + public static List getProjectViolations(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { + String relativePath = CX_ARM_VIOLATION.replace("{projectId}", Long.toString(projectId)).replace("{provider}", provider); + return (List) httpClient.getRequest(cxARMUrl, relativePath, CONTENT_TYPE_APPLICATION_JSON_V1, null, Policy.class, 200, "CxARM violations", true); + } +} diff --git a/src/main/java/com/cx/restclient/dto/BaseStatus.java b/src/src/main/java/com/cx/restclient/dto/BaseStatus.java similarity index 99% rename from src/main/java/com/cx/restclient/dto/BaseStatus.java rename to src/src/main/java/com/cx/restclient/dto/BaseStatus.java index 8d853702..30c41eee 100644 --- a/src/main/java/com/cx/restclient/dto/BaseStatus.java +++ b/src/src/main/java/com/cx/restclient/dto/BaseStatus.java @@ -6,6 +6,7 @@ * Created by Galn on 13/02/2018. */ @JsonIgnoreProperties(ignoreUnknown = true) + public class BaseStatus { private String baseId; private Status baseStatus; diff --git a/src/main/java/com/cx/restclient/dto/LoginRequest.java b/src/src/main/java/com/cx/restclient/dto/LoginRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/dto/LoginRequest.java rename to src/src/main/java/com/cx/restclient/dto/LoginRequest.java diff --git a/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/src/main/java/com/cx/restclient/dto/ScanResults.java similarity index 81% rename from src/main/java/com/cx/restclient/dto/ScanResults.java rename to src/src/main/java/com/cx/restclient/dto/ScanResults.java index 5d0357c9..0484ff76 100644 --- a/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -3,18 +3,19 @@ import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; - +@JsonIgnoreProperties(ignoreUnknown = true) public class ScanResults implements Serializable { - private SASTResults sastResults = new SASTResults(); - private OSAResults osaResults = new OSAResults(); + + private SASTResults sastResults; + private OSAResults osaResults; private Exception sastCreateException = null; private Exception sastWaitException = null; private Exception osaCreateException = null; private Exception osaWaitException = null; - private Exception generalException = null; public ScanResults() { } @@ -66,12 +67,4 @@ public Exception getOsaWaitException() { public void setOsaWaitException(Exception osaWaitException) { this.osaWaitException = osaWaitException; } - - public Exception getGeneralException() { - return generalException; - } - - public void setGeneralException(Exception generalException) { - this.generalException = generalException; - } } diff --git a/src/main/java/com/cx/restclient/dto/Status.java b/src/src/main/java/com/cx/restclient/dto/Status.java similarity index 100% rename from src/main/java/com/cx/restclient/dto/Status.java rename to src/src/main/java/com/cx/restclient/dto/Status.java diff --git a/src/main/java/com/cx/restclient/dto/Team.java b/src/src/main/java/com/cx/restclient/dto/Team.java similarity index 99% rename from src/main/java/com/cx/restclient/dto/Team.java rename to src/src/main/java/com/cx/restclient/dto/Team.java index 2f389cdd..9d591851 100644 --- a/src/main/java/com/cx/restclient/dto/Team.java +++ b/src/src/main/java/com/cx/restclient/dto/Team.java @@ -5,7 +5,6 @@ /** * Created by Galn on 14/02/2018. */ - @JsonIgnoreProperties(ignoreUnknown = true) public class Team { public String id; diff --git a/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java b/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java new file mode 100644 index 00000000..73ae1ff1 --- /dev/null +++ b/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java @@ -0,0 +1,33 @@ +package com.cx.restclient.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by Galn on 4/10/2018. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ThresholdResult { + private boolean isFail; + private String failDescription; + + public ThresholdResult(boolean isFail, String failDescription) { + this.isFail = isFail; + this.failDescription = failDescription; + } + + public boolean isFail() { + return isFail; + } + + public void setFail(boolean fail) { + isFail = fail; + } + + public String getFailDescription() { + return failDescription; + } + + public void setFailDescription(String failDescription) { + this.failDescription = failDescription; + } +} diff --git a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java b/src/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/dto/TokenLoginResponse.java rename to src/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java diff --git a/src/main/java/com/cx/restclient/exception/CxClientException.java b/src/src/main/java/com/cx/restclient/exception/CxClientException.java similarity index 100% rename from src/main/java/com/cx/restclient/exception/CxClientException.java rename to src/src/main/java/com/cx/restclient/exception/CxClientException.java diff --git a/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java b/src/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java similarity index 100% rename from src/main/java/com/cx/restclient/exception/CxHTTPClientException.java rename to src/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java diff --git a/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java b/src/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java similarity index 100% rename from src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java rename to src/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java diff --git a/src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java new file mode 100644 index 00000000..9c012d5c --- /dev/null +++ b/src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -0,0 +1,309 @@ +package com.cx.restclient.httpClient; + +import com.cx.restclient.common.ErrorMessage; +import com.cx.restclient.common.UrlUtils; +import com.cx.restclient.dto.TokenLoginResponse; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.exception.CxHTTPClientException; +import com.cx.restclient.exception.CxTokenExpiredException; +import org.apache.http.*; +import org.apache.http.auth.AuthSchemeProvider; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.*; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.NoConnectionReuseStrategy; +import org.apache.http.impl.auth.BasicSchemeFactory; +import org.apache.http.impl.auth.DigestSchemeFactory; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; +import org.apache.http.impl.conn.DefaultProxyRoutePlanner; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; +import org.slf4j.Logger; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import static com.cx.restclient.common.CxPARAM.AUTHENTICATION; +import static com.cx.restclient.common.CxPARAM.ORIGIN_HEADER; +import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; +import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; +import static org.apache.commons.lang3.StringUtils.isEmpty; + +/** + * Created by Galn on 05/02/2018. + */ +public class CxHttpClient { + + private static String HTTP_HOST = System.getProperty("http.proxyHost"); + private static String HTTP_PORT = System.getProperty("http.proxyPort"); + private static String HTTP_USERNAME = System.getProperty("http.proxyUser"); + private static String HTTP_PASSWORD = System.getProperty("http.proxyPassword"); + + private static String HTTPS_HOST = System.getProperty("https.proxyHost"); + private static String HTTPS_PORT = System.getProperty("https.proxyPort"); + private static String HTTPS_USERNAME = System.getProperty("https.proxyUser"); + private static String HTTPS_PASSWORD = System.getProperty("https.proxyPassword"); + + private static HttpClient apacheClient; + + private Logger logi; + private TokenLoginResponse token; + private String rootUri; + private final String username; + private final String password; + private String cxOrigin; + + public CxHttpClient(String hostname, String username, String password, String origin, + boolean disableSSLValidation, Logger logi, String proxyHost, int proxyPort, + String proxyUser, String proxyPassword) throws MalformedURLException { + this.logi = logi; + this.username = username; + this.password = password; + this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); + this.cxOrigin = origin; + //create httpclient + HttpClientBuilder cb = HttpClients.custom(); + cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); + setSSLTls(cb, "TLSv1.2", logi); + if (disableSSLValidation) { + try { + cb.setSSLSocketFactory(getSSLSF()); + cb.setConnectionManager(getHttpConnManager()); + } catch (CxClientException e) { + logi.warn("Failed to disable certificate verification: " + e.getMessage()); + } + } + setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); + cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); + cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); + cb.useSystemProperties(); + apacheClient = cb.build(); + } + + public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, Logger logi) throws MalformedURLException { + this.logi = logi; + this.username = username; + this.password = password; + this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); + this.cxOrigin = origin; + //create httpclient + HttpClientBuilder cb = HttpClients.custom(); + cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); + setSSLTls(cb, "TLSv1.2", logi); + if (disableSSLValidation) { + try { + cb.setSSLSocketFactory(getSSLSF()); + cb.setConnectionManager(getHttpConnManager()); + } catch (CxClientException e) { + logi.warn("Failed to disable certificate verification: " + e.getMessage()); + } + } + setProxy(cb, logi); + cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); + cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); + cb.useSystemProperties(); + apacheClient = cb.build(); + } + + private static void setCustomProxy(HttpClientBuilder cb, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, Logger logi) { + HttpHost proxy = null; + if (!isEmpty(proxyHost)) { + proxy = new HttpHost(proxyHost, proxyPort, "http"); + if (!isEmpty(proxyUser) && !isEmpty(proxyPassword)) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(proxyUser, proxyPassword)); + cb.setDefaultCredentialsProvider(credsProvider); + } + } + if (proxy != null) { + logi.info("Setting proxy for Checkmarx http client"); + cb.setProxy(proxy); + cb.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)); + cb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + } + } + + private static void setProxy(HttpClientBuilder cb, Logger logi) { + HttpHost proxyHost = null; + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + if (!isEmpty(HTTPS_HOST) && !isEmpty(HTTPS_PORT)) { + proxyHost = new HttpHost(HTTPS_HOST, Integer.parseInt(HTTPS_PORT), "https"); + if (!isEmpty(HTTPS_USERNAME) && !isEmpty(HTTPS_PASSWORD)) { + credsProvider.setCredentials(new AuthScope(HTTPS_HOST, Integer.parseInt(HTTPS_PORT)), new UsernamePasswordCredentials(HTTPS_USERNAME, HTTPS_PASSWORD)); + cb.setDefaultCredentialsProvider(credsProvider); + } + } else if (!isEmpty(HTTP_HOST) && !isEmpty(HTTP_PORT)) { + proxyHost = new HttpHost(HTTP_HOST, Integer.parseInt(HTTP_PORT), "http"); + if (!isEmpty(HTTP_USERNAME) && !isEmpty(HTTP_PASSWORD)) { + credsProvider.setCredentials(new AuthScope(HTTP_HOST, Integer.parseInt(HTTP_PORT)), new UsernamePasswordCredentials(HTTP_USERNAME, HTTP_PASSWORD)); + cb.setDefaultCredentialsProvider(credsProvider); + } + } + if (proxyHost != null) { + logi.info("Setting proxy for Checkmarx http client"); + cb.setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)); + cb.setProxy(proxyHost); + cb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + } + } + + private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { + TrustStrategy acceptingTrustStrategy = new TrustAllStrategy(); + SSLContext sslContext; + try { + sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + throw new CxClientException("Fail to set trust all certificate, 'SSLConnectionSocketFactory'", e); + } + return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + } + + private static BasicHttpClientConnectionManager getHttpConnManager() throws CxClientException { + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("https", getSSLSF()) + .register("http", new PlainConnectionSocketFactory()) + .build(); + return new BasicHttpClientConnectionManager(socketFactoryRegistry); + } + + private static Registry getAuthSchemeProviderRegistry() { + return RegistryBuilder.create() + .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .build(); + } + + public void login() throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } + + private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { + List parameters = new ArrayList(); + parameters.add(new BasicNameValuePair("username", username)); + parameters.add(new BasicNameValuePair("password", password)); + parameters.add(new BasicNameValuePair("grant_type", "password")); + parameters.add(new BasicNameValuePair("scope", "sast_rest_api cxarm_api")); + parameters.add(new BasicNameValuePair("client_id", "resource_owner_client")); + parameters.add(new BasicNameValuePair("client_secret", "014DF517-39D1-4453-B7B3-9930C563627C")); + + return new UrlEncodedFormEntity(parameters, "utf-8"); + } + + //GET REQUEST + public T getRequest(String relPath, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { + return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); + } + + public T getRequest(String rootURL, String relPath, String acceptHeader, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { + HttpGet get = new HttpGet(rootURL + relPath); + get.addHeader(HttpHeaders.ACCEPT, acceptHeader); + return request(get, contentType, null, responseType, expectStatus, "get " + failedMsg, isCollection, true); + } + + //POST REQUEST + public T postRequest(String relPath, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg) throws IOException, CxClientException { + HttpPost post = new HttpPost(rootUri + relPath); + return request(post, contentType, entity, responseType, expectStatus, failedMsg, false, true); + } + + //PUT REQUEST + public T putRequest(String relPath, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg) throws IOException, CxClientException { + HttpPut put = new HttpPut(rootUri + relPath); + return request(put, contentType, entity, responseType, expectStatus, failedMsg, false, true); + } + + //PATCH REQUEST + public void patchRequest(String relPath, String contentType, HttpEntity entity, int expectStatus, String failedMsg) throws IOException, CxClientException { + HttpPatch patch = new HttpPatch(rootUri + relPath); + request(patch, contentType, entity, null, expectStatus, failedMsg, false, true); + } + + private T request(HttpRequestBase httpMethod, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg, boolean isCollection, boolean retry) throws IOException, CxClientException { + if (contentType != null) { + httpMethod.addHeader("Content-type", contentType); + } + if (entity != null && httpMethod instanceof HttpEntityEnclosingRequestBase) { //Entity for Post methods + ((HttpEntityEnclosingRequestBase) httpMethod).setEntity(entity); + } + HttpResponse response = null; + int statusCode = 0; + + try { + httpMethod.addHeader(ORIGIN_HEADER, cxOrigin); + if (token != null) { + httpMethod.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); + } + + response = apacheClient.execute(httpMethod); + + statusCode = response.getStatusLine().getStatusCode(); + logi.trace("Response from: '" + httpMethod.getURI() + "' is: " + statusCode); + + if (statusCode == HttpStatus.SC_UNAUTHORIZED) { //Token expired + throw new CxTokenExpiredException(extractResponseBody(response)); + } + validateResponse(response, expectStatus, "Failed to " + failedMsg); + + //extract response as object and return the link + return convertToObject(response, responseType, isCollection); + } catch (UnknownHostException e) { + throw new CxHTTPClientException(ErrorMessage.CHECKMARX_SERVER_CONNECTION_FAILED.getErrorMessage()); + } catch (CxTokenExpiredException ex) { + if (retry) { + logi.warn("Access token expired for request: " + httpMethod.getURI() + ", Status code:" + statusCode + "requesting a new token. message: " + ex.getMessage()); + login(); + return request(httpMethod, contentType, entity, responseType, expectStatus, failedMsg, isCollection, false); + } + throw ex; + } finally { + httpMethod.releaseConnection(); + HttpClientUtils.closeQuietly(response); + } + } + + public void close() { + HttpClientUtils.closeQuietly(apacheClient); + } + + private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { + SSLContext sslContext = null; + try { + sslContext = SSLContextBuilder.create().useProtocol(protocol).build(); + builder.setSSLContext(sslContext); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + log.warn("Failed to set SSL TLS : " + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java b/src/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java similarity index 100% rename from src/main/java/com/cx/restclient/httpClient/utils/ContentType.java rename to src/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java similarity index 92% rename from src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java rename to src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 55227448..73593616 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -2,6 +2,7 @@ import com.cx.restclient.common.ErrorMessage; +import com.cx.restclient.common.ErrorUtil; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.fasterxml.jackson.databind.JavaType; @@ -70,6 +71,9 @@ private static T convertToCollectionObject(HttpResponse response, JavaType j public static void validateResponse(HttpResponse response, int status, String message) throws CxClientException { if (response.getStatusLine().getStatusCode() != status) { + if (ErrorUtil.isServerErrorCodes(response.getStatusLine().getStatusCode())) { + throw new CxClientException(ErrorMessage.SERVICE_UNAVAILABLE.getErrorMessage() + ", Error code: " + response.getStatusLine().getStatusCode()); + } String responseBody = extractResponseBody(response); responseBody = responseBody.replace("{", "").replace("}", "").replace(System.getProperty("line.separator"), " ").replace(" ", ""); throw new CxHTTPClientException(response.getStatusLine().getStatusCode(), message + ": " + responseBody); @@ -84,4 +88,5 @@ public static String extractResponseBody(HttpResponse response) { return ""; } } + } diff --git a/src/main/java/com/cx/restclient/osa/dto/CVE.java b/src/src/main/java/com/cx/restclient/osa/dto/CVE.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/CVE.java rename to src/src/main/java/com/cx/restclient/osa/dto/CVE.java diff --git a/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java b/src/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java rename to src/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java diff --git a/src/main/java/com/cx/restclient/osa/dto/Content.java b/src/src/main/java/com/cx/restclient/osa/dto/Content.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/Content.java rename to src/src/main/java/com/cx/restclient/osa/dto/Content.java diff --git a/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java b/src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java rename to src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java diff --git a/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java b/src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java rename to src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java diff --git a/src/main/java/com/cx/restclient/osa/dto/Library.java b/src/src/main/java/com/cx/restclient/osa/dto/Library.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/Library.java rename to src/src/main/java/com/cx/restclient/osa/dto/Library.java diff --git a/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java b/src/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/LoginRequest.java rename to src/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java b/src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java similarity index 89% rename from src/main/java/com/cx/restclient/osa/dto/OSAResults.java rename to src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java index 18934abc..b9faaa6e 100644 --- a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java +++ b/src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java @@ -1,6 +1,7 @@ package com.cx.restclient.osa.dto; -import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.cxArm.dto.Violation; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.util.ArrayList; @@ -9,11 +10,11 @@ import java.util.Map; import static com.cx.restclient.common.ShragaUtils.formatDate; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; /** * Created by Galn on 07/02/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class OSAResults implements Serializable { private String osaScanId; private OSASummaryResults results; @@ -29,7 +30,9 @@ public class OSAResults implements Serializable { private String scanStartTime; private String scanEndTime; - private List osaPolicies = new ArrayList<>(); + private List osaPolicies = new ArrayList<>(); + private List osaViolations = new ArrayList<>(); + public OSAResults() { } @@ -181,15 +184,23 @@ public void setScanEndTime(String scanEndTime) { this.scanEndTime = scanEndTime; } - public void addPolicy(Policy policy) { - this.osaPolicies.addAll(getPolicyList(policy)); + public List getOsaViolations() { + return osaViolations; + } + + public void setOsaViolations(List osaViolations) { + this.osaViolations = osaViolations; + } + + public void addAllViolations(List violations) { + this.osaViolations.addAll(violations); } - public List getOsaPolicies() { + public List getOsaPolicies() { return osaPolicies; } - public void setOsaPolicies(List osaPolicies) { + public void setOsaPolicies(List osaPolicies) { this.osaPolicies = osaPolicies; } } diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java b/src/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/OSAScanState.java rename to src/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java b/src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java rename to src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java b/src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java rename to src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java diff --git a/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java b/src/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java rename to src/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java diff --git a/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java b/src/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java rename to src/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java diff --git a/src/main/java/com/cx/restclient/osa/dto/Severity.java b/src/src/main/java/com/cx/restclient/osa/dto/Severity.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/Severity.java rename to src/src/main/java/com/cx/restclient/osa/dto/Severity.java diff --git a/src/main/java/com/cx/restclient/osa/dto/State.java b/src/src/main/java/com/cx/restclient/osa/dto/State.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/dto/State.java rename to src/src/main/java/com/cx/restclient/osa/dto/State.java diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAParam.java b/src/src/main/java/com/cx/restclient/osa/utils/OSAParam.java similarity index 100% rename from src/main/java/com/cx/restclient/osa/utils/OSAParam.java rename to src/src/main/java/com/cx/restclient/osa/utils/OSAParam.java diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java similarity index 93% rename from src/main/java/com/cx/restclient/osa/utils/OSAUtils.java rename to src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 3e85ed6d..e1962ab7 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -1,5 +1,6 @@ package com.cx.restclient.osa.utils; +import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; @@ -129,6 +130,14 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info("Vulnerable and updated: " + osaSummaryResults.getVulnerableAndUpdated()); log.info("Non-vulnerable libraries: " + osaSummaryResults.getNonVulnerableLibraries()); log.info(""); + if (enableViolations) { + if (osaResults.getOsaPolicies().isEmpty()){ + log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); + }else{ + log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); + log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); + } + } log.info("OSA scan results location: " + osaResults.getOsaProjectSummaryLink()); log.info("-----------------------------------------------------------------------------------------"); } diff --git a/src/main/java/com/cx/restclient/sast/dto/Comment.java b/src/src/main/java/com/cx/restclient/sast/dto/Comment.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/Comment.java rename to src/src/main/java/com/cx/restclient/sast/dto/Comment.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java b/src/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java rename to src/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java b/src/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java rename to src/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java b/src/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java rename to src/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java b/src/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java rename to src/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java b/src/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java rename to src/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CxID.java b/src/src/main/java/com/cx/restclient/sast/dto/CxID.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CxID.java rename to src/src/main/java/com/cx/restclient/sast/dto/CxID.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java b/src/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CxNameObj.java rename to src/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java b/src/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/CxValueObj.java rename to src/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java b/src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java similarity index 99% rename from src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java rename to src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java index 2a037413..c9d57795 100644 --- a/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java +++ b/src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java @@ -1,6 +1,5 @@ package com.cx.restclient.sast.dto; -import javax.xml.bind.annotation.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java b/src/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java rename to src/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java diff --git a/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java b/src/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java rename to src/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java diff --git a/src/main/java/com/cx/restclient/sast/dto/Preset.java b/src/src/main/java/com/cx/restclient/sast/dto/Preset.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/Preset.java rename to src/src/main/java/com/cx/restclient/sast/dto/Preset.java diff --git a/src/main/java/com/cx/restclient/sast/dto/Project.java b/src/src/main/java/com/cx/restclient/sast/dto/Project.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/Project.java rename to src/src/main/java/com/cx/restclient/sast/dto/Project.java diff --git a/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java b/src/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/QueueStatus.java rename to src/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java b/src/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ReportStatus.java rename to src/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java b/src/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java rename to src/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ReportType.java b/src/src/main/java/com/cx/restclient/sast/dto/ReportType.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ReportType.java rename to src/src/main/java/com/cx/restclient/sast/dto/ReportType.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java b/src/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java rename to src/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java similarity index 92% rename from src/main/java/com/cx/restclient/sast/dto/SASTResults.java rename to src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 69d83fcf..8ede02e5 100644 --- a/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,6 +1,7 @@ package com.cx.restclient.sast.dto; -import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.cxArm.dto.Violation; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.text.DateFormat; @@ -8,7 +9,6 @@ import java.text.SimpleDateFormat; import java.util.*; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -16,6 +16,7 @@ /** * Created by Galn on 05/02/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class SASTResults implements Serializable { private long scanId; @@ -48,7 +49,8 @@ public class SASTResults implements Serializable { private byte[] PDFReport; private String pdfFileName; - private List sastPolicies = new ArrayList<>(); + private List sastPolicies = new ArrayList<>(); + private List sastViolations = new ArrayList<>(); public enum Severity { @@ -100,10 +102,6 @@ public void setResults(long scanId, SASTStatisticsResponse statisticsResults, St setSastProjectLink(url, projectId); } - public void addPolicy(Policy policy) { - this.sastPolicies.addAll(getPolicyList(policy)); - } - public long getScanId() { return scanId; } @@ -360,16 +358,27 @@ private Date createTimeDate(String scanTime) throws ParseException { } private Date createEndDate(Date scanStartDate, Date scanTimeDate) { - long time = scanStartDate.getTime() + scanTimeDate.getTime(); + long time /*no c*/ = scanStartDate.getTime() + scanTimeDate.getTime(); return new Date(time); } - public List getSastPolicies() { + public List getSastViolations() { + return sastViolations; + } + + public void setSastViolations(List sastViolations) { + this.sastViolations = sastViolations; + } + + public void addAllViolations(List violations) { + this.sastViolations.addAll(violations); + } + + public List getSastPolicies() { return sastPolicies; } - public void setSastPolicies(List sastPolicies) { + public void setSastPolicies(List sastPolicies) { this.sastPolicies = sastPolicies; } - } diff --git a/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java b/src/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java rename to src/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java b/src/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java rename to src/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java diff --git a/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java b/src/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java rename to src/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java diff --git a/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java b/src/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java rename to src/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java diff --git a/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java b/src/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java rename to src/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java similarity index 88% rename from src/main/java/com/cx/restclient/sast/utils/SASTParam.java rename to src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index da289114..eb4103b7 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -18,17 +18,11 @@ public class SASTParam { public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; - public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/{projectId}/sourceCode/remoteSettings/{sourceType}";//todo maybe with ssh? - - - //Once it has results public static final String SAST_SCAN_RESULTS_STATISTICS = "sast/scans/{scanId}/resultsStatistics"; public static final String SAST_CREATE_REPORT = "reports/sastScan/"; //Create new report (get ID) public static final String SAST_GET_REPORT_STATUS = "reports/sastScan/{reportId}/status"; //Get report status public static final String SAST_GET_REPORT = "reports/sastScan/{reportId}"; //Get report status - public static final String SAST_GET_CXARM_STATUS = "sast/projects/{projectId}/publisher/policyFindings/status"; //Get report status - //ZIP PARAMS public static final long MAX_ZIP_SIZE_BYTES = 2147483648L; diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java similarity index 90% rename from src/main/java/com/cx/restclient/sast/utils/SASTUtils.java rename to src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 7eb55c5c..403541d8 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -62,18 +62,22 @@ public static void printSASTResultsToConsole(SASTResults sastResults,boolean ena log.info("Low severity results: " + sastResults.getLow() + lowNew); log.info("Information severity results: " + sastResults.getInformation() + infoNew); log.info(""); + /* if (enableViolations && !sastResults.getSastPolicies().isEmpty()) { + log.info("SAST violated policies names: " + StringUtils.join(sastResults.getSastPolicies(), ',')); + }*/ log.info("Scan results location: " + sastResults.getSastScanLink()); log.info("------------------------------------------------------------------------------------------\n"); } //PDF Report - public static String writePDFReport(byte[] scanReport, File workspace, String pdfFileName, Logger log) { + public static String writePDFReport(byte[] scanReport, File workspace, Logger log) { + String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); + String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; try { FileUtils.writeByteArrayToFile(new File(workspace + CX_REPORT_LOCATION, pdfFileName), scanReport); log.info("PDF report location: " + workspace + CX_REPORT_LOCATION + File.separator + pdfFileName); } catch (Exception e) { log.error("Failed to write PDF report to workspace: ", e.getMessage()); - pdfFileName =""; } return pdfFileName; } diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java b/src/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java rename to src/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java b/src/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java rename to src/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java b/src/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java rename to src/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java b/src/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java similarity index 100% rename from src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java rename to src/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java diff --git a/src/main/resources/com/cx/report/images/no-policy-violation-found.png b/src/src/main/resources/com/cx/report/images/no-policy-violation-found.png similarity index 100% rename from src/main/resources/com/cx/report/images/no-policy-violation-found.png rename to src/src/main/resources/com/cx/report/images/no-policy-violation-found.png diff --git a/src/main/resources/com/cx/report/images/policy-violation-found.png b/src/src/main/resources/com/cx/report/images/policy-violation-found.png similarity index 100% rename from src/main/resources/com/cx/report/images/policy-violation-found.png rename to src/src/main/resources/com/cx/report/images/policy-violation-found.png diff --git a/src/main/resources/com/cx/report/report.ftl b/src/src/main/resources/com/cx/report/report.ftl similarity index 89% rename from src/main/resources/com/cx/report/report.ftl rename to src/src/main/resources/com/cx/report/report.ftl index c2ed822f..e385d605 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/src/main/resources/com/cx/report/report.ftl @@ -34,7 +34,7 @@ background-color: #372F51; } - .cx-report .legend-color-box.new-legend-color { + .cx-report .legend-color-box.new-legend-color { background: linear-gradient(45deg, white 25%, #373050 25%, #373050 50%, white 50%, white 75%, #373050 75%); background-size: 4px 4px; } @@ -371,9 +371,10 @@ background-color: #373050; } - .threshold-exceeded, - .threshold-compliance, - .policy-compliance { + + .threshold-exceeded, + .threshold-compliance, + .policy-compliance{ min-width: 100%; display: inline-flex; font-size: 14px; @@ -382,21 +383,21 @@ padding: 4px 9px; } - .threshold-exceeded { + .threshold-exceeded { background-color: #DA2945; color: white; border-radius: 2px; font-weight: bold; } - - .policy-compliance { + .policy-compliance{ border-radius: 2px; font-weight: bold; } + .threshold-exceeded-icon, .threshold-compliance-icon, - .policy-compliance { + .policy-compliance{ display: inline-flex; padding-right: 6px; margin: auto 0; @@ -425,10 +426,11 @@ overflow: hidden; } - .cx-report .results-report .libraries-vulnerable-number { + .cx-report .results-report .libraries-vulnerable-number{ font-size: 18px; } + .cx-report .results-report .libraries-vulnerable-text { font-size: 12px; line-height: 15px; @@ -625,7 +627,6 @@ .cx-report .html-report.download-icon { margin-right: 6px; } - .cx-report .pdf-report.download-icon, { margin-right: 6px; border-left: solid 1px #d5d5d @@ -807,7 +808,6 @@ margin-top: -3px; margin-left: -7px; } - .scan-status .content-scan-status li { margin-top: 6px; margin-bottom: 6px; @@ -826,6 +826,7 @@ padding-top: 10px; } + .scan-status .indicator-scan-status.success { background-color: #38d87d; } @@ -838,7 +839,7 @@ margin-top: 5px; text-align: center; border: 1px solid #ffffff; - padding-left: 5px; + padding-left: 5px ; } .scan-status .indicator-scan-status.success .icon-scan-status { @@ -849,11 +850,13 @@ border: 1px solid #DD3D56; } + + + .scan-status .title-scan-status { font-size: 12px; font-weight: bold; padding-left: 16px; - padding-top: 10px; } .scan-status .content-scan-status .title-scan-status.failure { @@ -906,102 +909,91 @@ +
    Checkmarx Report
    - <#if buildFailed> -
    -
    -
    - - - error - Created with Sketch. - - - - - - - - - - - - - - - -
    -
    -
    -

    - Checkmarx scan found the following issues: -

    -
      - <#if config.sastEnabled && !sast.sastResultsReady> -
    • SAST Scan Failed
    • - - <#if config.osaEnabled && !osa.osaResultsReady> -
    • OSA Scan Failed
    • - - <#if policyViolated> -
    • ${policyViolatedCount} ${policyLabel} Violated
    • - + <#if buildFailed> +
      +
      +
      + + + error + Created with Sketch. + + + + + + + + + + + + + + + +
      +
      +
      +

      + Checkmarx scan found the following issues: +

      +
        + <#if policyViolated> +
      • ${osa.osaPolicies?size} ${policyLabel} Violated
      • + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded>
      • Exceeded CxSAST and CxOSA Vulnerability Thresholds
      • - <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> -
      • Exceeded CxSAST Vulnerability Threshold
      • - <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> -
      • Exceeded CxOSA Vulnerability Threshold
      • - -
      -
      -
      - <#else> -
      -
      -
      - - - OK - Created with Sketch. - - - - - - - - - - - - - - - - - - -
      -
      -
      -

      - Checkmarx Scan Passed -

      -
      -
      - - + <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> +
    • Exceeded CxSAST Vulnerability Threshold
    • + <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> +
    • Exceeded CxOSA Vulnerability Threshold
    • + +
    +
    +
    + <#else> +
    +
    +
    + + + OK + Created with Sketch. + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    + Checkmarx Scan Passed +

    +
    +
    + +
    @@ -1021,7 +1013,7 @@
    + +
    +
    +
    ${osa.osaViolations?size }
    +
    +
    + Policy Violated Libraries +
    +
    @@ -1957,6 +1958,66 @@
    + + <#if sast.sastViolations?size gt 0> +
    +
    +
    + + + Policy violation + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    Policy Violations
    +
    ${sast.sastViolations?size}
    +
    + +
    +
    @@ -1964,7 +2025,7 @@ <#if config.osaEnabled && osa.osaResultsReady> - <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0> + <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0 || osa.osaViolations?size gt 0>
    @@ -2123,8 +2184,7 @@
    Libraries:
    -
    ${osa.results.totalLibraries}
    +
    ${osa.results.totalLibraries}
    @@ -2298,140 +2358,71 @@ - - - - - - - - <#if (config.osaEnabled || config.sastEnabled) && policyViolated> - - <#if policyViolatedCount gt 0> -
    -
    -
    -
    -
    Policy
    -
    Management
    -
    -
    -
    - -
    -
    -
    -
    - - - Policy violation - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + <#list osa.osaViolations as osaViolation> + + + + + + +
    Library NamePolicyRuleDetection Date
    ${osaViolation.source}${osaViolation.policyName}${osaViolation.ruleName}${osaViolation.detectionDate}
    +
    +
    -
    Violated ${policyLabel}
    -
    ${policyViolatedCount}
    - - - - - - - - - <#if sast.sastPolicies?size gt 0> - <#list sast.sastPolicies as sastPoliciy> - - - - - - - - - - <#if osa.osaPolicies?size gt 0> - <#list osa.osaPolicies as osaPolicy> - - - - - - - - - -
    PolicyRuleType# of Rule ViolationsFirst Detection Date
    ${sastPoliciy.policyName}${sastPoliciy.ruleName}SAST${sastPoliciy.violations?size}${sastPoliciy.firstDetectionDate}
    ${osaPolicy.policyName}${osaPolicy.ruleName}OSA${osaPolicy.violations?size}${osaPolicy.firstDetectionDate}
    - -
    -
    -
    - - - +
    diff --git a/src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java b/src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java new file mode 100644 index 00000000..6036a528 --- /dev/null +++ b/src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java @@ -0,0 +1,49 @@ +//package com.cx.restclient.configuration; +// +//import com.cx.restclient.CxShragaClient; +//import com.cx.restclient.exception.CxClientException; +//import org.junit.Assert; +//import org.junit.Test; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.IOException; +//import java.util.Properties; +// +//public class ConnectionTest { +// +// private Logger log = LoggerFactory.getLogger(ConnectionTest.class.getName()); +// private static final String PROPERTIES_FILE = "config.properties"; +// private CxShragaClient client; +// private static Properties props; +// +//// @BeforeClass +//// public static void initTest() throws IOException { +//// props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); +//// } +// +// @Test +// public void ssoConnectionTest() { +// CxScanConfig config = initConfig(); +// try { +// client = new CxShragaClient(config, log); +// client.init(); +// } catch (IOException | CxClientException e) { +// e.printStackTrace(); +// log.error("Error running osa scan: " + e.getMessage()); +// Assert.fail(e.getMessage()); +// } +// } +// +// private CxScanConfig initConfig() { +// CxScanConfig config = new CxScanConfig(); +// config.setSastEnabled(true); +// config.setUseSSOLogin(true); +// config.setUrl("http://10.32.1.57"); +// config.setCxOrigin("common"); +// +// return config; +// } +// +// +//} From 89565ee8c5b0470fe3a9b5b2e5bafa8c7ebfcdb0 Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 18 Nov 2019 16:26:54 +0200 Subject: [PATCH 144/473] replaced old 9.0 with more updated version --- pom.xml | 23 +- .../java/com/cx/restclient/CxOSAClient.java | 21 +- .../java/com/cx/restclient/CxSASTClient.java | 156 +++++- .../com/cx/restclient/CxShragaClient.java | 94 +++- .../com/cx/restclient/common/CxPARAM.java | 16 +- .../cx/restclient/common/ErrorMessage.java | 0 .../com/cx/restclient/common/ShragaUtils.java | 25 +- .../com/cx/restclient/common/UrlUtils.java | 0 .../java/com/cx/restclient/common/Waiter.java | 83 ++++ .../common/summary/SummaryUtils.java | 161 +++++++ .../configuration/CxScanConfig.java | 129 ++++- .../cx/restclient/cxArm/dto/CxArmConfig.java | 0 .../cx/restclient/cxArm/dto/CxProviders.java | 3 - .../com/cx/restclient/cxArm/dto/Policy.java | 35 +- .../com/cx/restclient/cxArm/dto/Rule.java | 19 + .../cx/restclient/cxArm/dto/Violation.java | 15 - .../cx/restclient/cxArm/utils/CxARMUtils.java | 75 +++ .../com/cx/restclient/dto/BaseStatus.java | 1 - .../java/com/cx/restclient/dto/CxProxy.java | 60 +++ .../java/com/cx/restclient/dto/CxVersion.java | 28 ++ .../com/cx/restclient/dto/LoginRequest.java | 0 .../restclient/dto/RemoteSourceRequest.java | 96 ++++ .../cx/restclient/dto/RemoteSourceTypes.java | 25 + .../com/cx/restclient/dto/ScanResults.java | 17 +- .../java/com/cx/restclient/dto/Status.java | 0 .../main/java/com/cx/restclient/dto/Team.java | 1 + .../cx/restclient/dto/TokenLoginResponse.java | 0 .../exception/CxClientException.java | 0 .../exception/CxHTTPClientException.java | 0 .../exception/CxTokenExpiredException.java | 0 .../restclient/httpClient/CxHttpClient.java | 104 +++- .../httpClient/utils/ContentType.java | 0 .../httpClient/utils/HttpClientHelper.java | 6 +- .../java/com/cx/restclient/osa/dto/CVE.java | 0 .../restclient/osa/dto/CVEReportTableRow.java | 0 .../com/cx/restclient/osa/dto/Content.java | 0 .../osa/dto/CreateOSAScanRequest.java | 0 .../osa/dto/CreateOSAScanResponse.java | 0 .../com/cx/restclient/osa/dto/Library.java | 0 .../cx/restclient/osa/dto/LoginRequest.java | 0 .../com/cx/restclient/osa/dto/OSAResults.java | 25 +- .../cx/restclient/osa/dto/OSAScanState.java | 0 .../cx/restclient/osa/dto/OSAScanStatus.java | 0 .../restclient/osa/dto/OSAScanStatusEnum.java | 0 .../restclient/osa/dto/OSASummaryResults.java | 0 .../restclient/osa/dto/ScanConfiguration.java | 0 .../com/cx/restclient/osa/dto/Severity.java | 0 .../java/com/cx/restclient/osa/dto/State.java | 0 .../com/cx/restclient/osa/utils/OSAParam.java | 0 .../com/cx/restclient/osa/utils/OSAUtils.java | 9 - .../com/cx/restclient/sast/dto/Comment.java | 0 .../sast/dto/CreateProjectRequest.java | 0 .../sast/dto/CreateReportRequest.java | 0 .../sast/dto/CreateReportResponse.java | 0 .../sast/dto/CreateScanRequest.java | 0 .../cx/restclient/sast/dto/CurrentStatus.java | 0 .../cx/restclient/sast/dto/CxARMStatus.java | 48 ++ .../restclient/sast/dto/CxARMStatusEnum.java | 25 + .../java/com/cx/restclient/sast/dto/CxID.java | 0 .../com/cx/restclient/sast/dto/CxNameObj.java | 0 .../cx/restclient/sast/dto/CxValueObj.java | 0 .../cx/restclient/sast/dto/CxXMLResults.java | 1 + .../sast/dto/EmailNotifications.java | 0 .../restclient/sast/dto/LastScanResponse.java | 0 .../com/cx/restclient/sast/dto/Preset.java | 0 .../com/cx/restclient/sast/dto/Project.java | 0 .../cx/restclient/sast/dto/QueueStatus.java | 0 .../cx/restclient/sast/dto/ReportStatus.java | 0 .../restclient/sast/dto/ReportStatusEnum.java | 0 .../cx/restclient/sast/dto/ReportType.java | 0 .../sast/dto/ResponseQueueScanStatus.java | 0 .../cx/restclient/sast/dto/SASTResults.java | 31 +- .../sast/dto/SASTStatisticsResponse.java | 0 .../sast/dto/ScanSettingRequest.java | 0 .../sast/dto/ScanSettingResponse.java | 0 .../sast/dto/SupportedLanguage.java | 0 .../sast/dto/UpdateScanStatusRequest.java | 0 .../cx/restclient/sast/utils/SASTParam.java | 6 + .../cx/restclient/sast/utils/SASTUtils.java | 8 +- .../cx/restclient/sast/utils/zip/CxZip.java | 0 .../restclient/sast/utils/zip/CxZipUtils.java | 0 .../sast/utils/zip/ZipListener.java | 0 .../cx/restclient/sast/utils/zip/Zipper.java | 0 src/main/java/testi.java | 148 ++++++ .../images/no-policy-violation-found.png | Bin .../report/images/policy-violation-found.png | Bin .../main/resources/com/cx/report/report.ftl | 453 +++++++++--------- src/main/resources/common.properties | 1 + .../cx/restclient/common/CxGlobalMessage.java | 25 - .../com/cx/restclient/common/ErrorUtil.java | 35 -- .../java/com/cx/restclient/common/Waiter.java | 75 --- .../common/summary/SummaryUtils.java | 127 ----- .../cx/restclient/cxArm/utils/CxARMUtils.java | 21 - .../cx/restclient/dto/ThresholdResult.java | 33 -- .../configuration/ConnectionTest.java | 0 .../configuration/CxScanConfigTest.java | 2 +- .../connection/ConnectionTests.java | 55 +++ .../connection/ProjectScanTests.java | 117 +++++ .../java/com/cx/utility/TestingUtils.java | 18 + src/test/java/resources/config.properties | 2 + 100 files changed, 1741 insertions(+), 717 deletions(-) rename src/{src => }/main/java/com/cx/restclient/CxOSAClient.java (93%) rename src/{src => }/main/java/com/cx/restclient/CxSASTClient.java (70%) rename src/{src => }/main/java/com/cx/restclient/CxShragaClient.java (78%) rename src/{src => }/main/java/com/cx/restclient/common/CxPARAM.java (61%) rename src/{src => }/main/java/com/cx/restclient/common/ErrorMessage.java (100%) rename src/{src => }/main/java/com/cx/restclient/common/ShragaUtils.java (84%) rename src/{src => }/main/java/com/cx/restclient/common/UrlUtils.java (100%) create mode 100644 src/main/java/com/cx/restclient/common/Waiter.java create mode 100644 src/main/java/com/cx/restclient/common/summary/SummaryUtils.java rename src/{src => }/main/java/com/cx/restclient/configuration/CxScanConfig.java (79%) rename src/{src => }/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java (100%) rename src/{src => }/main/java/com/cx/restclient/cxArm/dto/CxProviders.java (74%) rename src/{src => }/main/java/com/cx/restclient/cxArm/dto/Policy.java (54%) create mode 100644 src/main/java/com/cx/restclient/cxArm/dto/Rule.java rename src/{src => }/main/java/com/cx/restclient/cxArm/dto/Violation.java (89%) create mode 100644 src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java rename src/{src => }/main/java/com/cx/restclient/dto/BaseStatus.java (99%) create mode 100644 src/main/java/com/cx/restclient/dto/CxProxy.java create mode 100644 src/main/java/com/cx/restclient/dto/CxVersion.java rename src/{src => }/main/java/com/cx/restclient/dto/LoginRequest.java (100%) create mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java create mode 100644 src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java rename src/{src => }/main/java/com/cx/restclient/dto/ScanResults.java (81%) rename src/{src => }/main/java/com/cx/restclient/dto/Status.java (100%) rename src/{src => }/main/java/com/cx/restclient/dto/Team.java (99%) rename src/{src => }/main/java/com/cx/restclient/dto/TokenLoginResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/exception/CxClientException.java (100%) rename src/{src => }/main/java/com/cx/restclient/exception/CxHTTPClientException.java (100%) rename src/{src => }/main/java/com/cx/restclient/exception/CxTokenExpiredException.java (100%) rename src/{src => }/main/java/com/cx/restclient/httpClient/CxHttpClient.java (78%) rename src/{src => }/main/java/com/cx/restclient/httpClient/utils/ContentType.java (100%) rename src/{src => }/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java (90%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/CVE.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/Content.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/Library.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/LoginRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/OSAResults.java (89%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/OSAScanState.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/Severity.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/dto/State.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/utils/OSAParam.java (100%) rename src/{src => }/main/java/com/cx/restclient/osa/utils/OSAUtils.java (93%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/Comment.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CurrentStatus.java (100%) create mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java create mode 100644 src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java rename src/{src => }/main/java/com/cx/restclient/sast/dto/CxID.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CxNameObj.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CxValueObj.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/CxXMLResults.java (99%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/EmailNotifications.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/LastScanResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/Preset.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/Project.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/QueueStatus.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ReportStatus.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ReportType.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/SASTResults.java (92%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/SASTParam.java (88%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/SASTUtils.java (90%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/zip/CxZip.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java (100%) rename src/{src => }/main/java/com/cx/restclient/sast/utils/zip/Zipper.java (100%) create mode 100644 src/main/java/testi.java rename src/{src => }/main/resources/com/cx/report/images/no-policy-violation-found.png (100%) rename src/{src => }/main/resources/com/cx/report/images/policy-violation-found.png (100%) rename src/{src => }/main/resources/com/cx/report/report.ftl (89%) create mode 100644 src/main/resources/common.properties delete mode 100644 src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java delete mode 100644 src/src/main/java/com/cx/restclient/common/ErrorUtil.java delete mode 100644 src/src/main/java/com/cx/restclient/common/Waiter.java delete mode 100644 src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java delete mode 100644 src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java delete mode 100644 src/src/main/java/com/cx/restclient/dto/ThresholdResult.java rename src/{src => }/test/java/com/cx/restclient/configuration/ConnectionTest.java (100%) create mode 100644 src/test/java/com/cx/restclient/connection/ConnectionTests.java create mode 100644 src/test/java/com/cx/restclient/connection/ProjectScanTests.java create mode 100644 src/test/java/com/cx/utility/TestingUtils.java create mode 100644 src/test/java/resources/config.properties diff --git a/pom.xml b/pom.xml index dfa4cfbf..98ae9461 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,15 @@ - + 4.0.0 com.checkmarx cx-client-common - 9.00.2 + 9.00.5 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. https://www.checkmarx.com - scm:git:git://github.com/CxRepositories/Cx-Client-Common.git scm:git:ssh://github.com/CxRepositories/Cx-Client-Common.git https://github.com/CxRepositories/Cx-Client-Common/tree/master @@ -79,7 +78,7 @@ org.slf4j slf4j-simple - 1.6.1 + 1.7.28 org.freemarker @@ -89,7 +88,7 @@ org.apache.httpcomponents httpmime - 4.4.1 + 4.5.4 org.apache.httpcomponents @@ -99,12 +98,12 @@ com.fasterxml.jackson.core jackson-databind - 2.9.9.3 + 2.9.10.1 org.bouncycastle bcprov-jdk15on - 1.60 + 1.64 plexus-utils @@ -114,7 +113,7 @@ org.codehaus.plexus plexus-archiver - 3.6.0 + 4.1.0 commons-compress @@ -137,9 +136,9 @@ 27.0-jre - org.whitesource - whitesource-fs-agent - 18.7.2-fix + com.checkmarx + cx-ws-fs-agent + 18.7.2.4 javax.xml.bind @@ -321,4 +320,4 @@ - \ No newline at end of file + diff --git a/src/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java similarity index 93% rename from src/src/main/java/com/cx/restclient/CxOSAClient.java rename to src/main/java/com/cx/restclient/CxOSAClient.java index 92f48c1d..0d07ad43 100644 --- a/src/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -2,12 +2,13 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.*; import com.cx.restclient.osa.utils.OSAUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; import org.whitesource.fs.ComponentScan; @@ -18,7 +19,7 @@ import java.util.Properties; import static com.cx.restclient.cxArm.dto.CxProviders.OPEN_SOURCE; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.osa.utils.OSAParam.*; @@ -71,7 +72,7 @@ public String createOSAScan(long projectId) throws IOException, CxClientExceptio return sendOSAScan(osaDependenciesJson, projectId); } - private String resolveOSADependencies() { + private String resolveOSADependencies() throws JsonProcessingException { log.info("Scanning for CxOSA compatible files"); Properties scannerProperties = config.getOsaFsaConfig(); if (scannerProperties == null) { @@ -83,17 +84,18 @@ private String resolveOSADependencies() { config.getOsaRunInstall(), log); } + ObjectMapper mapper = new ObjectMapper(); + log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); - return osaDependenciesJson; } public OSAResults getOSAResults(String scanId, long projectId) throws CxClientException, InterruptedException, IOException { log.info("-------------------------------------Get CxOSA Results:-----------------------------------"); log.info("Waiting for OSA scan to finish"); - OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, -1, log); + OSAScanStatus osaScanStatus = osaWaiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); log.info("OSA scan finished successfully. Retrieving OSA scan results"); log.info("Creating OSA reports"); @@ -114,6 +116,7 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx return osaResults; } + private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus, long projectId) throws CxClientException, IOException { OSASummaryResults osaSummaryResults = getOSAScanSummaryResults(scanId); List osaLibraries = getOSALibraries(scanId); @@ -126,10 +129,8 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus private void resolveOSAViolation(OSAResults osaResults, long projectId) { try { - for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value())) { - osaResults.getOsaPolicies().add(policy.getPolicyName()); - osaResults.addAllViolations(policy.getViolations()); - } + getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) + .forEach(osaResults::addPolicy); } catch (Exception ex) { log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } @@ -211,7 +212,7 @@ private void printOSAProgress(OSAScanStatus scanStatus, long startTime) { } private OSAScanStatus resolveOSAStatus(OSAScanStatus scanStatus) throws CxClientException { - if (Status.FAILED == scanStatus.getBaseStatus()) { + if (scanStatus == null || Status.FAILED == scanStatus.getBaseStatus()) { String failedMsg = scanStatus.getState() == null ? "" : "status [" + scanStatus.getState().getName() + "]. Reason: " + scanStatus.getState().getFailureReason(); throw new CxClientException("OSA scan cannot be completed. " + failedMsg); } diff --git a/src/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java similarity index 70% rename from src/src/main/java/com/cx/restclient/CxSASTClient.java rename to src/main/java/com/cx/restclient/CxSASTClient.java index 9a18af77..e41699fe 100644 --- a/src/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -2,26 +2,35 @@ import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.dto.RemoteSourceRequest; +import com.cx.restclient.dto.RemoteSourceTypes; import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.sast.dto.*; import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.json.JSONObject; import org.slf4j.Logger; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.cx.restclient.cxArm.dto.CxProviders.SAST; -import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolations; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getProjectViolatedPolicies; import static com.cx.restclient.httpClient.utils.ContentType.*; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -33,10 +42,13 @@ */ class CxSASTClient { + public static final String JENKINS = "jenkins"; private Logger log; private CxHttpClient httpClient; private CxScanConfig config; - private int reportTimeoutSec = 500; + private int reportTimeoutSec = 5000; + private int cxARMTimeoutSec = 1000; + private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { @Override public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { @@ -54,7 +66,7 @@ public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) } }; - private Waiter reportWaiter = new Waiter("Scan report", 5) { + private Waiter reportWaiter = new Waiter("Scan report", 10) { @Override public ReportStatus getStatus(String id) throws CxClientException, IOException { return getReportStatus(id); @@ -71,6 +83,23 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; + private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20) { + @Override + public CxARMStatus getStatus(String id) throws CxClientException, IOException { + return getCxARMStatus(id); + } + + @Override + public void printProgress(CxARMStatus cxARMStatus) { + printCxARMProgress(cxARMStatus, getStartTimeSec()); + } + + @Override + public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientException { + return resolveCxARMStatus(cxARMStatus); + } + }; + CxSASTClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; this.httpClient = client; @@ -82,10 +111,17 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce //CREATE SAST scan long createSASTScan(long projectId) throws IOException, CxClientException { log.info("-----------------------------------Create CxSAST Scan:------------------------------------"); - if (config.isAvoidDuplicateProjectScans() != null && config.isAvoidDuplicateProjectScans() && projectHasQueuedScans(projectId)) { throw new CxClientException("\nAvoid duplicate project scans in queue\n"); } + if (config.getRemoteType() == null) { //scan is local + return createLocalSASTScan(projectId); + } else { + return createRemoteSourceScan(projectId); + } + } + + private long createLocalSASTScan(long projectId) throws IOException, CxClientException { ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); @@ -119,6 +155,55 @@ long createSASTScan(long projectId) throws IOException, CxClientException { return createScanResponse.getId(); } + private long createRemoteSourceScan(long projectId) throws IOException, CxClientException { + HttpEntity entity; + RemoteSourceRequest req = new RemoteSourceRequest(config); + RemoteSourceTypes type = req.getType(); + + switch (type) { + case SVN: + case TFS: + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null); + builder.addTextBody("absoluteUrl", req.getUrl(), ContentType.APPLICATION_JSON); + builder.addTextBody("port", String.valueOf(req.getPort()), ContentType.APPLICATION_JSON); + builder.addTextBody("paths", StringUtils.join(req.getPaths(), ";"), ContentType.APPLICATION_JSON); + entity = builder.build(); + break; + case PERFORCE: + if (config.getPreforceMode() != null) { + req.setBrowseMode("Workspace"); + } else { + req.setBrowseMode("Depot"); + } + entity = new StringEntity(convertToJson(req), StandardCharsets.UTF_8); + break; + case SHARED: + entity = new StringEntity(new Gson().toJson(req), StandardCharsets.UTF_8); + break; + case GIT: + if (req.getPrivateKey().length < 1) { + Map content = new HashMap<>(); + content.put("url", config.getRemoteSrcUrl()); + content.put("branch", config.getRemoteSrcBranch()); + entity = new StringEntity(new JSONObject(content).toString(), StandardCharsets.UTF_8); + } else { + builder = MultipartEntityBuilder.create(); + builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); + builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); + entity = builder.build(); + } + break; + default: + log.error("todo"); + entity = new StringEntity("", StandardCharsets.UTF_8); + + } + return createRemoteSourceScan(projectId, entity, type.value()).getId(); + } + + //GET SAST results + reports public SASTResults waitForSASTResults(long scanId, long projectId) throws InterruptedException, IOException, CxClientException { SASTResults sastResults; @@ -131,9 +216,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr //retrieve SAST scan results sastResults = retrieveSASTResults(scanId, projectId); - /* if (config.getEnablePolicyViolations()) { + if (config.getEnablePolicyViolations()) { resolveSASTViolation(sastResults, projectId); - }*/ + } SASTUtils.printSASTResultsToConsole(sastResults, config.getEnablePolicyViolations(), log); //PDF report @@ -142,7 +227,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr byte[] pdfReport = getScanReport(sastResults.getScanId(), ReportType.PDF, CONTENT_TYPE_APPLICATION_PDF_V1); sastResults.setPDFReport(pdfReport); if (config.getReportsDir() != null) { - String pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), log); + String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); + String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; + pdfFileName = writePDFReport(pdfReport, config.getReportsDir(), pdfFileName, log); sastResults.setPdfFileName(pdfFileName); } } @@ -151,10 +238,9 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr private void resolveSASTViolation(SASTResults sastResults, long projectId) { try { - for (Policy policy : getProjectViolations(httpClient, config.getCxARMUrl(), projectId, SAST.value())) { - sastResults.getSastPolicies().add(policy.getPolicyName()); - sastResults.addAllViolations(policy.getViolations()); - } + cxARMWaiter.waitForTaskToFinish(Long.toString(projectId), cxARMTimeoutSec, log); + getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, SAST.value()) + .forEach(sastResults::addPolicy); } catch (Exception ex) { log.error("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); } @@ -196,7 +282,6 @@ public void cancelSASTScan(long scanId) throws IOException, CxClientException { log.info("SAST Scan canceled. (scanId: " + scanId + ")"); } - //**------ Private Methods ------**// private boolean projectHasQueuedScans(long projectId) throws IOException, CxClientException { List queuedScans = getQueueScans(projectId); @@ -245,6 +330,10 @@ private CxID createScan(CreateScanRequest request) throws CxClientException, IOE return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } + private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { + return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + } + private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientException, IOException { return httpClient.getRequest(SAST_SCAN_RESULTS_STATISTICS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, SASTStatisticsResponse.class, 200, "SAST scan statistics", false); } @@ -307,7 +396,7 @@ private void printSASTProgress(ResponseQueueScanStatus scanStatus, long startTim } private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { - if (Status.SUCCEEDED == scanStatus.getBaseStatus()) { + if (scanStatus != null && Status.SUCCEEDED == scanStatus.getBaseStatus()) { log.info("SAST scan finished successfully."); return scanStatus; } else { @@ -318,14 +407,14 @@ private ResponseQueueScanStatus resolveSASTStatus(ResponseQueueScanStatus scanSt //Report Waiter - overload methods private ReportStatus getReportStatus(String reportId) throws CxClientException, IOException { ReportStatus reportStatus = httpClient.getRequest(SAST_GET_REPORT_STATUS.replace("{reportId}", reportId), CONTENT_TYPE_APPLICATION_JSON_V1, ReportStatus.class, 200, " report status", false); - + reportStatus.setBaseId(reportId); String currentStatus = reportStatus.getStatus().getValue(); if (currentStatus.equals(ReportStatusEnum.INPROCESS.value())) { reportStatus.setBaseStatus(Status.IN_PROGRESS); } else if (currentStatus.equals(ReportStatusEnum.FAILED.value())) { reportStatus.setBaseStatus(Status.FAILED); } else { - reportStatus.setBaseStatus(Status.SUCCEEDED); + reportStatus.setBaseStatus(Status.SUCCEEDED); //todo fix it!! } return reportStatus; @@ -337,10 +426,43 @@ private void printReportProgress(ReportStatus reportStatus, long startTime) { } private ReportStatus resolveReportStatus(ReportStatus reportStatus) throws CxClientException { - if (Status.SUCCEEDED == reportStatus.getBaseStatus()) { + if (reportStatus != null && Status.SUCCEEDED == reportStatus.getBaseStatus()) { return reportStatus; } else { throw new CxClientException("Generation of scan report [id=" + reportStatus.getBaseId() + "] failed."); } } + + + //CxARM Waiter - overload methods + private CxARMStatus getCxARMStatus(String projectId) throws CxClientException, IOException { + CxARMStatus cxARMStatus = httpClient.getRequest(SAST_GET_CXARM_STATUS.replace("{projectId}", projectId), CONTENT_TYPE_APPLICATION_JSON_V1, CxARMStatus.class, 200, " cxARM status", false); + cxARMStatus.setBaseId(projectId); + + String currentStatus = cxARMStatus.getStatus(); + if (currentStatus.equals(CxARMStatusEnum.IN_PROGRESS.value())) { + cxARMStatus.setBaseStatus(Status.IN_PROGRESS); + } else if (currentStatus.equals(CxARMStatusEnum.FAILED.value())) { + cxARMStatus.setBaseStatus(Status.FAILED); + } else if (currentStatus.equals(CxARMStatusEnum.FINISHED.value())) { + cxARMStatus.setBaseStatus(Status.SUCCEEDED); + } else { + cxARMStatus.setBaseStatus(Status.FAILED); + } + + return cxARMStatus; + } + + private void printCxARMProgress(CxARMStatus cxARMStatus, long startTime) { + log.info("Waiting for server to retrieve policy violations. " + (startTime + cxARMTimeoutSec - (System.currentTimeMillis() / 1000)) + " seconds left to timeout"); //todo Liran + } + + private CxARMStatus resolveCxARMStatus(CxARMStatus cxARMStatus) throws CxClientException { + if (cxARMStatus != null && Status.SUCCEEDED == cxARMStatus.getBaseStatus()) { + return cxARMStatus; + } else { + throw new CxClientException("Getting policy violations of project [id=" + cxARMStatus.getBaseId() + "] failed."); //todo Liran + } + } + } diff --git a/src/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java similarity index 78% rename from src/src/main/java/com/cx/restclient/CxShragaClient.java rename to src/main/java/com/cx/restclient/CxShragaClient.java index baf5fc41..ef822cb8 100644 --- a/src/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -1,11 +1,10 @@ package com.cx.restclient; -import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.summary.SummaryUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; +import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; -import com.cx.restclient.dto.ThresholdResult; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; @@ -23,7 +22,7 @@ import java.util.Properties; import static com.cx.restclient.common.CxPARAM.*; -import static com.cx.restclient.common.ShragaUtils.isThresholdExceeded; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -56,7 +55,8 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), log, + config.isDisableCertificateValidation(), config.isUseSSOLogin(), + log, proxyHost, proxyPort, proxyUser, proxyPassword); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); @@ -70,7 +70,9 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), log); + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } @@ -85,8 +87,25 @@ public CxShragaClient(String serverUrl, String username, String password, String } //API Scans methods + public String getClientVersion() { + String version = ""; + try { + Properties properties = new Properties(); + java.io.InputStream is = getClass().getClassLoader().getResourceAsStream("common.properties"); + if (is != null) { + properties.load(is); + version = properties.getProperty("version"); + } + } catch (Exception e) { + + } + return version; + } + public void init() throws CxClientException, IOException { - log.info("Initializing Cx client"); + + log.info("Initializing Cx client [" + getClientVersion() + "]"); + getCxVersion(); login(); resolveTeam(); if (config.getSastEnabled()) { @@ -134,21 +153,27 @@ public OSAResults getLatestOSAResults() throws InterruptedException, CxClientExc return osaResults; } - public ThresholdResult getThresholdResult() { - StringBuilder res = new StringBuilder(""); - boolean isFail = isThresholdExceeded(config, sastResults, osaResults, res); - return new ThresholdResult(isFail, res.toString()); - } - - public boolean isPolicyViolated(StringBuilder failDescription) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && osaResults.getOsaViolations().size() > 0; - if (isPolicyViolated) { - failDescription.append(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()).append("\n"); + public void printIsProjectViolated() { + if (config.getEnablePolicyViolations()) { + log.info("-----------------------------------------------------------------------------------------"); + log.info("Policy Management: "); + log.info("--------------------"); + if (sastResults.getSastPolicies().isEmpty() && osaResults.getOsaPolicies().isEmpty()) { + log.info(PROJECT_POLICY_COMPLAINT_STATUS); + log.info("-----------------------------------------------------------------------------------------"); + } else { + log.info(PROJECT_POLICY_VIOLATED_STATUS); + if (!sastResults.getSastPolicies().isEmpty()) { + log.info("SAST violated policies names: " + getPoliciesNames(sastResults.getSastPolicies())); + } + if (!osaResults.getOsaPolicies().isEmpty()) { + log.info("OSA violated policies names: " + getPoliciesNames(osaResults.getOsaPolicies())); + } + log.info("-----------------------------------------------------------------------------------------"); + } } - return isPolicyViolated; } - private CxArmConfig getCxARMConfig() throws IOException, CxClientException { return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); } @@ -184,17 +209,44 @@ public void login() throws IOException, CxClientException { httpClient.login(); } + public void getCxVersion() throws IOException, CxClientException { + try { + config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); + String hotfix = ""; + try { + if (config.getCxVersion().getHotFix() != null && Integer.parseInt(config.getCxVersion().getHotFix()) > 0) { + hotfix = " Hotfix [" + config.getCxVersion().getHotFix() + "]."; + } + } catch (Exception ex) { + } + + log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); + + } catch (Exception ex) { + log.debug("Checkmarx server version [lower than 9.0]"); + } + } + public String getTeamIdByName(String teamName) throws CxClientException, IOException { - teamName = teamName.replace("\\", "/"); + teamName = replaceDelimiters(teamName); List allTeams = getTeamList(); for (Team team : allTeams) { - if (team.getFullName().replace("\\", "/").equalsIgnoreCase(teamName)) { //TODO caseSenesitive + String fullName = replaceDelimiters(team.getFullName()); + if (fullName.equalsIgnoreCase(teamName)) { //TODO caseSenesitive return team.getId(); } } throw new CxClientException("Could not resolve team ID from team name: " + teamName); } + private String replaceDelimiters(String teamName) { + while (teamName.contains("\\") || teamName.contains("//")) { + teamName = teamName.replace("\\", "/"); + teamName = teamName.replace("//", "/"); + } + return teamName; + } + public String getTeamNameById(String teamId) throws CxClientException, IOException { List allTeams = getTeamList(); for (Team team : allTeams) { @@ -316,4 +368,4 @@ private Project createNewProject(CreateProjectRequest request) throws CxClientEx return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } -} \ No newline at end of file +} diff --git a/src/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java similarity index 61% rename from src/src/main/java/com/cx/restclient/common/CxPARAM.java rename to src/main/java/com/cx/restclient/common/CxPARAM.java index 96c456f1..730a447b 100644 --- a/src/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -7,17 +7,25 @@ */ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; - public static final String ORIGIN_HEADER = "cxOrigin"; + public static final String SSO_AUTHENTICATION = "auth/ssologin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; public static final String CREATE_PROJECT = "projects";//Create new project (default preset and configuration) + public static final String CX_VERSION = "system/version"; + + public static final String CX_ARM_URL = "/Configurations/Portal"; + public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + + public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; - public static final String CX_ARM_URL ="/Configurations/Portal"; - public static final String CX_ARM_VIOLATION ="/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + public static final String ORIGIN_HEADER = "cxOrigin"; + public static final String CSRF_TOKEN_HEADER = "CXCSRFToken"; + public static final String PROJECT_POLICY_VIOLATED_STATUS = "Project policy status : violated"; + public static final String PROJECT_POLICY_COMPLAINT_STATUS = "Project policy status : compliant"; + public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; - } diff --git a/src/src/main/java/com/cx/restclient/common/ErrorMessage.java b/src/main/java/com/cx/restclient/common/ErrorMessage.java similarity index 100% rename from src/src/main/java/com/cx/restclient/common/ErrorMessage.java rename to src/main/java/com/cx/restclient/common/ErrorMessage.java diff --git a/src/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java similarity index 84% rename from src/src/main/java/com/cx/restclient/common/ShragaUtils.java rename to src/main/java/com/cx/restclient/common/ShragaUtils.java index a3a37e9c..b47afd06 100644 --- a/src/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -12,12 +12,31 @@ import java.util.List; import java.util.Map; +import static com.cx.restclient.common.CxPARAM.PROJECT_POLICY_VIOLATED_STATUS; + /** * Created by: dorg. * Date: 4/12/2018. */ public abstract class ShragaUtils { //Util methods + public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, OSAResults osaResults) { + StringBuilder res = new StringBuilder(""); + isThresholdExceeded(config, sastResults, osaResults, res); + isThresholdForNewResultExceeded(config, sastResults, res); + isPolicyViolated(config, sastResults, osaResults, res); + + return res.toString(); + } + + public static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { + boolean isPolicyViolated = config.getEnablePolicyViolations() && ((osaResults!=null && osaResults.getOsaPolicies().size() > 0) || (sastResults != null && sastResults.getSastPolicies().size() > 0)); + if(isPolicyViolated) { + res.append(PROJECT_POLICY_VIOLATED_STATUS).append("\n"); + } + return isPolicyViolated; + } + public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, OSAResults osaResults, StringBuilder res) { boolean thresholdExceeded = false; if (config.isSASTThresholdEffectivelyEnabled() && sastResults != null && sastResults.isSastResultsReady()) { @@ -69,7 +88,7 @@ public static boolean isThresholdForNewResultExceeded(CxScanConfig config, SASTR private static boolean isSeverityExceeded(int result, Integer threshold, StringBuilder res, String severity, String severityType) { boolean fail = false; if (threshold != null && result > threshold) { - res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append("\n"); + res.append(severityType).append(severity).append(" severity results are above threshold. Results: ").append(result).append(". Threshold: ").append(threshold).append(". \n"); fail = true; } return fail; @@ -122,10 +141,10 @@ public static Map> convertPatternsToLists(String filterPatt for (String filter : filters) { if (StringUtils.isNotEmpty(filter)) { if (!filter.startsWith("!")) { - inclusions.add(filter); + inclusions.add(filter.trim()); } else if (filter.length() > 1) { filter = filter.substring(1); // Trim the "!" - exclusions.add(filter); + exclusions.add(filter.trim()); } } } diff --git a/src/src/main/java/com/cx/restclient/common/UrlUtils.java b/src/main/java/com/cx/restclient/common/UrlUtils.java similarity index 100% rename from src/src/main/java/com/cx/restclient/common/UrlUtils.java rename to src/main/java/com/cx/restclient/common/UrlUtils.java diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java new file mode 100644 index 00000000..5d87a7ed --- /dev/null +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -0,0 +1,83 @@ +package com.cx.restclient.common; + +import com.cx.restclient.dto.BaseStatus; +import com.cx.restclient.dto.Status; +import com.cx.restclient.exception.CxClientException; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Date; + +/** + * Created by Galn on 13/02/2018. + */ +public abstract class Waiter { + + private int retry = 3; + private String scanType; + private int sleepIntervalSec; + + public Waiter(String scanType, int interval) { + this.scanType = scanType; + this.sleepIntervalSec = interval; + } + + private long startTimeSec; + + protected Status status = null; + + public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { + startTimeSec = System.currentTimeMillis() / 1000; + long elapsedTimeSec = 0L; + T obj; + + try { + obj = getStatus(taskId); + status = ((BaseStatus) obj).getBaseStatus(); + + + while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { + Thread.sleep(sleepIntervalSec * 1000); + try { + obj = getStatus(taskId); + status = ((BaseStatus) obj).getBaseStatus(); + log.debug(status.value()); + } catch (Exception e) { + log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); + retry--; + if (retry <= 0) { + throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + } + continue; + } + elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; + printProgress(obj); + + } + if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { + throw new CxClientException("Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); + } + } catch (Exception e) { + throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); + } + return resolveStatus(obj); + } + + public abstract T getStatus(String id) throws CxClientException, IOException; + + public abstract void printProgress(T status); + + public abstract T resolveStatus(T status) throws CxClientException; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public long getStartTimeSec() { + return startTimeSec; + } +} diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java new file mode 100644 index 00000000..af4a55b0 --- /dev/null +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -0,0 +1,161 @@ +package com.cx.restclient.common.summary; + +import com.cx.restclient.common.ShragaUtils; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.osa.dto.OSASummaryResults; +import com.cx.restclient.sast.dto.SASTResults; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.Version; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public abstract class SummaryUtils { + + public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { + + Configuration cfg = new Configuration(new Version("2.3.23")); + cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); + Template template = cfg.getTemplate("report.ftl"); + + Map templateData = new HashMap(); + templateData.put("config", config); + templateData.put("sast", sastResults); + templateData.put("osa", osaResults); + + //calculated params: + + boolean buildFailed = false; + boolean policyViolated = false; + int policyViolatedCount = 0; + //sast: + if (config.getSastEnabled()) { + if (sastResults.isSastResultsReady()) { + boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); + boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); + templateData.put("sastThresholdExceeded", sastThresholdExceeded); + templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); + buildFailed = sastThresholdExceeded || sastNewResultsExceeded; + //calculate sast bars: + float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); + float sastBarNorm = maxCount * 10f / 9f; + + //sast high bars + float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; + float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); + float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; + templateData.put("sastHighTotalHeight", sastHighTotalHeight); + templateData.put("sastHighNewHeight", sastHighNewHeight); + templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); + + //sast medium bars + float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; + float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); + float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; + templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); + templateData.put("sastMediumNewHeight", sastMediumNewHeight); + templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); + + //sast low bars + float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; + float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); + float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; + templateData.put("sastLowTotalHeight", sastLowTotalHeight); + templateData.put("sastLowNewHeight", sastLowNewHeight); + templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); + } else { + buildFailed = true; + } + } + + //osa: + if (config.getOsaEnabled()) { + if (osaResults.isOsaResultsReady()) { + boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); + templateData.put("osaThresholdExceeded", osaThresholdExceeded); + buildFailed |= osaThresholdExceeded; + + //calculate osa bars: + OSASummaryResults osaSummaryResults = osaResults.getResults(); + int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); + int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); + int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); + float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); + float osaBarNorm = osaMaxCount * 10f / 9f; + + float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; + float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; + float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; + + templateData.put("osaHighTotalHeight", osaHighTotalHeight); + templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); + templateData.put("osaLowTotalHeight", osaLowTotalHeight); + } else { + buildFailed = true; + } + } + + + if (config.getEnablePolicyViolations()) { + Map policies = new HashMap(); + + if (config.getSastEnabled() && sastResults.getSastPolicies().size() > 0) { + policyViolated = true; + policies = sastResults.getSastPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName, + Policy::getRuleName, + (left, right) -> { + return left; + } + )); + } + + if (config.getOsaEnabled() && osaResults.getOsaPolicies().size() > 0) { + policyViolated = true; + policies.putAll(osaResults.getOsaPolicies().stream().collect( + Collectors.toMap(Policy::getPolicyName, Policy::getRuleName, + (left, right) -> { + return left; + }))); + } + + policyViolatedCount = policies.size(); + String policyLabel = policyViolatedCount == 1 ? "Policy" : "Policies"; + templateData.put("policyLabel", policyLabel); + templateData.put("policyViolatedCount", policyViolatedCount); + } + + + templateData.put("policyViolated", policyViolated); + buildFailed |= policyViolated; + templateData.put("buildFailed", buildFailed); + + //generate the report: + StringWriter writer = new StringWriter(); + template.process(templateData, writer); + return writer.toString(); + } + + private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { + int minimalVisibilityHeight = 5; + //new high + float highNewHeightPx = (float) newCount / (float) count * totalHeight; + //if new height is between 1 and 9 - give it a minimum height and if theres enough spce in total height + if (isNewNeedChange(totalHeight, highNewHeightPx, minimalVisibilityHeight)) { + highNewHeightPx = minimalVisibilityHeight; + } + + return highNewHeightPx; + } + + private static boolean isNewNeedChange(float highTotalHeightPx, float highNewHeightPx, int minimalVisibilityHeight) { + return highNewHeightPx > 0 && highNewHeightPx < minimalVisibilityHeight && highTotalHeightPx > minimalVisibilityHeight * 2; + } +} \ No newline at end of file diff --git a/src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java similarity index 79% rename from src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java rename to src/main/java/com/cx/restclient/configuration/CxScanConfig.java index c7c79637..07a42048 100644 --- a/src/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -1,6 +1,7 @@ package com.cx.restclient.configuration; - +import com.cx.restclient.dto.CxVersion; +import com.cx.restclient.dto.RemoteSourceTypes; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -16,7 +17,11 @@ public class CxScanConfig implements Serializable { private Boolean osaEnabled = false; private String cxOrigin; + private CxVersion cxVersion; + private boolean disableCertificateValidation = false; + private boolean useSSOLogin = false; + private String sourceDir; private File reportsDir; private String username; @@ -26,6 +31,7 @@ public class CxScanConfig implements Serializable { private String teamPath; private String teamId; private Boolean denyProject = false; + private Boolean hideResults = false; private Boolean isPublic = true; private Boolean forceScan = false; private String presetName; @@ -33,6 +39,7 @@ public class CxScanConfig implements Serializable { private String sastFolderExclusions; private String sastFilterPattern; private Integer sastScanTimeoutInMinutes; + private Integer osaScanTimeoutInMinutes; private String scanComment; private Boolean isIncremental = false; private Boolean isSynchronous = false; @@ -63,6 +70,16 @@ public class CxScanConfig implements Serializable { private Boolean generateXmlReport = true; private String cxARMUrl; + private String[] paths; + //remote source control + private RemoteSourceTypes remoteType = null; + private String remoteSrcUser; + private String remoteSrcPass; + private String remoteSrcUrl; + private int remoteSrcPort; + private byte[] remoteSrcKeyFile; + private String remoteSrcBranch; + private String preforceMode; public CxScanConfig() { } @@ -107,6 +124,18 @@ public void setDisableCertificateValidation(boolean disableCertificateValidation this.disableCertificateValidation = disableCertificateValidation; } + public boolean isUseSSOLogin() { + return useSSOLogin; + } + + public void setUseSSOLogin(boolean useSSOLogin) { + this.useSSOLogin = useSSOLogin; + } + + public Boolean getAvoidDuplicateProjectScans() { + return avoidDuplicateProjectScans; + } + public String getSourceDir() { return sourceDir; } @@ -160,7 +189,7 @@ public String getTeamPath() { } public void setTeamPath(String teamPath) { - if(!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\")&& !teamPath.startsWith(("/"))){ + if (!StringUtils.isEmpty(teamPath) && !teamPath.startsWith("\\") && !teamPath.startsWith(("/"))) { teamPath = "\\" + teamPath; } this.teamPath = teamPath; @@ -238,6 +267,14 @@ public void setSastScanTimeoutInMinutes(Integer sastScanTimeoutInMinutes) { this.sastScanTimeoutInMinutes = sastScanTimeoutInMinutes; } + public Integer getOsaScanTimeoutInMinutes() { + return osaScanTimeoutInMinutes == null ? -1 : osaScanTimeoutInMinutes; + } + + public void setOsaScanTimeoutInMinutes(Integer sastOsaScanTimeoutInMinutes) { + this.osaScanTimeoutInMinutes = sastOsaScanTimeoutInMinutes; + } + public String getScanComment() { return scanComment; } @@ -450,6 +487,14 @@ public void setCxARMUrl(String cxARMUrl) { this.cxARMUrl = cxARMUrl; } + public Boolean getHideResults() { + return hideResults; + } + + public void setHideResults(Boolean hideResults) { + this.hideResults = hideResults; + } + public Boolean isAvoidDuplicateProjectScans() { return avoidDuplicateProjectScans; @@ -459,6 +504,78 @@ public void setAvoidDuplicateProjectScans(Boolean avoidDuplicateProjectScans) { this.avoidDuplicateProjectScans = avoidDuplicateProjectScans; } + public String getRemoteSrcUser() { + return remoteSrcUser; + } + + public void setRemoteSrcUser(String remoteSrcUser) { + this.remoteSrcUser = remoteSrcUser; + } + + public String getRemoteSrcPass() { + return remoteSrcPass; + } + + public void setRemoteSrcPass(String remoteSrcPass) { + this.remoteSrcPass = remoteSrcPass; + } + + public String getRemoteSrcUrl() { + return remoteSrcUrl; + } + + public void setRemoteSrcUrl(String remoteSrcUrl) { + this.remoteSrcUrl = remoteSrcUrl; + } + + public int getRemoteSrcPort() { + return remoteSrcPort; + } + + public void setRemoteSrcPort(int remoteSrcPort) { + this.remoteSrcPort = remoteSrcPort; + } + + public byte[] getRemoteSrcKeyFile() { + return remoteSrcKeyFile; + } + + public void setRemoteSrcKeyFile(byte[] remoteSrcKeyFile) { + this.remoteSrcKeyFile = remoteSrcKeyFile; + } + + public RemoteSourceTypes getRemoteType() { + return remoteType; + } + + public void setRemoteType(RemoteSourceTypes remoteType) { + this.remoteType = remoteType; + } + + public String[] getPaths() { + return paths; + } + + public void setPaths(String[] paths) { + this.paths = paths; + } + + public String getRemoteSrcBranch() { + return remoteSrcBranch; + } + + public void setRemoteSrcBranch(String remoteSrcBranch) { + this.remoteSrcBranch = remoteSrcBranch; + } + + public String getPreforceMode() { + return preforceMode; + } + + public void setPreforceMode(String preforceMode) { + this.preforceMode = preforceMode; + } + public Boolean getGenerateXmlReport() { return generateXmlReport; } @@ -466,4 +583,12 @@ public Boolean getGenerateXmlReport() { public void setGenerateXmlReport(Boolean generateXmlReport) { this.generateXmlReport = generateXmlReport; } + + public CxVersion getCxVersion() { + return cxVersion; + } + + public void setCxVersion(CxVersion cxVersion) { + this.cxVersion = cxVersion; + } } diff --git a/src/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java b/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java similarity index 100% rename from src/src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java rename to src/main/java/com/cx/restclient/cxArm/dto/CxArmConfig.java diff --git a/src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java b/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java similarity index 74% rename from src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java rename to src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java index 2315dfab..237fdb7c 100644 --- a/src/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/CxProviders.java @@ -1,12 +1,9 @@ package com.cx.restclient.cxArm.dto; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - /** * Created by Galn on 7/8/2018. */ -@JsonIgnoreProperties(ignoreUnknown = true) public enum CxProviders { OPEN_SOURCE("open_source"), SAST("sast"); diff --git a/src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java b/src/main/java/com/cx/restclient/cxArm/dto/Policy.java similarity index 54% rename from src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java rename to src/main/java/com/cx/restclient/cxArm/dto/Policy.java index 3b73a1f0..2696232d 100644 --- a/src/src/main/java/com/cx/restclient/cxArm/dto/Policy.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Policy.java @@ -13,8 +13,22 @@ public class Policy implements Serializable { long policyId; String policyName; + String ruleName; + String firstDetectionDate; List violations = new ArrayList(); + public Policy() { + } + + + public Policy(long policyId, String policyName, String ruleName, List violations, String firstDate) { + this.policyId = policyId; + this.policyName = policyName; + this.ruleName = ruleName; + this.violations = violations; + this.firstDetectionDate = firstDate; + } + public long getPolicyId() { return policyId; } @@ -36,11 +50,22 @@ public List getViolations() { } public void setViolations(List violations) { - //Add the policy name to the violations for the report - for(Violation violation: violations){ - violation.setPolicyName(policyName); - } - this.violations = violations; } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getFirstDetectionDate() { + return firstDetectionDate; + } + + public void setFirstDetectionDate(String firstDetectionDate) { + this.firstDetectionDate = firstDetectionDate; + } } diff --git a/src/main/java/com/cx/restclient/cxArm/dto/Rule.java b/src/main/java/com/cx/restclient/cxArm/dto/Rule.java new file mode 100644 index 00000000..d0028a8c --- /dev/null +++ b/src/main/java/com/cx/restclient/cxArm/dto/Rule.java @@ -0,0 +1,19 @@ +package com.cx.restclient.cxArm.dto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Galn on 11/11/2018. + */ +public class Rule { + List violations = new ArrayList(); + + public List getViolations() { + return violations; + } + + public void setViolations(List violations) { + this.violations = violations; + } +} diff --git a/src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java similarity index 89% rename from src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java rename to src/main/java/com/cx/restclient/cxArm/dto/Violation.java index 8445ab3c..4e18a82a 100644 --- a/src/src/main/java/com/cx/restclient/cxArm/dto/Violation.java +++ b/src/main/java/com/cx/restclient/cxArm/dto/Violation.java @@ -48,9 +48,6 @@ public class Violation implements Serializable { private String policyName; - private String detectionDate; - - public String getRuleId() { return ruleId; } @@ -194,16 +191,4 @@ public String getPolicyName() { public void setPolicyName(String policyName) { this.policyName = policyName; } - - public String getDetectionDate() { - if (detectionDate == null){ - String date = new Date(firstDetectionDateByArm).toString(); - detectionDate = formatDate(date, "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); - } - return detectionDate; - } - - public void setDetectionDate(String detectionDate) { - this.detectionDate = detectionDate; - } } diff --git a/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java new file mode 100644 index 00000000..fa416ba4 --- /dev/null +++ b/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java @@ -0,0 +1,75 @@ +package com.cx.restclient.cxArm.utils; + +import com.cx.restclient.cxArm.dto.Policy; +import com.cx.restclient.cxArm.dto.Violation; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.httpClient.CxHttpClient; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import static com.cx.restclient.common.CxPARAM.CX_ARM_VIOLATION; +import static com.cx.restclient.common.ShragaUtils.formatDate; +import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; + +/** + * Created by Galn on 7/30/2018. + */ +public abstract class CxARMUtils { + public static List getProjectViolatedPolicies(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { + String relativePath = CX_ARM_VIOLATION.replace("{projectId}", Long.toString(projectId)).replace("{provider}", provider); + return (List) httpClient.getRequest(cxARMUrl, relativePath, CONTENT_TYPE_APPLICATION_JSON_V1, null, Policy.class, 200, "CxARM violations", true); + } + + public static List getPolicyList(Policy policy) { + List policies = new ArrayList(); + Map> rules = resolveRules(policy.getViolations()); + Iterator it = rules.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = (Map.Entry) it.next(); + List violations = (List) pair.getValue(); + String firstDate = resolveFirstDate(violations); + policies.add(new Policy(policy.getPolicyId(), policy.getPolicyName(), pair.getKey().toString(), violations, firstDate)); + it.remove(); // avoids a ConcurrentModificationException + } + + return policies; + } + + private static Map> resolveRules(List violations) { + Map> rules = violations.stream().collect( + Collectors.toMap(Violation::getRuleName, e -> { + List ary = new ArrayList(); + ary.add(e); + return ary; + }, + (left, right) -> { + left.addAll(right); + return left; + })); + + return rules; + } + + private static String resolveFirstDate(List violations) { + Date firstDetectionDate = new Date(violations.get(0).getFirstDetectionDateByArm()); + for (Violation violation : violations) { + Date date = new Date(violation.getFirstDetectionDateByArm()); + if (date.before(firstDetectionDate)) { + firstDetectionDate = date; + } + } + String firstDate = formatDate(firstDetectionDate.toString(), "E MMM dd hh:mm:ss Z yyyy", "dd/MM/yy"); + return firstDate; + } + + public static String getPoliciesNames(List policies) { + String str =""; + for (Policy policy : policies){ + str += ", " + policy.getPolicyName(); + } + str = str.substring(1, str.length()); + return str; + } +} diff --git a/src/src/main/java/com/cx/restclient/dto/BaseStatus.java b/src/main/java/com/cx/restclient/dto/BaseStatus.java similarity index 99% rename from src/src/main/java/com/cx/restclient/dto/BaseStatus.java rename to src/main/java/com/cx/restclient/dto/BaseStatus.java index 30c41eee..8d853702 100644 --- a/src/src/main/java/com/cx/restclient/dto/BaseStatus.java +++ b/src/main/java/com/cx/restclient/dto/BaseStatus.java @@ -6,7 +6,6 @@ * Created by Galn on 13/02/2018. */ @JsonIgnoreProperties(ignoreUnknown = true) - public class BaseStatus { private String baseId; private Status baseStatus; diff --git a/src/main/java/com/cx/restclient/dto/CxProxy.java b/src/main/java/com/cx/restclient/dto/CxProxy.java new file mode 100644 index 00000000..59084b84 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/CxProxy.java @@ -0,0 +1,60 @@ +package com.cx.restclient.dto; + +/** + * Created by Galn on 4/2/2019. + */ +public class CxProxy { + private Boolean useProxy; + private String proxyHost; + private Integer proxyPort; + private String proxyScheme; + + + public CxProxy(Boolean useProxy, String proxyHost, Integer proxyPort, String scheme) { + this.useProxy = useProxy; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyScheme = scheme; + } + + public CxProxy() { + } + + public Boolean getUseProxy() { + return useProxy; + } + + public void setUseProxy(Boolean useProxy) { + this.useProxy = useProxy; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public Integer getProxyPort() { + return proxyPort; + } + + public void setProxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + } + + public void setProxyPort(String proxyPort) { + try { + this.proxyPort = Integer.parseInt(proxyPort); + }catch (Exception ex){} + } + + public String getProxyScheme() { + return proxyScheme; + } + + public void setProxyScheme(String proxyScheme) { + this.proxyScheme = proxyScheme; + } +} diff --git a/src/main/java/com/cx/restclient/dto/CxVersion.java b/src/main/java/com/cx/restclient/dto/CxVersion.java new file mode 100644 index 00000000..6d1d2d6e --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/CxVersion.java @@ -0,0 +1,28 @@ +package com.cx.restclient.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by Galn on 4/1/2019. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CxVersion { + private String version; + private String hotFix; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHotFix() { + return hotFix; + } + + public void setHotFix(String hotFix) { + this.hotFix = hotFix; + } +} diff --git a/src/src/main/java/com/cx/restclient/dto/LoginRequest.java b/src/main/java/com/cx/restclient/dto/LoginRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/dto/LoginRequest.java rename to src/main/java/com/cx/restclient/dto/LoginRequest.java diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java new file mode 100644 index 00000000..095b5f60 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -0,0 +1,96 @@ +package com.cx.restclient.dto; + +import com.cx.restclient.configuration.CxScanConfig; + +/** + * Created by Galn on 11/25/2018. + */ +public class RemoteSourceRequest { + String url; + int port; + private byte[] privateKey; + private String[] paths; + private String userName; + private String password; + private RemoteSourceTypes type; + private transient String browseMode; + ; + + public RemoteSourceRequest() { + } + + public RemoteSourceRequest(CxScanConfig config) { + this.userName = config.getRemoteSrcUser(); + this.password = config.getRemoteSrcPass(); + this.url = config.getRemoteSrcUrl(); + this.port = config.getRemoteSrcPort(); + this.privateKey = config.getRemoteSrcKeyFile(); + this.paths = config.getPaths(); + this.type = config.getRemoteType(); + } + + public String getUrl() { + return url; + } + + public void setUrl(String absoluteUrl) { + this.url = absoluteUrl; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public byte[] getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + + public String[] getPaths() { + return paths; + } + + public void setPaths(String[] paths) { + this.paths = paths; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public RemoteSourceTypes getType() { + return type; + } + + public void setType(RemoteSourceTypes type) { + this.type = type; + } + + public String getBrowseMode() { + return browseMode; + } + + public void setBrowseMode(String browseMode) { + this.browseMode = browseMode; + } + +} diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java b/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java new file mode 100644 index 00000000..02f07277 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceTypes.java @@ -0,0 +1,25 @@ +package com.cx.restclient.dto; + +/** + * Created by: Galn. + * Date: 25/11/2016. + */ +public enum RemoteSourceTypes { + + SHARED("shared"), + SVN("svn"), + GIT("git"), + TFS("tfs"), + PERFORCE("perforce"); + + private String value; + + RemoteSourceTypes(String value) { + this.value = value; + } + + public String value() { + return value; + } + +} \ No newline at end of file diff --git a/src/src/main/java/com/cx/restclient/dto/ScanResults.java b/src/main/java/com/cx/restclient/dto/ScanResults.java similarity index 81% rename from src/src/main/java/com/cx/restclient/dto/ScanResults.java rename to src/main/java/com/cx/restclient/dto/ScanResults.java index 0484ff76..5d0357c9 100644 --- a/src/src/main/java/com/cx/restclient/dto/ScanResults.java +++ b/src/main/java/com/cx/restclient/dto/ScanResults.java @@ -3,19 +3,18 @@ import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; -@JsonIgnoreProperties(ignoreUnknown = true) -public class ScanResults implements Serializable { - private SASTResults sastResults; - private OSAResults osaResults; +public class ScanResults implements Serializable { + private SASTResults sastResults = new SASTResults(); + private OSAResults osaResults = new OSAResults(); private Exception sastCreateException = null; private Exception sastWaitException = null; private Exception osaCreateException = null; private Exception osaWaitException = null; + private Exception generalException = null; public ScanResults() { } @@ -67,4 +66,12 @@ public Exception getOsaWaitException() { public void setOsaWaitException(Exception osaWaitException) { this.osaWaitException = osaWaitException; } + + public Exception getGeneralException() { + return generalException; + } + + public void setGeneralException(Exception generalException) { + this.generalException = generalException; + } } diff --git a/src/src/main/java/com/cx/restclient/dto/Status.java b/src/main/java/com/cx/restclient/dto/Status.java similarity index 100% rename from src/src/main/java/com/cx/restclient/dto/Status.java rename to src/main/java/com/cx/restclient/dto/Status.java diff --git a/src/src/main/java/com/cx/restclient/dto/Team.java b/src/main/java/com/cx/restclient/dto/Team.java similarity index 99% rename from src/src/main/java/com/cx/restclient/dto/Team.java rename to src/main/java/com/cx/restclient/dto/Team.java index 9d591851..2f389cdd 100644 --- a/src/src/main/java/com/cx/restclient/dto/Team.java +++ b/src/main/java/com/cx/restclient/dto/Team.java @@ -5,6 +5,7 @@ /** * Created by Galn on 14/02/2018. */ + @JsonIgnoreProperties(ignoreUnknown = true) public class Team { public String id; diff --git a/src/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java rename to src/main/java/com/cx/restclient/dto/TokenLoginResponse.java diff --git a/src/src/main/java/com/cx/restclient/exception/CxClientException.java b/src/main/java/com/cx/restclient/exception/CxClientException.java similarity index 100% rename from src/src/main/java/com/cx/restclient/exception/CxClientException.java rename to src/main/java/com/cx/restclient/exception/CxClientException.java diff --git a/src/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java b/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java similarity index 100% rename from src/src/main/java/com/cx/restclient/exception/CxHTTPClientException.java rename to src/main/java/com/cx/restclient/exception/CxHTTPClientException.java diff --git a/src/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java b/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java similarity index 100% rename from src/src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java rename to src/main/java/com/cx/restclient/exception/CxTokenExpiredException.java diff --git a/src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java similarity index 78% rename from src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java rename to src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 9c012d5c..542f5d17 100644 --- a/src/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -26,6 +26,7 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustAllStrategy; import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.NoConnectionReuseStrategy; import org.apache.http.impl.auth.BasicSchemeFactory; import org.apache.http.impl.auth.DigestSchemeFactory; @@ -36,11 +37,11 @@ import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -52,12 +53,12 @@ import java.util.ArrayList; import java.util.List; -import static com.cx.restclient.common.CxPARAM.AUTHENTICATION; -import static com.cx.restclient.common.CxPARAM.ORIGIN_HEADER; +import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; import static org.apache.commons.lang3.StringUtils.isEmpty; + /** * Created by Galn on 05/02/2018. */ @@ -75,25 +76,71 @@ public class CxHttpClient { private static HttpClient apacheClient; - private Logger logi; + private Logger log; private TokenLoginResponse token; private String rootUri; private final String username; private final String password; private String cxOrigin; + private Boolean useSSo = false; + + +// private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { +// public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { +// httpRequest.addHeader(ORIGIN_HEADER, cxOrigin); +// if (token != null) { +// httpRequest.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); +// } +// if (csrfToken != null) { +// httpRequest.addHeader(CSRF_TOKEN_HEADER, csrfToken); +// } +// if (cookies != null) { +// httpRequest.addHeader("cookie", cookies); +// } +// } +// }; + +// +// private final HttpResponseInterceptor responseFilter = new HttpResponseInterceptor() { +// +// public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { +// for (org.apache.http.cookie.Cookie c : cookieStore.getCookies()) { +// if (CSRF_TOKEN_HEADER.equals(c.getName())) { +// csrfToken = c.getValue(); +// } +// } +// Header[] setCookies = httpResponse.getHeaders("Set-Cookie"); +// StringBuilder sb = new StringBuilder(); +// for (Header h : setCookies) { +// sb.append(h.getValue()).append(";"); +// } +// cookies = (cookies == null ? "" : cookies) + sb.toString(); +// } +// }; + public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, Logger logi, String proxyHost, int proxyPort, - String proxyUser, String proxyPassword) throws MalformedURLException { - this.logi = logi; + boolean disableSSLValidation, boolean isSSO, Logger logi, + String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + this.log = logi; this.username = username; this.password = password; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; + this.useSSo = isSSO; //create httpclient HttpClientBuilder cb = HttpClients.custom(); cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - setSSLTls(cb, "TLSv1.2", logi); + setSSLTls("TLSv1.2", logi); + + /* TODO: verify that responseFilter is compatible (or needed) with refactor */ + +// if (isSSO) { +// this.useSSo = true; +// cookieStore = new BasicCookieStore(); +// cb.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); +// } + if (disableSSLValidation) { try { cb.setSSLSocketFactory(getSSLSF()); @@ -109,16 +156,24 @@ public CxHttpClient(String hostname, String username, String password, String or apacheClient = cb.build(); } - public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, Logger logi) throws MalformedURLException { - this.logi = logi; + public CxHttpClient(String hostname, String username, String password, String origin, + boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + this.log = logi; this.username = username; this.password = password; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; + this.useSSo = isSSO; + //create httpclient HttpClientBuilder cb = HttpClients.custom(); +// if (isSSO) { +// this.useSSo = true; +// cookieStore = new BasicCookieStore(); +// cb.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); +// } cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - setSSLTls(cb, "TLSv1.2", logi); + setSSLTls("TLSv1.2", logi); if (disableSSLValidation) { try { cb.setSSLSocketFactory(getSSLSF()); @@ -187,6 +242,7 @@ private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); } + private static BasicHttpClientConnectionManager getHttpConnManager() throws CxClientException { Registry socketFactoryRegistry = RegistryBuilder.create() .register("https", getSSLSF()) @@ -203,9 +259,14 @@ private static Registry getAuthSchemeProviderRegistry() { } public void login() throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + if (useSSo) { + HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); + request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } else { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } } private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { @@ -266,13 +327,12 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity } response = apacheClient.execute(httpMethod); - statusCode = response.getStatusLine().getStatusCode(); - logi.trace("Response from: '" + httpMethod.getURI() + "' is: " + statusCode); - if (statusCode == HttpStatus.SC_UNAUTHORIZED) { //Token expired + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { //Token expired throw new CxTokenExpiredException(extractResponseBody(response)); } + validateResponse(response, expectStatus, "Failed to " + failedMsg); //extract response as object and return the link @@ -281,7 +341,7 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity throw new CxHTTPClientException(ErrorMessage.CHECKMARX_SERVER_CONNECTION_FAILED.getErrorMessage()); } catch (CxTokenExpiredException ex) { if (retry) { - logi.warn("Access token expired for request: " + httpMethod.getURI() + ", Status code:" + statusCode + "requesting a new token. message: " + ex.getMessage()); + log.warn("Access token expired for request: " + httpMethod.getURI() + ", Status code:" + statusCode + "requesting a new token. message: " + ex.getMessage()); login(); return request(httpMethod, contentType, entity, responseType, expectStatus, failedMsg, isCollection, false); } @@ -296,11 +356,11 @@ public void close() { HttpClientUtils.closeQuietly(apacheClient); } - private void setSSLTls(HttpClientBuilder builder, String protocol, Logger log) { - SSLContext sslContext = null; + private void setSSLTls(String protocol, Logger log) { try { - sslContext = SSLContextBuilder.create().useProtocol(protocol).build(); - builder.setSSLContext(sslContext); + final SSLContext sslContext = SSLContext.getInstance(protocol); + sslContext.init(null, null, null); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); } catch (NoSuchAlgorithmException | KeyManagementException e) { log.warn("Failed to set SSL TLS : " + e.getMessage()); } diff --git a/src/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java b/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java similarity index 100% rename from src/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java rename to src/main/java/com/cx/restclient/httpClient/utils/ContentType.java diff --git a/src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java similarity index 90% rename from src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java rename to src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 73593616..d4b3c79f 100644 --- a/src/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -1,8 +1,6 @@ package com.cx.restclient.httpClient.utils; -import com.cx.restclient.common.ErrorMessage; -import com.cx.restclient.common.ErrorUtil; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.fasterxml.jackson.databind.JavaType; @@ -19,6 +17,7 @@ * Created by Galn on 06/02/2018. */ public abstract class HttpClientHelper { + public static T convertToObject(HttpResponse response, Class responseType, boolean isCollection) throws IOException, CxClientException { //No content if (responseType == null || response.getEntity() == null || response.getEntity().getContentLength() == 0) { @@ -71,9 +70,6 @@ private static T convertToCollectionObject(HttpResponse response, JavaType j public static void validateResponse(HttpResponse response, int status, String message) throws CxClientException { if (response.getStatusLine().getStatusCode() != status) { - if (ErrorUtil.isServerErrorCodes(response.getStatusLine().getStatusCode())) { - throw new CxClientException(ErrorMessage.SERVICE_UNAVAILABLE.getErrorMessage() + ", Error code: " + response.getStatusLine().getStatusCode()); - } String responseBody = extractResponseBody(response); responseBody = responseBody.replace("{", "").replace("}", "").replace(System.getProperty("line.separator"), " ").replace(" ", ""); throw new CxHTTPClientException(response.getStatusLine().getStatusCode(), message + ": " + responseBody); diff --git a/src/src/main/java/com/cx/restclient/osa/dto/CVE.java b/src/main/java/com/cx/restclient/osa/dto/CVE.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/CVE.java rename to src/main/java/com/cx/restclient/osa/dto/CVE.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java b/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java rename to src/main/java/com/cx/restclient/osa/dto/CVEReportTableRow.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/Content.java b/src/main/java/com/cx/restclient/osa/dto/Content.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/Content.java rename to src/main/java/com/cx/restclient/osa/dto/Content.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java b/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java rename to src/main/java/com/cx/restclient/osa/dto/CreateOSAScanRequest.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java b/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java rename to src/main/java/com/cx/restclient/osa/dto/CreateOSAScanResponse.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/Library.java b/src/main/java/com/cx/restclient/osa/dto/Library.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/Library.java rename to src/main/java/com/cx/restclient/osa/dto/Library.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java b/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/LoginRequest.java rename to src/main/java/com/cx/restclient/osa/dto/LoginRequest.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java similarity index 89% rename from src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java rename to src/main/java/com/cx/restclient/osa/dto/OSAResults.java index b9faaa6e..18934abc 100644 --- a/src/src/main/java/com/cx/restclient/osa/dto/OSAResults.java +++ b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java @@ -1,7 +1,6 @@ package com.cx.restclient.osa.dto; -import com.cx.restclient.cxArm.dto.Violation; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.cx.restclient.cxArm.dto.Policy; import java.io.Serializable; import java.util.ArrayList; @@ -10,11 +9,11 @@ import java.util.Map; import static com.cx.restclient.common.ShragaUtils.formatDate; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; /** * Created by Galn on 07/02/2018. */ -@JsonIgnoreProperties(ignoreUnknown = true) public class OSAResults implements Serializable { private String osaScanId; private OSASummaryResults results; @@ -30,9 +29,7 @@ public class OSAResults implements Serializable { private String scanStartTime; private String scanEndTime; - private List osaPolicies = new ArrayList<>(); - private List osaViolations = new ArrayList<>(); - + private List osaPolicies = new ArrayList<>(); public OSAResults() { } @@ -184,23 +181,15 @@ public void setScanEndTime(String scanEndTime) { this.scanEndTime = scanEndTime; } - public List getOsaViolations() { - return osaViolations; - } - - public void setOsaViolations(List osaViolations) { - this.osaViolations = osaViolations; - } - - public void addAllViolations(List violations) { - this.osaViolations.addAll(violations); + public void addPolicy(Policy policy) { + this.osaPolicies.addAll(getPolicyList(policy)); } - public List getOsaPolicies() { + public List getOsaPolicies() { return osaPolicies; } - public void setOsaPolicies(List osaPolicies) { + public void setOsaPolicies(List osaPolicies) { this.osaPolicies = osaPolicies; } } diff --git a/src/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java b/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/OSAScanState.java rename to src/main/java/com/cx/restclient/osa/dto/OSAScanState.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java b/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java rename to src/main/java/com/cx/restclient/osa/dto/OSAScanStatus.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java b/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java rename to src/main/java/com/cx/restclient/osa/dto/OSAScanStatusEnum.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java b/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java rename to src/main/java/com/cx/restclient/osa/dto/OSASummaryResults.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java b/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java rename to src/main/java/com/cx/restclient/osa/dto/ScanConfiguration.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/Severity.java b/src/main/java/com/cx/restclient/osa/dto/Severity.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/Severity.java rename to src/main/java/com/cx/restclient/osa/dto/Severity.java diff --git a/src/src/main/java/com/cx/restclient/osa/dto/State.java b/src/main/java/com/cx/restclient/osa/dto/State.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/dto/State.java rename to src/main/java/com/cx/restclient/osa/dto/State.java diff --git a/src/src/main/java/com/cx/restclient/osa/utils/OSAParam.java b/src/main/java/com/cx/restclient/osa/utils/OSAParam.java similarity index 100% rename from src/src/main/java/com/cx/restclient/osa/utils/OSAParam.java rename to src/main/java/com/cx/restclient/osa/utils/OSAParam.java diff --git a/src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java similarity index 93% rename from src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java rename to src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index e1962ab7..3e85ed6d 100644 --- a/src/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -1,6 +1,5 @@ package com.cx.restclient.osa.utils; -import com.cx.restclient.common.CxGlobalMessage; import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; @@ -130,14 +129,6 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info("Vulnerable and updated: " + osaSummaryResults.getVulnerableAndUpdated()); log.info("Non-vulnerable libraries: " + osaSummaryResults.getNonVulnerableLibraries()); log.info(""); - if (enableViolations) { - if (osaResults.getOsaPolicies().isEmpty()){ - log.info(CxGlobalMessage.PROJECT_POLICY_COMPLAINT_STATUS.getMessage()); - }else{ - log.info(CxGlobalMessage.PROJECT_POLICY_VIOLATED_STATUS.getMessage()); - log.info("OSA violated policies names: " + StringUtils.join(osaResults.getOsaPolicies(), ',')); - } - } log.info("OSA scan results location: " + osaResults.getOsaProjectSummaryLink()); log.info("-----------------------------------------------------------------------------------------"); } diff --git a/src/src/main/java/com/cx/restclient/sast/dto/Comment.java b/src/main/java/com/cx/restclient/sast/dto/Comment.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/Comment.java rename to src/main/java/com/cx/restclient/sast/dto/Comment.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java b/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java rename to src/main/java/com/cx/restclient/sast/dto/CreateProjectRequest.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java b/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java rename to src/main/java/com/cx/restclient/sast/dto/CreateReportRequest.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java b/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java rename to src/main/java/com/cx/restclient/sast/dto/CreateReportResponse.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java b/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java rename to src/main/java/com/cx/restclient/sast/dto/CreateScanRequest.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java b/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java rename to src/main/java/com/cx/restclient/sast/dto/CurrentStatus.java diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java new file mode 100644 index 00000000..691ee0d3 --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/CxARMStatus.java @@ -0,0 +1,48 @@ +package com.cx.restclient.sast.dto; + + +import com.cx.restclient.dto.BaseStatus; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by Galn on 07/03/2018. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CxARMStatus extends BaseStatus { + private CxID project; + private CxID scan; + String status; + String lastSync; + + public CxID getProject() { + return project; + } + + public void setProject(CxID project) { + this.project = project; + } + + public CxID getScan() { + return scan; + } + + public void setScan(CxID scan) { + this.scan = scan; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getLastSync() { + return lastSync; + } + + public void setLastSync(String lastSync) { + this.lastSync = lastSync; + } +} diff --git a/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java b/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java new file mode 100644 index 00000000..caf256aa --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/CxARMStatusEnum.java @@ -0,0 +1,25 @@ +package com.cx.restclient.sast.dto; + +/** + * Created by Galn on 07/03/2018. + */ +public enum CxARMStatusEnum { + + IN_PROGRESS("InProgress"), + FINISHED("Finished"), + FAILED("Failed"), + NONE("None"); + + private final String value; + + CxARMStatusEnum(String value) { + this.value = value; + } + + public String value() { + return this.value; + } + + +} + diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CxID.java b/src/main/java/com/cx/restclient/sast/dto/CxID.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CxID.java rename to src/main/java/com/cx/restclient/sast/dto/CxID.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java b/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CxNameObj.java rename to src/main/java/com/cx/restclient/sast/dto/CxNameObj.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java b/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/CxValueObj.java rename to src/main/java/com/cx/restclient/sast/dto/CxValueObj.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java b/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java similarity index 99% rename from src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java rename to src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java index c9d57795..2a037413 100644 --- a/src/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/CxXMLResults.java @@ -1,5 +1,6 @@ package com.cx.restclient.sast.dto; +import javax.xml.bind.annotation.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; diff --git a/src/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java b/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java rename to src/main/java/com/cx/restclient/sast/dto/EmailNotifications.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java b/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java rename to src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/Preset.java b/src/main/java/com/cx/restclient/sast/dto/Preset.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/Preset.java rename to src/main/java/com/cx/restclient/sast/dto/Preset.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/Project.java b/src/main/java/com/cx/restclient/sast/dto/Project.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/Project.java rename to src/main/java/com/cx/restclient/sast/dto/Project.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java b/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/QueueStatus.java rename to src/main/java/com/cx/restclient/sast/dto/QueueStatus.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java b/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ReportStatus.java rename to src/main/java/com/cx/restclient/sast/dto/ReportStatus.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java b/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java rename to src/main/java/com/cx/restclient/sast/dto/ReportStatusEnum.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ReportType.java b/src/main/java/com/cx/restclient/sast/dto/ReportType.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ReportType.java rename to src/main/java/com/cx/restclient/sast/dto/ReportType.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java b/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java rename to src/main/java/com/cx/restclient/sast/dto/ResponseQueueScanStatus.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java similarity index 92% rename from src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java rename to src/main/java/com/cx/restclient/sast/dto/SASTResults.java index 8ede02e5..69d83fcf 100644 --- a/src/src/main/java/com/cx/restclient/sast/dto/SASTResults.java +++ b/src/main/java/com/cx/restclient/sast/dto/SASTResults.java @@ -1,7 +1,6 @@ package com.cx.restclient.sast.dto; -import com.cx.restclient.cxArm.dto.Violation; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.cx.restclient.cxArm.dto.Policy; import java.io.Serializable; import java.text.DateFormat; @@ -9,6 +8,7 @@ import java.text.SimpleDateFormat; import java.util.*; +import static com.cx.restclient.cxArm.utils.CxARMUtils.getPolicyList; import static com.cx.restclient.sast.utils.SASTParam.PROJECT_LINK_FORMAT; import static com.cx.restclient.sast.utils.SASTParam.SCAN_LINK_FORMAT; @@ -16,7 +16,6 @@ /** * Created by Galn on 05/02/2018. */ -@JsonIgnoreProperties(ignoreUnknown = true) public class SASTResults implements Serializable { private long scanId; @@ -49,8 +48,7 @@ public class SASTResults implements Serializable { private byte[] PDFReport; private String pdfFileName; - private List sastPolicies = new ArrayList<>(); - private List sastViolations = new ArrayList<>(); + private List sastPolicies = new ArrayList<>(); public enum Severity { @@ -102,6 +100,10 @@ public void setResults(long scanId, SASTStatisticsResponse statisticsResults, St setSastProjectLink(url, projectId); } + public void addPolicy(Policy policy) { + this.sastPolicies.addAll(getPolicyList(policy)); + } + public long getScanId() { return scanId; } @@ -358,27 +360,16 @@ private Date createTimeDate(String scanTime) throws ParseException { } private Date createEndDate(Date scanStartDate, Date scanTimeDate) { - long time /*no c*/ = scanStartDate.getTime() + scanTimeDate.getTime(); + long time = scanStartDate.getTime() + scanTimeDate.getTime(); return new Date(time); } - public List getSastViolations() { - return sastViolations; - } - - public void setSastViolations(List sastViolations) { - this.sastViolations = sastViolations; - } - - public void addAllViolations(List violations) { - this.sastViolations.addAll(violations); - } - - public List getSastPolicies() { + public List getSastPolicies() { return sastPolicies; } - public void setSastPolicies(List sastPolicies) { + public void setSastPolicies(List sastPolicies) { this.sastPolicies = sastPolicies; } + } diff --git a/src/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java b/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java rename to src/main/java/com/cx/restclient/sast/dto/SASTStatisticsResponse.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java b/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java rename to src/main/java/com/cx/restclient/sast/dto/ScanSettingRequest.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java b/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java rename to src/main/java/com/cx/restclient/sast/dto/ScanSettingResponse.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java b/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java rename to src/main/java/com/cx/restclient/sast/dto/SupportedLanguage.java diff --git a/src/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java b/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java rename to src/main/java/com/cx/restclient/sast/dto/UpdateScanStatusRequest.java diff --git a/src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java similarity index 88% rename from src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java rename to src/main/java/com/cx/restclient/sast/utils/SASTParam.java index eb4103b7..da289114 100644 --- a/src/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -18,11 +18,17 @@ public class SASTParam { public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; + public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/{projectId}/sourceCode/remoteSettings/{sourceType}";//todo maybe with ssh? + + + //Once it has results public static final String SAST_SCAN_RESULTS_STATISTICS = "sast/scans/{scanId}/resultsStatistics"; public static final String SAST_CREATE_REPORT = "reports/sastScan/"; //Create new report (get ID) public static final String SAST_GET_REPORT_STATUS = "reports/sastScan/{reportId}/status"; //Get report status public static final String SAST_GET_REPORT = "reports/sastScan/{reportId}"; //Get report status + public static final String SAST_GET_CXARM_STATUS = "sast/projects/{projectId}/publisher/policyFindings/status"; //Get report status + //ZIP PARAMS public static final long MAX_ZIP_SIZE_BYTES = 2147483648L; diff --git a/src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java similarity index 90% rename from src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java rename to src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 403541d8..7eb55c5c 100644 --- a/src/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -62,22 +62,18 @@ public static void printSASTResultsToConsole(SASTResults sastResults,boolean ena log.info("Low severity results: " + sastResults.getLow() + lowNew); log.info("Information severity results: " + sastResults.getInformation() + infoNew); log.info(""); - /* if (enableViolations && !sastResults.getSastPolicies().isEmpty()) { - log.info("SAST violated policies names: " + StringUtils.join(sastResults.getSastPolicies(), ',')); - }*/ log.info("Scan results location: " + sastResults.getSastScanLink()); log.info("------------------------------------------------------------------------------------------\n"); } //PDF Report - public static String writePDFReport(byte[] scanReport, File workspace, Logger log) { - String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); - String pdfFileName = PDF_REPORT_NAME + "_" + now + ".pdf"; + public static String writePDFReport(byte[] scanReport, File workspace, String pdfFileName, Logger log) { try { FileUtils.writeByteArrayToFile(new File(workspace + CX_REPORT_LOCATION, pdfFileName), scanReport); log.info("PDF report location: " + workspace + CX_REPORT_LOCATION + File.separator + pdfFileName); } catch (Exception e) { log.error("Failed to write PDF report to workspace: ", e.getMessage()); + pdfFileName =""; } return pdfFileName; } diff --git a/src/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java b/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java rename to src/main/java/com/cx/restclient/sast/utils/zip/CxZip.java diff --git a/src/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java b/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java rename to src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java diff --git a/src/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java b/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java rename to src/main/java/com/cx/restclient/sast/utils/zip/ZipListener.java diff --git a/src/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java b/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java similarity index 100% rename from src/src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java rename to src/main/java/com/cx/restclient/sast/utils/zip/Zipper.java diff --git a/src/main/java/testi.java b/src/main/java/testi.java new file mode 100644 index 00000000..94b85aba --- /dev/null +++ b/src/main/java/testi.java @@ -0,0 +1,148 @@ +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.common.ShragaUtils; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.sast.dto.SASTResults; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + + +/** + * Created by Galn on 04/03/2018. + */ +public class testi { + private static String DEFAULT_FILTER_PATTERNS = "!**/_cvs/**/*, !**/.svn/**/*, !**/.hg/**/*, !**/.git/**/*, !**/.bzr/**/*, !**/bin/**/*," + + "!**/obj/**/*, !**/backup/**/*, !**/.idea/**/*, !**/*.DS_Store, !**/*.ipr, !**/*.iws, " + + "!**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, " + + "!**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, " + + "!**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, " + + "!**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.swf, " + + "!**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, " + + "!**/*.tar.gz, !**/*.tar, !**/*.gz, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, " + + "!**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.war, !**/*.ear, " + + "!**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, " + + "!**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, !**/node_modules/**/*"; + + private static String DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS = "*.zip, *.tgz, *.war, *.ear"; + + + public static void main(String[] args) throws Exception { + + + + SASTResults sastResults = null; + // SASTResults lastSastResults = null; + OSAResults osaResults = null; + // OSAResults lastOsaResults = null; + Logger logi = LoggerFactory.getLogger("testush"); + + + CxScanConfig config = setConfigi(); + + + CxShragaClient shraga = new CxShragaClient(config, logi); + // shraga.getClientVersion(); + shraga.init(); + + try { + if (config.getOsaEnabled()) { + shraga.createOSAScan(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getSastEnabled()) { + shraga.createSASTScan(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getSastEnabled()) { + sastResults = shraga.waitForSASTResults(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + try { + if (config.getOsaEnabled()) { + osaResults = shraga.waitForOSAResults(); + } + } catch (Exception ex) { + logi.error(ex.getMessage()); + } + + //lastSastResults = shraga.getLatestSASTResults(); + // lastOsaResults = shraga.getLatestOSAResults(); + if (config.getEnablePolicyViolations()) { + shraga.printIsProjectViolated(); + } + //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); + String s = shraga.generateHTMLSummary(); + File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); + FileUtils.writeStringToFile(file, s); + + shraga.close(); + } + + + private static CxScanConfig setConfigi() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); + //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); + config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); + //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); + config.setUsername("adi.gido@checkmarx.com"); + //config.setPassword("Cx123456!"); + config.setPassword("Cx@123456"); + config.setAvoidDuplicateProjectScans(false); + + // config.setUrl("http://10.32.1.91"); + config.setUrl("https://sast.checkmarx.net"); + config.setCxOrigin("common"); + config.setProjectName("Bamboo Plugin - Main"); + //config.setProjectName("OSAPROJ"); + config.setPresetName("Default"); + //config.setPresetId(7); + config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); + //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); + config.setSastFolderExclusions(""); + config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); + config.setSastScanTimeoutInMinutes(null); + config.setScanComment(""); + config.setIncremental(false); + config.setSynchronous(true); + config.setSastThresholdsEnabled(false); + config.setSastHighThreshold(1); + config.setSastMediumThreshold(1); + config.setSastLowThreshold(1); + config.setGeneratePDFReport(true); + config.setOsaEnabled(false); + + + config.setOsaFilterPattern("");//TODO check + config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); + config.setOsaRunInstall(true); + config.setOsaThresholdsEnabled(true); + config.setOsaHighThreshold(10); + config.setOsaMediumThreshold(0); + config.setOsaLowThreshold(0); + config.setDenyProject(false); + config.setPublic(true); + //config.setUseSSOLogin(false); + //config.setZipFile(); + //config.setOsaDependenciesJson(); + config.setEnablePolicyViolations(true); + + return config; + } + +} diff --git a/src/src/main/resources/com/cx/report/images/no-policy-violation-found.png b/src/main/resources/com/cx/report/images/no-policy-violation-found.png similarity index 100% rename from src/src/main/resources/com/cx/report/images/no-policy-violation-found.png rename to src/main/resources/com/cx/report/images/no-policy-violation-found.png diff --git a/src/src/main/resources/com/cx/report/images/policy-violation-found.png b/src/main/resources/com/cx/report/images/policy-violation-found.png similarity index 100% rename from src/src/main/resources/com/cx/report/images/policy-violation-found.png rename to src/main/resources/com/cx/report/images/policy-violation-found.png diff --git a/src/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl similarity index 89% rename from src/src/main/resources/com/cx/report/report.ftl rename to src/main/resources/com/cx/report/report.ftl index e385d605..c2ed822f 100644 --- a/src/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -34,7 +34,7 @@ background-color: #372F51; } - .cx-report .legend-color-box.new-legend-color { + .cx-report .legend-color-box.new-legend-color { background: linear-gradient(45deg, white 25%, #373050 25%, #373050 50%, white 50%, white 75%, #373050 75%); background-size: 4px 4px; } @@ -371,10 +371,9 @@ background-color: #373050; } - - .threshold-exceeded, - .threshold-compliance, - .policy-compliance{ + .threshold-exceeded, + .threshold-compliance, + .policy-compliance { min-width: 100%; display: inline-flex; font-size: 14px; @@ -383,21 +382,21 @@ padding: 4px 9px; } - .threshold-exceeded { + .threshold-exceeded { background-color: #DA2945; color: white; border-radius: 2px; font-weight: bold; } - .policy-compliance{ + + .policy-compliance { border-radius: 2px; font-weight: bold; } - .threshold-exceeded-icon, .threshold-compliance-icon, - .policy-compliance{ + .policy-compliance { display: inline-flex; padding-right: 6px; margin: auto 0; @@ -426,11 +425,10 @@ overflow: hidden; } - .cx-report .results-report .libraries-vulnerable-number{ + .cx-report .results-report .libraries-vulnerable-number { font-size: 18px; } - .cx-report .results-report .libraries-vulnerable-text { font-size: 12px; line-height: 15px; @@ -627,6 +625,7 @@ .cx-report .html-report.download-icon { margin-right: 6px; } + .cx-report .pdf-report.download-icon, { margin-right: 6px; border-left: solid 1px #d5d5d @@ -808,6 +807,7 @@ margin-top: -3px; margin-left: -7px; } + .scan-status .content-scan-status li { margin-top: 6px; margin-bottom: 6px; @@ -826,7 +826,6 @@ padding-top: 10px; } - .scan-status .indicator-scan-status.success { background-color: #38d87d; } @@ -839,7 +838,7 @@ margin-top: 5px; text-align: center; border: 1px solid #ffffff; - padding-left: 5px ; + padding-left: 5px; } .scan-status .indicator-scan-status.success .icon-scan-status { @@ -850,13 +849,11 @@ border: 1px solid #DD3D56; } - - - .scan-status .title-scan-status { font-size: 12px; font-weight: bold; padding-left: 16px; + padding-top: 10px; } .scan-status .content-scan-status .title-scan-status.failure { @@ -909,91 +906,102 @@ -
    Checkmarx Report
    - <#if buildFailed> -
    -
    -
    - - - error - Created with Sketch. - - - - - - - - - - - - - - - -
    -
    -
    -

    - Checkmarx scan found the following issues: -

    -
      - <#if policyViolated> -
    • ${osa.osaPolicies?size} ${policyLabel} Violated
    • - + <#if buildFailed> +
      +
      +
      + + + error + Created with Sketch. + + + + + + + + + + + + + + + +
      +
      +
      +

      + Checkmarx scan found the following issues: +

      +
        + <#if config.sastEnabled && !sast.sastResultsReady> +
      • SAST Scan Failed
      • + + <#if config.osaEnabled && !osa.osaResultsReady> +
      • OSA Scan Failed
      • + + <#if policyViolated> +
      • ${policyViolatedCount} ${policyLabel} Violated
      • + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded>
      • Exceeded CxSAST and CxOSA Vulnerability Thresholds
      • - <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> -
      • Exceeded CxSAST Vulnerability Threshold
      • - <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> -
      • Exceeded CxOSA Vulnerability Threshold
      • - -
      -
      -
      - <#else> -
      -
      -
      - - - OK - Created with Sketch. - - - - - - - - - - - - - - - - - - -
      -
      -
      -

      - Checkmarx Scan Passed -

      -
      -
      - - + <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)> +
    • Exceeded CxSAST Vulnerability Threshold
    • + <#elseif config.osaEnabled && osa.osaResultsReady && osaThresholdExceeded> +
    • Exceeded CxOSA Vulnerability Threshold
    • + +
    +
    +
    + <#else> +
    +
    +
    + + + OK + Created with Sketch. + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    + Checkmarx Scan Passed +

    +
    +
    + +
    @@ -1013,7 +1021,7 @@
    - -
    -
    -
    ${osa.osaViolations?size }
    -
    -
    - Policy Violated Libraries -
    -
    @@ -1958,66 +1957,6 @@
    - - <#if sast.sastViolations?size gt 0> -
    -
    -
    - - - Policy violation - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    Policy Violations
    -
    ${sast.sastViolations?size}
    -
    - -
    - @@ -2025,7 +1964,7 @@ <#if config.osaEnabled && osa.osaResultsReady> - <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0 || osa.osaViolations?size gt 0> + <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0>
    @@ -2184,7 +2123,8 @@
    Libraries:
    -
    ${osa.results.totalLibraries}
    +
    ${osa.results.totalLibraries}
    @@ -2358,72 +2298,141 @@ - <#if osa.osaViolations?size gt 0> -
    -
    -
    - - - Policy violation - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
    +
    +
    + + + + <#if (config.osaEnabled || config.sastEnabled) && policyViolated> + + <#if policyViolatedCount gt 0> +
    + +
    +
    +
    +
    + + + Policy violation + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    Violated ${policyLabel}
    +
    ${policyViolatedCount}
    +
    + + + + + + + + + <#if sast.sastPolicies?size gt 0> + <#list sast.sastPolicies as sastPoliciy> + + + + + + + + + + <#if osa.osaPolicies?size gt 0> + <#list osa.osaPolicies as osaPolicy> + + + + + + + + + +
    PolicyRuleType# of Rule ViolationsFirst Detection Date
    ${sastPoliciy.policyName}${sastPoliciy.ruleName}SAST${sastPoliciy.violations?size}${sastPoliciy.firstDetectionDate}
    ${osaPolicy.policyName}${osaPolicy.ruleName}OSA${osaPolicy.violations?size}${osaPolicy.firstDetectionDate}
    +
    -
    +
    + + + + + + diff --git a/src/main/resources/common.properties b/src/main/resources/common.properties new file mode 100644 index 00000000..713c9158 --- /dev/null +++ b/src/main/resources/common.properties @@ -0,0 +1 @@ +version = ${project.version} \ No newline at end of file diff --git a/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java b/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java deleted file mode 100644 index d0602b60..00000000 --- a/src/src/main/java/com/cx/restclient/common/CxGlobalMessage.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cx.restclient.common; - -/** - * Created by shaulv on 8/9/2018. - */ -public enum CxGlobalMessage { - - PROJECT_POLICY_STATUS("Project policy status : %s"), - PROJECT_POLICY_VIOLATED_STATUS("Project policy status : violated"), - PROJECT_POLICY_COMPLAINT_STATUS("Project policy status : compliant"); - - private String message; - - private CxGlobalMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public String formatMessage(Object... args) { - return String.format(message, args); - } -} diff --git a/src/src/main/java/com/cx/restclient/common/ErrorUtil.java b/src/src/main/java/com/cx/restclient/common/ErrorUtil.java deleted file mode 100644 index 208b69e8..00000000 --- a/src/src/main/java/com/cx/restclient/common/ErrorUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.cx.restclient.common; - -import org.apache.http.HttpStatus; - -import java.util.HashSet; -import java.util.Set; - -/** - * Created by shaulv on 6/20/2018. - */ -public class ErrorUtil { - - private ErrorUtil() { - - } - - private static Set serverErrorCodes = new HashSet<>(); - - static { - serverErrorCodes.add(HttpStatus.SC_INTERNAL_SERVER_ERROR); - serverErrorCodes.add(HttpStatus.SC_NOT_IMPLEMENTED); - serverErrorCodes.add(HttpStatus.SC_BAD_GATEWAY); - serverErrorCodes.add(HttpStatus.SC_SERVICE_UNAVAILABLE); - serverErrorCodes.add(HttpStatus.SC_GATEWAY_TIMEOUT); - serverErrorCodes.add(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED); - serverErrorCodes.add(HttpStatus.SC_INSUFFICIENT_STORAGE); - } - - public static boolean isServerErrorCodes(int errorCodes) { - if(serverErrorCodes.contains(errorCodes)) { - return true; - } - return false; - } -} diff --git a/src/src/main/java/com/cx/restclient/common/Waiter.java b/src/src/main/java/com/cx/restclient/common/Waiter.java deleted file mode 100644 index e852695f..00000000 --- a/src/src/main/java/com/cx/restclient/common/Waiter.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.cx.restclient.common; - -import com.cx.restclient.dto.BaseStatus; -import com.cx.restclient.dto.Status; -import com.cx.restclient.exception.CxClientException; -import org.slf4j.Logger; - -import java.io.IOException; -import java.util.Date; - -/** - * Created by Galn on 13/02/2018. - */ -public abstract class Waiter { - - private int retry = 5; - private String scanType; - private int sleepIntervalSec; - - public Waiter(String scanType, int interval) { - this.scanType = scanType; - this.sleepIntervalSec = interval; - } - - private long startTimeSec; - - protected Status status = null; - - public T waitForTaskToFinish(String taskId, Integer scanTimeoutSec, Logger log) throws CxClientException, InterruptedException { - startTimeSec = System.currentTimeMillis() / 1000; - long elapsedTimeSec = 0L; - status = Status.IN_PROGRESS; - T obj = null; - - while (status.equals(Status.IN_PROGRESS) && (scanTimeoutSec <= 0 || elapsedTimeSec < scanTimeoutSec)) { - Thread.sleep(sleepIntervalSec * 1000); - try { - obj = getStatus(taskId); - status = ((BaseStatus) obj).getBaseStatus(); - } catch (Exception e) { - log.debug("Failed to get status from " + scanType + ". retrying (" + (retry - 1) + " tries left). Error message: " + e.getMessage()); - if (retry <= 0) { - throw new CxClientException("Failed to get status from " + scanType + ". Error message: " + e.getMessage(), e); - } - retry--; - continue; - } - elapsedTimeSec = (new Date()).getTime() / 1000 - startTimeSec; - printProgress(obj); - - } - if (scanTimeoutSec > 0 && scanTimeoutSec <= elapsedTimeSec) { - throw new CxClientException( "Failed to perform " + scanType + ": " + scanType + " has been automatically aborted: reached the user-specified timeout (" + scanTimeoutSec / 60 + " minutes)"); - } - return resolveStatus(obj); - } - - public abstract T getStatus(String id) throws CxClientException, IOException; - - public abstract void printProgress(T status); - - public abstract T resolveStatus(T status) throws CxClientException; - - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; - } - - public long getStartTimeSec() { - return startTimeSec; - } -} diff --git a/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java deleted file mode 100644 index 6ba09564..00000000 --- a/src/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.cx.restclient.common.summary; - -import com.cx.restclient.common.ShragaUtils; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.restclient.osa.dto.OSASummaryResults; -import com.cx.restclient.sast.dto.SASTResults; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import freemarker.template.Version; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.Map; - -public abstract class SummaryUtils { - - public static String generateSummary(SASTResults sastResults, OSAResults osaResults, CxScanConfig config) throws IOException, TemplateException { - - Configuration cfg = new Configuration(new Version("2.3.23")); - cfg.setClassForTemplateLoading(SummaryUtils.class, "/com/cx/report"); - Template template = cfg.getTemplate("report.ftl"); - - Map templateData = new HashMap(); - templateData.put("config", config); - templateData.put("sast", sastResults); - templateData.put("osa", osaResults); - - //calculated params: - - boolean buildFailed = false; - boolean policyViolated = false; - //sast: - if (config.getSastEnabled() && sastResults.isSastResultsReady()) { - boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); - boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); - templateData.put("sastThresholdExceeded", sastThresholdExceeded); - templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); - buildFailed = sastThresholdExceeded || sastNewResultsExceeded; - //calculate sast bars: - float maxCount = Math.max(sastResults.getHigh(), Math.max(sastResults.getMedium(), sastResults.getLow())); - float sastBarNorm = maxCount * 10f / 9f; - - //sast high bars - float sastHighTotalHeight = (float) sastResults.getHigh() / sastBarNorm * 238f; - float sastHighNewHeight = calculateNewBarHeight(sastResults.getNewHigh(), sastResults.getHigh(), sastHighTotalHeight); - float sastHighRecurrentHeight = sastHighTotalHeight - sastHighNewHeight; - templateData.put("sastHighTotalHeight", sastHighTotalHeight); - templateData.put("sastHighNewHeight", sastHighNewHeight); - templateData.put("sastHighRecurrentHeight", sastHighRecurrentHeight); - /*if (config.getEnablePolicyViolations() && !sastResults.getSastViolations().isEmpty()){ - policyViolated = true; - }*/ - - //sast medium bars - float sastMediumTotalHeight = (float) sastResults.getMedium() / sastBarNorm * 238f; - float sastMediumNewHeight = calculateNewBarHeight(sastResults.getNewMedium(), sastResults.getMedium(), sastMediumTotalHeight); - float sastMediumRecurrentHeight = sastMediumTotalHeight - sastMediumNewHeight; - templateData.put("sastMediumTotalHeight", sastMediumTotalHeight); - templateData.put("sastMediumNewHeight", sastMediumNewHeight); - templateData.put("sastMediumRecurrentHeight", sastMediumRecurrentHeight); - - //sast low bars - float sastLowTotalHeight = (float) sastResults.getLow() / sastBarNorm * 238f; - float sastLowNewHeight = calculateNewBarHeight(sastResults.getNewLow(), sastResults.getLow(), sastLowTotalHeight); - float sastLowRecurrentHeight = sastLowTotalHeight - sastLowNewHeight; - templateData.put("sastLowTotalHeight", sastLowTotalHeight); - templateData.put("sastLowNewHeight", sastLowNewHeight); - templateData.put("sastLowRecurrentHeight", sastLowRecurrentHeight); - } - - //osa: - if (config.getOsaEnabled() && osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, osaResults, new StringBuilder()); - templateData.put("osaThresholdExceeded", osaThresholdExceeded); - buildFailed |= osaThresholdExceeded; - if (config.getEnablePolicyViolations() && !osaResults.getOsaViolations().isEmpty()){ - policyViolated = true; - } - //calculate osa bars: - OSASummaryResults osaSummaryResults = osaResults.getResults(); - int osaHigh = osaSummaryResults.getTotalHighVulnerabilities(); - int osaMedium = osaSummaryResults.getTotalMediumVulnerabilities(); - int osaLow = osaSummaryResults.getTotalLowVulnerabilities(); - float osaMaxCount = Math.max(osaHigh, Math.max(osaMedium, osaLow)); - float osaBarNorm = osaMaxCount * 10f / 9f; - - float osaHighTotalHeight = (float) osaHigh / osaBarNorm * 238f; - float osaMediumTotalHeight = (float) osaMedium / osaBarNorm * 238f; - float osaLowTotalHeight = (float) osaLow / osaBarNorm * 238f; - - templateData.put("osaHighTotalHeight", osaHighTotalHeight); - templateData.put("osaMediumTotalHeight", osaMediumTotalHeight); - templateData.put("osaLowTotalHeight", osaLowTotalHeight); - } - - String policyLabel = osaResults.getOsaPolicies().size() == 1? "Policy": "Policies"; - templateData.put("policyLabel", policyLabel); - - templateData.put("policyViolated", policyViolated); - buildFailed |= policyViolated; - templateData.put("buildFailed", buildFailed); - - //generate the report: - StringWriter writer = new StringWriter(); - template.process(templateData, writer); - return writer.toString(); - } - - private static float calculateNewBarHeight(int newCount, int count, float totalHeight) { - int minimalVisibilityHeight = 5; - //new high - float highNewHeightPx = (float) newCount / (float) count * totalHeight; - //if new height is between 1 and 9 - give it a minimum height and if theres enough spce in total height - if (isNewNeedChange(totalHeight, highNewHeightPx, minimalVisibilityHeight)) { - highNewHeightPx = minimalVisibilityHeight; - } - - return highNewHeightPx; - } - - private static boolean isNewNeedChange(float highTotalHeightPx, float highNewHeightPx, int minimalVisibilityHeight) { - return highNewHeightPx > 0 && highNewHeightPx < minimalVisibilityHeight && highTotalHeightPx > minimalVisibilityHeight * 2; - } -} \ No newline at end of file diff --git a/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java b/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java deleted file mode 100644 index 569814f4..00000000 --- a/src/src/main/java/com/cx/restclient/cxArm/utils/CxARMUtils.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cx.restclient.cxArm.utils; - -import com.cx.restclient.cxArm.dto.Policy; -import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.httpClient.CxHttpClient; - -import java.io.IOException; -import java.util.List; - -import static com.cx.restclient.common.CxPARAM.CX_ARM_VIOLATION; -import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; - -/** - * Created by Galn on 7/30/2018. - */ -public abstract class CxARMUtils { - public static List getProjectViolations(CxHttpClient httpClient, String cxARMUrl, long projectId, String provider) throws IOException, CxClientException { - String relativePath = CX_ARM_VIOLATION.replace("{projectId}", Long.toString(projectId)).replace("{provider}", provider); - return (List) httpClient.getRequest(cxARMUrl, relativePath, CONTENT_TYPE_APPLICATION_JSON_V1, null, Policy.class, 200, "CxARM violations", true); - } -} diff --git a/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java b/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java deleted file mode 100644 index 73ae1ff1..00000000 --- a/src/src/main/java/com/cx/restclient/dto/ThresholdResult.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.cx.restclient.dto; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -/** - * Created by Galn on 4/10/2018. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class ThresholdResult { - private boolean isFail; - private String failDescription; - - public ThresholdResult(boolean isFail, String failDescription) { - this.isFail = isFail; - this.failDescription = failDescription; - } - - public boolean isFail() { - return isFail; - } - - public void setFail(boolean fail) { - isFail = fail; - } - - public String getFailDescription() { - return failDescription; - } - - public void setFailDescription(String failDescription) { - this.failDescription = failDescription; - } -} diff --git a/src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java b/src/test/java/com/cx/restclient/configuration/ConnectionTest.java similarity index 100% rename from src/src/test/java/com/cx/restclient/configuration/ConnectionTest.java rename to src/test/java/com/cx/restclient/configuration/ConnectionTest.java diff --git a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java index 7e109b0a..41819691 100644 --- a/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java +++ b/src/test/java/com/cx/restclient/configuration/CxScanConfigTest.java @@ -24,7 +24,7 @@ public void CxScanConfig() { String password = "password"; String cxOrigin = "cxOrigin"; boolean disableCertificateValidation = false; - CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin,disableCertificateValidation); + CxScanConfig cxScanConfigWithParameters = new CxScanConfig(url, username, password, cxOrigin, disableCertificateValidation); assertEquals("Incorrect URL", cxScanConfigWithParameters.getUrl(), url); assertEquals("Incorrect userName", cxScanConfigWithParameters.getUsername(), username); diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/connection/ConnectionTests.java new file mode 100644 index 00000000..0c9de621 --- /dev/null +++ b/src/test/java/com/cx/restclient/connection/ConnectionTests.java @@ -0,0 +1,55 @@ +package com.cx.restclient.connection; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.utility.TestingUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Properties; + +public class ConnectionTests { + + private Logger log = LoggerFactory.getLogger(ConnectionTests.class.getName()); + private static final String PROPERTIES_FILE = "config.properties"; + private CxShragaClient client; + private static Properties props; + + @BeforeClass + public static void initTest() throws IOException { + props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); + } + + @Test + public void ssoConnectionTest() { + CxScanConfig config = initConfig(); + try { + client = new CxShragaClient(config, log); + client.init(); + } catch (IOException | CxClientException e) { + e.printStackTrace(); + log.error("Error running osa scan: " + e.getMessage()); + Assert.fail(e.getMessage()); + } + } + + private CxScanConfig initConfig() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setUseSSOLogin(true); + config.setUsername(props.getProperty("user")); + config.setPassword(props.getProperty("password")); + config.setUrl(props.getProperty("serverUrl")); + config.setCxOrigin("common"); + + return config; + } + + +} diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java new file mode 100644 index 00000000..48590780 --- /dev/null +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -0,0 +1,117 @@ +package com.cx.restclient.connection; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.sast.dto.SASTResults; +import com.cx.utility.TestingUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; + +@Ignore +public class ProjectScanTests { + + private static final String PROPERTIES_FILE = "config.properties"; + + private Logger log = LoggerFactory.getLogger(ProjectScanTests.class.getName()); + private CxShragaClient client; + private static Properties props; + + @BeforeClass + public static void initTest() throws IOException { + props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); + } + + @Test + public void runOsaScan() throws MalformedURLException { + CxScanConfig config = initOsaConfig(); + client = new CxShragaClient(config, log); + try { + client.init(); + client.createOSAScan(); + client.waitForOSAResults(); + final OSAResults latestOSAResults = client.getLatestOSAResults(); + Assert.assertNotNull(latestOSAResults.getOsaScanId(), "Expected valid osa scan id"); + } catch (IOException | CxClientException | InterruptedException e) { + e.printStackTrace(); + log.error("Error running osa scan: " + e.getMessage()); + Assert.fail(e.getMessage()); + } + } + + @Test + public void runSastScan() throws MalformedURLException { + CxScanConfig config = initSastConfig(); + client = new CxShragaClient(config, log); + try { + client.init(); + client.createSASTScan(); + client.waitForSASTResults(); + final SASTResults latestSASTResults = client.getLatestSASTResults(); + Assert.assertNotNull(String.valueOf(latestSASTResults.getScanId()), "Expected valid osa scan id"); + } catch (IOException | CxClientException | InterruptedException e) { + e.printStackTrace(); + log.error("Error running sast scan: " + e.getMessage()); + Assert.fail(e.getMessage()); + } + } + + + private CxScanConfig initSastConfig() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setReportsDir(new File("C:\\report")); + config.setSourceDir(props.getProperty("sastSource")); + config.setUsername(props.getProperty("user")); + config.setPassword(props.getProperty("password")); + config.setUrl(props.getProperty("serverUrl")); + config.setCxOrigin("common"); + config.setProjectName("sastOnlyScan"); + config.setPresetName("Default"); + config.setTeamPath("\\CxServer"); + config.setSynchronous(true); + config.setGeneratePDFReport(true); + config.setOsaEnabled(false); + config.setPresetName("Default"); +// config.setPresetId(7); + + return config; + } + + private CxScanConfig initOsaConfig() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(false); + config.setSourceDir("C:\\sources\\osa\\HighVul"); + config.setReportsDir(new File("C:\\report")); + config.setUsername("admin1"); + config.setPassword("Cx123456!"); + + config.setUrl("http://10.32.1.57"); + config.setCxOrigin("common"); + config.setProjectName("osaOnlyScan"); + config.setPresetName("Default"); + config.setTeamPath("\\CxServer"); + config.setSynchronous(true); + config.setGeneratePDFReport(true); + config.setOsaEnabled(true); + + config.setOsaRunInstall(true); + config.setOsaThresholdsEnabled(true); + config.setPublic(true); + + return config; + } + +} diff --git a/src/test/java/com/cx/utility/TestingUtils.java b/src/test/java/com/cx/utility/TestingUtils.java new file mode 100644 index 00000000..5871d639 --- /dev/null +++ b/src/test/java/com/cx/utility/TestingUtils.java @@ -0,0 +1,18 @@ +package com.cx.utility; + +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.util.Properties; + +public final class TestingUtils { + + public static Properties getProps(String propsName, Class clazz) throws IOException { + Properties properties = new Properties(); + ClassLoader classLoader = clazz.getClassLoader(); + URL resource = classLoader.getResource(propsName); + properties.load(new FileReader(resource.getFile())); + + return properties; + } +} diff --git a/src/test/java/resources/config.properties b/src/test/java/resources/config.properties new file mode 100644 index 00000000..b3effecf --- /dev/null +++ b/src/test/java/resources/config.properties @@ -0,0 +1,2 @@ +serverUrl=http://10.32.1.57 +sastSource=C:\\sources\\BookStore_Small_CLI \ No newline at end of file From 483f3eb783323cdab11b1472457acda2e435fdfc Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 19 Nov 2019 13:11:56 +0200 Subject: [PATCH 145/473] AB#286: changes in SCA API URLs, HTML report fixes. --- .../com/cx/restclient/CxShragaClient.java | 4 +- .../java/com/cx/restclient/SCAClient.java | 44 ++++++++++++------- .../common/summary/SummaryUtils.java | 4 +- .../configuration/CxScanConfig.java | 1 - .../java/com/cx/restclient/sca/SCAWaiter.java | 6 ++- .../restclient/sca/dto/SCASummaryResults.java | 6 +-- src/main/java/testi.java | 4 +- src/main/resources/com/cx/report/report.ftl | 12 ++--- 8 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 0616f134..2440936c 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -48,7 +48,7 @@ public class CxShragaClient { private DependencyScanner dependencyScanner; - public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException { + public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { this.config = config; this.log = log; this.httpClient = new CxHttpClient( @@ -67,7 +67,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept } //For Test Connection - public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 0b84bcf7..afc1abf2 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -1,7 +1,6 @@ package com.cx.restclient; import com.cx.restclient.common.DependencyScanner; -import com.cx.restclient.common.UrlUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.DependencyScanResults; @@ -35,8 +34,14 @@ /** * SCA - Software Composition Analysis - is the successor of OSA. */ -class SCAClient implements DependencyScanner { - private static final String API_PATH = "api/"; +public class SCAClient implements DependencyScanner { + private class ApiPaths { + private static final String PROJECTS = "/risk_management/api/projects"; + private static final String SUMMARY_REPORT = "/risk_management/api/riskReports/%s/summary"; + private static final String ZIP_UPLOAD = "/scans/api/scans/zip"; + private static final String SCAN_STATUS = "/scans/api/scans/%s/status"; + private static final String REPORT_ID = "/scans/api/scans/%s/riskReportId"; + } private final Logger log; private final CxScanConfig config; @@ -48,23 +53,22 @@ class SCAClient implements DependencyScanner { private final Waiter waiter; private String scanId; - SCAClient(Logger log, CxScanConfig config) throws MalformedURLException { + SCAClient(Logger log, CxScanConfig config) throws MalformedURLException, CxClientException { this.log = log; this.config = config; int pollInterval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; - int marRetries = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + int maxRetries = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; - SCAConfig scaConfig = config.getScaConfig(); - String apiBaseUrl = UrlUtils.parseURLToString(scaConfig.getApiUrl(), API_PATH); + SCAConfig scaConfig = safeGetScaConfig(); - httpClient = new CxHttpClient(apiBaseUrl, + httpClient = new CxHttpClient(scaConfig.getApiUrl(), config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); - waiter = new SCAWaiter("SCA scan", pollInterval, marRetries, httpClient, log); + waiter = new SCAWaiter("SCA scan", pollInterval, maxRetries, httpClient, ApiPaths.SCAN_STATUS, log); } @Override @@ -121,7 +125,7 @@ public DependencyScanResults getLatestScanResults() { private void login() throws IOException, CxClientException { log.info("Logging into SCA."); - SCAConfig scaConfig = config.getScaConfig(); + SCAConfig scaConfig = safeGetScaConfig(); LoginSettings settings = new LoginSettings(); settings.setAccessControlBaseUrl(scaConfig.getAccessControlUrl()); @@ -134,7 +138,7 @@ private void login() throws IOException, CxClientException { } private void resolveProject() throws IOException, CxClientException { - String projectName = config.getScaConfig().getProjectName(); + String projectName = safeGetScaConfig().getProjectName(); projectId = getProjectIdByName(projectName); if (projectId == null) { log.debug("Project not found, creating a new one."); @@ -146,7 +150,7 @@ private void resolveProject() throws IOException, CxClientException { private String getProjectIdByName(String name) throws IOException, CxClientException { log.debug("Getting project by name: " + name); - List allProjects = (List) httpClient.getRequest("projects", + List allProjects = (List) httpClient.getRequest(ApiPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, Project.class, HttpStatus.SC_OK, @@ -168,7 +172,7 @@ private String createProject(String name) throws CxClientException, IOException StringEntity entity = HttpClientHelper.convertToStringEntity(request); - Project newProject = httpClient.postRequest("projects", + Project newProject = httpClient.postRequest(ApiPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, entity, Project.class, @@ -193,7 +197,7 @@ private String uploadZipFile(File zipFile) throws IOException, CxClientException HttpEntity entity = builder.build(); - String scanId = httpClient.postRequest("scans/zip", null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); + String scanId = httpClient.postRequest(ApiPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); log.debug("Scan ID: " + scanId); return scanId; @@ -211,7 +215,7 @@ private SCAResults retrieveScanResults() throws IOException, CxClientException { private String getReportId() throws IOException, CxClientException { log.debug("Getting report ID by scan ID: " + scanId); - String path = String.format("scans/%s/riskReportId", scanId); + String path = String.format(ApiPaths.REPORT_ID, scanId); String reportId = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, String.class, @@ -225,7 +229,7 @@ private String getReportId() throws IOException, CxClientException { private SCASummaryResults getSummaryReport(String reportId) throws IOException, CxClientException { log.debug("Getting summary report."); - String path = String.format("riskReports/%s/summary", reportId); + String path = String.format(ApiPaths.SUMMARY_REPORT, reportId); SCASummaryResults result = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, @@ -236,4 +240,12 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException, return result; } + + private SCAConfig safeGetScaConfig() throws CxClientException { + SCAConfig result = config.getScaConfig(); + if (result == null) { + throw new CxClientException("SCA scan configuration is missing."); + } + return result; + } } diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 93a3564e..3f388730 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -79,7 +79,7 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu } //osa: - if (config.getDependencyScannerType() != DependencyScannerType.NONE) { + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { if (osaResults.isOsaResultsReady()) { boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, dependencyScanResults, new StringBuilder()); templateData.put("osaThresholdExceeded", osaThresholdExceeded); @@ -120,7 +120,7 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu )); } - if (config.getDependencyScannerType() != DependencyScannerType.NONE && + if (config.getDependencyScannerType() == DependencyScannerType.OSA && osaResults.getOsaPolicies().size() > 0) { policyViolated = true; policies.putAll(osaResults.getOsaPolicies().stream().collect( diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 0f0f2b84..7d7404ca 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -97,7 +97,6 @@ public class CxScanConfig implements Serializable { private Integer maxZipSize; private String defaultProjectName; - // private boolean scaEnabled; private SCAConfig scaConfig; private DependencyScannerType dependencyScannerType; diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index f23c4c5a..6836b255 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -14,17 +14,19 @@ public class SCAWaiter extends Waiter { private final CxHttpClient httpClient; + private final String scanStatusUrlPath; private final Logger log; - public SCAWaiter(String scanType, int interval, int retry, CxHttpClient httpClient, Logger log) { + public SCAWaiter(String scanType, int interval, int retry, CxHttpClient httpClient, String scanStatusUrlPath, Logger log) { super(scanType, interval, retry); this.httpClient = httpClient; + this.scanStatusUrlPath = scanStatusUrlPath; this.log = log; } @Override public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOException { - String path = String.format("scans/%s/status", scanId); + String path = String.format(scanStatusUrlPath, scanId); ScanStatusResponse response = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, diff --git a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java index 38124457..eb8c2d67 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java @@ -9,7 +9,7 @@ public class SCASummaryResults { private int lowVulnerabilitiesCount; private int totalPackages; private int directPackages; - private OffsetDateTime createdOn; + private String createdOn; private double riskScore; private int totalOutdatedPackages; @@ -61,11 +61,11 @@ public void setDirectPackages(int directPackages) { this.directPackages = directPackages; } - public OffsetDateTime getCreatedOn() { + public String getCreatedOn() { return createdOn; } - public void setCreatedOn(OffsetDateTime createdOn) { + public void setCreatedOn(String createdOn) { this.createdOn = createdOn; } diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 925777ba..3cf5d263 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -147,8 +147,8 @@ private static void configureOsa(CxScanConfig config) { private static void configureSca(CxScanConfig parentConfig) { SCAConfig config = new SCAConfig(); - config.setApiUrl("http://scaapp.lumodev.com"); - config.setAccessControlUrl("http://upgrade.dev-ac-checkmarx.com"); + config.setApiUrl("https://api.lumodev.com"); + config.setAccessControlUrl("https://upgrade.dev-ac-checkmarx.com"); config.setUsername("myusername"); config.setPassword("mypassword"); config.setTenant("Checkmarx"); diff --git a/src/main/resources/com/cx/report/report.ftl b/src/main/resources/com/cx/report/report.ftl index ac76ac42..be2b6974 100644 --- a/src/main/resources/com/cx/report/report.ftl +++ b/src/main/resources/com/cx/report/report.ftl @@ -947,17 +947,17 @@ <#if config.sastEnabled && !sast.sastResultsReady>
  • SAST Scan Failed
  • - <#if config.dependencyScannerType != "NONE" && !osa.osaResultsReady> + <#if config.dependencyScannerType == "OSA" && !osa.osaResultsReady>
  • OSA Scan Failed
  • <#if policyViolated>
  • ${policyViolatedCount} ${policyLabel} Violated
  • - <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.dependencyScannerType != "NONE" && osa.osaResultsReady && osaThresholdExceeded> + <#if config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded) && config.dependencyScannerType == "OSA" && osa.osaResultsReady && osaThresholdExceeded>
  • Exceeded CxSAST and CxOSA Vulnerability Thresholds
  • <#elseif config.sastEnabled && sast.sastResultsReady && (sastThresholdExceeded || sastNewResultsExceeded)>
  • Exceeded CxSAST Vulnerability Threshold
  • - <#elseif config.dependencyScannerType != "NONE" && osa.osaResultsReady && osaThresholdExceeded> + <#elseif config.dependencyScannerType == "OSA" && osa.osaResultsReady && osaThresholdExceeded>
  • Exceeded CxOSA Vulnerability Threshold
  • @@ -1307,7 +1307,7 @@ - <#if config.dependencyScannerType != "NONE"> + <#if config.dependencyScannerType == "OSA">
    CxOSA Vulnerabilities & Libraries
    @@ -1963,7 +1963,7 @@ - <#if config.dependencyScannerType != "NONE" && osa.osaResultsReady> + <#if config.dependencyScannerType == "OSA" && osa.osaResultsReady> <#if osa.osaHighCVEReportTable?size gt 0 || osa.osaMediumCVEReportTable?size gt 0 || osa.osaLowCVEReportTable?size gt 0>
    @@ -2305,7 +2305,7 @@ - <#if (config.dependencyScannerType != "NONE" || config.sastEnabled) && policyViolated> + <#if (config.dependencyScannerType == "OSA" || config.sastEnabled) && policyViolated> <#if policyViolatedCount gt 0>
    From 9ed7e640fa6af7b369a167403ce64efd1beb6ea3 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 19 Nov 2019 14:03:08 +0200 Subject: [PATCH 146/473] AB#287: using osaLocationPath property in SCA when applicable. --- src/main/java/com/cx/restclient/CxOSAClient.java | 3 +-- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- src/main/java/com/cx/restclient/SCAClient.java | 3 ++- .../java/com/cx/restclient/configuration/CxScanConfig.java | 4 ++++ src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 4 ++-- .../java/com/cx/restclient/sast/utils/zip/CxZipUtils.java | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 875e97ae..42cfd885 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -109,9 +109,8 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaFolderExclusions(), config.getOsaFilterPattern(), config.getOsaArchiveIncludePatterns(), - config.getSourceDir(), + config.getEffectiveSourceDirForDependencyScan(), config.getOsaRunInstall(), - config.getOsaLocationPath(), config.getOsaScanDepth(), log); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index f0c3b18b..fefb7c13 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -140,7 +140,7 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc //prepare sources for scan PathFilter filter = new PathFilter(config.getSastFolderExclusions(), config.getSastFilterPattern(), log); - File zipFile = CxZipUtils.getZippedSources(config, filter, log); + File zipFile = CxZipUtils.getZippedSources(config, filter, config.getSourceDir(), log); uploadZipFile(zipFile, projectId); CxZipUtils.deleteZippedSources(zipFile, config, log); diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index afc1abf2..735e543e 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -89,7 +89,8 @@ public String createScan(DependencyScanResults target) throws CxClientException PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); scanId = null; try { - File zipFile = CxZipUtils.getZippedSources(config, filter, log); + String sourceDir = config.getEffectiveSourceDirForDependencyScan(); + File zipFile = CxZipUtils.getZippedSources(config, filter, sourceDir, log); scanId = uploadZipFile(zipFile); CxZipUtils.deleteZippedSources(zipFile, config, log); } catch (IOException e) { diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 7d7404ca..85553587 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -170,6 +170,10 @@ public void setOsaLocationPath(String osaLocationPath) { this.osaLocationPath = osaLocationPath; } + public String getEffectiveSourceDirForDependencyScan() { + return osaLocationPath != null ? osaLocationPath : sourceDir; + } + public File getReportsDir() { return reportsDir; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 82fca684..85a320d8 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -47,7 +47,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, String osaScanDepth, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String sourceDir, boolean installBeforeScan, String osaScanDepth, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -96,7 +96,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S setResolveDependencies(ret, "false"); } - ret.put("d", osaLocationPath == null ? scanFolder : osaLocationPath); + ret.put("d", sourceDir); return ret; } diff --git a/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java b/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java index 51cad1d8..d81bd42b 100644 --- a/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/zip/CxZipUtils.java @@ -16,14 +16,14 @@ * CxZipUtils generates the patterns used for zipping the workspace folder */ public abstract class CxZipUtils { - public static File getZippedSources(CxScanConfig config, PathFilter filter, Logger log) throws IOException { + public static File getZippedSources(CxScanConfig config, PathFilter filter, String sourceDir, Logger log) throws IOException { File result = config.getZipFile(); if (result == null) { log.info("Zipping sources"); Long maxZipSize = config.getMaxZipSize() != null ? config.getMaxZipSize() * 1024 * 1024 : MAX_ZIP_SIZE_BYTES; CxZip cxZip = new CxZip(TEMP_FILE_NAME_TO_ZIP, maxZipSize, log); - result = cxZip.zipWorkspaceFolder(new File(config.getSourceDir()), filter); + result = cxZip.zipWorkspaceFolder(new File(sourceDir), filter); log.debug("The sources were zipped to " + result.getAbsolutePath()); } return result; From 0b1449946597bfe16e33fac666c6031cda5d6d12 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 19 Nov 2019 21:06:43 +0200 Subject: [PATCH 147/473] AB#286: using a structured scan summary instead of a StringBuilder. --- .../java/com/cx/restclient/CxSASTClient.java | 4 +- .../com/cx/restclient/common/ShragaUtils.java | 118 ------------ .../common/summary/SummaryUtils.java | 24 ++- .../dto/scansummary/ErrorSource.java | 6 + .../dto/scansummary/ScanSummary.java | 168 ++++++++++++++++++ .../restclient/dto/scansummary/Severity.java | 7 + .../dto/scansummary/ThresholdError.java | 31 ++++ 7 files changed, 230 insertions(+), 128 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/scansummary/ErrorSource.java create mode 100644 src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java create mode 100644 src/main/java/com/cx/restclient/dto/scansummary/Severity.java create mode 100644 src/main/java/com/cx/restclient/dto/scansummary/ThresholdError.java diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index fefb7c13..96d026cd 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -144,8 +144,6 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc uploadZipFile(zipFile, projectId); CxZipUtils.deleteZippedSources(zipFile, config, log); - //Start a new createSASTScan - log.info("Uploading zip file"); return createScan(projectId); } @@ -336,6 +334,8 @@ private void defineScanSetting(ScanSettingRequest scanSetting) throws IOExceptio } private void uploadZipFile(File zipFile, long projectId) throws CxClientException, IOException { + log.info("Uploading zip file"); + InputStreamBody streamBody = new InputStreamBody(new FileInputStream(zipFile.getAbsoluteFile()), ContentType.APPLICATION_OCTET_STREAM, "zippedSource"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index eb6c3cb6..b436969f 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -1,12 +1,5 @@ package com.cx.restclient.common; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.DependencyScanResults; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.restclient.osa.dto.OSASummaryResults; -import com.cx.restclient.sast.dto.SASTResults; -import com.cx.restclient.sca.dto.SCAResults; -import com.cx.restclient.sca.dto.SCASummaryResults; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -16,122 +9,11 @@ import java.util.List; import java.util.Map; -import static com.cx.restclient.common.CxPARAM.PROJECT_POLICY_VIOLATED_STATUS; - /** * Created by: dorg. * Date: 4/12/2018. */ public abstract class ShragaUtils { - //Util methods - public static String getBuildFailureResult(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults) { - StringBuilder res = new StringBuilder(); - isThresholdExceeded(config, sastResults, dependencyScanResults, res); - isThresholdForNewResultExceeded(config, sastResults, res); - isPolicyViolated(config, sastResults, dependencyScanResults, res); - - return res.toString(); - } - - private static boolean isPolicyViolated(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults, StringBuilder res) { - boolean isPolicyViolated = config.getEnablePolicyViolations() && - ((dependencyScanResults != null && - dependencyScanResults.getOsaResults() != null && - dependencyScanResults.getOsaResults().getOsaPolicies() != null && - dependencyScanResults.getOsaResults().getOsaPolicies().size() > 0) || - (sastResults != null && sastResults.getSastPolicies().size() > 0)); - - if (isPolicyViolated) { - res.append(PROJECT_POLICY_VIOLATED_STATUS).append("\n"); - } - return isPolicyViolated; - } - - public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastResults, DependencyScanResults dependencyScanResults, StringBuilder res) { - boolean thresholdExceeded = false; - if (config.isSASTThresholdEffectivelyEnabled() && sastResults != null && sastResults.isSastResultsReady()) { - final String SEVERITY_TYPE = "CxSAST"; - thresholdExceeded = isSeverityExceeded(sastResults.getHigh(), config.getSastHighThreshold(), res, "high", SEVERITY_TYPE); - thresholdExceeded |= isSeverityExceeded(sastResults.getMedium(), config.getSastMediumThreshold(), res, "medium", SEVERITY_TYPE); - thresholdExceeded |= isSeverityExceeded(sastResults.getLow(), config.getSastLowThreshold(), res, "low", SEVERITY_TYPE); - } - - if (config.isOSAThresholdEffectivelyEnabled() && dependencyScanResults != null) { - SCAResults scaResults = dependencyScanResults.getScaResults(); - OSAResults osaResults = dependencyScanResults.getOsaResults(); - int totalHigh = 0, totalMedium = 0, totalLow = 0; - String severityType = null; - - if (scaResults != null) { - SCASummaryResults summary = scaResults.getSummary(); - if (summary != null) { - severityType = "SCA"; - totalHigh = summary.getHighVulnerabilitiesCount(); - totalMedium = summary.getMediumVulnerabilitiesCount(); - totalLow = summary.getLowVulnerabilitiesCount(); - } - } else if (osaResults != null && osaResults.isOsaResultsReady()) { - OSASummaryResults summary = osaResults.getResults(); - if (summary != null) { - severityType = "CxOSA"; - totalHigh = summary.getTotalHighVulnerabilities(); - totalMedium = summary.getTotalMediumVulnerabilities(); - totalLow = summary.getTotalLowVulnerabilities(); - } - } - - if (severityType != null) { - thresholdExceeded |= isSeverityExceeded(totalHigh, config.getOsaHighThreshold(), res, "high", severityType); - thresholdExceeded |= isSeverityExceeded(totalMedium, config.getOsaMediumThreshold(), res, "medium", severityType); - thresholdExceeded |= isSeverityExceeded(totalLow, config.getOsaLowThreshold(), res, "low", severityType); - } - } - return thresholdExceeded; - } - - public static boolean isThresholdForNewResultExceeded(CxScanConfig config, SASTResults sastResults, StringBuilder res) { - boolean exceeded = false; - - if (sastResults != null && sastResults.isSastResultsReady() && config.getSastNewResultsThresholdEnabled()) { - String severity = config.getSastNewResultsThresholdSeverity(); - - if ("LOW".equals(severity)) { - if (sastResults.getNewLow() > 0) { - res.append("One or more new results of low severity\n"); - exceeded = true; - } - severity = "MEDIUM"; - } - - if ("MEDIUM".equals(severity)) { - if (sastResults.getNewMedium() > 0) { - res.append("One or more new results of medium severity\n"); - exceeded = true; - } - severity = "HIGH"; - } - - if ("HIGH".equals(severity)) { - if (sastResults.getNewHigh() > 0) { - res.append("One or more New results of high severity\n"); - exceeded = true; - } - } - } - - return exceeded; - } - - private static boolean isSeverityExceeded(int result, Integer threshold, StringBuilder res, String severity, String severityType) { - boolean fail = false; - if (threshold != null && result > threshold) { - res.append(String.format("%s %s severity results are above threshold. Results: %d. Threshold: %d.\n", - severityType, severity, result, threshold)); - fail = true; - } - return fail; - } - public static Map> generateIncludesExcludesPatternLists(String folderExclusions, String filterPattern, Logger log) { String excludeFoldersPattern = processExcludeFolders(folderExclusions, log); diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 3f388730..25fb19ba 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -1,10 +1,11 @@ package com.cx.restclient.common.summary; -import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.Policy; import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ScanResults; +import com.cx.restclient.dto.scansummary.ScanSummary; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; import com.cx.restclient.sast.dto.SASTResults; @@ -31,7 +32,14 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu Map templateData = new HashMap(); templateData.put("config", config); templateData.put("sast", sastResults); - templateData.put("osa", osaResults); + + // TODO: null value for "osa" should be handled inside the template. + templateData.put("osa", osaResults != null ? osaResults : new OSAResults()); + + ScanResults scanResults = new ScanResults(); + scanResults.setSastResults(sastResults); + scanResults.setDependencyScanResults(dependencyScanResults); + ScanSummary scanSummary = new ScanSummary(config, scanResults); //calculated params: @@ -41,9 +49,9 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu //sast: if (config.getSastEnabled()) { if (sastResults.isSastResultsReady()) { - boolean sastThresholdExceeded = ShragaUtils.isThresholdExceeded(config, sastResults, null, new StringBuilder()); - boolean sastNewResultsExceeded = ShragaUtils.isThresholdForNewResultExceeded(config, sastResults, new StringBuilder()); - templateData.put("sastThresholdExceeded", sastThresholdExceeded); + boolean sastThresholdExceeded = scanSummary.isSastThresholdExceeded(); + boolean sastNewResultsExceeded = scanSummary.isSastThresholdForNewResultsExceeded(); + templateData.put("sastThresholdExceeded", sastNewResultsExceeded); templateData.put("sastNewResultsExceeded", sastNewResultsExceeded); buildFailed = sastThresholdExceeded || sastNewResultsExceeded; //calculate sast bars: @@ -81,9 +89,9 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu //osa: if (config.getDependencyScannerType() == DependencyScannerType.OSA) { if (osaResults.isOsaResultsReady()) { - boolean osaThresholdExceeded = ShragaUtils.isThresholdExceeded(config, null, dependencyScanResults, new StringBuilder()); - templateData.put("osaThresholdExceeded", osaThresholdExceeded); - buildFailed |= osaThresholdExceeded; + boolean thresholdExceeded = scanSummary.isOsaThresholdExceeded(); + templateData.put("osaThresholdExceeded", thresholdExceeded); + buildFailed |= thresholdExceeded; //calculate osa bars: OSASummaryResults osaSummaryResults = osaResults.getResults(); diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ErrorSource.java b/src/main/java/com/cx/restclient/dto/scansummary/ErrorSource.java new file mode 100644 index 00000000..e8022db7 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/scansummary/ErrorSource.java @@ -0,0 +1,6 @@ +package com.cx.restclient.dto.scansummary; + +public enum ErrorSource { + SAST, + DEPENDENCY_SCANNER +} diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java new file mode 100644 index 00000000..cd7d5aca --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java @@ -0,0 +1,168 @@ +package com.cx.restclient.dto.scansummary; + +import com.cx.restclient.common.CxPARAM; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; +import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ScanResults; +import com.cx.restclient.osa.dto.OSAResults; +import com.cx.restclient.osa.dto.OSASummaryResults; +import com.cx.restclient.sast.dto.SASTResults; +import com.cx.restclient.sca.dto.SCAResults; +import com.cx.restclient.sca.dto.SCASummaryResults; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collects errors from a provided ScanResults object, based on scan config. + */ +public class ScanSummary { + private final DependencyScannerType dependencyScannerType; + private final List thresholdErrors = new ArrayList<>(); + private final List newResultThresholdErrors = new ArrayList<>(); + private final boolean policyViolated; + + public ScanSummary(CxScanConfig config, ScanResults scanResults) { + dependencyScannerType = config.getDependencyScannerType(); + + addSastThresholdErrors(config, scanResults.getSastResults()); + addDependencyScanThresholdErrors(config, scanResults.getDependencyScanResults()); + + addNewResultThresholdErrors(config, scanResults.getSastResults()); + + policyViolated = isPolicyViolated(config, scanResults); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + + for (ThresholdError error : thresholdErrors) { + String sourceForDisplay = (error.getSource() == ErrorSource.SAST) ? "SAST" : dependencyScannerType.toString(); + + result.append(String.format("%s %s severity results are above threshold. Results: %d. Threshold: %d.\n", + sourceForDisplay, + error.getSeverity().toString().toLowerCase(), + error.getValue(), + error.getThreshold())); + } + + for (Severity severity : newResultThresholdErrors) { + result.append(String.format("One or more new results of %s severity\n", severity.toString().toLowerCase())); + } + + if (policyViolated) { + result.append(CxPARAM.PROJECT_POLICY_VIOLATED_STATUS).append("\n"); + } + + return result.toString(); + } + + public List getThresholdErrors() { + return thresholdErrors; + } + + public boolean hasErrors() { + return !thresholdErrors.isEmpty() || !newResultThresholdErrors.isEmpty() || policyViolated; + } + + public boolean isSastThresholdExceeded() { + return thresholdErrors.stream().anyMatch(error -> error.getSource() == ErrorSource.SAST); + } + + public boolean isOsaThresholdExceeded() { + return thresholdErrors.stream().anyMatch(error -> error.getSource() == ErrorSource.DEPENDENCY_SCANNER); + } + + public boolean isSastThresholdForNewResultsExceeded() { + return !newResultThresholdErrors.isEmpty(); + } + + private void addSastThresholdErrors(CxScanConfig config, SASTResults sastResults) { + if (config.isSASTThresholdEffectivelyEnabled() && + sastResults != null && + sastResults.isSastResultsReady()) { + checkForThresholdError(sastResults.getHigh(), config.getSastHighThreshold(), ErrorSource.SAST, Severity.HIGH); + checkForThresholdError(sastResults.getMedium(), config.getSastMediumThreshold(), ErrorSource.SAST, Severity.MEDIUM); + checkForThresholdError(sastResults.getLow(), config.getSastLowThreshold(), ErrorSource.SAST, Severity.LOW); + } + } + + private void addDependencyScanThresholdErrors(CxScanConfig config, DependencyScanResults dependencyScanResults) { + if (config.isOSAThresholdEffectivelyEnabled() && dependencyScanResults != null) { + SCAResults scaResults = dependencyScanResults.getScaResults(); + OSAResults osaResults = dependencyScanResults.getOsaResults(); + int totalHigh = 0, totalMedium = 0, totalLow = 0; + String severityType = null; + + if (scaResults != null) { + SCASummaryResults summary = scaResults.getSummary(); + if (summary != null) { + severityType = "SCA"; + totalHigh = summary.getHighVulnerabilitiesCount(); + totalMedium = summary.getMediumVulnerabilitiesCount(); + totalLow = summary.getLowVulnerabilitiesCount(); + } + } else if (osaResults != null && osaResults.isOsaResultsReady()) { + OSASummaryResults summary = osaResults.getResults(); + if (summary != null) { + severityType = "CxOSA"; + totalHigh = summary.getTotalHighVulnerabilities(); + totalMedium = summary.getTotalMediumVulnerabilities(); + totalLow = summary.getTotalLowVulnerabilities(); + } + } + + if (severityType != null) { + checkForThresholdError(totalHigh, config.getOsaHighThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.HIGH); + checkForThresholdError(totalMedium, config.getOsaMediumThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.MEDIUM); + checkForThresholdError(totalLow, config.getOsaLowThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.LOW); + } + } + } + + private void addNewResultThresholdErrors(CxScanConfig config, SASTResults sastResults) { + if (sastResults != null && sastResults.isSastResultsReady() && config.getSastNewResultsThresholdEnabled()) { + String severity = config.getSastNewResultsThresholdSeverity(); + + if ("LOW".equals(severity)) { + if (sastResults.getNewLow() > 0) { + newResultThresholdErrors.add(Severity.LOW); + } + severity = "MEDIUM"; + } + + if ("MEDIUM".equals(severity)) { + if (sastResults.getNewMedium() > 0) { + newResultThresholdErrors.add(Severity.MEDIUM); + } + severity = "HIGH"; + } + + if ("HIGH".equals(severity)) { + if (sastResults.getNewHigh() > 0) { + newResultThresholdErrors.add(Severity.HIGH); + } + } + } + } + + private static boolean isPolicyViolated(CxScanConfig config, ScanResults scanResults) { + DependencyScanResults dependencyScanResults = scanResults.getDependencyScanResults(); + SASTResults sastResults = scanResults.getSastResults(); + + return config.getEnablePolicyViolations() && + ((dependencyScanResults != null && + dependencyScanResults.getOsaResults() != null && + dependencyScanResults.getOsaResults().getOsaPolicies().size() > 0) || + (sastResults != null && sastResults.getSastPolicies().size() > 0)); + } + + private void checkForThresholdError(int value, Integer threshold, ErrorSource source, Severity severity) { + if (threshold != null && value > threshold) { + ThresholdError error = new ThresholdError(source, severity, value, threshold); + thresholdErrors.add(error); + } + } +} diff --git a/src/main/java/com/cx/restclient/dto/scansummary/Severity.java b/src/main/java/com/cx/restclient/dto/scansummary/Severity.java new file mode 100644 index 00000000..3413ac0c --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/scansummary/Severity.java @@ -0,0 +1,7 @@ +package com.cx.restclient.dto.scansummary; + +public enum Severity { + HIGH, + MEDIUM, + LOW +} diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ThresholdError.java b/src/main/java/com/cx/restclient/dto/scansummary/ThresholdError.java new file mode 100644 index 00000000..3b002ed8 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/scansummary/ThresholdError.java @@ -0,0 +1,31 @@ +package com.cx.restclient.dto.scansummary; + +public class ThresholdError { + private ErrorSource source; + private Severity severity; + private final int value; + private final Integer threshold; + + public ThresholdError(ErrorSource source, Severity severity, int value, Integer threshold) { + this.source = source; + this.severity = severity; + this.value = value; + this.threshold = threshold; + } + + public ErrorSource getSource() { + return source; + } + + public Severity getSeverity() { + return severity; + } + + public int getValue() { + return value; + } + + public Integer getThreshold() { + return threshold; + } +} From 1835287ca51da7373a416a5a13392ff4a2a5064e Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 20 Nov 2019 10:19:59 +0200 Subject: [PATCH 148/473] added connection pool to the apache http client --- .../restclient/httpClient/CxHttpClient.java | 81 ++++++------------- 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 542f5d17..db44c5f7 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -34,8 +34,8 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.ProxyAuthenticationStrategy; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; import org.apache.http.impl.conn.DefaultProxyRoutePlanner; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; @@ -82,42 +82,7 @@ public class CxHttpClient { private final String username; private final String password; private String cxOrigin; - private Boolean useSSo = false; - - -// private final HttpRequestInterceptor requestFilter = new HttpRequestInterceptor() { -// public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { -// httpRequest.addHeader(ORIGIN_HEADER, cxOrigin); -// if (token != null) { -// httpRequest.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); -// } -// if (csrfToken != null) { -// httpRequest.addHeader(CSRF_TOKEN_HEADER, csrfToken); -// } -// if (cookies != null) { -// httpRequest.addHeader("cookie", cookies); -// } -// } -// }; - -// -// private final HttpResponseInterceptor responseFilter = new HttpResponseInterceptor() { -// -// public void process(HttpResponse httpResponse, HttpContext httpContext) throws HttpException, IOException { -// for (org.apache.http.cookie.Cookie c : cookieStore.getCookies()) { -// if (CSRF_TOKEN_HEADER.equals(c.getName())) { -// csrfToken = c.getValue(); -// } -// } -// Header[] setCookies = httpResponse.getHeaders("Set-Cookie"); -// StringBuilder sb = new StringBuilder(); -// for (Header h : setCookies) { -// sb.append(h.getValue()).append(";"); -// } -// cookies = (cookies == null ? "" : cookies) + sb.toString(); -// } -// }; - + private Boolean useSSo; public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, Logger logi, @@ -132,23 +97,17 @@ public CxHttpClient(String hostname, String username, String password, String or HttpClientBuilder cb = HttpClients.custom(); cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); setSSLTls("TLSv1.2", logi); - - /* TODO: verify that responseFilter is compatible (or needed) with refactor */ - -// if (isSSO) { -// this.useSSo = true; -// cookieStore = new BasicCookieStore(); -// cb.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); -// } - if (disableSSLValidation) { try { cb.setSSLSocketFactory(getSSLSF()); - cb.setConnectionManager(getHttpConnManager()); + cb.setConnectionManager(getSSLHttpConnManager()); } catch (CxClientException e) { logi.warn("Failed to disable certificate verification: " + e.getMessage()); } + } else { + cb.setConnectionManager(getHttpConnManager()); } + cb.setConnectionManagerShared(true); setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); @@ -167,21 +126,19 @@ public CxHttpClient(String hostname, String username, String password, String or //create httpclient HttpClientBuilder cb = HttpClients.custom(); -// if (isSSO) { -// this.useSSo = true; -// cookieStore = new BasicCookieStore(); -// cb.addInterceptorLast(responseFilter).setDefaultCookieStore(cookieStore); -// } cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); setSSLTls("TLSv1.2", logi); if (disableSSLValidation) { try { cb.setSSLSocketFactory(getSSLSF()); - cb.setConnectionManager(getHttpConnManager()); + cb.setConnectionManager(getSSLHttpConnManager()); } catch (CxClientException e) { logi.warn("Failed to disable certificate verification: " + e.getMessage()); } + } else { + cb.setConnectionManager(getHttpConnManager()); } + cb.setConnectionManagerShared(true); setProxy(cb, logi); cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); @@ -243,12 +200,26 @@ private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { } - private static BasicHttpClientConnectionManager getHttpConnManager() throws CxClientException { + private static PoolingHttpClientConnectionManager getSSLHttpConnManager() throws CxClientException { Registry socketFactoryRegistry = RegistryBuilder.create() .register("https", getSSLSF()) .register("http", new PlainConnectionSocketFactory()) .build(); - return new BasicHttpClientConnectionManager(socketFactoryRegistry); + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + connManager.setMaxTotal(50); + connManager.setDefaultMaxPerRoute(5); + return connManager; + } + + private static PoolingHttpClientConnectionManager getHttpConnManager() { + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("https", new PlainConnectionSocketFactory()) + .register("http", new PlainConnectionSocketFactory()) + .build(); + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + connManager.setMaxTotal(50); + connManager.setDefaultMaxPerRoute(5); + return connManager; } private static Registry getAuthSchemeProviderRegistry() { From f7d9c9466f501504290435a8125faabd47ea69f9 Mon Sep 17 00:00:00 2001 From: idana Date: Sun, 8 Sep 2019 10:56:50 +0300 Subject: [PATCH 149/473] proxy support, generate token (cherry picked from commit de6ad68dbecdb20a230becfda2116410aef538c1) --- .../com/cx/restclient/httpClient/CxHttpClient.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index db44c5f7..17e2045c 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -234,12 +234,17 @@ public void login() throws IOException, CxClientException { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } else { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); - HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - token = request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + token = generateToken(); } } + public TokenLoginResponse generateToken() throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } + private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { List parameters = new ArrayList(); parameters.add(new BasicNameValuePair("username", username)); From b2fe328e910998ec5fefefdace170ce548eb4641 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 20 Nov 2019 11:08:22 +0200 Subject: [PATCH 150/473] AB#286: printing SCA report summary in console. --- .../java/com/cx/restclient/SCAClient.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 735e543e..f1117e0b 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -158,13 +158,11 @@ private String getProjectIdByName(String name) throws IOException, CxClientExcep "SCA projects", true); - String result = allProjects.stream() + return allProjects.stream() .filter((Project project) -> name.equals(project.getName())) - .map(project -> project.getId()) + .map(Project::getId) .findFirst() .orElse(null); - - return result; } private String createProject(String name) throws CxClientException, IOException { @@ -239,9 +237,25 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException, "SCA report summary", false); + printSummary(result); + return result; } + // This method is for demo purposes and probably should be replaced in the future. + private void printSummary(SCASummaryResults summary) { + log.info("\n----SCA risk report summary----"); + log.info("Created on: " + summary.getCreatedOn()); + log.info("Direct packages: " + summary.getDirectPackages()); + log.info("High vulnerabilities: " + summary.getHighVulnerabilitiesCount()); + log.info("Medium vulnerabilities: " + summary.getMediumVulnerabilitiesCount()); + log.info("Low vulnerabilities: " + summary.getLowVulnerabilitiesCount()); + log.info("Risk report ID: " + summary.getRiskReportId()); + log.info("Risk score: " + summary.getRiskScore()); + log.info("Total packages: " + summary.getTotalPackages()); + log.info(String.format("Total outdated packages: %d\n", summary.getTotalOutdatedPackages())); + } + private SCAConfig safeGetScaConfig() throws CxClientException { SCAConfig result = config.getScaConfig(); if (result == null) { From 3a6f47840b0ca8a65f3eb865b3b0271f70bb6aef Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 9 Sep 2019 15:22:14 +0300 Subject: [PATCH 151/473] retrieve/revoke refresh token (cherry picked from commit c37433e4577947e0243e5f3f8b3a9edb55ef23ac) --- pom.xml | 70 +--------------- .../com/cx/restclient/CxShragaClient.java | 15 +++- .../com/cx/restclient/common/CxPARAM.java | 1 + .../configuration/CxScanConfig.java | 16 ++++ .../cx/restclient/dto/TokenLoginResponse.java | 9 +++ .../restclient/httpClient/CxHttpClient.java | 80 ++++++++++++++++--- .../com/cx/restclient/osa/dto/ClientType.java | 31 +++++++ 7 files changed, 142 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/cx/restclient/osa/dto/ClientType.java diff --git a/pom.xml b/pom.xml index 98ae9461..89d9f468 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.00.5 + 9.20.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -254,70 +254,4 @@ - - - Gal Or Nussbaum - gal.nussbaum@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - https://www.linkedin.com/in/gal-nussbaum-68b3a76a/de - - - - Dor Golan - dor.golan@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Shaul Valero - shaul.valero@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Dev TL - Developer - - - http://i.imgur.com/44Iil53.png - - - - Eyal Amor - eyal.amor@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - Architect - Developer - - - http://i.imgur.com/44Iil53.png - - - - Yair David - Yair.David@checkmarx.com - Checkmarx - https://www.checkmarx.com/ - - QA Manager - - - http://i.imgur.com/EVIS8LO.jpg - - - - + \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index ef822cb8..9dd60f39 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -5,9 +5,11 @@ import com.cx.restclient.cxArm.dto.CxArmConfig; import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.Team; +import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; +import com.cx.restclient.osa.dto.ClientType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; import org.apache.http.client.HttpResponseException; @@ -56,6 +58,7 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro config.getPassword(), config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), + config.getRefreshToken(), log, proxyHost, proxyPort, proxyUser, proxyPassword); sastClient = new CxSASTClient(httpClient, log, config); @@ -72,7 +75,8 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), - log); + config.getRefreshToken(), + config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } @@ -209,6 +213,15 @@ public void login() throws IOException, CxClientException { httpClient.login(); } + public String getToken() throws IOException, CxClientException { + final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(ClientType.CLI); + return tokenLoginResponse.getRefresh_token(); + } + + public void revokeToken(String token) throws IOException, CxClientException { + httpClient.revokeToken(token); + } + public void getCxVersion() throws IOException, CxClientException { try { config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index 730a447b..dcca603b 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -7,6 +7,7 @@ */ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; + public static final String REVOCATION = "auth/identity/connect/revocation"; public static final String SSO_AUTHENTICATION = "auth/ssologin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 07a42048..43b41423 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -26,6 +26,7 @@ public class CxScanConfig implements Serializable { private File reportsDir; private String username; private String password; + private String refreshToken; private String url; private String projectName; private String teamPath; @@ -92,6 +93,13 @@ public CxScanConfig(String url, String username, String password, String cxOrigi this.disableCertificateValidation = disableCertificateValidation; } + public CxScanConfig(String url, String refreshToken, String cxOrigin, boolean disableCertificateValidation) { + this.url = url; + this.refreshToken = refreshToken; + this.cxOrigin = cxOrigin; + this.disableCertificateValidation = disableCertificateValidation; + } + public Boolean getSastEnabled() { return sastEnabled; } @@ -160,6 +168,14 @@ public void setUsername(String username) { this.username = username; } + public void setRefreshToken(String token) { + this.refreshToken = token; + } + + public String getRefreshToken() { + return refreshToken; + } + public String getPassword() { return password; } diff --git a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java index a7e0f39f..948aa620 100644 --- a/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java +++ b/src/main/java/com/cx/restclient/dto/TokenLoginResponse.java @@ -10,6 +10,7 @@ public class TokenLoginResponse { private String access_token; private long expires_in; private String token_type; + private String refresh_token; public String getAccess_token() { return access_token; @@ -34,4 +35,12 @@ public String getToken_type() { public void setToken_type(String token_type) { this.token_type = token_type; } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 17e2045c..df2b545a 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -6,6 +6,7 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; +import com.cx.restclient.osa.dto.ClientType; import org.apache.http.*; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; @@ -47,6 +48,7 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -81,15 +83,17 @@ public class CxHttpClient { private String rootUri; private final String username; private final String password; + private final String refreshtoken; private String cxOrigin; private Boolean useSSo; public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, Logger logi, + boolean disableSSLValidation, boolean isSSO, String refreshtoken, Logger logi, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { this.log = logi; this.username = username; this.password = password; + this.refreshtoken = refreshtoken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; this.useSSo = isSSO; @@ -116,10 +120,11 @@ public CxHttpClient(String hostname, String username, String password, String or } public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, Logger logi) throws MalformedURLException { + boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi) throws MalformedURLException { this.log = logi; this.username = username; this.password = password; + this.refreshtoken = refreshToken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; this.useSSo = isSSO; @@ -230,7 +235,9 @@ private static Registry getAuthSchemeProviderRegistry() { } public void login() throws IOException, CxClientException { - if (useSSo) { + if (refreshtoken != null) { + token = getAccessTokenFromRefreshToken(); + } else if (useSSo) { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } else { @@ -239,24 +246,75 @@ public void login() throws IOException, CxClientException { } public TokenLoginResponse generateToken() throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(); + return generateToken(ClientType.RESOURCE_OWNER); + } + + public TokenLoginResponse generateToken(ClientType clientType) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateUrlEncodedFormEntity(clientType); HttpPost post = new HttpPost(rootUri + AUTHENTICATION); - return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, - TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + try { + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + } } - private UrlEncodedFormEntity generateUrlEncodedFormEntity() throws UnsupportedEncodingException { - List parameters = new ArrayList(); + private TokenLoginResponse getAccessTokenFromRefreshToken() throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateTokenFromRefreshEntity(ClientType.CLI); + HttpPost post = new HttpPost(rootUri + AUTHENTICATION); + try { + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Failed to generate access token from refresh token failure error was: %s", e.getMessage()), e); + } + } + + public void revokeToken(String token) throws IOException, CxClientException { + UrlEncodedFormEntity requestEntity = generateRevocationEntity(ClientType.CLI, token); + HttpPost post = new HttpPost(rootUri + REVOCATION); + try { + request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + String.class, HttpStatus.SC_OK, "revocation", false, false); + } catch (CxClientException e) { + throw new CxClientException(String.format("Token revocation failure error was: %s", e.getMessage()), e); + } + } + + private UrlEncodedFormEntity generateRevocationEntity(ClientType clientType, String token) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("token_type_hint", "refresh_token")); + parameters.add(new BasicNameValuePair("token", token)); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); + + return new UrlEncodedFormEntity(parameters, "utf-8"); + + } + + private UrlEncodedFormEntity generateUrlEncodedFormEntity(ClientType clientType) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair("username", username)); parameters.add(new BasicNameValuePair("password", password)); parameters.add(new BasicNameValuePair("grant_type", "password")); - parameters.add(new BasicNameValuePair("scope", "sast_rest_api cxarm_api")); - parameters.add(new BasicNameValuePair("client_id", "resource_owner_client")); - parameters.add(new BasicNameValuePair("client_secret", "014DF517-39D1-4453-B7B3-9930C563627C")); + parameters.add(new BasicNameValuePair("scope", clientType.getScopes())); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); return new UrlEncodedFormEntity(parameters, "utf-8"); } + private UrlEncodedFormEntity generateTokenFromRefreshEntity(ClientType clientType) throws UnsupportedEncodingException { + List parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); + parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); + parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); + parameters.add(new BasicNameValuePair("refresh_token", refreshtoken)); + + return new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8.name()); + } + //GET REQUEST public T getRequest(String relPath, String contentType, Class responseType, int expectStatus, String failedMsg, boolean isCollection) throws IOException, CxClientException { return getRequest(rootUri, relPath, CONTENT_TYPE_APPLICATION_JSON, contentType, responseType, expectStatus, failedMsg, isCollection); diff --git a/src/main/java/com/cx/restclient/osa/dto/ClientType.java b/src/main/java/com/cx/restclient/osa/dto/ClientType.java new file mode 100644 index 00000000..8843318c --- /dev/null +++ b/src/main/java/com/cx/restclient/osa/dto/ClientType.java @@ -0,0 +1,31 @@ +package com.cx.restclient.osa.dto; + +public enum ClientType { + + RESOURCE_OWNER("resource_owner_client", "sast_rest_api cxarm_api", + "014DF517-39D1-4453-B7B3-9930C563627C"), + CLI("cli_client", "sast_rest_api offline_access", + "B9D84EA8-E476-4E83-A628-8A342D74D3BD"); + + private String clientId; + private String scopes; + private String clientSecret; + + ClientType(String clientId, String scopes, String clientSecret) { + this.clientId = clientId; + this.scopes = scopes; + this.clientSecret = clientSecret; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getScopes() { + return scopes; + } + + public String getClientId() { + return clientId; + } +} From 31a968d0d6473a6297995699af5613ea50f1f62e Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 20 Nov 2019 12:33:52 +0200 Subject: [PATCH 152/473] AB#313: Using the same project name parameter for SAST and SCA. --- src/main/java/com/cx/restclient/SCAClient.java | 2 +- src/main/java/com/cx/restclient/sca/dto/SCAConfig.java | 9 --------- src/main/java/testi.java | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index f1117e0b..c02b678d 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -139,7 +139,7 @@ private void login() throws IOException, CxClientException { } private void resolveProject() throws IOException, CxClientException { - String projectName = safeGetScaConfig().getProjectName(); + String projectName = config.getProjectName(); projectId = getProjectIdByName(projectName); if (projectId == null) { log.debug("Project not found, creating a new one."); diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java index afcb5617..26e25d65 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java @@ -8,7 +8,6 @@ public class SCAConfig implements Serializable { private String username; private String password; private String tenant; - private String projectName; public String getApiUrl() { return apiUrl; @@ -49,12 +48,4 @@ public void setTenant(String tenant) { public String getTenant() { return tenant; } - - public String getProjectName() { - return projectName; - } - - public void setProjectName(String projectName) { - this.projectName = projectName; - } } diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 3cf5d263..6d46e7b2 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -152,7 +152,6 @@ private static void configureSca(CxScanConfig parentConfig) { config.setUsername("myusername"); config.setPassword("mypassword"); config.setTenant("Checkmarx"); - config.setProjectName("CommonClientScaTest3"); parentConfig.setScaConfig(config); } } From b772f0488fce492a9d03699b4b0c84fc1bcf7174 Mon Sep 17 00:00:00 2001 From: idana Date: Wed, 11 Sep 2019 11:43:49 +0300 Subject: [PATCH 153/473] added missing dependencies for JDK11 (cherry picked from commit 9d1e27852f97a5d33e441b5b449a8a6d7f6f48b5) # Conflicts: # pom.xml --- pom.xml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pom.xml b/pom.xml index 89d9f468..c0b1fc15 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,9 @@ UTF-8 + 1.2.0 + 2.3.0 + 1.18.6 Checkmarx @@ -170,6 +173,41 @@ + + + + com.sun.activation + javax.activation + ${javax.activation.version} + + + + javax.xml.bind + jaxb-api + ${jaxb.api.version} + + + + com.sun.xml.bind + jaxb-core + ${jaxb.api.version} + + + + com.sun.xml.bind + jaxb-impl + ${jaxb.api.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + From fac61891443d85c85e94fd0ad27f1033e2d442a7 Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 16 Sep 2019 17:00:20 +0300 Subject: [PATCH 154/473] added remote scan (cherry picked from commit 3a14b40bd56a4850b1cdda23c6f81607dcbb51ab) --- pom.xml | 2 +- .../java/com/cx/restclient/CxSASTClient.java | 22 ++++++++++++++----- .../restclient/dto/RemoteSourceRequest.java | 4 ++-- .../cx/restclient/sast/utils/SASTParam.java | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index c0b1fc15..7a2bfca5 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ org.apache.httpcomponents httpcore - 4.4 + 4.4.12 com.fasterxml.jackson.core diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index e41699fe..9f09a6cb 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -159,6 +159,7 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient HttpEntity entity; RemoteSourceRequest req = new RemoteSourceRequest(config); RemoteSourceTypes type = req.getType(); + boolean isSSH = false; switch (type) { case SVN: @@ -182,16 +183,17 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity(new Gson().toJson(req), StandardCharsets.UTF_8); break; case GIT: - if (req.getPrivateKey().length < 1) { + if (req.getPrivateKey().length == 0) { Map content = new HashMap<>(); content.put("url", config.getRemoteSrcUrl()); content.put("branch", config.getRemoteSrcBranch()); entity = new StringEntity(new JSONObject(content).toString(), StandardCharsets.UTF_8); } else { + isSSH = true; builder = MultipartEntityBuilder.create(); builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); + builder.addBinaryBody("privateKey", new byte[0], ContentType.MULTIPART_FORM_DATA, null); entity = builder.build(); } break; @@ -200,7 +202,14 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity("", StandardCharsets.UTF_8); } - return createRemoteSourceScan(projectId, entity, type.value()).getId(); + createRemoteSourceRequest(projectId, entity, type.value(), isSSH); + + CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); + log.info("Sending SAST scan request"); + CxID createScanResponse = createScan(scanRequest); + log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); + + return createScanResponse.getId(); } @@ -330,8 +339,11 @@ private CxID createScan(CreateScanRequest request) throws CxClientException, IOE return httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); } - private CxID createRemoteSourceScan(long projectId, HttpEntity entity, String sourceType) throws IOException, CxClientException { - return httpClient.postRequest(SAST_CREATE_REMOTE_SOURCE_SCAN.replace("{projectId}", Long.toString(projectId)).replace("{sourceType}", sourceType), CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { + final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), CONTENT_TYPE_APPLICATION_JSON_V1, + entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); + + return cxID; } private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientException, IOException { diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java index 095b5f60..06ed5316 100644 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -13,7 +13,7 @@ public class RemoteSourceRequest { private String userName; private String password; private RemoteSourceTypes type; - private transient String browseMode; + private transient String browseMode; ; public RemoteSourceRequest() { @@ -24,7 +24,7 @@ public RemoteSourceRequest(CxScanConfig config) { this.password = config.getRemoteSrcPass(); this.url = config.getRemoteSrcUrl(); this.port = config.getRemoteSrcPort(); - this.privateKey = config.getRemoteSrcKeyFile(); + this.privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); this.paths = config.getPaths(); this.type = config.getRemoteType(); } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index da289114..2ccf5a4e 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -18,7 +18,7 @@ public class SASTParam { public static final String SAST_GET_QUEUED_SCANS = "sast/scansQueue?projectId={projectId}"; - public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/{projectId}/sourceCode/remoteSettings/{sourceType}";//todo maybe with ssh? + public static final String SAST_CREATE_REMOTE_SOURCE_SCAN = "projects/%s/sourceCode/remoteSettings/%s/%s"; From 284c4771972d1bedec15bab8fc51bfe6af6f0c21 Mon Sep 17 00:00:00 2001 From: idana Date: Mon, 23 Sep 2019 17:11:20 +0300 Subject: [PATCH 155/473] removed double space (cherry picked from commit 295b361bfb4b24ed5553b0cfe74e31b92c132c6b) --- src/main/java/com/cx/restclient/common/ShragaUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/common/ShragaUtils.java b/src/main/java/com/cx/restclient/common/ShragaUtils.java index b47afd06..c599d30c 100644 --- a/src/main/java/com/cx/restclient/common/ShragaUtils.java +++ b/src/main/java/com/cx/restclient/common/ShragaUtils.java @@ -45,9 +45,9 @@ public static boolean isThresholdExceeded(CxScanConfig config, SASTResults sastR thresholdExceeded |= isSeverityExceeded(sastResults.getLow(), config.getSastLowThreshold(), res, "low", "CxSAST "); } if (config.isOSAThresholdEffectivelyEnabled() && osaResults != null && osaResults.isOsaResultsReady()) { - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalHighVulnerabilities(), config.getOsaHighThreshold(), res, "high", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalMediumVulnerabilities(), config.getOsaMediumThreshold(), res, "medium", "CxOSA "); - thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalLowVulnerabilities(), config.getOsaLowThreshold(), res, "low", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalHighVulnerabilities(), config.getOsaHighThreshold(), res, "high", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalMediumVulnerabilities(), config.getOsaMediumThreshold(), res, "medium", "CxOSA "); + thresholdExceeded |= isSeverityExceeded(osaResults.getResults().getTotalLowVulnerabilities(), config.getOsaLowThreshold(), res, "low", "CxOSA "); } return thresholdExceeded; } From 884875e059673756aea89b441045fbc793afcfe5 Mon Sep 17 00:00:00 2001 From: idana Date: Thu, 3 Oct 2019 14:14:30 +0300 Subject: [PATCH 156/473] added option to specify separate location for osa source (cherry picked from commit b6859d29f19e968cec54b148be18bba06c013841) --- src/main/java/com/cx/restclient/CxOSAClient.java | 1 + .../com/cx/restclient/configuration/CxScanConfig.java | 9 +++++++++ src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 5 ++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 0d07ad43..0bf33dc4 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -82,6 +82,7 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getOsaArchiveIncludePatterns(), config.getSourceDir(), config.getOsaRunInstall(), + config.getOsaLocationPath(), log); } ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 43b41423..a3208dff 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -23,6 +23,7 @@ public class CxScanConfig implements Serializable { private boolean useSSOLogin = false; private String sourceDir; + private String osaLocationPath; private File reportsDir; private String username; private String password; @@ -152,6 +153,14 @@ public void setSourceDir(String sourceDir) { this.sourceDir = sourceDir; } + public String getOsaLocationPath() { + return osaLocationPath; + } + + public void setOsaLocationPath(String osaLocationPath) { + this.osaLocationPath = osaLocationPath; + } + public File getReportsDir() { return reportsDir; } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 3e85ed6d..00f632d5 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -48,7 +48,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -97,8 +97,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S setResolveDependencies(ret,"false"); } - ret.put("d", scanFolder); - + ret.put("d", osaLocationPath == null ? scanFolder : osaLocationPath); return ret; } From 95f5ff857888495ec09d15f44d20729ea93b49cc Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Tue, 5 Nov 2019 15:19:58 +0200 Subject: [PATCH 157/473] Implemented missing common features used in CLI 9.2 Plugin (cherry picked from commit a023b2f13f5bcabf140f821237d974cd46b68f1c) --- pom.xml | 2 +- .../java/com/cx/restclient/CxOSAClient.java | 45 +++---- .../java/com/cx/restclient/CxSASTClient.java | 92 +++++++++------ .../java/com/cx/restclient/common/Waiter.java | 5 +- .../configuration/CxScanConfig.java | 91 +++++++++++++- .../restclient/dto/RemoteSourceRequest.java | 111 ++++++++++++------ .../com/cx/restclient/osa/utils/OSAUtils.java | 53 +++++++-- .../cx/restclient/sast/utils/SASTUtils.java | 19 +++ 8 files changed, 309 insertions(+), 109 deletions(-) diff --git a/pom.xml b/pom.xml index 7a2bfca5..c5d73853 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0-SNAPSHOT + 9.20.0-TEST-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 0bf33dc4..50cb0f04 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -33,28 +33,30 @@ class CxOSAClient { private CxHttpClient httpClient; private Logger log; private CxScanConfig config; - - private Waiter osaWaiter = new Waiter("CxOSA scan", 20) { - @Override - public OSAScanStatus getStatus(String id) throws CxClientException, IOException { - return getOSAScanStatus(id); - } - - @Override - public void printProgress(OSAScanStatus scanStatus) { - printOSAProgress(scanStatus, getStartTimeSec()); - } - - @Override - public OSAScanStatus resolveStatus(OSAScanStatus scanStatus) throws CxClientException { - return resolveOSAStatus(scanStatus); - } - }; + private Waiter osaWaiter; public CxOSAClient(CxHttpClient client, Logger log, CxScanConfig config) { this.log = log; this.httpClient = client; this.config = config; + int interval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; + int retry = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + osaWaiter = new Waiter("CxOSA scan", interval, retry) { + @Override + public OSAScanStatus getStatus(String id) throws CxClientException, IOException { + return getOSAScanStatus(id); + } + + @Override + public void printProgress(OSAScanStatus scanStatus) { + printOSAProgress(scanStatus, getStartTimeSec()); + } + + @Override + public OSAScanStatus resolveStatus(OSAScanStatus scanStatus) throws CxClientException { + return resolveOSAStatus(scanStatus); + } + }; } //API @@ -83,13 +85,14 @@ private String resolveOSADependencies() throws JsonProcessingException { config.getSourceDir(), config.getOsaRunInstall(), config.getOsaLocationPath(), + config.getOsaScanDepth(), log); } ObjectMapper mapper = new ObjectMapper(); log.info("Scanner properties: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(scannerProperties.toString())); ComponentScan componentScan = new ComponentScan(scannerProperties); String osaDependenciesJson = componentScan.scan(); - OSAUtils.writeToOsaListToFile(config.getReportsDir(), osaDependenciesJson, log); + OSAUtils.writeToOsaListToFile(OSAUtils.getWorkDirectory(config.getReportsDir(), config.getOsaGenerateJsonReport()), osaDependenciesJson, log); return osaDependenciesJson; } @@ -109,9 +112,9 @@ public OSAResults getOSAResults(String scanId, long projectId) throws CxClientEx OSAUtils.printOSAResultsToConsole(osaResults, config.getEnablePolicyViolations(), log); if (config.getReportsDir() != null) { - writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), log); - writeJsonToFile(OSA_LIBRARIES_NAME, osaResults.getOsaLibraries(), config.getReportsDir(), log); - writeJsonToFile(OSA_VULNERABILITIES_NAME, osaResults.getOsaVulnerabilities(), config.getReportsDir(), log); + writeJsonToFile(OSA_SUMMARY_NAME, osaResults.getResults(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); + writeJsonToFile(OSA_LIBRARIES_NAME, osaResults.getOsaLibraries(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); + writeJsonToFile(OSA_VULNERABILITIES_NAME, osaResults.getOsaVulnerabilities(), config.getReportsDir(), config.getOsaGenerateJsonReport(), log); } return osaResults; diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 9f09a6cb..5eb707c9 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -11,7 +11,6 @@ import com.cx.restclient.sast.utils.SASTUtils; import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.google.gson.Gson; -import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; @@ -48,25 +47,9 @@ class CxSASTClient { private CxScanConfig config; private int reportTimeoutSec = 5000; private int cxARMTimeoutSec = 1000; + private Waiter sastWaiter; - private Waiter sastWaiter = new Waiter("CxSAST scan", 20) { - @Override - public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { - return getSASTScanStatus(id); - } - - @Override - public void printProgress(ResponseQueueScanStatus scanStatus) { - printSASTProgress(scanStatus, getStartTimeSec()); - } - - @Override - public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { - return resolveSASTStatus(scanStatus); - } - }; - - private Waiter reportWaiter = new Waiter("Scan report", 10) { + private Waiter reportWaiter = new Waiter("Scan report", 10, 3) { @Override public ReportStatus getStatus(String id) throws CxClientException, IOException { return getReportStatus(id); @@ -83,7 +66,7 @@ public ReportStatus resolveStatus(ReportStatus reportStatus) throws CxClientExce } }; - private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20) { + private Waiter cxARMWaiter = new Waiter("CxARM policy violations", 20, 3) { @Override public CxARMStatus getStatus(String id) throws CxClientException, IOException { return getCxARMStatus(id); @@ -104,6 +87,24 @@ public CxARMStatus resolveStatus(CxARMStatus cxARMStatus) throws CxClientExcepti this.log = log; this.httpClient = client; this.config = config; + int interval = config.getProgressInterval() != null ? config.getProgressInterval() : 20; + int retry = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; + sastWaiter = new Waiter("CxSAST scan", interval, retry) { + @Override + public ResponseQueueScanStatus getStatus(String id) throws CxClientException, IOException { + return getSASTScanStatus(id); + } + + @Override + public void printProgress(ResponseQueueScanStatus scanStatus) { + printSASTProgress(scanStatus, getStartTimeSec()); + } + + @Override + public ResponseQueueScanStatus resolveStatus(ResponseQueueScanStatus scanStatus) throws CxClientException { + return resolveSASTStatus(scanStatus); + } + }; } //**------ API ------**// @@ -137,7 +138,8 @@ private long createLocalSASTScan(long projectId) throws IOException, CxClientExc //prepare sources for scan if (config.getZipFile() == null) { log.info("Zipping sources"); - File zipTempFile = CxZipUtils.zipWorkspaceFolder(config, MAX_ZIP_SIZE_BYTES, log); + Long maxZipSize = config.getMaxZipSize() != null ? config.getMaxZipSize() * 1024 * 1024 : MAX_ZIP_SIZE_BYTES; + File zipTempFile = CxZipUtils.zipWorkspaceFolder(config, maxZipSize, log); //Upload zipped source file uploadZipFile(zipTempFile, projectId); deleteTempZipFile(zipTempFile, log); @@ -163,16 +165,23 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient switch (type) { case SVN: + if (req.getPrivateKey() != null && req.getPrivateKey().length > 1) { + isSSH = true; + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null) + .addTextBody("absoluteUrl", req.getUri().getAbsoluteUrl()) + .addTextBody("port", String.valueOf(req.getUri().getPort())) + .addTextBody("paths", config.getSourceDir()); //todo add paths to req OR using without + entity = builder.build(); + } else { + entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); + } + break; case TFS: - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.APPLICATION_JSON, null); - builder.addTextBody("absoluteUrl", req.getUrl(), ContentType.APPLICATION_JSON); - builder.addTextBody("port", String.valueOf(req.getPort()), ContentType.APPLICATION_JSON); - builder.addTextBody("paths", StringUtils.join(req.getPaths(), ";"), ContentType.APPLICATION_JSON); - entity = builder.build(); + entity = new StringEntity(convertToJson(req), ContentType.APPLICATION_JSON); break; case PERFORCE: - if (config.getPreforceMode() != null) { + if (config.getPerforceMode() != null) { req.setBrowseMode("Workspace"); } else { req.setBrowseMode("Depot"); @@ -183,17 +192,17 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity(new Gson().toJson(req), StandardCharsets.UTF_8); break; case GIT: - if (req.getPrivateKey().length == 0) { + if (req.getPrivateKey() == null || req.getPrivateKey().length < 1) { Map content = new HashMap<>(); - content.put("url", config.getRemoteSrcUrl()); + content.put("url", req.getUri().getAbsoluteUrl()); content.put("branch", config.getRemoteSrcBranch()); entity = new StringEntity(new JSONObject(content).toString(), StandardCharsets.UTF_8); } else { isSSH = true; - builder = MultipartEntityBuilder.create(); - builder.addTextBody("url", req.getUrl(), ContentType.APPLICATION_JSON); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("url", req.getUri().getAbsoluteUrl(), ContentType.APPLICATION_JSON); builder.addTextBody("branch", config.getRemoteSrcBranch(), ContentType.APPLICATION_JSON); //todo add branch to req OR using without this else?? - builder.addBinaryBody("privateKey", new byte[0], ContentType.MULTIPART_FORM_DATA, null); + builder.addBinaryBody("privateKey", req.getPrivateKey(), ContentType.MULTIPART_FORM_DATA, null); entity = builder.build(); } break; @@ -242,6 +251,20 @@ public SASTResults waitForSASTResults(long scanId, long projectId) throws Interr sastResults.setPdfFileName(pdfFileName); } } + // CLI report/s + else if (!config.getReports().isEmpty()) { + for (Map.Entry report : config.getReports().entrySet()) { + if (report != null) { + log.info("Generating " + report.getKey().value() + " report"); + byte[] scanReport = getScanReport(sastResults.getScanId(), report.getKey(), CONTENT_TYPE_APPLICATION_PDF_V1); + writeReport(scanReport, report.getValue(), log); + if (report.getKey().value().equals("PDF")) { + sastResults.setPDFReport(scanReport); + sastResults.setPdfFileName(report.getValue()); + } + } + } + } return sastResults; } @@ -327,9 +350,10 @@ private void defineScanSetting(ScanSettingRequest scanSetting) throws IOExceptio } private void uploadZipFile(File zipFile, long projectId) throws CxClientException, IOException { + InputStreamBody streamBody = new InputStreamBody(new FileInputStream(zipFile.getAbsoluteFile()), ContentType.APPLICATION_OCTET_STREAM, "zippedSource"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - builder.addBinaryBody("zippedSource", zipFile); + builder.addPart("zippedSource", streamBody); HttpEntity entity = builder.build(); httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, entity, null, 204, "upload ZIP file"); } diff --git a/src/main/java/com/cx/restclient/common/Waiter.java b/src/main/java/com/cx/restclient/common/Waiter.java index 5d87a7ed..5a912db5 100644 --- a/src/main/java/com/cx/restclient/common/Waiter.java +++ b/src/main/java/com/cx/restclient/common/Waiter.java @@ -13,13 +13,14 @@ */ public abstract class Waiter { - private int retry = 3; + private int retry; private String scanType; private int sleepIntervalSec; - public Waiter(String scanType, int interval) { + public Waiter(String scanType, int interval, int retry) { this.scanType = scanType; this.sleepIntervalSec = interval; + this.retry = retry; } private long startTimeSec; diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index a3208dff..a3a7e207 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,10 +2,13 @@ import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.RemoteSourceTypes; +import com.cx.restclient.sast.dto.ReportType; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; /** @@ -25,6 +28,8 @@ public class CxScanConfig implements Serializable { private String sourceDir; private String osaLocationPath; private File reportsDir; + // Map / (e.g. PDF to its file path) + private Map reports = new HashMap<>(); private String username; private String password; private String refreshToken; @@ -81,7 +86,15 @@ public class CxScanConfig implements Serializable { private int remoteSrcPort; private byte[] remoteSrcKeyFile; private String remoteSrcBranch; - private String preforceMode; + private String perforceMode; + + // CLI config properties + private Integer progressInterval; + private Integer osaProgressInterval; + private Integer connectionRetries; + private String osaScanDepth; + private Integer maxZipSize; + private String defaultProjectName; public CxScanConfig() { } @@ -593,12 +606,12 @@ public void setRemoteSrcBranch(String remoteSrcBranch) { this.remoteSrcBranch = remoteSrcBranch; } - public String getPreforceMode() { - return preforceMode; + public String getPerforceMode() { + return perforceMode; } - public void setPreforceMode(String preforceMode) { - this.preforceMode = preforceMode; + public void setPerforceMode(String perforceMode) { + this.perforceMode = perforceMode; } public Boolean getGenerateXmlReport() { @@ -616,4 +629,72 @@ public CxVersion getCxVersion() { public void setCxVersion(CxVersion cxVersion) { this.cxVersion = cxVersion; } + + public Integer getProgressInterval() { + return progressInterval; + } + + public void setProgressInterval(Integer progressInterval) { + this.progressInterval = progressInterval; + } + + public Integer getOsaProgressInterval() { + return osaProgressInterval; + } + + public void setOsaProgressInterval(Integer osaProgressInterval) { + this.osaProgressInterval = osaProgressInterval; + } + + public Integer getConnectionRetries() { + return connectionRetries; + } + + public void setConnectionRetries(Integer connectionRetries) { + this.connectionRetries = connectionRetries; + } + + public String getOsaScanDepth() { + return osaScanDepth; + } + + public void setOsaScanDepth(String osaScanDepth) { + this.osaScanDepth = osaScanDepth; + } + + public Integer getMaxZipSize() { + return maxZipSize; + } + + public void setMaxZipSize(Integer maxZipSize) { + this.maxZipSize = maxZipSize; + } + + public String getDefaultProjectName() { + return defaultProjectName; + } + + public void setDefaultProjectName(String defaultProjectName) { + this.defaultProjectName = defaultProjectName; + } + + public Map getReports() { + return reports; + } + + public void addPDFReport(String pdfReportPath) { + reports.put(ReportType.PDF, pdfReportPath); + } + + public void addXMLReport(String xmlReportPath) { + reports.put(ReportType.XML, xmlReportPath); + } + + public void addCSVReport(String csvReportPath) { + reports.put(ReportType.CSV, csvReportPath); + } + + public void addRTFReport(String rtfReportPath) { + reports.put(ReportType.RTF, rtfReportPath); + } } diff --git a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java index 06ed5316..23f755b9 100644 --- a/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java +++ b/src/main/java/com/cx/restclient/dto/RemoteSourceRequest.java @@ -1,48 +1,100 @@ package com.cx.restclient.dto; import com.cx.restclient.configuration.CxScanConfig; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; /** * Created by Galn on 11/25/2018. */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties({ "type" }) public class RemoteSourceRequest { - String url; - int port; + + public class Credentials { + private String userName; + private String password; + + public Credentials(String userName, String password) { + this.userName = userName; + this.password = password; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } + + public class Uri { + private String absoluteUrl; + private int port; + + public Uri(String absoluteUrl, int port) { + this.absoluteUrl = absoluteUrl; + this.port = port; + } + + public String getAbsoluteUrl() { + return absoluteUrl; + } + + public void setAbsoluteUrl(String absoluteUrl) { + this.absoluteUrl = absoluteUrl; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + } + + private Credentials credentials; + private Uri uri; private byte[] privateKey; private String[] paths; - private String userName; - private String password; private RemoteSourceTypes type; - private transient String browseMode; - ; + private String browseMode; public RemoteSourceRequest() { } public RemoteSourceRequest(CxScanConfig config) { - this.userName = config.getRemoteSrcUser(); - this.password = config.getRemoteSrcPass(); - this.url = config.getRemoteSrcUrl(); - this.port = config.getRemoteSrcPort(); - this.privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); - this.paths = config.getPaths(); - this.type = config.getRemoteType(); + credentials = new Credentials(config.getRemoteSrcUser(), config.getRemoteSrcPass()); + uri = new Uri(config.getRemoteSrcUrl(), config.getRemoteSrcPort()); + privateKey = config.getRemoteSrcKeyFile() == null ? new byte[0] : config.getRemoteSrcKeyFile(); + paths = config.getPaths(); + type = config.getRemoteType(); } - public String getUrl() { - return url; + public Credentials getCredentials() { + return credentials; } - public void setUrl(String absoluteUrl) { - this.url = absoluteUrl; + public void setCredentials(Credentials credentials) { + this.credentials = credentials; } - public int getPort() { - return port; + public Uri getUri() { + return uri; } - public void setPort(int port) { - this.port = port; + public void setUri(Uri uri) { + this.uri = uri; } public byte[] getPrivateKey() { @@ -61,22 +113,6 @@ public void setPaths(String[] paths) { this.paths = paths; } - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - public RemoteSourceTypes getType() { return type; } @@ -92,5 +128,4 @@ public String getBrowseMode() { public void setBrowseMode(String browseMode) { this.browseMode = browseMode; } - } diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 00f632d5..8e8b0f9c 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -48,7 +48,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, String osaScanDepth, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -86,7 +86,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("archiveIncludes", DEFAULT_ARCHIVE_INCLUDES); } - ret.put("archiveExtractionDepth", "4"); + ret.put("archiveExtractionDepth", StringUtils.isNotEmpty(osaScanDepth) ? osaScanDepth : "4"); if (installBeforeScan) { ret.put("npm.runPreStep", "true"); @@ -132,15 +132,52 @@ public static void printOSAResultsToConsole(OSAResults osaResults, boolean enabl log.info("-----------------------------------------------------------------------------------------"); } - public static void writeJsonToFile(String name, Object jsonObj, File workDirectory, Logger log) { + public static File getWorkDirectory(File filePath, Boolean osaGenerateJsonReport) { + if (filePath == null) { + return null; + } + + if (!osaGenerateJsonReport) { + return filePath; + } + + File workDirectory; + if (!filePath.isAbsolute()) { + workDirectory = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION); + } + else { + workDirectory = filePath.getParentFile(); + } + if (!workDirectory.exists()) { + workDirectory.mkdirs(); + } + + return workDirectory; + } + + public static void writeJsonToFile(String name, Object jsonObj, File workDirectory, Boolean cliOsaGenerateJsonReport, Logger log) { try { ObjectMapper objectMapper = new ObjectMapper(); - String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); - String fileName = name + "_" + now + ".json"; - File jsonFile = new File(workDirectory + CX_REPORT_LOCATION, fileName); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj); - FileUtils.writeStringToFile(jsonFile, json); - log.info(name + " json location: " + workDirectory + CX_REPORT_LOCATION + File.separator + fileName); + + if(cliOsaGenerateJsonReport) { + workDirectory = new File(workDirectory.getPath().replace(".json", "_" + name + ".json")); + if (!workDirectory.isAbsolute()) { + workDirectory = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION + File.separator + workDirectory); + } + if (!workDirectory.getParentFile().exists()) { + workDirectory.getParentFile().mkdirs(); + } + FileUtils.writeStringToFile(workDirectory, json); + log.info(name + " json location: " + workDirectory); + } + else { + String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); + String fileName = name + "_" + now + ".json"; + File jsonFile = new File(workDirectory + CX_REPORT_LOCATION, fileName); + FileUtils.writeStringToFile(jsonFile, json); + log.info(name + " json location: " + workDirectory + CX_REPORT_LOCATION + File.separator + fileName); + } } catch (Exception ex) { log.warn("Failed to write OSA JSON report (" + name + ") to file: " + ex.getMessage()); } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 7eb55c5c..18c73505 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -77,4 +77,23 @@ public static String writePDFReport(byte[] scanReport, File workspace, String pd } return pdfFileName; } + + // CLI Report/s + public static void writeReport(byte[] scanReport, String reportName, Logger log) { + try { + File reportFile = new File(reportName); + if (!reportFile.isAbsolute()) { + reportFile = new File(System.getProperty("user.dir") + CX_REPORT_LOCATION + File.separator + reportFile); + } + + if (!reportFile.getParentFile().exists()) { + reportFile.getParentFile().mkdirs(); + } + + FileUtils.writeByteArrayToFile(reportFile, scanReport); + log.info("report location: " + reportFile.getAbsolutePath()); + } catch (Exception e) { + log.error("Failed to write report: ", e.getMessage()); + } + } } From 63bf2e0b502721f2d763ebbf4d1c984e49b4a0e2 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Thu, 7 Nov 2019 13:41:19 +0200 Subject: [PATCH 158/473] jdk11 support (cherry picked from commit 46f7d9c12e62c879faaf8f6faebba342ed6c78aa) --- pom.xml | 39 ++++++------------- .../com/cx/restclient/osa/dto/OSAResults.java | 2 + .../cx/restclient/sast/utils/SASTUtils.java | 8 +++- .../com/cx/restclient/sast/dto/jaxb.index | 1 + .../cx/restclient/sast/dto/jaxb.properties | 2 + 5 files changed, 23 insertions(+), 29 deletions(-) create mode 100644 src/main/resources/com/cx/restclient/sast/dto/jaxb.index create mode 100644 src/main/resources/com/cx/restclient/sast/dto/jaxb.properties diff --git a/pom.xml b/pom.xml index c5d73853..675a88c5 100644 --- a/pom.xml +++ b/pom.xml @@ -174,40 +174,25 @@ - - - com.sun.activation - javax.activation - ${javax.activation.version} - - - - javax.xml.bind - jaxb-api - ${jaxb.api.version} - - - - com.sun.xml.bind - jaxb-core - ${jaxb.api.version} - - - - com.sun.xml.bind - jaxb-impl - ${jaxb.api.version} - - org.projectlombok lombok ${lombok.version} provided + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.2 + - - + + + org.glassfish.jaxb + jaxb-runtime + 2.3.2 + diff --git a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java index 18934abc..fbceb19f 100644 --- a/src/main/java/com/cx/restclient/osa/dto/OSAResults.java +++ b/src/main/java/com/cx/restclient/osa/dto/OSAResults.java @@ -1,6 +1,7 @@ package com.cx.restclient.osa.dto; import com.cx.restclient.cxArm.dto.Policy; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.util.ArrayList; @@ -14,6 +15,7 @@ /** * Created by Galn on 07/02/2018. */ +@JsonIgnoreProperties(ignoreUnknown = true) public class OSAResults implements Serializable { private String osaScanId; private OSASummaryResults results; diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java index 18c73505..57dd9292 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTUtils.java @@ -3,11 +3,12 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.CxXMLResults; import com.cx.restclient.sast.dto.SASTResults; +import com.sun.xml.bind.v2.JAXBContextFactory; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; - +import java.util.Collections; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -36,7 +37,10 @@ public static CxXMLResults convertToXMLResult(byte[] cxReport) throws CxClientEx CxXMLResults reportObj = null; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cxReport); try { - JAXBContext jaxbContext = JAXBContext.newInstance(CxXMLResults.class); + + JAXBContextFactory jaxbContextFactory = new JAXBContextFactory(); + JAXBContext jaxbContext = jaxbContextFactory.createContext(CxXMLResults.class.getPackage().getName(), + CxXMLResults.class.getClassLoader(),Collections.emptyMap()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); reportObj = (CxXMLResults) unmarshaller.unmarshal(byteArrayInputStream); diff --git a/src/main/resources/com/cx/restclient/sast/dto/jaxb.index b/src/main/resources/com/cx/restclient/sast/dto/jaxb.index new file mode 100644 index 00000000..7436e11f --- /dev/null +++ b/src/main/resources/com/cx/restclient/sast/dto/jaxb.index @@ -0,0 +1 @@ +CxXMLResults \ No newline at end of file diff --git a/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties b/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties new file mode 100644 index 00000000..47e77158 --- /dev/null +++ b/src/main/resources/com/cx/restclient/sast/dto/jaxb.properties @@ -0,0 +1,2 @@ +javax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.JAXBContextFactory +javax.xml.bind.context.factory=com.sun.xml.bind.v2.JAXBContextFactory \ No newline at end of file From 42a5950676272d376bb7193120ff3eccedaef16a Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Wed, 13 Nov 2019 13:52:32 +0200 Subject: [PATCH 159/473] merging 9.00->9.20 --- src/main/java/com/cx/restclient/CxSASTClient.java | 2 ++ src/main/java/com/cx/restclient/CxShragaClient.java | 5 +++-- .../com/cx/restclient/httpClient/CxHttpClient.java | 12 ++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 5eb707c9..91951cd8 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -16,10 +16,12 @@ import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.InputStreamBody; import org.json.JSONObject; import org.slf4j.Logger; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 9dd60f39..536b3bb3 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -57,7 +57,8 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro config.getUsername(), config.getPassword(), config.getCxOrigin(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), config.getRefreshToken(), log, proxyHost, proxyPort, proxyUser, proxyPassword); @@ -76,7 +77,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.isDisableCertificateValidation(), config.isUseSSOLogin(), config.getRefreshToken(), - config.isDisableCertificateValidation(), config.isUseSSOLogin(), log); + log); sastClient = new CxSASTClient(httpClient, log, config); osaClient = new CxOSAClient(httpClient, log, config); } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index df2b545a..489a77ee 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -83,17 +83,17 @@ public class CxHttpClient { private String rootUri; private final String username; private final String password; - private final String refreshtoken; + private final String refreshToken; private String cxOrigin; private Boolean useSSo; public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, String refreshtoken, Logger logi, + boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { this.log = logi; this.username = username; this.password = password; - this.refreshtoken = refreshtoken; + this.refreshToken = refreshToken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; this.useSSo = isSSO; @@ -124,7 +124,7 @@ public CxHttpClient(String hostname, String username, String password, String or this.log = logi; this.username = username; this.password = password; - this.refreshtoken = refreshToken; + this.refreshToken = refreshToken; this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); this.cxOrigin = origin; this.useSSo = isSSO; @@ -235,7 +235,7 @@ private static Registry getAuthSchemeProviderRegistry() { } public void login() throws IOException, CxClientException { - if (refreshtoken != null) { + if (refreshToken != null) { token = getAccessTokenFromRefreshToken(); } else if (useSSo) { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); @@ -310,7 +310,7 @@ private UrlEncodedFormEntity generateTokenFromRefreshEntity(ClientType clientTyp parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); parameters.add(new BasicNameValuePair("client_secret", clientType.getClientSecret())); - parameters.add(new BasicNameValuePair("refresh_token", refreshtoken)); + parameters.add(new BasicNameValuePair("refresh_token", refreshToken)); return new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8.name()); } From 94cb1b4e862c8af20e495e0678bec650fa4bba13 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 21 Nov 2019 10:26:01 +0200 Subject: [PATCH 160/473] Proper sorting order for Severity values. Exposed isPolicyViolated property. --- .../com/cx/restclient/dto/scansummary/ScanSummary.java | 8 ++++++-- .../java/com/cx/restclient/dto/scansummary/Severity.java | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java index cd7d5aca..b3bd020f 100644 --- a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java +++ b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java @@ -31,7 +31,7 @@ public ScanSummary(CxScanConfig config, ScanResults scanResults) { addNewResultThresholdErrors(config, scanResults.getSastResults()); - policyViolated = isPolicyViolated(config, scanResults); + policyViolated = determinePolicyViolation(config, scanResults); } @Override @@ -67,6 +67,10 @@ public boolean hasErrors() { return !thresholdErrors.isEmpty() || !newResultThresholdErrors.isEmpty() || policyViolated; } + public boolean isPolicyViolated() { + return policyViolated; + } + public boolean isSastThresholdExceeded() { return thresholdErrors.stream().anyMatch(error -> error.getSource() == ErrorSource.SAST); } @@ -148,7 +152,7 @@ private void addNewResultThresholdErrors(CxScanConfig config, SASTResults sastRe } } - private static boolean isPolicyViolated(CxScanConfig config, ScanResults scanResults) { + private static boolean determinePolicyViolation(CxScanConfig config, ScanResults scanResults) { DependencyScanResults dependencyScanResults = scanResults.getDependencyScanResults(); SASTResults sastResults = scanResults.getSastResults(); diff --git a/src/main/java/com/cx/restclient/dto/scansummary/Severity.java b/src/main/java/com/cx/restclient/dto/scansummary/Severity.java index 3413ac0c..5773df9e 100644 --- a/src/main/java/com/cx/restclient/dto/scansummary/Severity.java +++ b/src/main/java/com/cx/restclient/dto/scansummary/Severity.java @@ -1,7 +1,7 @@ package com.cx.restclient.dto.scansummary; public enum Severity { - HIGH, + LOW, MEDIUM, - LOW + HIGH } From 92b79dd52b2ff270680348399924d7f183093b66 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 25 Nov 2019 12:34:56 +0200 Subject: [PATCH 161/473] AB#327: fixes after merge with 9_00_master-integration-branch. --- .../java/com/cx/restclient/CxOSAClient.java | 1 - .../java/com/cx/restclient/CxSASTClient.java | 9 +-- .../com/cx/restclient/CxShragaClient.java | 17 ++-- .../configuration/CxScanConfig.java | 8 -- .../restclient/httpClient/CxHttpClient.java | 77 +++++-------------- .../com/cx/restclient/osa/utils/OSAUtils.java | 4 +- .../connection/ProjectScanTests.java | 35 ++++----- src/test/java/resources/config.properties | 2 - src/test/resources/config.properties | 5 ++ 9 files changed, 51 insertions(+), 107 deletions(-) delete mode 100644 src/test/java/resources/config.properties create mode 100644 src/test/resources/config.properties diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index ad676e7f..4ed1074e 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -44,7 +44,6 @@ public void setProjectId(long projectId) { this.projectId = projectId; } - @Override public OSAScanStatus getStatus(String id) throws CxClientException, IOException { return getOSAScanStatus(id); } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 384855a2..ba1d8f2d 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -204,11 +204,6 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient } createRemoteSourceRequest(projectId, entity, type.value(), isSSH); - CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); - log.info("Sending SAST scan request"); - CxID createScanResponse = createScan(scanRequest); - log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); - return createScan(projectId); } @@ -350,7 +345,7 @@ private void uploadZipFile(File zipFile, long projectId) throws CxClientExceptio httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, entity, null, 204, "upload ZIP file"); } - private CxID createScan(long projectId) throws CxClientException, IOException { + private long createScan(long projectId) throws CxClientException, IOException { CreateScanRequest scanRequest = new CreateScanRequest(projectId, config.getIncremental(), config.getPublic(), config.getForceScan(), config.getScanComment() == null ? "" : config.getScanComment()); log.info("Sending SAST scan request"); @@ -358,7 +353,7 @@ private CxID createScan(long projectId) throws CxClientException, IOException { CxID createScanResponse = httpClient.postRequest(SAST_CREATE_SCAN, CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 201, "create new SAST Scan"); log.info(String.format("SAST Scan created successfully. Link to project state: " + config.getUrl() + LINK_FORMAT, projectId)); - return createScanResponse; + return createScanResponse.getId(); } private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index b2f34941..0d323d05 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -52,7 +52,7 @@ public class CxShragaClient { private DependencyScanner dependencyScanner; public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int proxyPort, - String proxyUser, String proxyPassword) throws MalformedURLException { + String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this.config = config; this.log = log; this.httpClient = new CxHttpClient( @@ -71,8 +71,12 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro } } + public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { + this(config, log, null, 0, null, null); + } + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, - Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log, proxyHost, proxyPort, proxyUser, proxyPassword); } @@ -227,15 +231,6 @@ public void revokeToken(String token) throws IOException, CxClientException { httpClient.revokeToken(token); } - public String getToken() throws IOException, CxClientException { - final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(ClientType.CLI); - return tokenLoginResponse.getRefresh_token(); - } - - public void revokeToken(String token) throws IOException, CxClientException { - httpClient.revokeToken(token); - } - public void getCxVersion() throws IOException, CxClientException { try { config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 9f71ff91..85553587 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -126,14 +126,6 @@ public void setSastEnabled(Boolean sastEnabled) { this.sastEnabled = sastEnabled; } - public Boolean getOsaEnabled() { - return osaEnabled; - } - - public void setOsaEnabled(Boolean osaEnabled) { - this.osaEnabled = osaEnabled; - } - public String getCxOrigin() { return cxOrigin; } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 66dd3fd3..b276bb6c 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -83,77 +83,49 @@ public class CxHttpClient { private Logger log; private TokenLoginResponse token; private String rootUri; - private final String username; - private final String password; - private final String refreshToken; private String cxOrigin; private Boolean useSSo; private LoginSettings lastLoginSettings; - public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi, + public CxHttpClient(String rootUri, String origin, + boolean disableSSLValidation, boolean isSSO, Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { - this.log = logi; - this.username = username; - this.password = password; - this.refreshToken = refreshToken; - this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); + this.log = log; + this.rootUri = rootUri; this.cxOrigin = origin; this.useSSo = isSSO; //create httpclient HttpClientBuilder cb = HttpClients.custom(); cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - setSSLTls("TLSv1.2", logi); + setSSLTls("TLSv1.2", log); if (disableSSLValidation) { try { cb.setSSLSocketFactory(getSSLSF()); cb.setConnectionManager(getSSLHttpConnManager()); } catch (CxClientException e) { - logi.warn("Failed to disable certificate verification: " + e.getMessage()); + log.warn("Failed to disable certificate verification: " + e.getMessage()); } } else { cb.setConnectionManager(getHttpConnManager()); } cb.setConnectionManagerShared(true); - setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); - cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); - cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); - cb.useSystemProperties(); - apacheClient = cb.build(); - } - public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi) throws MalformedURLException { - this.log = logi; - this.username = username; - this.password = password; - this.refreshToken = refreshToken; - this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); - this.cxOrigin = origin; - this.useSSo = isSSO; - - //create httpclient - HttpClientBuilder cb = HttpClients.custom(); - cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - setSSLTls("TLSv1.2", logi); - if (disableSSLValidation) { - try { - cb.setSSLSocketFactory(getSSLSF()); - cb.setConnectionManager(getSSLHttpConnManager()); - } catch (CxClientException e) { - logi.warn("Failed to disable certificate verification: " + e.getMessage()); - } - } else { - cb.setConnectionManager(getHttpConnManager()); + if (proxyHost != null) { + setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, log); + } + else { + setProxy(cb, log); } - cb.setConnectionManagerShared(true); - setProxy(cb, logi); cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); cb.useSystemProperties(); apacheClient = cb.build(); } + public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) throws MalformedURLException { + this(rootUri, origin, disableSSLValidation, isSSO, log, null, 0, null, null); + } + private static void setCustomProxy(HttpClientBuilder cb, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, Logger logi) { HttpHost proxy = null; if (!isEmpty(proxyHost)) { @@ -239,8 +211,8 @@ private static Registry getAuthSchemeProviderRegistry() { public void login(LoginSettings settings) throws IOException, CxClientException { lastLoginSettings = settings; - if (refreshToken != null) { - token = getAccessTokenFromRefreshToken(); + if (settings.getRefreshToken() != null) { + token = getAccessTokenFromRefreshToken(settings); } else if (useSSo) { HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); @@ -261,17 +233,6 @@ public TokenLoginResponse generateToken(LoginSettings settings) throws IOExcepti } } - public void revokeToken(String token) throws IOException, CxClientException { - UrlEncodedFormEntity requestEntity = generateRevocationEntity(ClientType.CLI, token); - HttpPost post = new HttpPost(rootUri + REVOCATION); - try { - request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, - String.class, HttpStatus.SC_OK, "revocation", false, false); - } catch (CxClientException e) { - throw new CxClientException(String.format("Token revocation failure error was: %s", e.getMessage()), e); - } - } - private TokenLoginResponse getAccessTokenFromRefreshToken(LoginSettings settings) throws IOException, CxClientException { UrlEncodedFormEntity requestEntity = generateTokenFromRefreshEntity(settings); String fullUrl = UrlUtils.parseURLToString(settings.getAccessControlBaseUrl(), AUTHENTICATION); @@ -309,8 +270,8 @@ private UrlEncodedFormEntity generateRevocationEntity(ClientType clientType, Str private UrlEncodedFormEntity generateUrlEncodedFormEntity(LoginSettings settings) throws UnsupportedEncodingException { ClientType clientType = settings.getClientTypeForPasswordAuth(); List parameters = new ArrayList<>(); - parameters.add(new BasicNameValuePair("username", username)); - parameters.add(new BasicNameValuePair("password", password)); + parameters.add(new BasicNameValuePair("username", settings.getUsername())); + parameters.add(new BasicNameValuePair("password", settings.getPassword())); parameters.add(new BasicNameValuePair("grant_type", "password")); parameters.add(new BasicNameValuePair("scope", clientType.getScopes())); parameters.add(new BasicNameValuePair("client_id", clientType.getClientId())); diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 82fca684..85a320d8 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -47,7 +47,7 @@ public static String composeProjectOSASummaryLink(String url, long projectId) { return String.format(url + "/CxWebClient/SPA/#/viewer/project/%s", projectId); } - public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String scanFolder, boolean installBeforeScan, String osaLocationPath, String osaScanDepth, Logger log) { + public static Properties generateOSAScanConfiguration(String folderExclusions, String filterPatterns, String archiveIncludes, String sourceDir, boolean installBeforeScan, String osaScanDepth, Logger log) { Properties ret = new Properties(); filterPatterns = StringUtils.defaultString(filterPatterns); archiveIncludes = StringUtils.defaultString(archiveIncludes); @@ -96,7 +96,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S setResolveDependencies(ret, "false"); } - ret.put("d", osaLocationPath == null ? scanFolder : osaLocationPath); + ret.put("d", sourceDir); return ret; } diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index 48590780..cc14e321 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -2,8 +2,9 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; +import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.SASTResults; import com.cx.utility.TestingUtils; import org.junit.Assert; @@ -14,10 +15,8 @@ import org.slf4j.LoggerFactory; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; -import java.net.URL; import java.util.Properties; @Ignore @@ -35,16 +34,17 @@ public static void initTest() throws IOException { } @Test - public void runOsaScan() throws MalformedURLException { + public void runOsaScan() throws MalformedURLException, CxClientException { CxScanConfig config = initOsaConfig(); client = new CxShragaClient(config, log); try { client.init(); - client.createOSAScan(); - client.waitForOSAResults(); - final OSAResults latestOSAResults = client.getLatestOSAResults(); - Assert.assertNotNull(latestOSAResults.getOsaScanId(), "Expected valid osa scan id"); - } catch (IOException | CxClientException | InterruptedException e) { + client.createDependencyScan(); + client.waitForDependencyScanResults(); + final DependencyScanResults latestOSAResults = client.getLatestDependencyScanResults(); + Assert.assertNotNull(latestOSAResults.getOsaResults()); + Assert.assertNotNull("Expected valid osa scan id", latestOSAResults.getOsaResults().getOsaScanId()); + } catch (IOException | CxClientException e) { e.printStackTrace(); log.error("Error running osa scan: " + e.getMessage()); Assert.fail(e.getMessage()); @@ -52,7 +52,7 @@ public void runOsaScan() throws MalformedURLException { } @Test - public void runSastScan() throws MalformedURLException { + public void runSastScan() throws MalformedURLException, CxClientException { CxScanConfig config = initSastConfig(); client = new CxShragaClient(config, log); try { @@ -60,7 +60,7 @@ public void runSastScan() throws MalformedURLException { client.createSASTScan(); client.waitForSASTResults(); final SASTResults latestSASTResults = client.getLatestSASTResults(); - Assert.assertNotNull(String.valueOf(latestSASTResults.getScanId()), "Expected valid osa scan id"); + Assert.assertNotEquals("Expected valid SAST scan id", 0, latestSASTResults.getScanId()); } catch (IOException | CxClientException | InterruptedException e) { e.printStackTrace(); log.error("Error running sast scan: " + e.getMessage()); @@ -68,7 +68,6 @@ public void runSastScan() throws MalformedURLException { } } - private CxScanConfig initSastConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); @@ -83,7 +82,7 @@ private CxScanConfig initSastConfig() { config.setTeamPath("\\CxServer"); config.setSynchronous(true); config.setGeneratePDFReport(true); - config.setOsaEnabled(false); + config.setDependencyScannerType(DependencyScannerType.NONE); config.setPresetName("Default"); // config.setPresetId(7); @@ -92,20 +91,20 @@ private CxScanConfig initSastConfig() { private CxScanConfig initOsaConfig() { CxScanConfig config = new CxScanConfig(); + config.setDependencyScannerType(DependencyScannerType.OSA); config.setSastEnabled(false); - config.setSourceDir("C:\\sources\\osa\\HighVul"); + config.setSourceDir(props.getProperty("osaSource")); config.setReportsDir(new File("C:\\report")); - config.setUsername("admin1"); - config.setPassword("Cx123456!"); + config.setUsername(props.getProperty("user")); + config.setPassword(props.getProperty("password")); - config.setUrl("http://10.32.1.57"); + config.setUrl(props.getProperty("serverUrl")); config.setCxOrigin("common"); config.setProjectName("osaOnlyScan"); config.setPresetName("Default"); config.setTeamPath("\\CxServer"); config.setSynchronous(true); config.setGeneratePDFReport(true); - config.setOsaEnabled(true); config.setOsaRunInstall(true); config.setOsaThresholdsEnabled(true); diff --git a/src/test/java/resources/config.properties b/src/test/java/resources/config.properties deleted file mode 100644 index b3effecf..00000000 --- a/src/test/java/resources/config.properties +++ /dev/null @@ -1,2 +0,0 @@ -serverUrl=http://10.32.1.57 -sastSource=C:\\sources\\BookStore_Small_CLI \ No newline at end of file diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties new file mode 100644 index 00000000..457872f9 --- /dev/null +++ b/src/test/resources/config.properties @@ -0,0 +1,5 @@ +serverUrl=http://10.32.1.57 +user=myuser +password=mypassword +sastSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource +osaSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource From d1bf45d932d755398febb66d6c7709f0e3f60ccb Mon Sep 17 00:00:00 2001 From: morad Date: Mon, 25 Nov 2019 13:00:48 +0200 Subject: [PATCH 162/473] update fsa version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ef2dfea..7e74da7b 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ com.checkmarx cx-ws-fs-agent - 18.7.2.3 + 18.7.2.4 javax.xml.bind From 32d8e4352f98fb7bfbf2dbf839906bd6357d9799 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 25 Nov 2019 13:16:27 +0200 Subject: [PATCH 163/473] AB#327: Removed getting proxy settings from system properties, because the settings now come from the outside. --- .../restclient/httpClient/CxHttpClient.java | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index b276bb6c..99365d4e 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -67,17 +67,6 @@ * Created by Galn on 05/02/2018. */ public class CxHttpClient { - - private static String HTTP_HOST = System.getProperty("http.proxyHost"); - private static String HTTP_PORT = System.getProperty("http.proxyPort"); - private static String HTTP_USERNAME = System.getProperty("http.proxyUser"); - private static String HTTP_PASSWORD = System.getProperty("http.proxyPassword"); - - private static String HTTPS_HOST = System.getProperty("https.proxyHost"); - private static String HTTPS_PORT = System.getProperty("https.proxyPort"); - private static String HTTPS_USERNAME = System.getProperty("https.proxyUser"); - private static String HTTPS_PASSWORD = System.getProperty("https.proxyPassword"); - private static HttpClient apacheClient; private Logger log; @@ -89,7 +78,7 @@ public class CxHttpClient { public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log, - String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + String proxyHost, int proxyPort, String proxyUser, String proxyPassword) { this.log = log; this.rootUri = rootUri; this.cxOrigin = origin; @@ -113,16 +102,14 @@ public CxHttpClient(String rootUri, String origin, if (proxyHost != null) { setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, log); } - else { - setProxy(cb, log); - } + cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); cb.useSystemProperties(); apacheClient = cb.build(); } - public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) throws MalformedURLException { + public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) { this(rootUri, origin, disableSSLValidation, isSSO, log, null, 0, null, null); } @@ -144,30 +131,6 @@ private static void setCustomProxy(HttpClientBuilder cb, String proxyHost, int p } } - private static void setProxy(HttpClientBuilder cb, Logger logi) { - HttpHost proxyHost = null; - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - if (!isEmpty(HTTPS_HOST) && !isEmpty(HTTPS_PORT)) { - proxyHost = new HttpHost(HTTPS_HOST, Integer.parseInt(HTTPS_PORT), "https"); - if (!isEmpty(HTTPS_USERNAME) && !isEmpty(HTTPS_PASSWORD)) { - credsProvider.setCredentials(new AuthScope(HTTPS_HOST, Integer.parseInt(HTTPS_PORT)), new UsernamePasswordCredentials(HTTPS_USERNAME, HTTPS_PASSWORD)); - cb.setDefaultCredentialsProvider(credsProvider); - } - } else if (!isEmpty(HTTP_HOST) && !isEmpty(HTTP_PORT)) { - proxyHost = new HttpHost(HTTP_HOST, Integer.parseInt(HTTP_PORT), "http"); - if (!isEmpty(HTTP_USERNAME) && !isEmpty(HTTP_PASSWORD)) { - credsProvider.setCredentials(new AuthScope(HTTP_HOST, Integer.parseInt(HTTP_PORT)), new UsernamePasswordCredentials(HTTP_USERNAME, HTTP_PASSWORD)); - cb.setDefaultCredentialsProvider(credsProvider); - } - } - if (proxyHost != null) { - logi.info("Setting proxy for Checkmarx http client"); - cb.setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)); - cb.setProxy(proxyHost); - cb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - } - } - private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { TrustStrategy acceptingTrustStrategy = new TrustAllStrategy(); SSLContext sslContext; From 13817ba339e83313e021eef5485cbad163cab44f Mon Sep 17 00:00:00 2001 From: morad Date: Mon, 25 Nov 2019 15:31:43 +0200 Subject: [PATCH 164/473] update master version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 675a88c5..3d32f802 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0-TEST-SNAPSHOT + 9.20.0-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 59c29c05b929cdd1995a1a287759ea7c8516b586 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 25 Nov 2019 16:22:58 +0200 Subject: [PATCH 165/473] Added a test for SCA scan. --- .../connection/ProjectScanTests.java | 63 ++++++++++++++++--- src/test/resources/config.properties | 4 +- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index cc14e321..08d83fa2 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -6,6 +6,7 @@ import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.SASTResults; +import com.cx.restclient.sca.dto.SCAConfig; import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.BeforeClass; @@ -41,9 +42,10 @@ public void runOsaScan() throws MalformedURLException, CxClientException { client.init(); client.createDependencyScan(); client.waitForDependencyScanResults(); - final DependencyScanResults latestOSAResults = client.getLatestDependencyScanResults(); - Assert.assertNotNull(latestOSAResults.getOsaResults()); - Assert.assertNotNull("Expected valid osa scan id", latestOSAResults.getOsaResults().getOsaScanId()); + final DependencyScanResults results = client.getLatestDependencyScanResults(); + Assert.assertNull(results.getScaResults()); + Assert.assertNotNull(results.getOsaResults()); + Assert.assertNotNull("Expected valid osa scan id", results.getOsaResults().getOsaScanId()); } catch (IOException | CxClientException e) { e.printStackTrace(); log.error("Error running osa scan: " + e.getMessage()); @@ -59,7 +61,7 @@ public void runSastScan() throws MalformedURLException, CxClientException { client.init(); client.createSASTScan(); client.waitForSASTResults(); - final SASTResults latestSASTResults = client.getLatestSASTResults(); + SASTResults latestSASTResults = client.getLatestSASTResults(); Assert.assertNotEquals("Expected valid SAST scan id", 0, latestSASTResults.getScanId()); } catch (IOException | CxClientException | InterruptedException e) { e.printStackTrace(); @@ -68,12 +70,31 @@ public void runSastScan() throws MalformedURLException, CxClientException { } } + @Test + public void runScaScan() throws MalformedURLException, CxClientException { + CxScanConfig config = initScaConfig(); + client = new CxShragaClient(config, log); + try { + client.init(); + client.createDependencyScan(); + DependencyScanResults results = client.waitForDependencyScanResults(); + Assert.assertNotNull(results); + Assert.assertNull(results.getOsaResults()); + Assert.assertNotNull(results.getScaResults()); + Assert.assertNotNull(results.getScaResults().getSummary()); + Assert.assertNotNull(results.getScaResults().getScanId()); + } catch (Exception e) { + log.error("Error running SCA scan: " + e); + Assert.fail(e.getMessage()); + } + } + private CxScanConfig initSastConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); config.setReportsDir(new File("C:\\report")); config.setSourceDir(props.getProperty("sastSource")); - config.setUsername(props.getProperty("user")); + config.setUsername(props.getProperty("username")); config.setPassword(props.getProperty("password")); config.setUrl(props.getProperty("serverUrl")); config.setCxOrigin("common"); @@ -93,12 +114,12 @@ private CxScanConfig initOsaConfig() { CxScanConfig config = new CxScanConfig(); config.setDependencyScannerType(DependencyScannerType.OSA); config.setSastEnabled(false); - config.setSourceDir(props.getProperty("osaSource")); + config.setSourceDir(props.getProperty("dependencyScanSourceDir")); config.setReportsDir(new File("C:\\report")); - config.setUsername(props.getProperty("user")); + config.setUrl(props.getProperty("serverUrl")); + config.setUsername(props.getProperty("username")); config.setPassword(props.getProperty("password")); - config.setUrl(props.getProperty("serverUrl")); config.setCxOrigin("common"); config.setProjectName("osaOnlyScan"); config.setPresetName("Default"); @@ -113,4 +134,30 @@ private CxScanConfig initOsaConfig() { return config; } + private CxScanConfig initScaConfig() { + CxScanConfig config = new CxScanConfig(); + config.setUrl(props.getProperty("serverUrl")); + config.setUsername(props.getProperty("username")); + config.setPassword(props.getProperty("password")); + config.setDependencyScannerType(DependencyScannerType.SCA); + config.setSastEnabled(false); + config.setSourceDir(props.getProperty("dependencyScanSourceDir")); + config.setOsaThresholdsEnabled(true); + config.setProjectName("scaOnlyScan"); + config.setTeamPath("\\CxServer"); + + // Disabling certificate validation, otherwise we'll get an error during SCA login. + // TODO: fix HTTPS logic in CxHttpClient. + config.setDisableCertificateValidation(true); + + SCAConfig sca = new SCAConfig(); + sca.setApiUrl("https://api.lumodev.com"); + sca.setAccessControlUrl("https://upgrade.dev-ac-checkmarx.com"); + sca.setTenant("Checkmarx"); + sca.setUsername(props.getProperty("sca.username")); + sca.setPassword(props.getProperty("sca.password")); + config.setScaConfig(sca); + + return config; + } } diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties index 457872f9..3b642d08 100644 --- a/src/test/resources/config.properties +++ b/src/test/resources/config.properties @@ -2,4 +2,6 @@ serverUrl=http://10.32.1.57 user=myuser password=mypassword sastSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource -osaSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource +dependencyScanSourceDir=c:\\cxdev\\projectsToScan\\SastAndOsaSource +sca.username=myuser +sca.password=mypassword From 0f66bb42481e0c6c646550026905f21c5ff49625 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 26 Nov 2019 12:11:41 +0200 Subject: [PATCH 166/473] Fixed SSL error when disableSSLValidation was set to false. Removed duplicate code. --- .../com/cx/restclient/CxShragaClient.java | 8 +-- .../restclient/httpClient/CxHttpClient.java | 72 ++++++------------- .../connection/ProjectScanTests.java | 4 +- 3 files changed, 29 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 536b3bb3..c62491ad 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -49,7 +49,7 @@ public class CxShragaClient { private OSAResults osaResults = new OSAResults(); public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int proxyPort, - String proxyUser, String proxyPassword) throws MalformedURLException { + String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this.config = config; this.log = log; this.httpClient = new CxHttpClient( @@ -66,7 +66,7 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro osaClient = new CxOSAClient(httpClient, log, config); } - public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException { + public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { this.config = config; this.log = log; this.httpClient = new CxHttpClient( @@ -83,11 +83,11 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept } public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, - Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log, proxyHost, proxyPort, proxyUser, proxyPassword); } - public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException { + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 489a77ee..a52d05c4 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -89,7 +89,7 @@ public class CxHttpClient { public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi, - String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException { + String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this.log = logi; this.username = username; this.password = password; @@ -103,16 +103,23 @@ public CxHttpClient(String hostname, String username, String password, String or setSSLTls("TLSv1.2", logi); if (disableSSLValidation) { try { - cb.setSSLSocketFactory(getSSLSF()); - cb.setConnectionManager(getSSLHttpConnManager()); + cb.setSSLSocketFactory(getTrustAllSSLSocketFactory()); + cb.setConnectionManager(getHttpConnectionManager(true)); } catch (CxClientException e) { logi.warn("Failed to disable certificate verification: " + e.getMessage()); } } else { - cb.setConnectionManager(getHttpConnManager()); + cb.setConnectionManager(getHttpConnectionManager(false)); } cb.setConnectionManagerShared(true); - setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); + + if (proxyHost != null) { + setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); + } + else { + setProxy(cb, logi); + } + cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); cb.useSystemProperties(); @@ -120,35 +127,8 @@ public CxHttpClient(String hostname, String username, String password, String or } public CxHttpClient(String hostname, String username, String password, String origin, - boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi) throws MalformedURLException { - this.log = logi; - this.username = username; - this.password = password; - this.refreshToken = refreshToken; - this.rootUri = UrlUtils.parseURLToString(hostname, "CxRestAPI/"); - this.cxOrigin = origin; - this.useSSo = isSSO; - - //create httpclient - HttpClientBuilder cb = HttpClients.custom(); - cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - setSSLTls("TLSv1.2", logi); - if (disableSSLValidation) { - try { - cb.setSSLSocketFactory(getSSLSF()); - cb.setConnectionManager(getSSLHttpConnManager()); - } catch (CxClientException e) { - logi.warn("Failed to disable certificate verification: " + e.getMessage()); - } - } else { - cb.setConnectionManager(getHttpConnManager()); - } - cb.setConnectionManagerShared(true); - setProxy(cb, logi); - cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); - cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); - cb.useSystemProperties(); - apacheClient = cb.build(); + boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi) throws MalformedURLException, CxClientException { + this(hostname, username, password, origin, disableSSLValidation, isSSO, refreshToken, logi, null, 0, null, null); } private static void setCustomProxy(HttpClientBuilder cb, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, Logger logi) { @@ -193,7 +173,7 @@ private static void setProxy(HttpClientBuilder cb, Logger logi) { } } - private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { + private static SSLConnectionSocketFactory getTrustAllSSLSocketFactory() throws CxClientException { TrustStrategy acceptingTrustStrategy = new TrustAllStrategy(); SSLContext sslContext; try { @@ -204,21 +184,15 @@ private static SSLConnectionSocketFactory getSSLSF() throws CxClientException { return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); } - - private static PoolingHttpClientConnectionManager getSSLHttpConnManager() throws CxClientException { - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("https", getSSLSF()) - .register("http", new PlainConnectionSocketFactory()) - .build(); - PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - connManager.setMaxTotal(50); - connManager.setDefaultMaxPerRoute(5); - return connManager; - } - - private static PoolingHttpClientConnectionManager getHttpConnManager() { + private static PoolingHttpClientConnectionManager getHttpConnectionManager(boolean disableSSLValidation) throws CxClientException { + ConnectionSocketFactory factory; + if (disableSSLValidation) { + factory = getTrustAllSSLSocketFactory(); + } else { + factory = new SSLConnectionSocketFactory(SSLContexts.createDefault()); + } Registry socketFactoryRegistry = RegistryBuilder.create() - .register("https", new PlainConnectionSocketFactory()) + .register("https", factory) .register("http", new PlainConnectionSocketFactory()) .build(); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index 48590780..96609d54 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -35,7 +35,7 @@ public static void initTest() throws IOException { } @Test - public void runOsaScan() throws MalformedURLException { + public void runOsaScan() throws MalformedURLException, CxClientException { CxScanConfig config = initOsaConfig(); client = new CxShragaClient(config, log); try { @@ -52,7 +52,7 @@ public void runOsaScan() throws MalformedURLException { } @Test - public void runSastScan() throws MalformedURLException { + public void runSastScan() throws MalformedURLException, CxClientException { CxScanConfig config = initSastConfig(); client = new CxShragaClient(config, log); try { From c67bc9e56a4732e28603df58182f494cca65ee9b Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 26 Nov 2019 13:17:06 +0200 Subject: [PATCH 167/473] Fixes after merge. --- src/main/java/com/cx/restclient/httpClient/CxHttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 3bdd77a1..29e9ea1c 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -78,7 +78,7 @@ public class CxHttpClient { public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log, - String proxyHost, int proxyPort, String proxyUser, String proxyPassword) { + String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws CxClientException { this.log = log; this.rootUri = rootUri; this.cxOrigin = origin; @@ -109,7 +109,7 @@ public CxHttpClient(String rootUri, String origin, apacheClient = cb.build(); } - public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) { + public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) throws CxClientException { this(rootUri, origin, disableSSLValidation, isSSO, log, null, 0, null, null); } From a73513ef793cd2cabaa09067fe7a84076df3b2f5 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 26 Nov 2019 14:05:57 +0200 Subject: [PATCH 168/473] Fixes after changes in access control server and SCA API. --- .../java/com/cx/restclient/SCAClient.java | 8 ++--- .../dto/scansummary/ScanSummary.java | 6 ++-- .../com/cx/restclient/osa/dto/ClientType.java | 2 +- .../restclient/sca/dto/SCASummaryResults.java | 30 +++++++++---------- .../connection/ProjectScanTests.java | 10 ++----- src/test/resources/config.properties | 5 +++- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index c02b678d..98178692 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -53,7 +53,7 @@ private class ApiPaths { private final Waiter waiter; private String scanId; - SCAClient(Logger log, CxScanConfig config) throws MalformedURLException, CxClientException { + SCAClient(Logger log, CxScanConfig config) throws CxClientException { this.log = log; this.config = config; @@ -247,9 +247,9 @@ private void printSummary(SCASummaryResults summary) { log.info("\n----SCA risk report summary----"); log.info("Created on: " + summary.getCreatedOn()); log.info("Direct packages: " + summary.getDirectPackages()); - log.info("High vulnerabilities: " + summary.getHighVulnerabilitiesCount()); - log.info("Medium vulnerabilities: " + summary.getMediumVulnerabilitiesCount()); - log.info("Low vulnerabilities: " + summary.getLowVulnerabilitiesCount()); + log.info("High vulnerabilities: " + summary.getHighVulnerabilityCount()); + log.info("Medium vulnerabilities: " + summary.getMediumVulnerabilityCount()); + log.info("Low vulnerabilities: " + summary.getLowVulnerabilityCount()); log.info("Risk report ID: " + summary.getRiskReportId()); log.info("Risk score: " + summary.getRiskScore()); log.info("Total packages: " + summary.getTotalPackages()); diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java index b3bd020f..09b95355 100644 --- a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java +++ b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java @@ -104,9 +104,9 @@ private void addDependencyScanThresholdErrors(CxScanConfig config, DependencySca SCASummaryResults summary = scaResults.getSummary(); if (summary != null) { severityType = "SCA"; - totalHigh = summary.getHighVulnerabilitiesCount(); - totalMedium = summary.getMediumVulnerabilitiesCount(); - totalLow = summary.getLowVulnerabilitiesCount(); + totalHigh = summary.getHighVulnerabilityCount(); + totalMedium = summary.getMediumVulnerabilityCount(); + totalLow = summary.getLowVulnerabilityCount(); } } else if (osaResults != null && osaResults.isOsaResultsReady()) { OSASummaryResults summary = osaResults.getResults(); diff --git a/src/main/java/com/cx/restclient/osa/dto/ClientType.java b/src/main/java/com/cx/restclient/osa/dto/ClientType.java index 5e8d2214..f0ecb9a2 100644 --- a/src/main/java/com/cx/restclient/osa/dto/ClientType.java +++ b/src/main/java/com/cx/restclient/osa/dto/ClientType.java @@ -8,7 +8,7 @@ public enum ClientType { CLI("cli_client", "sast_rest_api offline_access", "B9D84EA8-E476-4E83-A628-8A342D74D3BD"), - SCA_CLI("sca_resource_owner_client", "sca_api offline_access", ""); + SCA_CLI("sca_resource_owner", "sca_api offline_access", ""); private String clientId; private String scopes; diff --git a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java index eb8c2d67..f878d0ec 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java @@ -4,9 +4,9 @@ public class SCASummaryResults { private String riskReportId; - private int highVulnerabilitiesCount; - private int mediumVulnerabilitiesCount; - private int lowVulnerabilitiesCount; + private int highVulnerabilityCount; + private int mediumVulnerabilityCount; + private int lowVulnerabilityCount; private int totalPackages; private int directPackages; private String createdOn; @@ -21,28 +21,28 @@ public void setRiskReportId(String riskReportId) { this.riskReportId = riskReportId; } - public int getHighVulnerabilitiesCount() { - return highVulnerabilitiesCount; + public int getHighVulnerabilityCount() { + return highVulnerabilityCount; } - public void setHighVulnerabilitiesCount(int highVulnerabilitiesCount) { - this.highVulnerabilitiesCount = highVulnerabilitiesCount; + public void setHighVulnerabilityCount(int highVulnerabilityCount) { + this.highVulnerabilityCount = highVulnerabilityCount; } - public int getMediumVulnerabilitiesCount() { - return mediumVulnerabilitiesCount; + public int getMediumVulnerabilityCount() { + return mediumVulnerabilityCount; } - public void setMediumVulnerabilitiesCount(int mediumVulnerabilitiesCount) { - this.mediumVulnerabilitiesCount = mediumVulnerabilitiesCount; + public void setMediumVulnerabilityCount(int mediumVulnerabilityCount) { + this.mediumVulnerabilityCount = mediumVulnerabilityCount; } - public int getLowVulnerabilitiesCount() { - return lowVulnerabilitiesCount; + public int getLowVulnerabilityCount() { + return lowVulnerabilityCount; } - public void setLowVulnerabilitiesCount(int lowVulnerabilitiesCount) { - this.lowVulnerabilitiesCount = lowVulnerabilitiesCount; + public void setLowVulnerabilityCount(int lowVulnerabilityCount) { + this.lowVulnerabilityCount = lowVulnerabilityCount; } public int getTotalPackages() { diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index 08d83fa2..89aba916 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -146,14 +146,10 @@ private CxScanConfig initScaConfig() { config.setProjectName("scaOnlyScan"); config.setTeamPath("\\CxServer"); - // Disabling certificate validation, otherwise we'll get an error during SCA login. - // TODO: fix HTTPS logic in CxHttpClient. - config.setDisableCertificateValidation(true); - SCAConfig sca = new SCAConfig(); - sca.setApiUrl("https://api.lumodev.com"); - sca.setAccessControlUrl("https://upgrade.dev-ac-checkmarx.com"); - sca.setTenant("Checkmarx"); + sca.setApiUrl(props.getProperty("sca.apiUrl")); + sca.setAccessControlUrl(props.getProperty("sca.accessControlUrl")); + sca.setTenant(props.getProperty("sca.tenant")); sca.setUsername(props.getProperty("sca.username")); sca.setPassword(props.getProperty("sca.password")); config.setScaConfig(sca); diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties index 3b642d08..8a638b3b 100644 --- a/src/test/resources/config.properties +++ b/src/test/resources/config.properties @@ -1,7 +1,10 @@ serverUrl=http://10.32.1.57 -user=myuser +username=myuser password=mypassword sastSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource dependencyScanSourceDir=c:\\cxdev\\projectsToScan\\SastAndOsaSource +sca.apiUrl=https://api.lumodev.com +sca.accessControlUrl=https://v2.ac-checkmarx.com sca.username=myuser sca.password=mypassword +sca.tenant=mytenant \ No newline at end of file From 1ec9c6e41db219ee8befc3cf999b50e10ca9a9f2 Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Tue, 26 Nov 2019 14:29:16 +0200 Subject: [PATCH 169/473] fixed CxARM invalid scope issue --- src/main/java/com/cx/restclient/CxOSAClient.java | 4 ++-- src/main/java/com/cx/restclient/CxSASTClient.java | 4 ++-- src/main/java/com/cx/restclient/CxShragaClient.java | 4 ++-- .../java/com/cx/restclient/httpClient/CxHttpClient.java | 8 +++++++- src/main/java/com/cx/restclient/osa/dto/ClientType.java | 4 ++++ 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxOSAClient.java b/src/main/java/com/cx/restclient/CxOSAClient.java index 50cb0f04..ac7a882d 100644 --- a/src/main/java/com/cx/restclient/CxOSAClient.java +++ b/src/main/java/com/cx/restclient/CxOSAClient.java @@ -131,12 +131,12 @@ private OSAResults retrieveOSAResults(String scanId, OSAScanStatus osaScanStatus return results; } - private void resolveOSAViolation(OSAResults osaResults, long projectId) { + private void resolveOSAViolation(OSAResults osaResults, long projectId) throws CxClientException { try { getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, OPEN_SOURCE.value()) .forEach(osaResults::addPolicy); } catch (Exception ex) { - log.error("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); + throw new CxClientException("CxARM is not available. Policy violations for OSA cannot be calculated: " + ex.getMessage()); } } diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 91951cd8..0e00641b 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -270,13 +270,13 @@ else if (!config.getReports().isEmpty()) { return sastResults; } - private void resolveSASTViolation(SASTResults sastResults, long projectId) { + private void resolveSASTViolation(SASTResults sastResults, long projectId) throws CxClientException { try { cxARMWaiter.waitForTaskToFinish(Long.toString(projectId), cxARMTimeoutSec, log); getProjectViolatedPolicies(httpClient, config.getCxARMUrl(), projectId, SAST.value()) .forEach(sastResults::addPolicy); } catch (Exception ex) { - log.error("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); + throw new CxClientException("CxARM is not available. Policy violations for SAST cannot be calculated: " + ex.getMessage()); } } diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index c62491ad..deb9c5af 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -310,11 +310,11 @@ private void resolveTeam() throws CxClientException, IOException { printTeamPath(); } - private void resolveCxARMUrl() { + private void resolveCxARMUrl() throws CxClientException { try { this.config.setCxARMUrl(getCxARMConfig().getCxARMPolicyURL()); } catch (Exception ex) { - log.error("CxARM is not available. Policy violations cannot be calculated: " + ex.getMessage()); + throw new CxClientException("CxARM is not available. Policy violations cannot be calculated: " + ex.getMessage()); } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index a52d05c4..484e05e8 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -230,7 +230,13 @@ public TokenLoginResponse generateToken(ClientType clientType) throws IOExceptio return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } catch (CxClientException e) { - throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + if(!e.getMessage().contains("invalid_scope")) { + throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + } + ClientType.RESOURCE_OWNER.setScopes("sast_rest_api"); + requestEntity = generateUrlEncodedFormEntity(ClientType.RESOURCE_OWNER); + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } } diff --git a/src/main/java/com/cx/restclient/osa/dto/ClientType.java b/src/main/java/com/cx/restclient/osa/dto/ClientType.java index 8843318c..45006464 100644 --- a/src/main/java/com/cx/restclient/osa/dto/ClientType.java +++ b/src/main/java/com/cx/restclient/osa/dto/ClientType.java @@ -25,6 +25,10 @@ public String getScopes() { return scopes; } + public void setScopes(String scopes) { + this.scopes = scopes; + } + public String getClientId() { return clientId; } From e6d711d566206ec1a274c2fe19da28dd7055d147 Mon Sep 17 00:00:00 2001 From: idana Date: Tue, 26 Nov 2019 15:11:59 +0200 Subject: [PATCH 170/473] Changed CxClientException to RuntimeException to avoid breaking compatibility with plugins --- .../java/com/cx/restclient/exception/CxClientException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/exception/CxClientException.java b/src/main/java/com/cx/restclient/exception/CxClientException.java index 6284f00e..df27ac27 100644 --- a/src/main/java/com/cx/restclient/exception/CxClientException.java +++ b/src/main/java/com/cx/restclient/exception/CxClientException.java @@ -3,7 +3,7 @@ /** * Created by Galn on 05/02/2018. */ -public class CxClientException extends Exception { +public class CxClientException extends RuntimeException { public CxClientException() { super(); } From 2778c8e271d76cff82394ba04128160e7dcae825 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 26 Nov 2019 15:42:34 +0200 Subject: [PATCH 171/473] Fix for AB#329: SCA-only scan no longer fails if SAST parameters are omitted. --- .../com/cx/restclient/CxShragaClient.java | 83 +++++++++++-------- .../java/com/cx/restclient/SCAClient.java | 5 ++ .../configuration/CxScanConfig.java | 4 + .../connection/ProjectScanTests.java | 34 ++++---- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 0d323d05..8553022a 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -55,18 +55,23 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { this.config = config; this.log = log; - this.httpClient = new CxHttpClient( - UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), - config.getCxOrigin(), - config.isDisableCertificateValidation(), - config.isUseSSOLogin(), - log, - proxyHost, proxyPort, proxyUser, proxyPassword); - sastClient = new CxSASTClient(httpClient, log, config); - - if (config.getDependencyScannerType() == DependencyScannerType.OSA) { - dependencyScanner = new CxOSAClient(httpClient, log, config); - } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { + + if (config.isSastOrOSAEnabled()) { + this.httpClient = new CxHttpClient( + UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), + config.getCxOrigin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + log, + proxyHost, proxyPort, proxyUser, proxyPassword); + sastClient = new CxSASTClient(httpClient, log, config); + + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { + dependencyScanner = new CxOSAClient(httpClient, log, config); + } + } + + if (config.getDependencyScannerType() == DependencyScannerType.SCA) { dependencyScanner = new SCAClient(log, config); } } @@ -102,16 +107,18 @@ public String getClientVersion() { public void init() throws CxClientException, IOException { log.info("Initializing Cx client [" + getClientVersion() + "]"); - getCxVersion(); - login(); - resolveTeam(); - if (config.getSastEnabled()) { - resolvePreset(); - } - if (config.getEnablePolicyViolations()) { - resolveCxARMUrl(); + if (config.isSastOrOSAEnabled()) { + getCxVersion(); + login(); + resolveTeam(); + if (config.getSastEnabled()) { + resolvePreset(); + } + if (config.getEnablePolicyViolations()) { + resolveCxARMUrl(); + } + resolveProject(); } - resolveProject(); if (dependencyScanner != null) { dependencyScanner.init(); @@ -119,40 +126,37 @@ public void init() throws CxClientException, IOException { } public long createSASTScan() throws IOException, CxClientException { - sastScanId = sastClient.createSASTScan(projectId); + sastScanId = getSastClient().createSASTScan(projectId); sastResults.setSastScanLink(config.getUrl(), sastScanId, projectId); return sastScanId; } public String createDependencyScan() throws CxClientException { - ensureDependencyScannerExists(); - String scanId = dependencyScanner.createScan(dependencyScanResults); + String scanId = getDependencyScanner().createScan(dependencyScanResults); return scanId; } public void cancelSASTScan() throws IOException, CxClientException { - sastClient.cancelSASTScan(sastScanId); + getSastClient().cancelSASTScan(sastScanId); } public SASTResults waitForSASTResults() throws InterruptedException, CxClientException, IOException { - sastResults = sastClient.waitForSASTResults(sastScanId, projectId); + sastResults = getSastClient().waitForSASTResults(sastScanId, projectId); return sastResults; } public SASTResults getLatestSASTResults() throws InterruptedException, CxClientException, IOException { - sastResults = sastClient.getLatestSASTResults(projectId); + sastResults = getSastClient().getLatestSASTResults(projectId); return sastResults; } public DependencyScanResults waitForDependencyScanResults() throws CxClientException { - ensureDependencyScannerExists(); - dependencyScanner.waitForScanResults(dependencyScanResults); + getDependencyScanner().waitForScanResults(dependencyScanResults); return dependencyScanResults; } public DependencyScanResults getLatestDependencyScanResults() throws CxClientException { - ensureDependencyScannerExists(); - dependencyScanResults = dependencyScanner.getLatestScanResults(); + dependencyScanResults = getDependencyScanner().getLatestScanResults(); return dependencyScanResults; } @@ -410,11 +414,20 @@ private LoginSettings getDefaultLoginSettings() throws MalformedURLException { return result; } - private void ensureDependencyScannerExists() throws CxClientException { + private DependencyScanner getDependencyScanner() throws CxClientException { if (dependencyScanner == null) { - throw new CxClientException( - String.format("Unable to continue: dependency scanner type was set to %s in config.", - DependencyScannerType.NONE)); + String message = String.format("The action can't be performed, because dependency scanner type is set to %s in scan configuration.", + DependencyScannerType.NONE); + + throw new CxClientException(message); + } + return dependencyScanner; + } + + private CxSASTClient getSastClient() throws CxClientException { + if (sastClient == null) { + throw new CxClientException("The action can't be performed, because SAST is disabled in scan configuration."); } + return sastClient; } } \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 98178692..49d1872e 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -14,6 +14,7 @@ import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.SCAWaiter; import com.cx.restclient.sca.dto.*; +import com.google.common.base.Strings; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; @@ -151,6 +152,10 @@ private void resolveProject() throws IOException, CxClientException { private String getProjectIdByName(String name) throws IOException, CxClientException { log.debug("Getting project by name: " + name); + if (Strings.isNullOrEmpty(name)) { + throw new CxClientException("Non-empty project name must be provided."); + } + List allProjects = (List) httpClient.getRequest(ApiPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, Project.class, diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 85553587..0c01f8af 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -715,4 +715,8 @@ public DependencyScannerType getDependencyScannerType() { public void setDependencyScannerType(DependencyScannerType scannerType) { this.dependencyScannerType = scannerType; } + + public boolean isSastOrOSAEnabled() { + return sastEnabled || dependencyScannerType == DependencyScannerType.OSA; + } } diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index 89aba916..eeb455c6 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -41,15 +41,13 @@ public void runOsaScan() throws MalformedURLException, CxClientException { try { client.init(); client.createDependencyScan(); - client.waitForDependencyScanResults(); - final DependencyScanResults results = client.getLatestDependencyScanResults(); + DependencyScanResults results = client.waitForDependencyScanResults(); + Assert.assertNotNull(results); Assert.assertNull(results.getScaResults()); Assert.assertNotNull(results.getOsaResults()); Assert.assertNotNull("Expected valid osa scan id", results.getOsaResults().getOsaScanId()); - } catch (IOException | CxClientException e) { - e.printStackTrace(); - log.error("Error running osa scan: " + e.getMessage()); - Assert.fail(e.getMessage()); + } catch (Exception e) { + failOnException(e); } } @@ -60,13 +58,11 @@ public void runSastScan() throws MalformedURLException, CxClientException { try { client.init(); client.createSASTScan(); - client.waitForSASTResults(); - SASTResults latestSASTResults = client.getLatestSASTResults(); - Assert.assertNotEquals("Expected valid SAST scan id", 0, latestSASTResults.getScanId()); - } catch (IOException | CxClientException | InterruptedException e) { - e.printStackTrace(); - log.error("Error running sast scan: " + e.getMessage()); - Assert.fail(e.getMessage()); + SASTResults results = client.waitForSASTResults(); + Assert.assertNotNull(results); + Assert.assertNotEquals("Expected valid SAST scan id", 0, results.getScanId()); + } catch (Exception e) { + failOnException(e); } } @@ -84,8 +80,7 @@ public void runScaScan() throws MalformedURLException, CxClientException { Assert.assertNotNull(results.getScaResults().getSummary()); Assert.assertNotNull(results.getScaResults().getScanId()); } catch (Exception e) { - log.error("Error running SCA scan: " + e); - Assert.fail(e.getMessage()); + failOnException(e); } } @@ -136,15 +131,11 @@ private CxScanConfig initOsaConfig() { private CxScanConfig initScaConfig() { CxScanConfig config = new CxScanConfig(); - config.setUrl(props.getProperty("serverUrl")); - config.setUsername(props.getProperty("username")); - config.setPassword(props.getProperty("password")); config.setDependencyScannerType(DependencyScannerType.SCA); config.setSastEnabled(false); config.setSourceDir(props.getProperty("dependencyScanSourceDir")); config.setOsaThresholdsEnabled(true); config.setProjectName("scaOnlyScan"); - config.setTeamPath("\\CxServer"); SCAConfig sca = new SCAConfig(); sca.setApiUrl(props.getProperty("sca.apiUrl")); @@ -156,4 +147,9 @@ private CxScanConfig initScaConfig() { return config; } + + private void failOnException(Exception e) { + log.error("Error running scan.", e); + Assert.fail(e.getMessage()); + } } From d9b3f4d6c3aa0b270b8feb1012bce8fa316a9f7c Mon Sep 17 00:00:00 2001 From: ghannamz <55191809+ghannamz@users.noreply.github.com> Date: Tue, 26 Nov 2019 16:52:26 +0200 Subject: [PATCH 172/473] Update pom.xml update common version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3d32f802..aefdfb42 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.0-SNAPSHOT + 9.20.1-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -277,4 +277,4 @@ - \ No newline at end of file + From 4d280f3783374b89a18febf7fa90604022fac3d0 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 27 Nov 2019 11:32:08 +0200 Subject: [PATCH 173/473] Fix for AB#328: added proxy support for SCA. Wrapped proxy config into class. Fixed HTTPS proxy configuration. --- .../com/cx/restclient/CxShragaClient.java | 16 +----- .../java/com/cx/restclient/SCAClient.java | 1 + .../configuration/CxScanConfig.java | 16 ++++++ .../com/cx/restclient/dto/ProxyConfig.java | 49 +++++++++++++++++ .../restclient/httpClient/CxHttpClient.java | 45 +++++++-------- .../connection/ProjectScanTests.java | 55 ++++++++++++++----- src/test/resources/config.properties | 4 +- 7 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/ProxyConfig.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 8553022a..ff57278d 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -51,8 +51,7 @@ public class CxShragaClient { private DependencyScanner dependencyScanner; - public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int proxyPort, - String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { + public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { this.config = config; this.log = log; @@ -62,8 +61,8 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), - log, - proxyHost, proxyPort, proxyUser, proxyPassword); + config.getProxyConfig(), + log); sastClient = new CxSASTClient(httpClient, log, config); if (config.getDependencyScannerType() == DependencyScannerType.OSA) { @@ -76,15 +75,6 @@ public CxShragaClient(CxScanConfig config, Logger log, String proxyHost, int pro } } - public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { - this(config, log, null, 0, null, null); - } - - public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, - Logger log, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws MalformedURLException, CxClientException { - this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log, proxyHost, proxyPort, proxyUser, proxyPassword); - } - public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 49d1872e..069bbe91 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -67,6 +67,7 @@ private class ApiPaths { config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), + config.getProxyConfig(), log); waiter = new SCAWaiter("SCA scan", pollInterval, maxRetries, httpClient, ApiPaths.SCAN_STATUS, log); diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 0c01f8af..b9b01399 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -2,6 +2,7 @@ import com.cx.restclient.dto.CxVersion; import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ProxyConfig; import com.cx.restclient.dto.RemoteSourceTypes; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sast.dto.ReportType; @@ -100,6 +101,8 @@ public class CxScanConfig implements Serializable { private SCAConfig scaConfig; private DependencyScannerType dependencyScannerType; + private ProxyConfig proxyConfig; + public CxScanConfig() { } @@ -716,7 +719,20 @@ public void setDependencyScannerType(DependencyScannerType scannerType) { this.dependencyScannerType = scannerType; } + /** + * SAST and OSA are currently deployed on-premises, whereas SCA is deployed in a cloud. + * If SAST or OSA are enabled, some of the config properties are mandatory (url, username, password etc). + * Otherwise, these properties are optional. + */ public boolean isSastOrOSAEnabled() { return sastEnabled || dependencyScannerType == DependencyScannerType.OSA; } + + public ProxyConfig getProxyConfig() { + return proxyConfig; + } + + public void setProxyConfig(ProxyConfig proxyConfig) { + this.proxyConfig = proxyConfig; + } } diff --git a/src/main/java/com/cx/restclient/dto/ProxyConfig.java b/src/main/java/com/cx/restclient/dto/ProxyConfig.java new file mode 100644 index 00000000..d32f7b62 --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/ProxyConfig.java @@ -0,0 +1,49 @@ +package com.cx.restclient.dto; + +public class ProxyConfig { + private String host; + private int port; + private String username; + private String password; + private boolean useHttps; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isUseHttps() { + return useHttps; + } + + public void setUseHttps(boolean useHttps) { + this.useHttps = useHttps; + } +} diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 29e9ea1c..0f58bda2 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -3,6 +3,7 @@ import com.cx.restclient.common.ErrorMessage; import com.cx.restclient.common.UrlUtils; import com.cx.restclient.dto.LoginSettings; +import com.cx.restclient.dto.ProxyConfig; import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; @@ -44,6 +45,7 @@ import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; +import javax.annotation.Nullable; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import java.io.IOException; @@ -76,9 +78,8 @@ public class CxHttpClient { private Boolean useSSo; private LoginSettings lastLoginSettings; - public CxHttpClient(String rootUri, String origin, - boolean disableSSLValidation, boolean isSSO, Logger log, - String proxyHost, int proxyPort, String proxyUser, String proxyPassword) throws CxClientException { + public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, + @Nullable ProxyConfig proxyConfig, Logger log) throws CxClientException { this.log = log; this.rootUri = rootUri; this.cxOrigin = origin; @@ -99,9 +100,7 @@ public CxHttpClient(String rootUri, String origin, } cb.setConnectionManagerShared(true); - if (proxyHost != null) { - setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, log); - } + setCustomProxy(cb, proxyConfig, log); cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); @@ -109,26 +108,24 @@ public CxHttpClient(String rootUri, String origin, apacheClient = cb.build(); } - public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, Logger log) throws CxClientException { - this(rootUri, origin, disableSSLValidation, isSSO, log, null, 0, null, null); - } - - private static void setCustomProxy(HttpClientBuilder cb, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, Logger logi) { - HttpHost proxy = null; - if (!isEmpty(proxyHost)) { - proxy = new HttpHost(proxyHost, proxyPort, "http"); - if (!isEmpty(proxyUser) && !isEmpty(proxyPassword)) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(proxyUser, proxyPassword)); - cb.setDefaultCredentialsProvider(credsProvider); - } + private static void setCustomProxy(HttpClientBuilder cb, ProxyConfig proxyConfig, Logger logi) { + if (proxyConfig == null) { + return; } - if (proxy != null) { - logi.info("Setting proxy for Checkmarx http client"); - cb.setProxy(proxy); - cb.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)); - cb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + + String scheme = proxyConfig.isUseHttps() ? "https" : "http"; + HttpHost proxy = new HttpHost(proxyConfig.getHost(), proxyConfig.getPort(), scheme); + if (!isEmpty(proxyConfig.getUsername()) && !isEmpty(proxyConfig.getPassword())) { + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(proxyConfig.getUsername(), proxyConfig.getPassword()); + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(proxy), credentials); + cb.setDefaultCredentialsProvider(credsProvider); } + + logi.info("Setting proxy for Checkmarx http client"); + cb.setProxy(proxy); + cb.setRoutePlanner(new DefaultProxyRoutePlanner(proxy)); + cb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); } private static SSLConnectionSocketFactory getTrustAllSSLSocketFactory() throws CxClientException { diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index eeb455c6..3a7d7a87 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -4,6 +4,7 @@ import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ProxyConfig; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.SASTResults; import com.cx.restclient.sca.dto.SCAConfig; @@ -26,7 +27,6 @@ public class ProjectScanTests { private static final String PROPERTIES_FILE = "config.properties"; private Logger log = LoggerFactory.getLogger(ProjectScanTests.class.getName()); - private CxShragaClient client; private static Properties props; @BeforeClass @@ -37,7 +37,7 @@ public static void initTest() throws IOException { @Test public void runOsaScan() throws MalformedURLException, CxClientException { CxScanConfig config = initOsaConfig(); - client = new CxShragaClient(config, log); + CxShragaClient client = new CxShragaClient(config, log); try { client.init(); client.createDependencyScan(); @@ -54,22 +54,38 @@ public void runOsaScan() throws MalformedURLException, CxClientException { @Test public void runSastScan() throws MalformedURLException, CxClientException { CxScanConfig config = initSastConfig(); - client = new CxShragaClient(config, log); - try { - client.init(); - client.createSASTScan(); - SASTResults results = client.waitForSASTResults(); - Assert.assertNotNull(results); - Assert.assertNotEquals("Expected valid SAST scan id", 0, results.getScanId()); - } catch (Exception e) { - failOnException(e); - } + runSastScan(config); + } + + @Test + public void runSastScanWithProxy() throws MalformedURLException, CxClientException { + CxScanConfig config = initSastConfig(); + setProxy(config); + runSastScan(config); } @Test public void runScaScan() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); - client = new CxShragaClient(config, log); + runScaScan(config); + } + + @Test + public void runScaScanWithProxy() throws MalformedURLException, CxClientException { + CxScanConfig config = initScaConfig(); + setProxy(config); + runScaScan(config); + } + + private void setProxy(CxScanConfig config) { + ProxyConfig proxyConfig = new ProxyConfig(); + proxyConfig.setHost(props.getProperty("proxy.host")); + proxyConfig.setPort(Integer.parseInt(props.getProperty("proxy.port"))); + config.setProxyConfig(proxyConfig); + } + + private void runScaScan(CxScanConfig config) throws MalformedURLException, CxClientException { + CxShragaClient client = new CxShragaClient(config, log); try { client.init(); client.createDependencyScan(); @@ -84,6 +100,19 @@ public void runScaScan() throws MalformedURLException, CxClientException { } } + private void runSastScan(CxScanConfig config) throws MalformedURLException, CxClientException { + CxShragaClient client = new CxShragaClient(config, log); + try { + client.init(); + client.createSASTScan(); + SASTResults results = client.waitForSASTResults(); + Assert.assertNotNull(results); + Assert.assertNotEquals("Expected valid SAST scan id", 0, results.getScanId()); + } catch (Exception e) { + failOnException(e); + } + } + private CxScanConfig initSastConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties index 8a638b3b..901800a3 100644 --- a/src/test/resources/config.properties +++ b/src/test/resources/config.properties @@ -7,4 +7,6 @@ sca.apiUrl=https://api.lumodev.com sca.accessControlUrl=https://v2.ac-checkmarx.com sca.username=myuser sca.password=mypassword -sca.tenant=mytenant \ No newline at end of file +sca.tenant=mytenant +proxy.host=10.32.1.188 +proxy.port=11110 \ No newline at end of file From c0e327bb14f6decfb0950123ea91262e60689943 Mon Sep 17 00:00:00 2001 From: ghannamz <55191809+ghannamz@users.noreply.github.com> Date: Wed, 27 Nov 2019 18:07:45 +0200 Subject: [PATCH 174/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aefdfb42..fcc10ef3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.1-SNAPSHOT + 9.20.1 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 44172afb7d1dcb0056a6eb4cc5ca1aeb9ae7405e Mon Sep 17 00:00:00 2001 From: ghannamz <55191809+ghannamz@users.noreply.github.com> Date: Wed, 27 Nov 2019 18:11:55 +0200 Subject: [PATCH 175/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fcc10ef3..aefdfb42 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.1 + 9.20.1-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 1e3902a4e6a3e1ae37c87ac80fbb87ff8415ad4e Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 28 Nov 2019 11:32:23 +0200 Subject: [PATCH 176/473] AB#312: implemented web report link generation. --- .../java/com/cx/restclient/SCAClient.java | 62 ++++++++++++++----- .../com/cx/restclient/sca/dto/SCAConfig.java | 9 +++ .../com/cx/restclient/sca/dto/SCAResults.java | 9 +++ .../connection/ProjectScanTests.java | 12 +++- src/test/resources/config.properties | 1 + 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 069bbe91..824695e5 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -1,6 +1,7 @@ package com.cx.restclient; import com.cx.restclient.common.DependencyScanner; +import com.cx.restclient.common.UrlUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.DependencyScanResults; @@ -25,23 +26,23 @@ import org.apache.http.entity.mime.content.StringBody; import org.slf4j.Logger; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.MalformedURLException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; /** * SCA - Software Composition Analysis - is the successor of OSA. */ public class SCAClient implements DependencyScanner { - private class ApiPaths { + private static class UrlPaths { private static final String PROJECTS = "/risk_management/api/projects"; private static final String SUMMARY_REPORT = "/risk_management/api/riskReports/%s/summary"; private static final String ZIP_UPLOAD = "/scans/api/scans/zip"; private static final String SCAN_STATUS = "/scans/api/scans/%s/status"; private static final String REPORT_ID = "/scans/api/scans/%s/riskReportId"; + private static final String WEB_REPORT = "/#/projects/%s/report/%s"; } private final Logger log; @@ -61,7 +62,7 @@ private class ApiPaths { int pollInterval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; int maxRetries = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; - SCAConfig scaConfig = safeGetScaConfig(); + SCAConfig scaConfig = getScaConfig(); httpClient = new CxHttpClient(scaConfig.getApiUrl(), config.getCxOrigin(), @@ -70,7 +71,7 @@ private class ApiPaths { config.getProxyConfig(), log); - waiter = new SCAWaiter("SCA scan", pollInterval, maxRetries, httpClient, ApiPaths.SCAN_STATUS, log); + waiter = new SCAWaiter("SCA scan", pollInterval, maxRetries, httpClient, UrlPaths.SCAN_STATUS, log); } @Override @@ -117,6 +118,10 @@ public void waitForScanResults(DependencyScanResults target) throws CxClientExce throw new CxClientException("Error retrieving SCA scan results.", e); } + if (!Strings.isNullOrEmpty(scaResult.getWebReportLink())) { + log.info("SCA scan results location: " + scaResult.getWebReportLink()); + } + target.setScaResults(scaResult); } @@ -128,7 +133,7 @@ public DependencyScanResults getLatestScanResults() { private void login() throws IOException, CxClientException { log.info("Logging into SCA."); - SCAConfig scaConfig = safeGetScaConfig(); + SCAConfig scaConfig = getScaConfig(); LoginSettings settings = new LoginSettings(); settings.setAccessControlBaseUrl(scaConfig.getAccessControlUrl()); @@ -157,7 +162,7 @@ private String getProjectIdByName(String name) throws IOException, CxClientExcep throw new CxClientException("Non-empty project name must be provided."); } - List allProjects = (List) httpClient.getRequest(ApiPaths.PROJECTS, + List allProjects = (List) httpClient.getRequest(UrlPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, Project.class, HttpStatus.SC_OK, @@ -177,7 +182,7 @@ private String createProject(String name) throws CxClientException, IOException StringEntity entity = HttpClientHelper.convertToStringEntity(request); - Project newProject = httpClient.postRequest(ApiPaths.PROJECTS, + Project newProject = httpClient.postRequest(UrlPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, entity, Project.class, @@ -202,7 +207,7 @@ private String uploadZipFile(File zipFile) throws IOException, CxClientException HttpEntity entity = builder.build(); - String scanId = httpClient.postRequest(ApiPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); + String scanId = httpClient.postRequest(UrlPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); log.debug("Scan ID: " + scanId); return scanId; @@ -210,17 +215,44 @@ private String uploadZipFile(File zipFile) throws IOException, CxClientException private SCAResults retrieveScanResults() throws IOException, CxClientException { String reportId = getReportId(); - SCASummaryResults scanSummary = getSummaryReport(reportId); SCAResults result = new SCAResults(); result.setScanId(scanId); + + SCASummaryResults scanSummary = getSummaryReport(reportId); result.setSummary(scanSummary); + + String reportLink = getWebReportLink(reportId); + result.setWebReportLink(reportLink); + return result; + } + + private String getWebReportLink(String reportId) { + String MESSAGE = "Unable to generate web report link. "; + String result = null; + try { + String webAppUrl = getScaConfig().getWebAppUrl(); + if (Strings.isNullOrEmpty(webAppUrl)) { + log.warn(MESSAGE + "Web app URL is not specified."); + } else { + String encoding = StandardCharsets.UTF_8.name(); + String path = String.format(UrlPaths.WEB_REPORT, + URLEncoder.encode(projectId, encoding), + URLEncoder.encode(reportId, encoding)); + + result = UrlUtils.parseURLToString(webAppUrl, path); + } + } catch (MalformedURLException e) { + log.warn(MESSAGE + "Invalid web app URL.", e); + } catch (Exception e) { + log.warn(MESSAGE, e); + } return result; } private String getReportId() throws IOException, CxClientException { log.debug("Getting report ID by scan ID: " + scanId); - String path = String.format(ApiPaths.REPORT_ID, scanId); + String path = String.format(UrlPaths.REPORT_ID, scanId); String reportId = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, String.class, @@ -234,7 +266,7 @@ private String getReportId() throws IOException, CxClientException { private SCASummaryResults getSummaryReport(String reportId) throws IOException, CxClientException { log.debug("Getting summary report."); - String path = String.format(ApiPaths.SUMMARY_REPORT, reportId); + String path = String.format(UrlPaths.SUMMARY_REPORT, reportId); SCASummaryResults result = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, @@ -262,7 +294,7 @@ private void printSummary(SCASummaryResults summary) { log.info(String.format("Total outdated packages: %d\n", summary.getTotalOutdatedPackages())); } - private SCAConfig safeGetScaConfig() throws CxClientException { + private SCAConfig getScaConfig() throws CxClientException { SCAConfig result = config.getScaConfig(); if (result == null) { throw new CxClientException("SCA scan configuration is missing."); diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java index 26e25d65..f2ab145f 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java @@ -8,6 +8,7 @@ public class SCAConfig implements Serializable { private String username; private String password; private String tenant; + private String webAppUrl; public String getApiUrl() { return apiUrl; @@ -48,4 +49,12 @@ public void setTenant(String tenant) { public String getTenant() { return tenant; } + + public String getWebAppUrl() { + return webAppUrl; + } + + public void setWebAppUrl(String webAppUrl) { + this.webAppUrl = webAppUrl; + } } diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAResults.java b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java index 261ee2ef..a8c57998 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCAResults.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java @@ -3,6 +3,7 @@ public class SCAResults { private String scanId; private SCASummaryResults summary; + private String webReportLink; public void setScanId(String scanId) { this.scanId = scanId; @@ -19,4 +20,12 @@ public void setSummary(SCASummaryResults summary) { public SCASummaryResults getSummary() { return summary; } + + public void setWebReportLink(String webReportLink) { + this.webReportLink = webReportLink; + } + + public String getWebReportLink() { + return webReportLink; + } } diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index 3a7d7a87..c6e5f2d2 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -8,6 +8,7 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.SASTResults; import com.cx.restclient.sca.dto.SCAConfig; +import com.cx.restclient.sca.dto.SCAResults; import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.BeforeClass; @@ -92,9 +93,12 @@ private void runScaScan(CxScanConfig config) throws MalformedURLException, CxCli DependencyScanResults results = client.waitForDependencyScanResults(); Assert.assertNotNull(results); Assert.assertNull(results.getOsaResults()); - Assert.assertNotNull(results.getScaResults()); - Assert.assertNotNull(results.getScaResults().getSummary()); - Assert.assertNotNull(results.getScaResults().getScanId()); + + SCAResults scaResults = results.getScaResults(); + Assert.assertNotNull(scaResults); + Assert.assertNotNull(scaResults.getSummary()); + Assert.assertNotNull(scaResults.getScanId()); + Assert.assertNotNull(scaResults.getWebReportLink()); } catch (Exception e) { failOnException(e); } @@ -172,6 +176,8 @@ private CxScanConfig initScaConfig() { sca.setTenant(props.getProperty("sca.tenant")); sca.setUsername(props.getProperty("sca.username")); sca.setPassword(props.getProperty("sca.password")); + sca.setWebAppUrl(props.getProperty("sca.webAppUrl")); + config.setScaConfig(sca); return config; diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties index 901800a3..7620288b 100644 --- a/src/test/resources/config.properties +++ b/src/test/resources/config.properties @@ -5,6 +5,7 @@ sastSource=c:\\cxdev\\projectsToScan\\SastAndOsaSource dependencyScanSourceDir=c:\\cxdev\\projectsToScan\\SastAndOsaSource sca.apiUrl=https://api.lumodev.com sca.accessControlUrl=https://v2.ac-checkmarx.com +sca.webAppUrl=https://sca.lumodev.com sca.username=myuser sca.password=mypassword sca.tenant=mytenant From d30531e1a16f61a30bcd9450586ee0d2a60430bd Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 1 Dec 2019 13:40:39 +0200 Subject: [PATCH 177/473] AB#347: Fixed: httpClient is null while testing connection, getting teams etc. --- .../com/cx/restclient/CxShragaClient.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index ff57278d..5444a7af 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -55,22 +55,21 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept this.config = config; this.log = log; - if (config.isSastOrOSAEnabled()) { - this.httpClient = new CxHttpClient( - UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), - config.getCxOrigin(), - config.isDisableCertificateValidation(), - config.isUseSSOLogin(), - config.getProxyConfig(), - log); + this.httpClient = new CxHttpClient( + UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), + config.getCxOrigin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + config.getProxyConfig(), + log); + + if (config.getSastEnabled()) { sastClient = new CxSASTClient(httpClient, log, config); - - if (config.getDependencyScannerType() == DependencyScannerType.OSA) { - dependencyScanner = new CxOSAClient(httpClient, log, config); - } } - if (config.getDependencyScannerType() == DependencyScannerType.SCA) { + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { + dependencyScanner = new CxOSAClient(httpClient, log, config); + } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { dependencyScanner = new SCAClient(log, config); } } From 14495e4bb604f09fc9b5c5f329e096c42ba62185 Mon Sep 17 00:00:00 2001 From: idanA Date: Sun, 1 Dec 2019 14:05:49 +0200 Subject: [PATCH 178/473] added sso login option --- pom.xml | 5 + .../com/cx/restclient/common/CxPARAM.java | 6 +- .../restclient/httpClient/CxHttpClient.java | 133 +++++++- src/main/java/testi.java | 296 +++++++++--------- .../connection/ConnectionTests.java | 5 +- .../{java => }/resources/config.properties | 2 +- 6 files changed, 284 insertions(+), 163 deletions(-) rename src/test/{java => }/resources/config.properties (60%) diff --git a/pom.xml b/pom.xml index aefdfb42..2fa09353 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,11 @@ httpcore 4.4.12 + + org.apache.httpcomponents + httpclient-win + 4.5.10 + com.fasterxml.jackson.core jackson-databind diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index dcca603b..ba45738f 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -8,7 +8,7 @@ public abstract class CxPARAM { public static final String AUTHENTICATION = "auth/identity/connect/token"; public static final String REVOCATION = "auth/identity/connect/revocation"; - public static final String SSO_AUTHENTICATION = "auth/ssologin"; + public static final String SSO_AUTHENTICATION = "auth/identity/externalLogin"; public static final String CXPRESETS = "sast/presets"; public static final String CXTEAMS = "auth/teams"; public static final String CREATE_PROJECT = "projects";//Create new project (default preset and configuration) @@ -17,6 +17,8 @@ public abstract class CxPARAM { public static final String CX_ARM_URL = "/Configurations/Portal"; public static final String CX_ARM_VIOLATION = "/cxarm/policymanager/projects/{projectId}/violations?provider={provider}"; + public static final String BROWSER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"; + public static final String CX_REPORT_LOCATION = File.separator + "Checkmarx" + File.separator + "Reports"; @@ -29,4 +31,6 @@ public abstract class CxPARAM { public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; + + } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 484e05e8..5f116fbf 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -7,10 +7,12 @@ import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import com.cx.restclient.osa.dto.ClientType; +import com.google.gson.Gson; import org.apache.http.*; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.config.AuthSchemes; @@ -26,17 +28,19 @@ import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.cookie.Cookie; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.NoConnectionReuseStrategy; import org.apache.http.impl.auth.BasicSchemeFactory; import org.apache.http.impl.auth.DigestSchemeFactory; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.impl.auth.win.WindowsCredentialsProvider; +import org.apache.http.impl.auth.win.WindowsNTLMSchemeFactory; +import org.apache.http.impl.auth.win.WindowsNegotiateSchemeFactory; +import org.apache.http.impl.client.*; import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; @@ -52,6 +56,7 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -86,6 +91,7 @@ public class CxHttpClient { private final String refreshToken; private String cxOrigin; private Boolean useSSo; + private CookieStore cookieStore = new BasicCookieStore(); public CxHttpClient(String hostname, String username, String password, String origin, boolean disableSSLValidation, boolean isSSO, String refreshToken, Logger logi, @@ -115,12 +121,16 @@ public CxHttpClient(String hostname, String username, String password, String or if (proxyHost != null) { setCustomProxy(cb, proxyHost, proxyPort, proxyUser, proxyPassword, logi); - } - else { + } else { setProxy(cb, logi); } - cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); + if (useSSo) { + cb.setDefaultCredentialsProvider(new WindowsCredentialsProvider(new SystemDefaultCredentialsProvider())); + cb.setDefaultCookieStore(cookieStore); + } else { + cb.setConnectionReuseStrategy(new NoConnectionReuseStrategy()); + } cb.setDefaultAuthSchemeRegistry(getAuthSchemeProviderRegistry()); cb.useSystemProperties(); apacheClient = cb.build(); @@ -205,6 +215,8 @@ private static Registry getAuthSchemeProviderRegistry() { return RegistryBuilder.create() .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.NTLM, new WindowsNTLMSchemeFactory(null)) + .register(AuthSchemes.SPNEGO, new WindowsNegotiateSchemeFactory(null)) .build(); } @@ -212,13 +224,86 @@ public void login() throws IOException, CxClientException { if (refreshToken != null) { token = getAccessTokenFromRefreshToken(); } else if (useSSo) { - HttpPost post = new HttpPost(rootUri + SSO_AUTHENTICATION); - request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), new StringEntity(""), TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); + token = ssoLogin(); } else { token = generateToken(); } } + private TokenLoginResponse ssoLogin() throws CxClientException { + HttpUriRequest request; + HttpResponse response = null; + final String BASE_URL = "/auth/identity/"; + + RequestConfig requestConfig = RequestConfig.custom() + .setRedirectsEnabled(false) + .setAuthenticationEnabled(true) + .setCookieSpec(CookieSpecs.STANDARD) + .build(); + try { + //Request1 + request = RequestBuilder.post() + .setUri(rootUri + SSO_AUTHENTICATION) + .setConfig(requestConfig) + .setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()) + .setEntity(generateSSOEntity()) + .build(); + + response = apacheClient.execute(request); + + //Request2 + String cookies = retrieveCookies(); + String redirectURL = response.getHeaders("Location")[0].getValue(); + request = RequestBuilder.get() + .setUri(rootUri + BASE_URL + redirectURL) + .setConfig(requestConfig) + .setHeader("Cookie", cookies) + .setHeader("Upgrade-Insecure-Requests", "1") + .build(); + response = apacheClient.execute(request); + + //Request3 + cookies = retrieveCookies(); + redirectURL = response.getHeaders("Location")[0].getValue(); + redirectURL = rootUri + redirectURL.replace("/CxRestAPI/", ""); + request = RequestBuilder.get() + .setUri(redirectURL) + .setConfig(requestConfig) + .setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString()) + .setHeader("Cookie", cookies) + .build(); + response = apacheClient.execute(request); + return extractToken(response); + } catch (IOException e) { + log.error("Fail to login with windows authentication: " + e.getMessage()); + throw new CxClientException("Fail to login with windows authentication: " + e.getMessage()); + } + } + + private TokenLoginResponse extractToken(HttpResponse response) { + String redirectURL = response.getHeaders("Location")[0].getValue(); + if (!redirectURL.contains("access_token")) { + throw new CxClientException("Failed retrieving access token from server"); + } + return new Gson().fromJson(urlToJson(redirectURL), TokenLoginResponse.class); + } + + private String urlToJson(String url) { + url = url.replaceAll("=", "\":\""); + url = url.replaceAll("&", "\",\""); + return "{\"" + url + "\"}"; + } + + private String retrieveCookies() { + List cookieList = cookieStore.getCookies(); + String cookies = ""; + for (Cookie cookie : cookieList) { + cookies += cookie.getName() + "=" + cookie.getValue() + ";"; + } + + return cookies; + } + public TokenLoginResponse generateToken() throws IOException, CxClientException { return generateToken(ClientType.RESOURCE_OWNER); } @@ -230,7 +315,7 @@ public TokenLoginResponse generateToken(ClientType clientType) throws IOExceptio return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } catch (CxClientException e) { - if(!e.getMessage().contains("invalid_scope")) { + if (!e.getMessage().contains("invalid_scope")) { throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); } ClientType.RESOURCE_OWNER.setScopes("sast_rest_api"); @@ -380,4 +465,32 @@ private void setSSLTls(String protocol, Logger log) { } } + //TODO handle missing scope issue with management_and_orchestration_api + private StringEntity generateSSOEntity() throws CxClientException { + final String clientId = "cxsast_client"; + final String redirectUri = "%2Fcxwebclient%2FauthCallback.html%3F"; + final String responseType = "id_token%20token"; + final String nonce = "9313f0902ba64e50bc564f5137f35a52"; + final String isPrompt = "true"; + final String scopes = "sast_api openid sast-permissions access-control-permissions access_control_api management_and_orchestration_api".replace(" ", "%20"); + final String providerId = "2"; //windows provider id + + String redirectUrl = MessageFormat.format("/CxRestAPI/auth/identity/connect/authorize/callback" + + "?client_id={0}" + + "&redirect_uri={1}" + redirectUri + + "&response_type={2}" + + "&scope={3}" + + "&nonce={4}" + + "&prompt={5}" + , clientId, rootUri, responseType, scopes, nonce, isPrompt); + try { + List urlParameters = new ArrayList<>(); + urlParameters.add(new BasicNameValuePair("redirectUrl", redirectUrl)); + urlParameters.add(new BasicNameValuePair("providerid", providerId)); + return new UrlEncodedFormEntity(urlParameters, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new CxClientException(e.getMessage()); + } + } + } diff --git a/src/main/java/testi.java b/src/main/java/testi.java index 94b85aba..dc9d85ac 100644 --- a/src/main/java/testi.java +++ b/src/main/java/testi.java @@ -1,148 +1,148 @@ -import com.cx.restclient.CxShragaClient; -import com.cx.restclient.common.ShragaUtils; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.restclient.sast.dto.SASTResults; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; - - -/** - * Created by Galn on 04/03/2018. - */ -public class testi { - private static String DEFAULT_FILTER_PATTERNS = "!**/_cvs/**/*, !**/.svn/**/*, !**/.hg/**/*, !**/.git/**/*, !**/.bzr/**/*, !**/bin/**/*," + - "!**/obj/**/*, !**/backup/**/*, !**/.idea/**/*, !**/*.DS_Store, !**/*.ipr, !**/*.iws, " + - "!**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, " + - "!**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, " + - "!**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, " + - "!**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.swf, " + - "!**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, " + - "!**/*.tar.gz, !**/*.tar, !**/*.gz, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, " + - "!**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.war, !**/*.ear, " + - "!**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, " + - "!**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, !**/node_modules/**/*"; - - private static String DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS = "*.zip, *.tgz, *.war, *.ear"; - - - public static void main(String[] args) throws Exception { - - - - SASTResults sastResults = null; - // SASTResults lastSastResults = null; - OSAResults osaResults = null; - // OSAResults lastOsaResults = null; - Logger logi = LoggerFactory.getLogger("testush"); - - - CxScanConfig config = setConfigi(); - - - CxShragaClient shraga = new CxShragaClient(config, logi); - // shraga.getClientVersion(); - shraga.init(); - - try { - if (config.getOsaEnabled()) { - shraga.createOSAScan(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getSastEnabled()) { - shraga.createSASTScan(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getSastEnabled()) { - sastResults = shraga.waitForSASTResults(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - try { - if (config.getOsaEnabled()) { - osaResults = shraga.waitForOSAResults(); - } - } catch (Exception ex) { - logi.error(ex.getMessage()); - } - - //lastSastResults = shraga.getLatestSASTResults(); - // lastOsaResults = shraga.getLatestOSAResults(); - if (config.getEnablePolicyViolations()) { - shraga.printIsProjectViolated(); - } - //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); - String s = shraga.generateHTMLSummary(); - File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); - FileUtils.writeStringToFile(file, s); - - shraga.close(); - } - - - private static CxScanConfig setConfigi() { - CxScanConfig config = new CxScanConfig(); - config.setSastEnabled(true); - config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); - //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); - config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); - //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); - config.setUsername("adi.gido@checkmarx.com"); - //config.setPassword("Cx123456!"); - config.setPassword("Cx@123456"); - config.setAvoidDuplicateProjectScans(false); - - // config.setUrl("http://10.32.1.91"); - config.setUrl("https://sast.checkmarx.net"); - config.setCxOrigin("common"); - config.setProjectName("Bamboo Plugin - Main"); - //config.setProjectName("OSAPROJ"); - config.setPresetName("Default"); - //config.setPresetId(7); - config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); - //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); - config.setSastFolderExclusions(""); - config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); - config.setSastScanTimeoutInMinutes(null); - config.setScanComment(""); - config.setIncremental(false); - config.setSynchronous(true); - config.setSastThresholdsEnabled(false); - config.setSastHighThreshold(1); - config.setSastMediumThreshold(1); - config.setSastLowThreshold(1); - config.setGeneratePDFReport(true); - config.setOsaEnabled(false); - - - config.setOsaFilterPattern("");//TODO check - config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); - config.setOsaRunInstall(true); - config.setOsaThresholdsEnabled(true); - config.setOsaHighThreshold(10); - config.setOsaMediumThreshold(0); - config.setOsaLowThreshold(0); - config.setDenyProject(false); - config.setPublic(true); - //config.setUseSSOLogin(false); - //config.setZipFile(); - //config.setOsaDependenciesJson(); - config.setEnablePolicyViolations(true); - - return config; - } - -} +//import com.cx.restclient.CxShragaClient; +//import com.cx.restclient.common.ShragaUtils; +//import com.cx.restclient.configuration.CxScanConfig; +//import com.cx.restclient.osa.dto.OSAResults; +//import com.cx.restclient.sast.dto.SASTResults; +//import org.apache.commons.io.FileUtils; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.File; +// +// +///** +// * Created by Galn on 04/03/2018. +// */ +//public class testi { +// private static String DEFAULT_FILTER_PATTERNS = "!**/_cvs/**/*, !**/.svn/**/*, !**/.hg/**/*, !**/.git/**/*, !**/.bzr/**/*, !**/bin/**/*," + +// "!**/obj/**/*, !**/backup/**/*, !**/.idea/**/*, !**/*.DS_Store, !**/*.ipr, !**/*.iws, " + +// "!**/*.bak, !**/*.tmp, !**/*.aac, !**/*.aif, !**/*.iff, !**/*.m3u, !**/*.mid, !**/*.mp3, " + +// "!**/*.mpa, !**/*.ra, !**/*.wav, !**/*.wma, !**/*.3g2, !**/*.3gp, !**/*.asf, !**/*.asx, " + +// "!**/*.avi, !**/*.flv, !**/*.mov, !**/*.mp4, !**/*.mpg, !**/*.rm, !**/*.swf, !**/*.vob, " + +// "!**/*.wmv, !**/*.bmp, !**/*.gif, !**/*.jpg, !**/*.png, !**/*.psd, !**/*.tif, !**/*.swf, " + +// "!**/*.jar, !**/*.zip, !**/*.rar, !**/*.exe, !**/*.dll, !**/*.pdb, !**/*.7z, !**/*.gz, " + +// "!**/*.tar.gz, !**/*.tar, !**/*.gz, !**/*.ahtm, !**/*.ahtml, !**/*.fhtml, !**/*.hdm, " + +// "!**/*.hdml, !**/*.hsql, !**/*.ht, !**/*.hta, !**/*.htc, !**/*.htd, !**/*.war, !**/*.ear, " + +// "!**/*.htmls, !**/*.ihtml, !**/*.mht, !**/*.mhtm, !**/*.mhtml, !**/*.ssi, !**/*.stm, " + +// "!**/*.stml, !**/*.ttml, !**/*.txn, !**/*.xhtm, !**/*.xhtml, !**/*.class, !**/*.iml, !Checkmarx/Reports/*.*, !**/node_modules/**/*"; +// +// private static String DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS = "*.zip, *.tgz, *.war, *.ear"; +// +// +// public static void main(String[] args) throws Exception { +// +// +// +// SASTResults sastResults = null; +// // SASTResults lastSastResults = null; +// OSAResults osaResults = null; +// // OSAResults lastOsaResults = null; +// Logger logi = LoggerFactory.getLogger("testush"); +// +// +// CxScanConfig config = setConfigi(); +// +// +// CxShragaClient shraga = new CxShragaClient(config, logi); +// // shraga.getClientVersion(); +// shraga.init(); +// +// try { +// if (config.getOsaEnabled()) { +// shraga.createOSAScan(); +// } +// } catch (Exception ex) { +// logi.error(ex.getMessage()); +// } +// +// try { +// if (config.getSastEnabled()) { +// shraga.createSASTScan(); +// } +// } catch (Exception ex) { +// logi.error(ex.getMessage()); +// } +// +// try { +// if (config.getSastEnabled()) { +// sastResults = shraga.waitForSASTResults(); +// } +// } catch (Exception ex) { +// logi.error(ex.getMessage()); +// } +// +// try { +// if (config.getOsaEnabled()) { +// osaResults = shraga.waitForOSAResults(); +// } +// } catch (Exception ex) { +// logi.error(ex.getMessage()); +// } +// +// //lastSastResults = shraga.getLatestSASTResults(); +// // lastOsaResults = shraga.getLatestOSAResults(); +// if (config.getEnablePolicyViolations()) { +// shraga.printIsProjectViolated(); +// } +// //String buildFailedResult = ShragaUtils.getBuildFailureResult(config, sastResults, osaResults); +// String s = shraga.generateHTMLSummary(); +// File file = new File("C:\\Users\\galn\\Desktop\\New folder\\a.html"); +// FileUtils.writeStringToFile(file, s); +// +// shraga.close(); +// } +// +// +// private static CxScanConfig setConfigi() { +// CxScanConfig config = new CxScanConfig(); +// config.setSastEnabled(true); +// config.setSourceDir("C:\\cxdev\\CxPlugins\\Bamboo-Plugin"); +// //config.setSourceDir("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\Folder1\\Folder2\\Folder3"); +// config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\reportsDir")); +// //config.setReportsDir(new File("C:\\Users\\galn\\Desktop\\restiDir\\srcDir\\SAST\\ss")); +// config.setUsername("adi.gido@checkmarx.com"); +// //config.setPassword("Cx123456!"); +// config.setPassword("Cx@123456"); +// config.setAvoidDuplicateProjectScans(false); +// +// // config.setUrl("http://10.32.1.91"); +// config.setUrl("https://sast.checkmarx.net"); +// config.setCxOrigin("common"); +// config.setProjectName("Bamboo Plugin - Main"); +// //config.setProjectName("OSAPROJ"); +// config.setPresetName("Default"); +// //config.setPresetId(7); +// config.setTeamPath("\\CxServer\\SP\\Plugins\\Plugins_Team"); +// //config.setTeamId("00000000-1111-1111-b111-989c9070eb11"); +// config.setSastFolderExclusions(""); +// config.setSastFilterPattern(DEFAULT_FILTER_PATTERNS); +// config.setSastScanTimeoutInMinutes(null); +// config.setScanComment(""); +// config.setIncremental(false); +// config.setSynchronous(true); +// config.setSastThresholdsEnabled(false); +// config.setSastHighThreshold(1); +// config.setSastMediumThreshold(1); +// config.setSastLowThreshold(1); +// config.setGeneratePDFReport(true); +// config.setOsaEnabled(false); +// +// +// config.setOsaFilterPattern("");//TODO check +// config.setOsaArchiveIncludePatterns(DEFAULT_OSA_ARCHIVE_INCLUDE_PATTERNS); +// config.setOsaRunInstall(true); +// config.setOsaThresholdsEnabled(true); +// config.setOsaHighThreshold(10); +// config.setOsaMediumThreshold(0); +// config.setOsaLowThreshold(0); +// config.setDenyProject(false); +// config.setPublic(true); +// //config.setUseSSOLogin(false); +// //config.setZipFile(); +// //config.setOsaDependenciesJson(); +// config.setEnablePolicyViolations(true); +// +// return config; +// } +// +//} diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/connection/ConnectionTests.java index 0c9de621..59a76783 100644 --- a/src/test/java/com/cx/restclient/connection/ConnectionTests.java +++ b/src/test/java/com/cx/restclient/connection/ConnectionTests.java @@ -43,11 +43,10 @@ private CxScanConfig initConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); config.setUseSSOLogin(true); - config.setUsername(props.getProperty("user")); - config.setPassword(props.getProperty("password")); config.setUrl(props.getProperty("serverUrl")); config.setCxOrigin("common"); - + config.setTeamId("1"); + config.setPresetName("Default"); return config; } diff --git a/src/test/java/resources/config.properties b/src/test/resources/config.properties similarity index 60% rename from src/test/java/resources/config.properties rename to src/test/resources/config.properties index b3effecf..306312ad 100644 --- a/src/test/java/resources/config.properties +++ b/src/test/resources/config.properties @@ -1,2 +1,2 @@ -serverUrl=http://10.32.1.57 +serverUrl=http:// sastSource=C:\\sources\\BookStore_Small_CLI \ No newline at end of file From cfd27e642f0be41fdd5cf65c20025993252d3259 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 1 Dec 2019 15:09:08 +0200 Subject: [PATCH 179/473] Added config validation, tests. --- .../com/cx/restclient/CxShragaClient.java | 44 +++++++++++++------ .../java/com/cx/restclient/SCAClient.java | 13 +++--- .../connection/CommonClientTest.java | 26 +++++++++++ .../connection/ConnectionTests.java | 23 ++-------- .../connection/GetTeamListTests.java | 35 +++++++++++++++ .../connection/ProjectScanTests.java | 24 +--------- 6 files changed, 103 insertions(+), 62 deletions(-) create mode 100644 src/test/java/com/cx/restclient/connection/CommonClientTest.java create mode 100644 src/test/java/com/cx/restclient/connection/GetTeamListTests.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 5444a7af..3b3b07c5 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -6,13 +6,12 @@ import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.cxArm.dto.CxArmConfig; import com.cx.restclient.dto.*; -import com.cx.restclient.dto.TokenLoginResponse; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.osa.dto.ClientType; -import com.cx.restclient.osa.dto.ClientType; import com.cx.restclient.sast.dto.*; +import org.apache.commons.lang3.StringUtils; import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; @@ -52,28 +51,45 @@ public class CxShragaClient { private DependencyScanner dependencyScanner; public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLException, CxClientException { + validateConfig(config); + this.config = config; this.log = log; - this.httpClient = new CxHttpClient( - UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), - config.getCxOrigin(), - config.isDisableCertificateValidation(), - config.isUseSSOLogin(), - config.getProxyConfig(), - log); - - if (config.getSastEnabled()) { + if (config.isSastOrOSAEnabled()) { + this.httpClient = new CxHttpClient( + UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), + config.getCxOrigin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + config.getProxyConfig(), + log); sastClient = new CxSASTClient(httpClient, log, config); + + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { + dependencyScanner = new CxOSAClient(httpClient, log, config); + } } - if (config.getDependencyScannerType() == DependencyScannerType.OSA) { - dependencyScanner = new CxOSAClient(httpClient, log, config); - } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { + if (config.getDependencyScannerType() == DependencyScannerType.SCA) { dependencyScanner = new SCAClient(log, config); } } + private void validateConfig(CxScanConfig config) throws CxClientException { + String message = null; + if (config == null) { + message = "Non-null config must be provided."; + } else if (!StringUtils.isEmpty(config.getUrl()) && !config.isSastOrOSAEnabled()) { + message = "Config contains server URL, but neither SAST nor OSA is enabled. Please enable SAST, OSA or both in config."; + } else if (StringUtils.isEmpty(config.getUrl()) && config.isSastOrOSAEnabled()) { + message = "Server URL is required when SAST or OSA is enabled."; + } + if (message != null) { + throw new CxClientException(message); + } + } + public CxShragaClient(String serverUrl, String username, String password, String origin, boolean disableCertificateValidation, Logger log) throws MalformedURLException, CxClientException { this(new CxScanConfig(serverUrl, username, password, origin, disableCertificateValidation), log); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 824695e5..61256158 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -15,7 +15,7 @@ import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.SCAWaiter; import com.cx.restclient.sca.dto.*; -import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; @@ -26,7 +26,10 @@ import org.apache.http.entity.mime.content.StringBody; import org.slf4j.Logger; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -118,7 +121,7 @@ public void waitForScanResults(DependencyScanResults target) throws CxClientExce throw new CxClientException("Error retrieving SCA scan results.", e); } - if (!Strings.isNullOrEmpty(scaResult.getWebReportLink())) { + if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { log.info("SCA scan results location: " + scaResult.getWebReportLink()); } @@ -158,7 +161,7 @@ private void resolveProject() throws IOException, CxClientException { private String getProjectIdByName(String name) throws IOException, CxClientException { log.debug("Getting project by name: " + name); - if (Strings.isNullOrEmpty(name)) { + if (StringUtils.isEmpty(name)) { throw new CxClientException("Non-empty project name must be provided."); } @@ -232,7 +235,7 @@ private String getWebReportLink(String reportId) { String result = null; try { String webAppUrl = getScaConfig().getWebAppUrl(); - if (Strings.isNullOrEmpty(webAppUrl)) { + if (StringUtils.isEmpty(webAppUrl)) { log.warn(MESSAGE + "Web app URL is not specified."); } else { String encoding = StandardCharsets.UTF_8.name(); diff --git a/src/test/java/com/cx/restclient/connection/CommonClientTest.java b/src/test/java/com/cx/restclient/connection/CommonClientTest.java new file mode 100644 index 00000000..04ba087a --- /dev/null +++ b/src/test/java/com/cx/restclient/connection/CommonClientTest.java @@ -0,0 +1,26 @@ +package com.cx.restclient.connection; + +import com.cx.utility.TestingUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Properties; + +public abstract class CommonClientTest { + private static final String PROPERTIES_FILE = "config.properties"; + Logger log = LoggerFactory.getLogger(ConnectionTests.class.getName()); + static Properties props; + + @BeforeClass + public static void initTest() throws IOException { + props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); + } + + void failOnException(Exception e) { + log.error("Unexpected exception.", e); + Assert.fail(e.getMessage()); + } +} diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/connection/ConnectionTests.java index 0c9de621..71ab12ef 100644 --- a/src/test/java/com/cx/restclient/connection/ConnectionTests.java +++ b/src/test/java/com/cx/restclient/connection/ConnectionTests.java @@ -3,34 +3,17 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.osa.dto.OSAResults; -import com.cx.utility.TestingUtils; import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Properties; - -public class ConnectionTests { - - private Logger log = LoggerFactory.getLogger(ConnectionTests.class.getName()); - private static final String PROPERTIES_FILE = "config.properties"; - private CxShragaClient client; - private static Properties props; - - @BeforeClass - public static void initTest() throws IOException { - props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); - } +public class ConnectionTests extends CommonClientTest { @Test public void ssoConnectionTest() { CxScanConfig config = initConfig(); try { - client = new CxShragaClient(config, log); + CxShragaClient client = new CxShragaClient(config, log); client.init(); } catch (IOException | CxClientException e) { e.printStackTrace(); @@ -43,7 +26,7 @@ private CxScanConfig initConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); config.setUseSSOLogin(true); - config.setUsername(props.getProperty("user")); + config.setUsername(props.getProperty("username")); config.setPassword(props.getProperty("password")); config.setUrl(props.getProperty("serverUrl")); config.setCxOrigin("common"); diff --git a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java new file mode 100644 index 00000000..1669a2a4 --- /dev/null +++ b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java @@ -0,0 +1,35 @@ +package com.cx.restclient.connection; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.Team; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class GetTeamListTests extends CommonClientTest { + @Test + public void getTeamListTest() { + CxScanConfig config = initConfig(); + try { + CxShragaClient client = new CxShragaClient(config, log); + client.login(); + List teams = client.getTeamList(); + Assert.assertNotNull(teams); + Assert.assertFalse(teams.isEmpty()); + } catch (Exception e) { + failOnException(e); + } + } + + private CxScanConfig initConfig() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setUsername(props.getProperty("username")); + config.setPassword(props.getProperty("password")); + config.setUrl(props.getProperty("serverUrl")); + config.setCxOrigin("common"); + return config; + } +} diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index c6e5f2d2..ef705209 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -9,32 +9,15 @@ import com.cx.restclient.sast.dto.SASTResults; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sca.dto.SCAResults; -import com.cx.utility.TestingUtils; import org.junit.Assert; -import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; -import java.io.IOException; import java.net.MalformedURLException; -import java.util.Properties; @Ignore -public class ProjectScanTests { - - private static final String PROPERTIES_FILE = "config.properties"; - - private Logger log = LoggerFactory.getLogger(ProjectScanTests.class.getName()); - private static Properties props; - - @BeforeClass - public static void initTest() throws IOException { - props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); - } - +public class ProjectScanTests extends CommonClientTest { @Test public void runOsaScan() throws MalformedURLException, CxClientException { CxScanConfig config = initOsaConfig(); @@ -182,9 +165,4 @@ private CxScanConfig initScaConfig() { return config; } - - private void failOnException(Exception e) { - log.error("Error running scan.", e); - Assert.fail(e.getMessage()); - } } From 7aa2bf4832646ba3fddc7c1f2da89b0f27d0c447 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 2 Dec 2019 11:17:48 +0200 Subject: [PATCH 180/473] Fixed working with server URL. --- .../java/com/cx/restclient/CxShragaClient.java | 15 +++++++-------- .../restclient/connection/GetTeamListTests.java | 1 - 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 3b3b07c5..f095b27d 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -56,7 +56,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept this.config = config; this.log = log; - if (config.isSastOrOSAEnabled()) { + if (!StringUtils.isEmpty(config.getUrl())) { this.httpClient = new CxHttpClient( UrlUtils.parseURLToString(config.getUrl(), "CxRestAPI/"), config.getCxOrigin(), @@ -64,14 +64,15 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.isUseSSOLogin(), config.getProxyConfig(), log); - sastClient = new CxSASTClient(httpClient, log, config); + } - if (config.getDependencyScannerType() == DependencyScannerType.OSA) { - dependencyScanner = new CxOSAClient(httpClient, log, config); - } + if (config.getSastEnabled()) { + sastClient = new CxSASTClient(httpClient, log, config); } - if (config.getDependencyScannerType() == DependencyScannerType.SCA) { + if (config.getDependencyScannerType() == DependencyScannerType.OSA) { + dependencyScanner = new CxOSAClient(httpClient, log, config); + } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { dependencyScanner = new SCAClient(log, config); } } @@ -80,8 +81,6 @@ private void validateConfig(CxScanConfig config) throws CxClientException { String message = null; if (config == null) { message = "Non-null config must be provided."; - } else if (!StringUtils.isEmpty(config.getUrl()) && !config.isSastOrOSAEnabled()) { - message = "Config contains server URL, but neither SAST nor OSA is enabled. Please enable SAST, OSA or both in config."; } else if (StringUtils.isEmpty(config.getUrl()) && config.isSastOrOSAEnabled()) { message = "Server URL is required when SAST or OSA is enabled."; } diff --git a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java index 1669a2a4..8cae278b 100644 --- a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java +++ b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java @@ -25,7 +25,6 @@ public void getTeamListTest() { private CxScanConfig initConfig() { CxScanConfig config = new CxScanConfig(); - config.setSastEnabled(true); config.setUsername(props.getProperty("username")); config.setPassword(props.getProperty("password")); config.setUrl(props.getProperty("serverUrl")); From 897633494c7f1ee84f1d1717e54878f04460fe3b Mon Sep 17 00:00:00 2001 From: morad Date: Thu, 5 Dec 2019 11:41:34 +0200 Subject: [PATCH 181/473] update version in 9.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2fa09353..3b4a64c7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.1-SNAPSHOT + 9.20.2 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From ad0976bf7a274cdb5e21fca63420f0eb5ba8e351 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 5 Dec 2019 11:43:00 +0200 Subject: [PATCH 182/473] Fix after API path changes. --- src/main/java/com/cx/restclient/SCAClient.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 61256158..2182ca64 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -40,11 +40,15 @@ */ public class SCAClient implements DependencyScanner { private static class UrlPaths { - private static final String PROJECTS = "/risk_management/api/projects"; - private static final String SUMMARY_REPORT = "/risk_management/api/riskReports/%s/summary"; - private static final String ZIP_UPLOAD = "/scans/api/scans/zip"; - private static final String SCAN_STATUS = "/scans/api/scans/%s/status"; - private static final String REPORT_ID = "/scans/api/scans/%s/riskReportId"; + private static final String RISK_MANAGEMENT_API = "/risk-management/api/"; + private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; + private static final String SUMMARY_REPORT = RISK_MANAGEMENT_API + "riskReports/%s/summary"; + + private static final String SCAN_API = "/emerald/api/scans/"; + private static final String ZIP_UPLOAD = SCAN_API + "zip"; + private static final String SCAN_STATUS = SCAN_API + "%s/status"; + private static final String REPORT_ID = SCAN_API + "%s/riskReportId"; + private static final String WEB_REPORT = "/#/projects/%s/report/%s"; } From a17fe6323f4033b820bc59b54bc528dc868ff15c Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 5 Dec 2019 11:46:12 +0200 Subject: [PATCH 183/473] AB#361: Implemented SCA connection testing. --- .../com/cx/restclient/CxShragaClient.java | 19 +++++++++++++- .../java/com/cx/restclient/SCAClient.java | 25 +++++++++++++------ .../restclient/dto/DependencyScannerType.java | 16 +++++++++--- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index f095b27d..7a94777a 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -73,7 +73,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept if (config.getDependencyScannerType() == DependencyScannerType.OSA) { dependencyScanner = new CxOSAClient(httpClient, log, config); } else if (config.getDependencyScannerType() == DependencyScannerType.SCA) { - dependencyScanner = new SCAClient(log, config); + dependencyScanner = new SCAClient(config, log); } } @@ -191,6 +191,23 @@ public void printIsProjectViolated() { } } + /** + * @param config + * The following config properties are used: + * scaConfig + * proxyConfig + * cxOrigin + * disableCertificateValidation + */ + public static void testScaConnection(CxScanConfig config, Logger log) throws CxClientException { + SCAClient client = new SCAClient(config, log); + try { + client.testConnection(); + } catch (IOException e) { + throw new CxClientException(e); + } + } + private CxArmConfig getCxARMConfig() throws IOException, CxClientException { return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 2182ca64..60846ef7 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -61,8 +61,7 @@ private static class UrlPaths { private String projectId; private final Waiter waiter; private String scanId; - - SCAClient(Logger log, CxScanConfig config) throws CxClientException { + SCAClient(CxScanConfig config, Logger log) throws CxClientException { this.log = log; this.config = config; @@ -138,6 +137,12 @@ public DependencyScanResults getLatestScanResults() { return null; } + void testConnection() throws IOException, CxClientException { + // The calls below allow to check both access control and API connectivity. + login(); + getProjects(); + } + private void login() throws IOException, CxClientException { log.info("Logging into SCA."); SCAConfig scaConfig = getScaConfig(); @@ -169,12 +174,7 @@ private String getProjectIdByName(String name) throws IOException, CxClientExcep throw new CxClientException("Non-empty project name must be provided."); } - List allProjects = (List) httpClient.getRequest(UrlPaths.PROJECTS, - ContentType.CONTENT_TYPE_APPLICATION_JSON, - Project.class, - HttpStatus.SC_OK, - "SCA projects", - true); + List allProjects = getProjects(); return allProjects.stream() .filter((Project project) -> name.equals(project.getName())) @@ -183,6 +183,15 @@ private String getProjectIdByName(String name) throws IOException, CxClientExcep .orElse(null); } + private List getProjects() throws IOException, CxClientException { + return (List) httpClient.getRequest(UrlPaths.PROJECTS, + ContentType.CONTENT_TYPE_APPLICATION_JSON, + Project.class, + HttpStatus.SC_OK, + "SCA projects", + true); + } + private String createProject(String name) throws CxClientException, IOException { CreateProjectRequest request = new CreateProjectRequest(); request.setName(name); diff --git a/src/main/java/com/cx/restclient/dto/DependencyScannerType.java b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java index 8e572dd3..a63658c6 100644 --- a/src/main/java/com/cx/restclient/dto/DependencyScannerType.java +++ b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java @@ -4,8 +4,18 @@ public enum DependencyScannerType { /** * Indicates that dependency scan should not be performed. */ - NONE, + NONE("None"), - OSA, - SCA + OSA("CxOSA"), + SCA("SCA"); + + private final String displayName; + + DependencyScannerType(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } } From 77ede37275501b43f2998621810f776198134f7b Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 5 Dec 2019 12:01:31 +0200 Subject: [PATCH 184/473] AB#361: Added SCA connection unit test. --- .../cx/restclient/connection/ConnectionTests.java | 13 +++++++++++++ .../cx/restclient/connection/ProjectScanTests.java | 10 ++-------- .../connection/VerifyConnectionTests.java | 13 +++++++++++++ src/test/java/com/cx/utility/TestingUtils.java | 13 +++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/connection/ConnectionTests.java index 71ab12ef..03c2dbb3 100644 --- a/src/test/java/com/cx/restclient/connection/ConnectionTests.java +++ b/src/test/java/com/cx/restclient/connection/ConnectionTests.java @@ -3,6 +3,7 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.exception.CxClientException; +import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.Test; @@ -22,6 +23,18 @@ public void ssoConnectionTest() { } } + @Test + public void scaConnectionTest() { + CxScanConfig config = new CxScanConfig(); + config.setCxOrigin("common"); + config.setScaConfig(TestingUtils.getScaConfig(props)); + try { + CxShragaClient.testScaConnection(config, log); + } catch (CxClientException e) { + failOnException(e); + } + } + private CxScanConfig initConfig() { CxScanConfig config = new CxScanConfig(); config.setSastEnabled(true); diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java index ef705209..2831ac46 100644 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java @@ -9,6 +9,7 @@ import com.cx.restclient.sast.dto.SASTResults; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sca.dto.SCAResults; +import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -153,14 +154,7 @@ private CxScanConfig initScaConfig() { config.setOsaThresholdsEnabled(true); config.setProjectName("scaOnlyScan"); - SCAConfig sca = new SCAConfig(); - sca.setApiUrl(props.getProperty("sca.apiUrl")); - sca.setAccessControlUrl(props.getProperty("sca.accessControlUrl")); - sca.setTenant(props.getProperty("sca.tenant")); - sca.setUsername(props.getProperty("sca.username")); - sca.setPassword(props.getProperty("sca.password")); - sca.setWebAppUrl(props.getProperty("sca.webAppUrl")); - + SCAConfig sca = TestingUtils.getScaConfig(props); config.setScaConfig(sca); return config; diff --git a/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java b/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java new file mode 100644 index 00000000..f6303c46 --- /dev/null +++ b/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java @@ -0,0 +1,13 @@ +package com.cx.restclient.connection; + +import com.cx.restclient.configuration.CxScanConfig; +import org.junit.Test; + +public class VerifyConnectionTests { + @Test + public void verifyConnectionSuccess() { + CxScanConfig config = new CxScanConfig(); + config.setCxOrigin("common"); + + } +} diff --git a/src/test/java/com/cx/utility/TestingUtils.java b/src/test/java/com/cx/utility/TestingUtils.java index 5871d639..ce76f7d4 100644 --- a/src/test/java/com/cx/utility/TestingUtils.java +++ b/src/test/java/com/cx/utility/TestingUtils.java @@ -1,5 +1,7 @@ package com.cx.utility; +import com.cx.restclient.sca.dto.SCAConfig; + import java.io.FileReader; import java.io.IOException; import java.net.URL; @@ -15,4 +17,15 @@ public static Properties getProps(String propsName, Class clazz) throws IOExcept return properties; } + + public static SCAConfig getScaConfig(Properties props) { + SCAConfig result = new SCAConfig(); + result.setApiUrl(props.getProperty("sca.apiUrl")); + result.setAccessControlUrl(props.getProperty("sca.accessControlUrl")); + result.setTenant(props.getProperty("sca.tenant")); + result.setUsername(props.getProperty("sca.username")); + result.setPassword(props.getProperty("sca.password")); + result.setWebAppUrl(props.getProperty("sca.webAppUrl")); + return result; + } } From eb23de1f6d266d89f4c70b0cf8f349e27adbbe55 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 10 Dec 2019 15:56:19 +0200 Subject: [PATCH 185/473] Fixes after merge. --- src/main/java/com/cx/restclient/CxShragaClient.java | 1 + src/main/java/com/cx/restclient/SCAClient.java | 2 ++ .../cx/restclient/connection/ConnectionTests.java | 1 + .../connection/VerifyConnectionTests.java | 13 ------------- 4 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index cf1bb4af..d54b4847 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -62,6 +62,7 @@ public CxShragaClient(CxScanConfig config, Logger log) throws MalformedURLExcept config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), + config.getRefreshToken(), config.getProxyConfig(), log); } diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 60846ef7..c4a0fda8 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -61,6 +61,7 @@ private static class UrlPaths { private String projectId; private final Waiter waiter; private String scanId; + SCAClient(CxScanConfig config, Logger log) throws CxClientException { this.log = log; this.config = config; @@ -74,6 +75,7 @@ private static class UrlPaths { config.getCxOrigin(), config.isDisableCertificateValidation(), config.isUseSSOLogin(), + null, config.getProxyConfig(), log); diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/connection/ConnectionTests.java index 95f8c16d..f9a3efbc 100644 --- a/src/test/java/com/cx/restclient/connection/ConnectionTests.java +++ b/src/test/java/com/cx/restclient/connection/ConnectionTests.java @@ -3,6 +3,7 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.exception.CxClientException; +import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java b/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java deleted file mode 100644 index f6303c46..00000000 --- a/src/test/java/com/cx/restclient/connection/VerifyConnectionTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cx.restclient.connection; - -import com.cx.restclient.configuration.CxScanConfig; -import org.junit.Test; - -public class VerifyConnectionTests { - @Test - public void verifyConnectionSuccess() { - CxScanConfig config = new CxScanConfig(); - config.setCxOrigin("common"); - - } -} From 6a4d27b5fa22466fcd540a41ba828bfe9d925266 Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Sun, 15 Dec 2019 13:17:44 +0200 Subject: [PATCH 186/473] fixed proxy with credentials issue --- src/main/java/com/cx/restclient/CxSASTClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 0e00641b..a133788a 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -12,6 +12,7 @@ import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.google.gson.Gson; import org.apache.http.HttpEntity; +import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; @@ -357,7 +358,7 @@ private void uploadZipFile(File zipFile, long projectId) throws CxClientExceptio builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); builder.addPart("zippedSource", streamBody); HttpEntity entity = builder.build(); - httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, entity, null, 204, "upload ZIP file"); + httpClient.postRequest(SAST_ZIP_ATTACHMENTS.replace("{projectId}", Long.toString(projectId)), null, new BufferedHttpEntity(entity), null, 204, "upload ZIP file"); } private CxID createScan(CreateScanRequest request) throws CxClientException, IOException { From fac5d57ebb1999d78c92f480a4be9aad385a983c Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 15 Dec 2019 15:25:23 +0200 Subject: [PATCH 187/473] Fixed an exception after a new field was added to SCA getProjects response. --- .../httpClient/utils/HttpClientHelper.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 51b85ebc..03b4f8b9 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -3,6 +3,7 @@ import com.cx.restclient.exception.CxClientException; import com.cx.restclient.exception.CxHTTPClientException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -38,7 +39,7 @@ public static T convertToObject(HttpResponse response, Class responseType } private static T convertToStrObject(HttpResponse response, Class valueType) throws CxClientException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = getObjectMapper(); try { if (response.getEntity() == null) { return null; @@ -52,7 +53,7 @@ private static T convertToStrObject(HttpResponse response, Class valueTyp } public static String convertToJson(Object o) throws CxClientException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = getObjectMapper(); try { return mapper.writeValueAsString(o); } catch (Exception e) { @@ -65,7 +66,7 @@ public static StringEntity convertToStringEntity(Object o) throws CxClientExcept } private static T convertToCollectionObject(HttpResponse response, JavaType javaType) throws CxClientException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = getObjectMapper(); try { String json = IOUtils.toString(response.getEntity().getContent(), Charset.defaultCharset()); return mapper.readValue(json, javaType); @@ -74,6 +75,14 @@ private static T convertToCollectionObject(HttpResponse response, JavaType j } } + private static ObjectMapper getObjectMapper() { + ObjectMapper result = new ObjectMapper(); + + // Prevent UnrecognizedPropertyException if additional fields are added to API responses. + result.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return result; + } + public static void validateResponse(HttpResponse response, int status, String message) throws CxClientException { if (response.getStatusLine().getStatusCode() != status) { String responseBody = extractResponseBody(response); From 20f4ec424dc82fa202ae4f98dd0f0751a818d4e9 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 16 Dec 2019 15:22:15 +0200 Subject: [PATCH 188/473] Using SCA pre-production environment due to dev environment instability. --- src/test/resources/config.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/resources/config.properties b/src/test/resources/config.properties index 152cd9f4..2698b754 100644 --- a/src/test/resources/config.properties +++ b/src/test/resources/config.properties @@ -3,9 +3,9 @@ username=myuser password=mypassword sastSource=C:\\sources\\BookStore_Small_CLI dependencyScanSourceDir=c:\\cxdev\\projectsToScan\\SastAndOsaSource -sca.apiUrl=https://api.lumodev.com +sca.apiUrl=https://ppe-api.cxsca.net sca.accessControlUrl=https://v2.ac-checkmarx.com -sca.webAppUrl=https://sca.lumodev.com +sca.webAppUrl=https://sca.cxsca.net sca.username=myuser sca.password=mypassword sca.tenant=mytenant From e10679f793fb33fd17ccc4e34c32b5bb13d50e3a Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 16 Dec 2019 15:26:12 +0200 Subject: [PATCH 189/473] Fixes after changes in SCA API. --- src/main/java/com/cx/restclient/SCAClient.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index c4a0fda8..127f057e 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -43,13 +43,12 @@ private static class UrlPaths { private static final String RISK_MANAGEMENT_API = "/risk-management/api/"; private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; private static final String SUMMARY_REPORT = RISK_MANAGEMENT_API + "riskReports/%s/summary"; + private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; + private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; - private static final String SCAN_API = "/emerald/api/scans/"; - private static final String ZIP_UPLOAD = SCAN_API + "zip"; - private static final String SCAN_STATUS = SCAN_API + "%s/status"; - private static final String REPORT_ID = SCAN_API + "%s/riskReportId"; + private static final String ZIP_UPLOAD = "/emerald/api/scans/zip"; - private static final String WEB_REPORT = "/#/projects/%s/report/%s"; + private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } private final Logger log; @@ -225,7 +224,7 @@ private String uploadZipFile(File zipFile) throws IOException, CxClientException HttpEntity entity = builder.build(); - String scanId = httpClient.postRequest(UrlPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_OK, "upload ZIP file"); + String scanId = httpClient.postRequest(UrlPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_CREATED, "upload ZIP file"); log.debug("Scan ID: " + scanId); return scanId; From ae42c30936c2334ed8d7a87b3e34c020ca63c86a Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 18 Dec 2019 13:52:50 +0200 Subject: [PATCH 190/473] Replaced unnecessary string with boolean. --- .../com/cx/restclient/dto/scansummary/ScanSummary.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java index 09b95355..5fdf92f6 100644 --- a/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java +++ b/src/main/java/com/cx/restclient/dto/scansummary/ScanSummary.java @@ -98,12 +98,12 @@ private void addDependencyScanThresholdErrors(CxScanConfig config, DependencySca SCAResults scaResults = dependencyScanResults.getScaResults(); OSAResults osaResults = dependencyScanResults.getOsaResults(); int totalHigh = 0, totalMedium = 0, totalLow = 0; - String severityType = null; + boolean hasSummary = false; if (scaResults != null) { SCASummaryResults summary = scaResults.getSummary(); if (summary != null) { - severityType = "SCA"; + hasSummary = true; totalHigh = summary.getHighVulnerabilityCount(); totalMedium = summary.getMediumVulnerabilityCount(); totalLow = summary.getLowVulnerabilityCount(); @@ -111,14 +111,14 @@ private void addDependencyScanThresholdErrors(CxScanConfig config, DependencySca } else if (osaResults != null && osaResults.isOsaResultsReady()) { OSASummaryResults summary = osaResults.getResults(); if (summary != null) { - severityType = "CxOSA"; + hasSummary = true; totalHigh = summary.getTotalHighVulnerabilities(); totalMedium = summary.getTotalMediumVulnerabilities(); totalLow = summary.getTotalLowVulnerabilities(); } } - if (severityType != null) { + if (hasSummary) { checkForThresholdError(totalHigh, config.getOsaHighThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.HIGH); checkForThresholdError(totalMedium, config.getOsaMediumThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.MEDIUM); checkForThresholdError(totalLow, config.getOsaLowThreshold(), ErrorSource.DEPENDENCY_SCANNER, Severity.LOW); From 01a35850b387c414b67b2ba40b1c241d288fec8f Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 18 Dec 2019 13:53:55 +0200 Subject: [PATCH 191/473] AB#409: Replaced 'SCA' with 'CxSCA' in log messages. --- .../java/com/cx/restclient/SCAClient.java | 29 +++++++++---------- .../restclient/dto/DependencyScannerType.java | 2 +- .../java/com/cx/restclient/sca/SCAWaiter.java | 8 ++--- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 127f057e..73c4a026 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -78,7 +78,7 @@ private static class UrlPaths { config.getProxyConfig(), log); - waiter = new SCAWaiter("SCA scan", pollInterval, maxRetries, httpClient, UrlPaths.SCAN_STATUS, log); + waiter = new SCAWaiter("CxSCA scan", pollInterval, maxRetries, httpClient, UrlPaths.SCAN_STATUS, log); } @Override @@ -87,14 +87,13 @@ public void init() throws CxClientException { login(); resolveProject(); } catch (IOException e) { - throw new CxClientException("Failed to init SCA Client.", e); + throw new CxClientException("Failed to init CxSCA Client.", e); } } @Override public String createScan(DependencyScanResults target) throws CxClientException { - log.info("----------------------------------- Create SCA Scan:------------------------------------"); - log.info("Creating SCA scan"); + log.info("----------------------------------- Create CxSCA Scan:------------------------------------"); PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); scanId = null; @@ -104,7 +103,7 @@ public String createScan(DependencyScanResults target) throws CxClientException scanId = uploadZipFile(zipFile); CxZipUtils.deleteZippedSources(zipFile, config, log); } catch (IOException e) { - throw new CxClientException("Error creating SCA scan.", e); + throw new CxClientException("Error creating CxSCA scan.", e); } return scanId; @@ -112,21 +111,21 @@ public String createScan(DependencyScanResults target) throws CxClientException @Override public void waitForScanResults(DependencyScanResults target) throws CxClientException { - log.info("------------------------------------Get SCA Results:-----------------------------------"); + log.info("------------------------------------Get CxSCA Results:-----------------------------------"); - log.info("Waiting for SCA scan to finish"); + log.info("Waiting for CxSCA scan to finish"); waiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); - log.info("SCA scan finished successfully. Retrieving SCA scan results."); + log.info("CxSCA scan finished successfully. Retrieving CxSCA scan results."); SCAResults scaResult; try { scaResult = retrieveScanResults(); } catch (IOException e) { - throw new CxClientException("Error retrieving SCA scan results.", e); + throw new CxClientException("Error retrieving CxSCA scan results.", e); } if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { - log.info("SCA scan results location: " + scaResult.getWebReportLink()); + log.info("CxSCA scan results location: " + scaResult.getWebReportLink()); } target.setScaResults(scaResult); @@ -145,7 +144,7 @@ void testConnection() throws IOException, CxClientException { } private void login() throws IOException, CxClientException { - log.info("Logging into SCA."); + log.info("Logging into CxSCA."); SCAConfig scaConfig = getScaConfig(); LoginSettings settings = new LoginSettings(); @@ -189,7 +188,7 @@ private List getProjects() throws IOException, CxClientException { ContentType.CONTENT_TYPE_APPLICATION_JSON, Project.class, HttpStatus.SC_OK, - "SCA projects", + "CxSCA projects", true); } @@ -289,7 +288,7 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException, ContentType.CONTENT_TYPE_APPLICATION_JSON, SCASummaryResults.class, HttpStatus.SC_OK, - "SCA report summary", + "CxSCA report summary", false); printSummary(result); @@ -299,7 +298,7 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException, // This method is for demo purposes and probably should be replaced in the future. private void printSummary(SCASummaryResults summary) { - log.info("\n----SCA risk report summary----"); + log.info("\n----CxSCA risk report summary----"); log.info("Created on: " + summary.getCreatedOn()); log.info("Direct packages: " + summary.getDirectPackages()); log.info("High vulnerabilities: " + summary.getHighVulnerabilityCount()); @@ -314,7 +313,7 @@ private void printSummary(SCASummaryResults summary) { private SCAConfig getScaConfig() throws CxClientException { SCAConfig result = config.getScaConfig(); if (result == null) { - throw new CxClientException("SCA scan configuration is missing."); + throw new CxClientException("CxSCA scan configuration is missing."); } return result; } diff --git a/src/main/java/com/cx/restclient/dto/DependencyScannerType.java b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java index a63658c6..3d75048c 100644 --- a/src/main/java/com/cx/restclient/dto/DependencyScannerType.java +++ b/src/main/java/com/cx/restclient/dto/DependencyScannerType.java @@ -7,7 +7,7 @@ public enum DependencyScannerType { NONE("None"), OSA("CxOSA"), - SCA("SCA"); + SCA("CxSCA"); private final String displayName; diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index 6836b255..61a01577 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -32,7 +32,7 @@ public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOE ContentType.CONTENT_TYPE_APPLICATION_JSON, ScanStatusResponse.class, HttpStatus.SC_OK, - "SCA scan status", + "CxSCA scan status", false); return response; @@ -40,7 +40,7 @@ public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOE @Override public void printProgress(ScanStatusResponse statusResponse) { - log.info(String.format("Waiting for SCA scan results. Elapsed time: %s. Status: %s.", + log.info(String.format("Waiting for CxSCA scan results. Elapsed time: %s. Status: %s.", ShragaUtils.getTimestampSince(getStartTimeSec()), statusResponse.getName().getValue())); } @@ -54,11 +54,11 @@ public ScanStatusResponse resolveStatus(ScanStatusResponse lastStatusResponse) t lastStatusResponse.getName(), lastStatusResponse.getMessage()); } - throw new CxClientException("SCA scan cannot be completed. " + details); + throw new CxClientException("CxSCA scan cannot be completed. " + details); } if (lastStatusResponse.getName() == StatusName.DONE) { - log.info("SCA scan finished."); + log.info("CxSCA scan finished."); } return lastStatusResponse; } From 6581ff0a2d0350bead9adfc54f15f93019c263d3 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 22 Dec 2019 10:44:44 +0200 Subject: [PATCH 192/473] Updated SCA API path. --- src/main/java/com/cx/restclient/SCAClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 73c4a026..406c3a66 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -46,7 +46,7 @@ private static class UrlPaths { private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; - private static final String ZIP_UPLOAD = "/emerald/api/scans/zip"; + private static final String ZIP_UPLOAD = "/scan-runner/api/scans/zip"; private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } From e5d8058ae6694850a9213ac444ad5914ae317743 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 22 Dec 2019 15:55:08 +0200 Subject: [PATCH 193/473] Changed version to a branch-specific value for easier build automation. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 52423f45..578a49ec 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.2 + 9.20.SCA-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From fa3496b44184de60a9f383fe5e3a140c389807ae Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Tue, 24 Dec 2019 11:55:01 +0200 Subject: [PATCH 194/473] Add support for sbt and composer in OSA Scan --- pom.xml | 2 +- .../com/cx/restclient/osa/utils/OSAUtils.java | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 3b4a64c7..e85fd1ab 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.2 + 9.20.3 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 82fca684..ec661207 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -11,10 +11,7 @@ import java.io.File; import java.nio.charset.Charset; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import static com.cx.restclient.common.CxPARAM.CX_REPORT_LOCATION; @@ -91,7 +88,10 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("npm.runPreStep", "true"); ret.put("bower.runPreStep", "false"); ret.put("npm.ignoreScripts", "true"); + ret.put("php.runPreStep", "true"); + ret.put("sbt.runPreStep", "true"); setResolveDependencies(ret, "true"); + ret.put("sbt.targetFolder", getSbtTargetFolder(scanFolder)); } else { setResolveDependencies(ret, "false"); } @@ -105,6 +105,33 @@ private static void setResolveDependencies(Properties ret, String resolveDepende ret.put("nuget.restoreDependencies", resolveDependencies); ret.put("python.resolveDependencies", resolveDependencies); ret.put("python.ignorePipInstallErrors", resolveDependencies); + ret.put("php.resolveDependencies", resolveDependencies); + ret.put("sbt.resolveDependencies", resolveDependencies); + } + + private static String getSbtTargetFolder(String sourceFolder) { + List files = new ArrayList(); + files = getBuildSbtFiles(sourceFolder, files); + if(!files.isEmpty()) { + return files.get(0).getAbsolutePath().replace("build.sbt", "target"); + } + return "target"; + } + + private static List getBuildSbtFiles(String path, List inputFiles) { + File folder = new File(path); + List files = Arrays.asList(folder.listFiles()); + for (File file : files) { + if (file.isFile()) { + if(file.getName().endsWith("build.sbt")) { + inputFiles.add(file); + } + } + else if (file.isDirectory()) { + inputFiles = getBuildSbtFiles(file.getAbsolutePath(), inputFiles); + } + } + return inputFiles; } public static void printOSAResultsToConsole(OSAResults osaResults, boolean enableViolations, Logger log) { From e35095d991c9516388b43cfd5b73f86831f701d9 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 24 Dec 2019 15:59:56 +0200 Subject: [PATCH 195/473] Fixed an error after merge. --- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index a01f8abe..8cc83ce9 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -91,7 +91,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S ret.put("php.runPreStep", "true"); ret.put("sbt.runPreStep", "true"); setResolveDependencies(ret, "true"); - ret.put("sbt.targetFolder", getSbtTargetFolder(scanFolder)); + ret.put("sbt.targetFolder", getSbtTargetFolder(sourceDir)); } else { setResolveDependencies(ret, "false"); } From 9b097884f385d6c8da3eeaa9ff1fe463e4dea691 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Wed, 25 Dec 2019 14:49:20 +0200 Subject: [PATCH 196/473] Fix for AB#421: prevent an error in Jenkins agent. --- src/main/java/com/cx/restclient/dto/ProxyConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/dto/ProxyConfig.java b/src/main/java/com/cx/restclient/dto/ProxyConfig.java index d32f7b62..8ba63f1a 100644 --- a/src/main/java/com/cx/restclient/dto/ProxyConfig.java +++ b/src/main/java/com/cx/restclient/dto/ProxyConfig.java @@ -1,6 +1,8 @@ package com.cx.restclient.dto; -public class ProxyConfig { +import java.io.Serializable; + +public class ProxyConfig implements Serializable { private String host; private int port; private String username; From abd8fab04a8769a7de2e248b89ec24aa587521c4 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 26 Dec 2019 13:42:32 +0200 Subject: [PATCH 197/473] Fix for a serialization error in Jenkins agents. --- src/main/java/com/cx/restclient/sca/dto/SCAResults.java | 4 +++- .../java/com/cx/restclient/sca/dto/SCASummaryResults.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAResults.java b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java index a8c57998..86448f88 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCAResults.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAResults.java @@ -1,6 +1,8 @@ package com.cx.restclient.sca.dto; -public class SCAResults { +import java.io.Serializable; + +public class SCAResults implements Serializable { private String scanId; private SCASummaryResults summary; private String webReportLink; diff --git a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java index f878d0ec..0a44cf70 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCASummaryResults.java @@ -1,8 +1,8 @@ package com.cx.restclient.sca.dto; -import java.time.OffsetDateTime; +import java.io.Serializable; -public class SCASummaryResults { +public class SCASummaryResults implements Serializable { private String riskReportId; private int highVulnerabilityCount; private int mediumVulnerabilityCount; From 1ea8bd319dc2975a0252eca2c0b040108ef9807b Mon Sep 17 00:00:00 2001 From: Oleg Mikashevsky Date: Tue, 7 Jan 2020 10:41:36 +0200 Subject: [PATCH 198/473] Add teamPath to request header --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxShragaClient.java | 11 ++++++----- src/main/java/com/cx/restclient/common/CxPARAM.java | 2 +- .../com/cx/restclient/httpClient/CxHttpClient.java | 6 ++++++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index a5569281..390814a2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.SCA-SNAPSHOT + 9.20.SCA.TEAMPATH-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index d54b4847..fa728642 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -41,7 +41,7 @@ public class CxShragaClient { private Logger log; private CxScanConfig config; private long projectId; - + private String teamPath; private CxSASTClient sastClient; private long sastScanId; @@ -342,6 +342,7 @@ private void resolveTeam() throws CxClientException, IOException { config.setTeamId(getTeamIdByName(config.getTeamPath())); } printTeamPath(); + httpClient.setTeamPathHeader(this.teamPath); } private void resolveCxARMUrl() throws CxClientException { @@ -372,11 +373,11 @@ private void printPresetName() { private void printTeamPath() { try { - String teamPath = config.getTeamPath(); - if (teamPath == null) { - teamPath = getTeamNameById(config.getTeamId()); + this.teamPath = config.getTeamPath(); + if (this.teamPath == null) { + this.teamPath = getTeamNameById(config.getTeamId()); } - log.info("full team path: " + teamPath); + log.info("full team path: " + this.teamPath); } catch (Exception e) { } } diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index 9b14c7cd..4cfbcee3 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -31,6 +31,6 @@ public abstract class CxPARAM { public static final String DENY_NEW_PROJECT_ERROR = "Creation of the new project [{projectName}] is not authorized. " + "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; - + public static final String TEAM_PATH = "cxTeamPath"; } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index e75894c4..47dacc73 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -94,6 +94,7 @@ public class CxHttpClient { private String cxOrigin; private Boolean useSSo; private LoginSettings lastLoginSettings; + private String teamPath; private CookieStore cookieStore = new BasicCookieStore(); public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, String refreshToken, @@ -381,6 +382,10 @@ public void patchRequest(String relPath, String contentType, HttpEntity entity, request(patch, contentType, entity, null, expectStatus, failedMsg, false, true); } + public void setTeamPathHeader(String teamPath){ + this.teamPath = teamPath; + } + private T request(HttpRequestBase httpMethod, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg, boolean isCollection, boolean retry) throws IOException, CxClientException { if (contentType != null) { httpMethod.addHeader("Content-type", contentType); @@ -393,6 +398,7 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity try { httpMethod.addHeader(ORIGIN_HEADER, cxOrigin); + httpMethod.addHeader(TEAM_PATH, this.teamPath); if (token != null) { httpMethod.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); } From 2b6efe954226447e74608580a87db0e917366da7 Mon Sep 17 00:00:00 2001 From: OlegM Date: Wed, 8 Jan 2020 15:10:37 +0200 Subject: [PATCH 199/473] Add teamPath header for all non /auth/ requests --- .../com/cx/restclient/CxShragaClient.java | 26 ++++++++++++++++++- .../restclient/httpClient/CxHttpClient.java | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index fa728642..1d7d6dcd 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -116,6 +116,7 @@ public void init() throws CxClientException, IOException { getCxVersion(); login(); resolveTeam(); + httpClient.setTeamPathHeader(this.teamPath); if (config.getSastEnabled()) { resolvePreset(); } @@ -210,6 +211,7 @@ public static void testScaConnection(CxScanConfig config, Logger log) throws CxC } private CxArmConfig getCxARMConfig() throws IOException, CxClientException { + httpClient.setTeamPathHeader(this.teamPath); return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); } @@ -223,7 +225,10 @@ public String generateHTMLSummary(SASTResults sastResults, DependencyScanResults public List getAllProjects() throws IOException, CxClientException { List projects = null; + List teamList = getTeamList(); + try { + httpClient.setTeamPathHeader(this.teamPath); projects = (List) httpClient.getRequest(SAST_GET_All_PROJECTS, CONTENT_TYPE_APPLICATION_JSON_V1, Project.class, 200, "all projects", true); } catch (HttpResponseException ex) { if (ex.getStatusCode() != 404) { @@ -316,19 +321,36 @@ public int getPresetIdByName(String presetName) throws CxClientException, IOExce throw new CxClientException("Could not resolve preset ID from preset name: " + presetName); } - public List getTeamList() throws IOException, CxClientException { + private List populateTeamList() throws IOException, CxClientException { return (List) httpClient.getRequest(CXTEAMS, CONTENT_TYPE_APPLICATION_JSON_V1, Team.class, 200, "team list", true); } + public List getTeamList() throws IOException, CxClientException { + + List teamList = populateTeamList(); + //If there is no chosen teamPath, just add first one from the teams list as default + if(StringUtils.isEmpty(this.teamPath) && teamList!=null && !teamList.isEmpty()){ + this.teamPath= teamList.get(0).getFullName(); + } + httpClient.setTeamPathHeader(this.teamPath); + log.debug("getTeamList setTeamPathHeader " + this.teamPath); + return teamList; + } + public Preset getPresetById(int presetId) throws IOException, CxClientException { + httpClient.setTeamPathHeader(this.teamPath); return httpClient.getRequest(CXPRESETS + "/" + presetId, CONTENT_TYPE_APPLICATION_JSON_V1, Preset.class, 200, "preset by id", false); } public List getPresetList() throws IOException, CxClientException { + List teamList = getTeamList(); + httpClient.setTeamPathHeader(this.teamPath); return (List) httpClient.getRequest(CXPRESETS, CONTENT_TYPE_APPLICATION_JSON_V1, Preset.class, 200, "preset list", true); } public List getConfigurationSetList() throws IOException, CxClientException { + List teamList = getTeamList(); + httpClient.setTeamPathHeader(this.teamPath); return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } @@ -407,6 +429,7 @@ private List getProjectByName(String projectName, String teamId) throws String projectNamePath = SAST_GET_PROJECT.replace("{name}", projectName).replace("{teamId}", teamId); List projects = null; try { + httpClient.setTeamPathHeader(this.teamPath); projects = (List) httpClient.getRequest(projectNamePath, CONTENT_TYPE_APPLICATION_JSON_V1, Project.class, 200, "project by name: " + projectName, true); } catch (CxHTTPClientException ex) { if (ex.getStatusCode() != 404) { @@ -418,6 +441,7 @@ private List getProjectByName(String projectName, String teamId) throws private Project createNewProject(CreateProjectRequest request) throws CxClientException, IOException { String json = convertToJson(request); + httpClient.setTeamPathHeader(this.teamPath); StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8); return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 47dacc73..0ae1ff8f 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -398,6 +398,7 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity try { httpMethod.addHeader(ORIGIN_HEADER, cxOrigin); + log.debug("request setTeamPathHeader " + this.teamPath); httpMethod.addHeader(TEAM_PATH, this.teamPath); if (token != null) { httpMethod.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); From c5ebca461585e639b8d721578b35bc0f52a074e6 Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Mon, 13 Jan 2020 11:46:47 +0200 Subject: [PATCH 200/473] Update pom.xml FSA Upgrade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a5569281..ec1ccd16 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ com.checkmarx cx-ws-fs-agent - 18.7.2.4 + 20.0.0 javax.xml.bind From 50c6dce5b88af439e0dff0f78437460f4c3b86fb Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Mon, 13 Jan 2020 12:20:39 +0200 Subject: [PATCH 201/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec1ccd16..da4f6a06 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.SCA-SNAPSHOT + 9.20.4.SCA-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 57a07d6fb930782e9bcf9edbc45ddfe9d8a2e516 Mon Sep 17 00:00:00 2001 From: GhannamZ Date: Tue, 14 Jan 2020 19:55:02 +0200 Subject: [PATCH 202/473] Fixed GenerateToken & Invalid CxARM Scope issues --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxShragaClient.java | 1 + .../java/com/cx/restclient/httpClient/CxHttpClient.java | 9 ++++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index da4f6a06..1f565f3d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.4.SCA-SNAPSHOT + 9.20.5.SCA-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index d54b4847..60db953f 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -249,6 +249,7 @@ public void login() throws IOException, CxClientException { public String getToken() throws IOException, CxClientException { LoginSettings settings = getDefaultLoginSettings(); + settings.setClientTypeForPasswordAuth(ClientType.CLI); final TokenLoginResponse tokenLoginResponse = httpClient.generateToken(settings); return tokenLoginResponse.getRefresh_token(); } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index e75894c4..6a3c7da3 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -285,7 +285,14 @@ public TokenLoginResponse generateToken(LoginSettings settings) throws IOExcepti return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } catch (CxClientException e) { - throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + if (!e.getMessage().contains("invalid_scope")) { + throw new CxClientException(String.format("Failed to generate access token, failure error was: %s", e.getMessage()), e); + } + ClientType.RESOURCE_OWNER.setScopes("sast_rest_api"); + settings.setClientTypeForPasswordAuth(ClientType.RESOURCE_OWNER); + requestEntity = generateUrlEncodedFormEntity(settings); + return request(post, ContentType.APPLICATION_FORM_URLENCODED.toString(), requestEntity, + TokenLoginResponse.class, HttpStatus.SC_OK, "authenticate", false, false); } } From bc4fcffa41915bec8fc2b9b114be6fc201e65428 Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Mon, 20 Jan 2020 10:01:29 +0200 Subject: [PATCH 203/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e85fd1ab..96443a47 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ com.checkmarx cx-ws-fs-agent - 18.7.2.4 + 20.0.0 javax.xml.bind From 299fb42fd718f347ea0bc38948c9514e44fbda4f Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Mon, 20 Jan 2020 10:03:27 +0200 Subject: [PATCH 204/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96443a47..4c7587a9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.3 + 9.20.5 jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 6f449699ee13c89b341ec44c855401d7db25f6f8 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Tue, 21 Jan 2020 13:14:50 +0200 Subject: [PATCH 205/473] add teampath to header --- .../com/cx/restclient/CxShragaClient.java | 35 ++++++++++++++++--- .../com/cx/restclient/common/CxPARAM.java | 2 +- .../restclient/httpClient/CxHttpClient.java | 6 ++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index deb9c5af..b13549ea 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -12,6 +12,7 @@ import com.cx.restclient.osa.dto.ClientType; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.sast.dto.*; +import org.apache.commons.lang.StringUtils; import org.apache.http.client.HttpResponseException; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; @@ -40,7 +41,7 @@ public class CxShragaClient { private Logger log; private CxScanConfig config; private long projectId; - + private String teamPath; private CxSASTClient sastClient; private CxOSAClient osaClient; private long sastScanId; @@ -113,6 +114,7 @@ public void init() throws CxClientException, IOException { getCxVersion(); login(); resolveTeam(); + httpClient.setTeamPathHeader(this.teamPath); if (config.getSastEnabled()) { resolvePreset(); } @@ -180,6 +182,7 @@ public void printIsProjectViolated() { } private CxArmConfig getCxARMConfig() throws IOException, CxClientException { + httpClient.setTeamPathHeader(this.teamPath); return httpClient.getRequest(CX_ARM_URL, CONTENT_TYPE_APPLICATION_JSON_V1, CxArmConfig.class, 200, "CxARM URL", false); } @@ -193,7 +196,9 @@ public String generateHTMLSummary(SASTResults sastResults, OSAResults osaResults public List getAllProjects() throws IOException, CxClientException { List projects = null; + List teamList = getTeamList(); try { + httpClient.setTeamPathHeader(this.teamPath); projects = (List) httpClient.getRequest(SAST_GET_All_PROJECTS, CONTENT_TYPE_APPLICATION_JSON_V1, Project.class, 200, "all projects", true); } catch (HttpResponseException ex) { if (ex.getStatusCode() != 404) { @@ -282,19 +287,36 @@ public int getPresetIdByName(String presetName) throws CxClientException, IOExce throw new CxClientException("Could not resolve preset ID from preset name: " + presetName); } - public List getTeamList() throws IOException, CxClientException { + private List populateTeamList() throws IOException, CxClientException { return (List) httpClient.getRequest(CXTEAMS, CONTENT_TYPE_APPLICATION_JSON_V1, Team.class, 200, "team list", true); } + public List getTeamList() throws IOException, CxClientException { + + List teamList = populateTeamList(); + //If there is no chosen teamPath, just add first one from the teams list as default + if(StringUtils.isEmpty(this.teamPath) && teamList!=null && !teamList.isEmpty()){ + this.teamPath= teamList.get(0).getFullName(); + } + httpClient.setTeamPathHeader(this.teamPath); + log.debug("getTeamList setTeamPathHeader " + this.teamPath); + return teamList; + } + public Preset getPresetById(int presetId) throws IOException, CxClientException { + httpClient.setTeamPathHeader(this.teamPath); return httpClient.getRequest(CXPRESETS + "/" + presetId, CONTENT_TYPE_APPLICATION_JSON_V1, Preset.class, 200, "preset by id", false); } public List getPresetList() throws IOException, CxClientException { + List teamList = getTeamList(); + httpClient.setTeamPathHeader(this.teamPath); return (List) httpClient.getRequest(CXPRESETS, CONTENT_TYPE_APPLICATION_JSON_V1, Preset.class, 200, "preset list", true); } public List getConfigurationSetList() throws IOException, CxClientException { + List teamList = getTeamList(); + httpClient.setTeamPathHeader(this.teamPath); return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } @@ -308,6 +330,7 @@ private void resolveTeam() throws CxClientException, IOException { config.setTeamId(getTeamIdByName(config.getTeamPath())); } printTeamPath(); + httpClient.setTeamPathHeader(this.teamPath); } private void resolveCxARMUrl() throws CxClientException { @@ -338,11 +361,11 @@ private void printPresetName() { private void printTeamPath() { try { - String teamPath = config.getTeamPath(); + this.teamPath = config.getTeamPath(); if (teamPath == null) { - teamPath = getTeamNameById(config.getTeamId()); + this.teamPath = getTeamNameById(config.getTeamId()); } - log.info("full team path: " + teamPath); + log.info("full team path: " + this.teamPath); } catch (Exception e) { } } @@ -367,6 +390,7 @@ private List getProjectByName(String projectName, String teamId) throws String projectNamePath = SAST_GET_PROJECT.replace("{name}", projectName).replace("{teamId}", teamId); List projects = null; try { + httpClient.setTeamPathHeader(this.teamPath); projects = (List) httpClient.getRequest(projectNamePath, CONTENT_TYPE_APPLICATION_JSON_V1, Project.class, 200, "project by name: " + projectName, true); } catch (CxHTTPClientException ex) { if (ex.getStatusCode() != 404) { @@ -378,6 +402,7 @@ private List getProjectByName(String projectName, String teamId) throws private Project createNewProject(CreateProjectRequest request) throws CxClientException, IOException { String json = convertToJson(request); + httpClient.setTeamPathHeader(this.teamPath); StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8); return httpClient.postRequest(CREATE_PROJECT, CONTENT_TYPE_APPLICATION_JSON_V1, entity, Project.class, 201, "create new project: " + request.getName()); } diff --git a/src/main/java/com/cx/restclient/common/CxPARAM.java b/src/main/java/com/cx/restclient/common/CxPARAM.java index ba45738f..24539920 100644 --- a/src/main/java/com/cx/restclient/common/CxPARAM.java +++ b/src/main/java/com/cx/restclient/common/CxPARAM.java @@ -32,5 +32,5 @@ public abstract class CxPARAM { "Please use an existing project. \nYou can enable the creation of new projects by disabling" + "" + " the Deny new Checkmarx projects creation checkbox in the Checkmarx plugin global settings.\n"; - + public static final String TEAM_PATH = "cxTeamPath"; } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 5f116fbf..7c9feae0 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -91,6 +91,7 @@ public class CxHttpClient { private final String refreshToken; private String cxOrigin; private Boolean useSSo; + private String teamPath; private CookieStore cookieStore = new BasicCookieStore(); public CxHttpClient(String hostname, String username, String password, String origin, @@ -408,6 +409,9 @@ public void patchRequest(String relPath, String contentType, HttpEntity entity, HttpPatch patch = new HttpPatch(rootUri + relPath); request(patch, contentType, entity, null, expectStatus, failedMsg, false, true); } + public void setTeamPathHeader(String teamPath){ + this.teamPath = teamPath; + } private T request(HttpRequestBase httpMethod, String contentType, HttpEntity entity, Class responseType, int expectStatus, String failedMsg, boolean isCollection, boolean retry) throws IOException, CxClientException { if (contentType != null) { @@ -421,6 +425,8 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity try { httpMethod.addHeader(ORIGIN_HEADER, cxOrigin); + httpMethod.addHeader(TEAM_PATH, this.teamPath); + log.debug("request setTeamPathHeader " + this.teamPath); if (token != null) { httpMethod.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); } From d04c294ea3f73baa7e0cdd547bd888335b2de0ea Mon Sep 17 00:00:00 2001 From: salmanmak Date: Wed, 5 Feb 2020 18:03:19 +0200 Subject: [PATCH 206/473] Update pom.xml with new common version 2020.1.1.SCA --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab4d9e7d..de2fcb2e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 9.20.SCA.TEAMPATH-SNAPSHOT + 2020.1.1.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 28bbc8a8257c12d0cb3cb0109f277de5dc84ba6a Mon Sep 17 00:00:00 2001 From: salmanmak Date: Thu, 6 Feb 2020 10:29:41 +0200 Subject: [PATCH 207/473] Update SCA apis --- pom.xml | 2 +- src/main/java/com/cx/restclient/SCAClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index de2fcb2e..dd4043c5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.1.SCA + 2020.1.2.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 406c3a66..1b8e68fd 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -40,13 +40,13 @@ */ public class SCAClient implements DependencyScanner { private static class UrlPaths { - private static final String RISK_MANAGEMENT_API = "/risk-management/api/"; + private static final String RISK_MANAGEMENT_API = "/risk-management/"; private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; private static final String SUMMARY_REPORT = RISK_MANAGEMENT_API + "riskReports/%s/summary"; private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; - private static final String ZIP_UPLOAD = "/scan-runner/api/scans/zip"; + private static final String ZIP_UPLOAD = "/scan-runner/scans/zip"; private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } From 1006fbf8eb8dc87ba35f1488777ec2384294b6ee Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Thu, 13 Feb 2020 16:17:52 +0200 Subject: [PATCH 208/473] Update pom.xml - Vulnerability Fix --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd4043c5..83b21b9a 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10.1 + 2.9.10.2 org.bouncycastle From 70196166bb1d6552fe7c02e014ad1f84b4fe4e32 Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Mon, 17 Feb 2020 18:07:16 +0200 Subject: [PATCH 209/473] Update pom.xml with new version 2020.1.3.SCA --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 83b21b9a..d79d0273 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.2.SCA + 2020.1.3.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From b03f316fa682c28eb9d62dcadb51eedd2bdeefaf Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Sun, 23 Feb 2020 09:33:32 +0200 Subject: [PATCH 210/473] Update pom.xml with new FSA version 20.0.2 and new common version 2020.1.4.SCA --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d79d0273..7c7537f4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.3.SCA + 2020.1.4.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -146,7 +146,7 @@ com.checkmarx cx-ws-fs-agent - 20.0.0 + 20.0.2 javax.xml.bind From a7386b7b368804b15a392f321ae319199f762f86 Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Sun, 1 Mar 2020 09:56:12 +0200 Subject: [PATCH 211/473] Update pom.xml Update common SCA version to 2020.1.5.SCA and FSA to 20.0.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7c7537f4..812484fc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.4.SCA + 2020.1.5.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -146,7 +146,7 @@ com.checkmarx cx-ws-fs-agent - 20.0.2 + 20.0.3 javax.xml.bind From f67691e783697540ff42528e043d03a41e56e676 Mon Sep 17 00:00:00 2001 From: morad Date: Sun, 1 Mar 2020 11:31:00 +0200 Subject: [PATCH 212/473] update jackson-databind version and update cx version 2020.1.6 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 812484fc..bc56d8d4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.5.SCA + 2020.1.6.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -106,7 +106,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10.2 + 2.9.10.3 org.bouncycastle From 1296cc6cd3ec58b60dfc9de8ba27ec23caa7be17 Mon Sep 17 00:00:00 2001 From: morad Date: Sun, 1 Mar 2020 17:13:18 +0200 Subject: [PATCH 213/473] update jackson-databind version 2.10.2 and update cx version 2020.1.7.SCA --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bc56d8d4..3b62a04b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.6.SCA + 2020.1.7.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -106,7 +106,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10.3 + 2.10.2 org.bouncycastle From c71d9e52c33acfb915e03b53515312dd0a9dc9f8 Mon Sep 17 00:00:00 2001 From: Majd Date: Wed, 4 Mar 2020 17:46:41 +0200 Subject: [PATCH 214/473] Handle Null Pointer exception, and update pom to 2020.1.8.SCA BUGID:522 --- pom.xml | 2 +- .../java/com/cx/restclient/common/summary/SummaryUtils.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3b62a04b..85be2c65 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.7.SCA + 2020.1.8.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java index 25fb19ba..96b65f55 100644 --- a/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java +++ b/src/main/java/com/cx/restclient/common/summary/SummaryUtils.java @@ -6,6 +6,8 @@ import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.dto.ScanResults; import com.cx.restclient.dto.scansummary.ScanSummary; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.osa.dto.OSAResults; import com.cx.restclient.osa.dto.OSASummaryResults; import com.cx.restclient.sast.dto.SASTResults; @@ -88,7 +90,7 @@ public static String generateSummary(SASTResults sastResults, DependencyScanResu //osa: if (config.getDependencyScannerType() == DependencyScannerType.OSA) { - if (osaResults.isOsaResultsReady()) { + if (osaResults!=null && osaResults.isOsaResultsReady()) { boolean thresholdExceeded = scanSummary.isOsaThresholdExceeded(); templateData.put("osaThresholdExceeded", thresholdExceeded); buildFailed |= thresholdExceeded; From 2856fa438c54192462ebc28a45052bef84702db2 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Thu, 5 Mar 2020 14:46:12 +0200 Subject: [PATCH 215/473] add public endpoint for ides plugin rest migration --- .../java/com/cx/restclient/CxSASTClient.java | 6 +-- .../com/cx/restclient/CxShragaClient.java | 17 +++++++ .../cx/restclient/sast/dto/DateAndTime.java | 47 +++++++++++++++++++ .../restclient/sast/dto/LastScanResponse.java | 9 ++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sast/dto/DateAndTime.java diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 4ce5085c..6c386b60 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -326,7 +326,7 @@ private boolean isStatusToAvoid(String status) { return false; } - private ScanSettingResponse getScanSetting(long projectId) throws IOException, CxClientException { + public ScanSettingResponse getScanSetting(long projectId) throws IOException, CxClientException { return httpClient.getRequest(SAST_GET_SCAN_SETTINGS.replace("{projectId}", Long.toString(projectId)), CONTENT_TYPE_APPLICATION_JSON_V1, ScanSettingResponse.class, 200, "Scan setting", false); } @@ -368,7 +368,7 @@ private SASTStatisticsResponse getScanStatistics(long scanId) throws CxClientExc return httpClient.getRequest(SAST_SCAN_RESULTS_STATISTICS.replace("{scanId}", Long.toString(scanId)), CONTENT_TYPE_APPLICATION_JSON_V1, SASTStatisticsResponse.class, 200, "SAST scan statistics", false); } - private List getLatestSASTStatus(long projectId) throws CxClientException, IOException { + public List getLatestSASTStatus(long projectId) throws CxClientException, IOException { return (List) httpClient.getRequest(SAST_GET_PROJECT_SCANS.replace("{projectId}", Long.toString(projectId)), CONTENT_TYPE_APPLICATION_JSON_V1, LastScanResponse.class, 200, "last SAST scan ID", true); } @@ -395,7 +395,7 @@ private byte[] getReport(long reportId, String contentType) throws CxClientExcep } //SCAN Waiter - overload methods - private ResponseQueueScanStatus getSASTScanStatus(String scanId) throws CxClientException, IOException { + public ResponseQueueScanStatus getSASTScanStatus(String scanId) throws CxClientException, IOException { ResponseQueueScanStatus scanStatus = httpClient.getRequest(SAST_QUEUE_SCAN_STATUS.replace("{scanId}", scanId), CONTENT_TYPE_APPLICATION_JSON_V1, ResponseQueueScanStatus.class, 200, "SAST scan status", false); String currentStatus = scanStatus.getStage().getValue(); diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index ef5029ac..a4603ef5 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -478,4 +478,21 @@ private CxSASTClient getSastClient() throws CxClientException { } return sastClient; } + + public ResponseQueueScanStatus getStatus(String scanId) throws IOException { + return sastClient.getSASTScanStatus(scanId); + } + + + public Long getProjectId(){ + return projectId; + } + + public ScanSettingResponse getScanSetting(Long projectId) throws IOException { + return sastClient.getScanSetting(projectId); + } + + public List getLastScansByProjectId(long projectId) throws IOException { + return sastClient.getLatestSASTStatus(projectId); + } } \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/sast/dto/DateAndTime.java b/src/main/java/com/cx/restclient/sast/dto/DateAndTime.java new file mode 100644 index 00000000..5c90cd7a --- /dev/null +++ b/src/main/java/com/cx/restclient/sast/dto/DateAndTime.java @@ -0,0 +1,47 @@ +package com.cx.restclient.sast.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.Date; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DateAndTime { + private Date startedOn; + private Date finishedOn; + private Date engineStartedOn; + private Date engineFinishedOn; + + public DateAndTime() { + } + + public Date getStartedOn() { + return startedOn; + } + + public void setStartedOn(Date startedOn) { + this.startedOn = startedOn; + } + + public Date getFinishedOn() { + return finishedOn; + } + + public void setFinishedOn(Date finishedOn) { + this.finishedOn = finishedOn; + } + + public Date getEngineStartedOn() { + return engineStartedOn; + } + + public void setEngineStartedOn(Date engineStartedOn) { + this.engineStartedOn = engineStartedOn; + } + + public Date getEngineFinishedOn() { + return engineFinishedOn; + } + + public void setEngineFinishedOn(Date engineFinishedOn) { + this.engineFinishedOn = engineFinishedOn; + } +} diff --git a/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java b/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java index 6da1944c..eb675f43 100644 --- a/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java +++ b/src/main/java/com/cx/restclient/sast/dto/LastScanResponse.java @@ -9,6 +9,7 @@ public class LastScanResponse { private long id; private CxNameObj status; + private DateAndTime dateAndTime; public long getId() { return id; @@ -25,4 +26,12 @@ public CxNameObj getStatus() { public void setStatus(CxNameObj status) { this.status = status; } + + public DateAndTime getDateAndTime() { + return dateAndTime; + } + + public void setDateAndTime(DateAndTime dateAndTime) { + this.dateAndTime = dateAndTime; + } } From 80de7571a3a921199ef68bd5fc8dcf0930f1b728 Mon Sep 17 00:00:00 2001 From: MuhammedS Date: Wed, 11 Mar 2020 12:05:50 +0200 Subject: [PATCH 216/473] add new api for retrieving project by id --- .../java/com/cx/restclient/CxShragaClient.java | 15 +++++++++++++++ .../restclient/httpClient/utils/ContentType.java | 1 + .../java/com/cx/restclient/sast/dto/Project.java | 9 +++++++++ .../com/cx/restclient/sast/utils/SASTParam.java | 1 + 4 files changed, 26 insertions(+) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index a4603ef5..e24bf096 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -26,6 +26,7 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; +import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V21; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -440,6 +441,20 @@ private List getProjectByName(String projectName, String teamId) throws return projects; } + public Project getProjectById(String projectId) throws IOException, CxClientException { + String projectNamePath = SAST_GET_PROJECT_BY_ID.replace("{projectId}", projectId); + Project projects = null; + try { + httpClient.setTeamPathHeader(this.teamPath); + projects = httpClient.getRequest(projectNamePath, CONTENT_TYPE_APPLICATION_JSON_V21, Project.class, 200, "project by id: " + projectId, false); + } catch (CxHTTPClientException ex) { + if (ex.getStatusCode() != 404) { + throw ex; + } + } + return projects; + } + private Project createNewProject(CreateProjectRequest request) throws CxClientException, IOException { String json = convertToJson(request); httpClient.setTeamPathHeader(this.teamPath); diff --git a/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java b/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java index de4a4616..40056d48 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java @@ -5,6 +5,7 @@ */ public class ContentType { public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json"; + public static final String CONTENT_TYPE_APPLICATION_JSON_V21 = "application/json;v=2.1"; public static final String CONTENT_TYPE_APPLICATION_JSON_V1 = "application/json;v=1.0"; public static final String CONTENT_TYPE_APPLICATION_XML_V1 = "application/xml;v=1.0"; public static final String CONTENT_TYPE_APPLICATION_PDF_V1 = "application/pdf;v=1.0"; diff --git a/src/main/java/com/cx/restclient/sast/dto/Project.java b/src/main/java/com/cx/restclient/sast/dto/Project.java index 4ba6c835..22528849 100644 --- a/src/main/java/com/cx/restclient/sast/dto/Project.java +++ b/src/main/java/com/cx/restclient/sast/dto/Project.java @@ -8,6 +8,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Project { private long id; + private String owner; private String name; private String teamId; private boolean isPublic; @@ -43,4 +44,12 @@ public boolean getIsPublic() { public void setIsPublic(boolean isPublic) { this.isPublic = isPublic; } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } } diff --git a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java index 2ccf5a4e..cbc64d92 100644 --- a/src/main/java/com/cx/restclient/sast/utils/SASTParam.java +++ b/src/main/java/com/cx/restclient/sast/utils/SASTParam.java @@ -11,6 +11,7 @@ public class SASTParam { public static final String SAST_CREATE_SCAN = "sast/scans"; //Run a new Scan public static final String SAST_SCAN = "sast/scans/{scanId}"; //Get Scan status (by scan ID) public static final String SAST_QUEUE_SCAN_STATUS = "sast/scansQueue/{scanId}"; + public static final String SAST_GET_PROJECT_BY_ID = "projects/{projectId}"; public static final String SAST_GET_PROJECT = "projects?projectname={name}&teamid={teamId}";// Get project) public static final String SAST_GET_All_PROJECTS = "projects";// Get project) public static final String SAST_ZIP_ATTACHMENTS = "projects/{projectId}/sourceCode/attachments";//Attach ZIP file From 24386f0c712206e4bee07f345df0dc16897f1e74 Mon Sep 17 00:00:00 2001 From: salmanmak <55487464+salmanmak@users.noreply.github.com> Date: Wed, 11 Mar 2020 17:46:55 +0200 Subject: [PATCH 217/473] Update common master version to 2020.1.9.SCA --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 85be2c65..99bf7e23 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.8.SCA + 2020.1.9.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 9e29283ee40da03e1eca2236af581a76f877c84f Mon Sep 17 00:00:00 2001 From: Majd Date: Tue, 24 Mar 2020 10:57:25 +0200 Subject: [PATCH 218/473] save reports into the file not into directory, bug fix #541 --- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 8cc83ce9..6ff94b3f 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -194,8 +194,9 @@ public static void writeJsonToFile(String name, Object jsonObj, File workDirecto if (!workDirectory.getParentFile().exists()) { workDirectory.getParentFile().mkdirs(); } - FileUtils.writeStringToFile(workDirectory, json); - log.info(name + " json location: " + workDirectory); + File jsonFile = new File(workDirectory + File.separator + name); + FileUtils.writeStringToFile(jsonFile , json); + log.info(name + " json location: " + jsonFile); } else { String now = new SimpleDateFormat("dd_MM_yyyy-HH_mm_ss").format(new Date()); From 216772ca0ac922bb8f2af0954b812bad4cfc1c85 Mon Sep 17 00:00:00 2001 From: Majd Date: Tue, 24 Mar 2020 10:59:13 +0200 Subject: [PATCH 219/473] increment the version to 2020.1.10.SCA --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 99bf7e23..6ed83f9b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.9.SCA + 2020.1.10.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From 05b48bdb34b3488b588aaa088d8522e3b3dc3b38 Mon Sep 17 00:00:00 2001 From: Majd Date: Thu, 2 Apr 2020 11:19:02 +0300 Subject: [PATCH 220/473] handling new parameter EngineConfiguration and updating version to 2020.1.11.SCA --- pom.xml | 2 +- .../java/com/cx/restclient/CxSASTClient.java | 5 +-- .../com/cx/restclient/CxShragaClient.java | 26 ++++++++++++++++ .../configuration/CxScanConfig.java | 12 ++++++- .../restclient/dto/EngineConfiguration.java | 31 +++++++++++++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/cx/restclient/dto/EngineConfiguration.java diff --git a/pom.xml b/pom.xml index 6ed83f9b..a2487851 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.10.SCA + 2020.1.11.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 6c386b60..ef676549 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -3,10 +3,7 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.PathFilter; -import com.cx.restclient.dto.RemoteSourceRequest; -import com.cx.restclient.dto.RemoteSourceTypes; -import com.cx.restclient.dto.Status; +import com.cx.restclient.dto.*; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.sast.dto.*; diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index e24bf096..18dfb9c1 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -124,6 +124,9 @@ public void init() throws CxClientException, IOException { if (config.getEnablePolicyViolations()) { resolveCxARMUrl(); } + if(config.getEngineConfigurationName() != null){ + resolveEngineConfiguration(); + } resolveProject(); } @@ -132,6 +135,23 @@ public void init() throws CxClientException, IOException { } } + private void resolveEngineConfiguration() throws IOException { + if(config.getEngineConfigurationId() == null && config.getEngineConfigurationName() == null){ + config.setEngineConfigurationId(1); + }else if(config.getEngineConfigurationName() != null){ + final List engineConfigurations = getEngineConfiguration(); + for (EngineConfiguration engineConfiguration : engineConfigurations) { + if (engineConfiguration.getName().equalsIgnoreCase(config.getEngineConfigurationName())) { + config.setEngineConfigurationId(engineConfiguration.getId()); + log.info("Engine configuration: \"" + config.getEngineConfigurationName() + "\" was validated in server"); + } + } + if (config.getEngineConfigurationId() == null){ + throw new CxClientException("Engine configuration: \"" + config.getEngineConfigurationName() + "\" was not found in server"); + } + } + } + public long createSASTScan() throws IOException, CxClientException { sastScanId = getSastClient().createSASTScan(projectId); sastResults.setSastScanLink(config.getUrl(), sastScanId, projectId); @@ -356,6 +376,12 @@ public List getConfigurationSetList() throws IOException, CxClientExc return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, CxNameObj.class, 200, "engine configurations", true); } + public List getEngineConfiguration() throws IOException { + List teamList = getTeamList(); + httpClient.setTeamPathHeader(this.teamPath); + return (List) httpClient.getRequest(SAST_ENGINE_CONFIG, CONTENT_TYPE_APPLICATION_JSON_V1, EngineConfiguration.class, 200, "engine configurations", true); + } + public void setOsaFSAProperties(Properties fsaConfig) { //For CxMaven plugin config.setOsaFsaConfig(fsaConfig); } diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index b9b01399..65178eb3 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -61,9 +61,19 @@ public class CxScanConfig implements Serializable { private Boolean generatePDFReport = false; private File zipFile; - private Integer engineConfigurationId = 1; + private Integer engineConfigurationId; + private String engineConfigurationName; private String osaFolderExclusions; + + public String getEngineConfigurationName() { + return engineConfigurationName; + } + + public void setEngineConfigurationName(String engineConfigurationName) { + this.engineConfigurationName = engineConfigurationName; + } + private String osaFilterPattern; private String osaArchiveIncludePatterns; private Boolean osaGenerateJsonReport = true; diff --git a/src/main/java/com/cx/restclient/dto/EngineConfiguration.java b/src/main/java/com/cx/restclient/dto/EngineConfiguration.java new file mode 100644 index 00000000..e3f9d1eb --- /dev/null +++ b/src/main/java/com/cx/restclient/dto/EngineConfiguration.java @@ -0,0 +1,31 @@ +package com.cx.restclient.dto; + +public class EngineConfiguration { + + private int id; + + private String name; + + public EngineConfiguration() { + } + + public EngineConfiguration(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From 2fabe0b8723ef8562172acec715cbbfcdabe8016 Mon Sep 17 00:00:00 2001 From: Majd Date: Thu, 2 Apr 2020 14:24:19 +0300 Subject: [PATCH 221/473] resolve engine configuration fix --- src/main/java/com/cx/restclient/CxShragaClient.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 18dfb9c1..cc5584e4 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -124,9 +124,7 @@ public void init() throws CxClientException, IOException { if (config.getEnablePolicyViolations()) { resolveCxARMUrl(); } - if(config.getEngineConfigurationName() != null){ - resolveEngineConfiguration(); - } + resolveEngineConfiguration(); resolveProject(); } From f612d7b9dc558202203839a5be604f8b586907e4 Mon Sep 17 00:00:00 2001 From: Natalie B <57710915+natalieb07@users.noreply.github.com> Date: Mon, 6 Apr 2020 13:24:02 +0300 Subject: [PATCH 222/473] Added sonarCloud scan to this project --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index a2487851..e97bbc57 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,12 @@ 1.2.0 2.3.0 1.18.6 + + + checkmarx-ltd_Cx-Client-Common + checkmarx-ltd + https://sonarcloud.io + 4749799b2183ff936d8003f0ba7faebe777dab11 Checkmarx From 85d2e43d4051b6d5c6087f4402401461edf405cd Mon Sep 17 00:00:00 2001 From: Majd Date: Wed, 8 Apr 2020 17:20:32 +0300 Subject: [PATCH 223/473] SSO fix, was not working with 8.9 and lower --- pom.xml | 2 +- .../com/cx/restclient/CxShragaClient.java | 13 +++-- .../com/cx/restclient/dto/LoginSettings.java | 9 ++++ .../restclient/httpClient/CxHttpClient.java | 47 ++++++++++++++++++- .../connection/GetTeamListTests.java | 2 +- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index e97bbc57..ea371ae7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.11.SCA + 2020.1.12.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index cc5584e4..7c6ae54b 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -114,8 +114,8 @@ public String getClientVersion() { public void init() throws CxClientException, IOException { log.info("Initializing Cx client [" + getClientVersion() + "]"); if (config.isSastOrOSAEnabled()) { - getCxVersion(); - login(); + String version = getCxVersion(); + login(version); resolveTeam(); httpClient.setTeamPathHeader(this.teamPath); if (config.getSastEnabled()) { @@ -262,12 +262,13 @@ public void close() { } //HELP config Methods - public void login() throws IOException, CxClientException { + public void login(String version) throws IOException, CxClientException { // perform login to server log.info("Logging into the Checkmarx service."); LoginSettings settings = getDefaultLoginSettings(); settings.setRefreshToken(config.getRefreshToken()); + settings.setVersion(version); httpClient.login(settings); } @@ -282,7 +283,8 @@ public void revokeToken(String token) throws IOException, CxClientException { httpClient.revokeToken(token); } - public void getCxVersion() throws IOException, CxClientException { + public String getCxVersion() throws IOException, CxClientException { + String version = ""; try { config.setCxVersion(httpClient.getRequest(CX_VERSION, CONTENT_TYPE_APPLICATION_JSON_V1, CxVersion.class, 200, "cx Version", false)); String hotfix = ""; @@ -293,11 +295,14 @@ public void getCxVersion() throws IOException, CxClientException { } catch (Exception ex) { } + version = config.getCxVersion().getVersion(); log.info("Checkmarx server version [" + config.getCxVersion().getVersion() + "]." + hotfix); } catch (Exception ex) { + version = "lower than 9.0"; log.debug("Checkmarx server version [lower than 9.0]"); } + return version; } public String getTeamIdByName(String teamName) throws CxClientException, IOException { diff --git a/src/main/java/com/cx/restclient/dto/LoginSettings.java b/src/main/java/com/cx/restclient/dto/LoginSettings.java index 21e0648e..3a1ea924 100644 --- a/src/main/java/com/cx/restclient/dto/LoginSettings.java +++ b/src/main/java/com/cx/restclient/dto/LoginSettings.java @@ -8,6 +8,7 @@ public class LoginSettings { private String password; private CharSequence tenant; private String refreshToken; + private String version; // TODO: find a way to use a single client type here. private ClientType clientTypeForRefreshToken; @@ -68,4 +69,12 @@ public CharSequence getTenant() { public void setTenant(CharSequence tenant) { this.tenant = tenant; } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 2d51a368..a04cbb32 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -96,6 +96,7 @@ public class CxHttpClient { private LoginSettings lastLoginSettings; private String teamPath; private CookieStore cookieStore = new BasicCookieStore(); + private HttpClientBuilder cb = HttpClients.custom(); public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, boolean isSSO, String refreshToken, @Nullable ProxyConfig proxyConfig, Logger log) throws CxClientException { @@ -105,7 +106,6 @@ public CxHttpClient(String rootUri, String origin, boolean disableSSLValidation, this.cxOrigin = origin; this.useSSo = isSSO; //create httpclient - HttpClientBuilder cb = HttpClients.custom(); cb.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); setSSLTls("TLSv1.2", log); if (disableSSLValidation) { @@ -198,12 +198,55 @@ public void login(LoginSettings settings) throws IOException, CxClientException if (settings.getRefreshToken() != null) { token = getAccessTokenFromRefreshToken(settings); } else if (useSSo) { - token = ssoLogin(); + if (settings.getVersion().equals("lower than 9.0")) { + ssoLegacyLogin(); + } else { + token = ssoLogin(); + } } else { token = generateToken(settings); } } + public void ssoLegacyLogin() throws CxClientException { + HttpUriRequest request; + HttpResponse loginResponse = null; + + String cxCookie = null; + String csrfToken = null; + + try { + request = RequestBuilder.post() + .setUri(rootUri + "auth/ssologin") + .setConfig(RequestConfig.DEFAULT) + .setEntity(new StringEntity("", StandardCharsets.UTF_8)) + .build(); + + loginResponse = apacheClient.execute(request); + + } catch (IOException e) { + log.error("Fail to login with windows authentication: " + e.getMessage()); + throw new CxClientException("Fail to login with windows authentication: " + e.getMessage()); + } finally { + HttpClientUtils.closeQuietly(loginResponse); + } + + for (Cookie cookie : cookieStore.getCookies()) { + if (cookie.getName().equals(CSRF_TOKEN_HEADER)) { + csrfToken = cookie.getValue(); + } + if (cookie.getName().equals("cxCookie")) { + cxCookie = cookie.getValue(); + } + } + + List
    headers = new ArrayList<>(); + headers.add(new BasicHeader(CSRF_TOKEN_HEADER, csrfToken)); + headers.add(new BasicHeader("cookie", String.format("CXCSRFToken=%s; cxCookie=%s", csrfToken, cxCookie))); + + apacheClient = cb.setDefaultHeaders(headers).build(); + } + private TokenLoginResponse ssoLogin() throws CxClientException { HttpUriRequest request; HttpResponse response = null; diff --git a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java index 8cae278b..7693f26a 100644 --- a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java +++ b/src/test/java/com/cx/restclient/connection/GetTeamListTests.java @@ -14,7 +14,7 @@ public void getTeamListTest() { CxScanConfig config = initConfig(); try { CxShragaClient client = new CxShragaClient(config, log); - client.login(); + client.login("9.0"); List teams = client.getTeamList(); Assert.assertNotNull(teams); Assert.assertFalse(teams.isEmpty()); From 30385db0738e2d4926a2cd37ec95d750cc4a2efa Mon Sep 17 00:00:00 2001 From: cx-muhammed Date: Thu, 9 Apr 2020 15:07:47 +0300 Subject: [PATCH 224/473] support login using session cookies - sast 8.9 --- pom.xml | 2 +- .../com/cx/restclient/CxShragaClient.java | 7 ++++- .../configuration/CxScanConfig.java | 15 +++++++---- .../com/cx/restclient/dto/LoginSettings.java | 9 +++++++ .../restclient/httpClient/CxHttpClient.java | 27 +++++++++++++------ 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index ea371ae7..e916bf95 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.12.SCA + 2020.1.13.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 7c6ae54b..88c20c5c 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -13,6 +13,7 @@ import com.cx.restclient.sast.dto.*; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.HttpResponseException; +import org.apache.http.cookie.Cookie; import org.apache.http.entity.StringEntity; import org.slf4j.Logger; @@ -499,7 +500,7 @@ private LoginSettings getDefaultLoginSettings() throws MalformedURLException { result.setUsername(config.getUsername()); result.setPassword(config.getPassword()); - + result.getSessionCookies().addAll(config.getSessionCookie()); result.setClientTypeForPasswordAuth(ClientType.RESOURCE_OWNER); result.setClientTypeForRefreshToken(ClientType.CLI); @@ -539,4 +540,8 @@ public ScanSettingResponse getScanSetting(Long projectId) throws IOException { public List getLastScansByProjectId(long projectId) throws IOException { return sastClient.getLatestSASTStatus(projectId); } + + public List ssoLegacyLogin(){ + return httpClient.ssoLegacyLogin(); + } } \ No newline at end of file diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 65178eb3..011d399e 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -7,12 +7,10 @@ import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sast.dto.ReportType; import org.apache.commons.lang3.StringUtils; - +import org.apache.http.cookie.Cookie; import java.io.File; import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; /** * Created by galn on 21/12/2016. @@ -110,7 +108,7 @@ public void setEngineConfigurationName(String engineConfigurationName) { private SCAConfig scaConfig; private DependencyScannerType dependencyScannerType; - + private List sessionCookies = new ArrayList<>(); private ProxyConfig proxyConfig; public CxScanConfig() { @@ -745,4 +743,11 @@ public ProxyConfig getProxyConfig() { public void setProxyConfig(ProxyConfig proxyConfig) { this.proxyConfig = proxyConfig; } + + public void addCookie(Cookie cookie){ + this.sessionCookies.add(cookie); + } + public List getSessionCookie() { + return this.sessionCookies; + } } diff --git a/src/main/java/com/cx/restclient/dto/LoginSettings.java b/src/main/java/com/cx/restclient/dto/LoginSettings.java index 3a1ea924..1f3805cc 100644 --- a/src/main/java/com/cx/restclient/dto/LoginSettings.java +++ b/src/main/java/com/cx/restclient/dto/LoginSettings.java @@ -1,6 +1,10 @@ package com.cx.restclient.dto; import com.cx.restclient.osa.dto.ClientType; +import org.apache.http.cookie.Cookie; + +import java.util.ArrayList; +import java.util.List; public class LoginSettings { private String accessControlBaseUrl; @@ -8,6 +12,7 @@ public class LoginSettings { private String password; private CharSequence tenant; private String refreshToken; + private List sessionCookies = new ArrayList<>(); private String version; // TODO: find a way to use a single client type here. @@ -77,4 +82,8 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } + + public List getSessionCookies() { + return sessionCookies; + } } diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index a04cbb32..d0e2ca70 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -54,7 +54,6 @@ import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; @@ -67,7 +66,6 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; -import static org.apache.commons.lang3.StringUtils.isEmpty; /** @@ -195,9 +193,16 @@ private static Registry getAuthSchemeProviderRegistry() { public void login(LoginSettings settings) throws IOException, CxClientException { lastLoginSettings = settings; + + if(!settings.getSessionCookies().isEmpty()){ + setSessionCookies(settings.getSessionCookies()); + return; + } + if (settings.getRefreshToken() != null) { token = getAccessTokenFromRefreshToken(settings); - } else if (useSSo) { + } + else if (useSSo) { if (settings.getVersion().equals("lower than 9.0")) { ssoLegacyLogin(); } else { @@ -208,13 +213,10 @@ public void login(LoginSettings settings) throws IOException, CxClientException } } - public void ssoLegacyLogin() throws CxClientException { + public ArrayList ssoLegacyLogin() throws CxClientException { HttpUriRequest request; HttpResponse loginResponse = null; - String cxCookie = null; - String csrfToken = null; - try { request = RequestBuilder.post() .setUri(rootUri + "auth/ssologin") @@ -230,8 +232,17 @@ public void ssoLegacyLogin() throws CxClientException { } finally { HttpClientUtils.closeQuietly(loginResponse); } + setSessionCookies(cookieStore.getCookies()); + + //return cookies clone - for IDE's usage + return new ArrayList<>(cookieStore.getCookies()); + } + + private void setSessionCookies(List cookies){ + String cxCookie = null; + String csrfToken = null; - for (Cookie cookie : cookieStore.getCookies()) { + for (Cookie cookie : cookies) { if (cookie.getName().equals(CSRF_TOKEN_HEADER)) { csrfToken = cookie.getValue(); } From 904a87efdec1031d93397d16e77d24d66eb1159f Mon Sep 17 00:00:00 2001 From: cx-muhammed Date: Tue, 21 Apr 2020 15:29:54 +0300 Subject: [PATCH 225/473] returning httpresponse without converting --- pom.xml | 2 +- .../com/cx/restclient/httpClient/utils/HttpClientHelper.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e916bf95..fbfd17a3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.13.SCA + 2020.1.14.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 03b4f8b9..9024c678 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -22,6 +22,10 @@ public abstract class HttpClientHelper { public static T convertToObject(HttpResponse response, Class responseType, boolean isCollection) throws IOException, CxClientException { + if(responseType != null && responseType.isInstance(response)) { + return (T) response; + } + //No content if (responseType == null || response.getEntity() == null || response.getEntity().getContentLength() == 0) { return null; @@ -34,6 +38,7 @@ public static T convertToObject(HttpResponse response, Class responseType if (isCollection) { return convertToCollectionObject(response, TypeFactory.defaultInstance().constructCollectionType(List.class, responseType)); } + //convert to T return convertToStrObject(response, responseType); } From 68f303ff337aa013af6ef0d609a26638188b884a Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Wed, 22 Apr 2020 19:19:50 +0300 Subject: [PATCH 226/473] add new variable to scanConfig --- .../com/cx/restclient/configuration/CxScanConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java index 65178eb3..8ed840cc 100644 --- a/src/main/java/com/cx/restclient/configuration/CxScanConfig.java +++ b/src/main/java/com/cx/restclient/configuration/CxScanConfig.java @@ -38,6 +38,7 @@ public class CxScanConfig implements Serializable { private String url; private String projectName; private String teamPath; + private String mvnPath; private String teamId; private Boolean denyProject = false; private Boolean hideResults = false; @@ -668,6 +669,13 @@ public Integer getConnectionRetries() { public void setConnectionRetries(Integer connectionRetries) { this.connectionRetries = connectionRetries; } + public String getMvnPath() { + return mvnPath; + } + + public void setMvnPath(String mvnPath) { + this.mvnPath = mvnPath; + } public String getOsaScanDepth() { return osaScanDepth; From a78a16909ef77966afe30c2730710c34ba25ade6 Mon Sep 17 00:00:00 2001 From: morad Date: Thu, 23 Apr 2020 12:43:24 +0300 Subject: [PATCH 227/473] overloading login method --- .../com/cx/restclient/CxShragaClient.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 7c6ae54b..bff2b4b6 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -134,9 +134,9 @@ public void init() throws CxClientException, IOException { } private void resolveEngineConfiguration() throws IOException { - if(config.getEngineConfigurationId() == null && config.getEngineConfigurationName() == null){ + if (config.getEngineConfigurationId() == null && config.getEngineConfigurationName() == null) { config.setEngineConfigurationId(1); - }else if(config.getEngineConfigurationName() != null){ + } else if (config.getEngineConfigurationName() != null) { final List engineConfigurations = getEngineConfiguration(); for (EngineConfiguration engineConfiguration : engineConfigurations) { if (engineConfiguration.getName().equalsIgnoreCase(config.getEngineConfigurationName())) { @@ -144,7 +144,7 @@ private void resolveEngineConfiguration() throws IOException { log.info("Engine configuration: \"" + config.getEngineConfigurationName() + "\" was validated in server"); } } - if (config.getEngineConfigurationId() == null){ + if (config.getEngineConfigurationId() == null) { throw new CxClientException("Engine configuration: \"" + config.getEngineConfigurationName() + "\" was not found in server"); } } @@ -213,12 +213,11 @@ public void printIsProjectViolated() { } /** - * @param config - * The following config properties are used: - * scaConfig - * proxyConfig - * cxOrigin - * disableCertificateValidation + * @param config The following config properties are used: + * scaConfig + * proxyConfig + * cxOrigin + * disableCertificateValidation */ public static void testScaConnection(CxScanConfig config, Logger log) throws CxClientException { SCAClient client = new SCAClient(config, log); @@ -260,12 +259,18 @@ public List getAllProjects() throws IOException, CxClientException { public void close() { httpClient.close(); } + //HELP config Methods + + public void login() throws IOException { + String version = getCxVersion(); + login(version); + } + public void login(String version) throws IOException, CxClientException { // perform login to server log.info("Logging into the Checkmarx service."); - LoginSettings settings = getDefaultLoginSettings(); settings.setRefreshToken(config.getRefreshToken()); settings.setVersion(version); @@ -354,12 +359,12 @@ public List getTeamList() throws IOException, CxClientException { List teamList = populateTeamList(); //If there is no chosen teamPath, just add first one from the teams list as default - if(StringUtils.isEmpty(this.teamPath) && teamList!=null && !teamList.isEmpty()){ - this.teamPath= teamList.get(0).getFullName(); + if (StringUtils.isEmpty(this.teamPath) && teamList != null && !teamList.isEmpty()) { + this.teamPath = teamList.get(0).getFullName(); } httpClient.setTeamPathHeader(this.teamPath); log.debug("getTeamList setTeamPathHeader " + this.teamPath); - return teamList; + return teamList; } public Preset getPresetById(int presetId) throws IOException, CxClientException { @@ -528,7 +533,7 @@ public ResponseQueueScanStatus getStatus(String scanId) throws IOException { } - public Long getProjectId(){ + public Long getProjectId() { return projectId; } From c356cf683209e03bffca97cbbacba1053e50cf91 Mon Sep 17 00:00:00 2001 From: morad1992 <55655463+morad1992@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:21:41 +0300 Subject: [PATCH 228/473] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ea371ae7..e916bf95 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.12.SCA + 2020.1.13.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From f83749ea4e932d07a79fe79effd05ce345945d82 Mon Sep 17 00:00:00 2001 From: ilandn Date: Wed, 29 Apr 2020 21:48:58 -0500 Subject: [PATCH 229/473] bugid: 203507 - Gradle running assemble even if 'install' flag is false CR_by: n/a --- src/main/java/com/cx/restclient/osa/utils/OSAUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java index 6ff94b3f..7194daae 100644 --- a/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java +++ b/src/main/java/com/cx/restclient/osa/utils/OSAUtils.java @@ -101,6 +101,7 @@ public static Properties generateOSAScanConfiguration(String folderExclusions, S } private static void setResolveDependencies(Properties ret, String resolveDependencies) { + ret.put("gradle.runAssembleCommand", resolveDependencies); ret.put("nuget.resolveDependencies", resolveDependencies); ret.put("nuget.restoreDependencies", resolveDependencies); ret.put("python.resolveDependencies", resolveDependencies); From 1f5c2a836e62f9e21370dcc7e2dede4e273b7d91 Mon Sep 17 00:00:00 2001 From: cx-muhammed Date: Thu, 30 Apr 2020 16:51:04 +0300 Subject: [PATCH 230/473] Update getproject API version --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxShragaClient.java | 5 ++--- .../java/com/cx/restclient/httpClient/utils/ContentType.java | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index fbfd17a3..076a4d1f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.14.SCA + 2020.1.15.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxShragaClient.java b/src/main/java/com/cx/restclient/CxShragaClient.java index 88c20c5c..8efc959c 100644 --- a/src/main/java/com/cx/restclient/CxShragaClient.java +++ b/src/main/java/com/cx/restclient/CxShragaClient.java @@ -26,8 +26,7 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.cxArm.utils.CxARMUtils.getPoliciesNames; -import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V1; -import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON_V21; +import static com.cx.restclient.httpClient.utils.ContentType.*; import static com.cx.restclient.httpClient.utils.HttpClientHelper.convertToJson; import static com.cx.restclient.sast.utils.SASTParam.*; @@ -476,7 +475,7 @@ public Project getProjectById(String projectId) throws IOException, CxClientExce Project projects = null; try { httpClient.setTeamPathHeader(this.teamPath); - projects = httpClient.getRequest(projectNamePath, CONTENT_TYPE_APPLICATION_JSON_V21, Project.class, 200, "project by id: " + projectId, false); + projects = httpClient.getRequest(projectNamePath, CONTENT_TYPE_APPLICATION_JSON_V2, Project.class, 200, "project by id: " + projectId, false); } catch (CxHTTPClientException ex) { if (ex.getStatusCode() != 404) { throw ex; diff --git a/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java b/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java index 40056d48..7fca6c17 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/ContentType.java @@ -6,6 +6,7 @@ public class ContentType { public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json"; public static final String CONTENT_TYPE_APPLICATION_JSON_V21 = "application/json;v=2.1"; + public static final String CONTENT_TYPE_APPLICATION_JSON_V2 = "application/json;v=2.0"; public static final String CONTENT_TYPE_APPLICATION_JSON_V1 = "application/json;v=1.0"; public static final String CONTENT_TYPE_APPLICATION_XML_V1 = "application/xml;v=1.0"; public static final String CONTENT_TYPE_APPLICATION_PDF_V1 = "application/pdf;v=1.0"; From 74bfd2060b046937cd42882d9efe7188b3d8928d Mon Sep 17 00:00:00 2001 From: Majd Date: Wed, 6 May 2020 14:11:34 +0300 Subject: [PATCH 231/473] fix bugs AB#691 and AB#692 configuration and Preset are not working when using remote source for the scan. raising the version of the common to 2020.2.1.SCA --- pom.xml | 2 +- .../java/com/cx/restclient/CxSASTClient.java | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index e916bf95..66358448 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.1.13.SCA + 2020.2.1.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index ef676549..6d309bc4 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -125,18 +125,7 @@ long createSASTScan(long projectId) throws IOException, CxClientException { } private long createLocalSASTScan(long projectId) throws IOException, CxClientException { - - ScanSettingResponse scanSettingResponse = getScanSetting(projectId); - ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); - scanSettingRequest.setEngineConfigurationId(scanSettingResponse.getEngineConfiguration().getId());//todo check for null - scanSettingRequest.setProjectId(projectId); - scanSettingRequest.setPresetId(config.getPresetId()); - if (config.getEngineConfigurationId() != null) { - scanSettingRequest.setEngineConfigurationId(config.getEngineConfigurationId()); - } - //Define createSASTScan settings - defineScanSetting(scanSettingRequest); - + ConfigureScanSettings(projectId); //prepare sources for scan PathFilter filter = new PathFilter(config.getSastFolderExclusions(), config.getSastFilterPattern(), log); File zipFile = CxZipUtils.getZippedSources(config, filter, config.getSourceDir(), log); @@ -200,11 +189,26 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity("", StandardCharsets.UTF_8); } + ConfigureScanSettings(projectId); createRemoteSourceRequest(projectId, entity, type.value(), isSSH); return createScan(projectId); } + + private void ConfigureScanSettings(long projectId) throws IOException { + ScanSettingResponse scanSettingResponse = getScanSetting(projectId); + ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); + scanSettingRequest.setEngineConfigurationId(scanSettingResponse.getEngineConfiguration().getId());//todo check for null + scanSettingRequest.setProjectId(projectId); + scanSettingRequest.setPresetId(config.getPresetId()); + if (config.getEngineConfigurationId() != null) { + scanSettingRequest.setEngineConfigurationId(config.getEngineConfigurationId()); + } + //Define createSASTScan settings + defineScanSetting(scanSettingRequest); + } + //GET SAST results + reports public SASTResults waitForSASTResults(long scanId, long projectId) throws InterruptedException, IOException, CxClientException { SASTResults sastResults; From 186c3e97e0a255c367c87ba5ae7499b7a096a50d Mon Sep 17 00:00:00 2001 From: Majd Date: Wed, 6 May 2020 15:34:15 +0300 Subject: [PATCH 232/473] cleaning code --- src/main/java/com/cx/restclient/CxSASTClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 6d309bc4..2d710da0 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -125,7 +125,7 @@ long createSASTScan(long projectId) throws IOException, CxClientException { } private long createLocalSASTScan(long projectId) throws IOException, CxClientException { - ConfigureScanSettings(projectId); + configureScanSettings(projectId); //prepare sources for scan PathFilter filter = new PathFilter(config.getSastFolderExclusions(), config.getSastFilterPattern(), log); File zipFile = CxZipUtils.getZippedSources(config, filter, config.getSourceDir(), log); @@ -189,14 +189,14 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient entity = new StringEntity("", StandardCharsets.UTF_8); } - ConfigureScanSettings(projectId); + configureScanSettings(projectId); createRemoteSourceRequest(projectId, entity, type.value(), isSSH); return createScan(projectId); } - private void ConfigureScanSettings(long projectId) throws IOException { + private void configureScanSettings(long projectId) throws IOException { ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); scanSettingRequest.setEngineConfigurationId(scanSettingResponse.getEngineConfiguration().getId());//todo check for null From cca03bfd377972aeecc9bb7777e6e186b89f4207 Mon Sep 17 00:00:00 2001 From: Majd Date: Wed, 6 May 2020 18:14:58 +0300 Subject: [PATCH 233/473] solve log4j warning problem, and raised the version by one to be 2020.2.2.SCA --- pom.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 66358448..17437bca 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.1.SCA + 2020.2.2.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. @@ -86,8 +86,13 @@ org.slf4j - slf4j-simple - 1.7.28 + slf4j-log4j12 + 1.7.21 + + + log4j + log4j + 1.2.17 org.freemarker From cae909d9e0554b0d255aeb2a2df926ac43d162a6 Mon Sep 17 00:00:00 2001 From: ilandn Date: Thu, 7 May 2020 16:54:19 -0500 Subject: [PATCH 234/473] bugid: 204546 - multipart request fix CR_by: n/a --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxSASTClient.java | 9 ++++++--- .../java/com/cx/restclient/httpClient/CxHttpClient.java | 6 ++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 17437bca..4f2edcf8 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.2.SCA + 2020.2.3.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 2d710da0..8dc4eab9 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -3,7 +3,10 @@ import com.cx.restclient.common.ShragaUtils; import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.*; +import com.cx.restclient.dto.PathFilter; +import com.cx.restclient.dto.RemoteSourceRequest; +import com.cx.restclient.dto.RemoteSourceTypes; +import com.cx.restclient.dto.Status; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.sast.dto.*; @@ -196,7 +199,7 @@ private long createRemoteSourceScan(long projectId) throws IOException, CxClient } - private void configureScanSettings(long projectId) throws IOException { + private void configureScanSettings(long projectId) throws IOException { ScanSettingResponse scanSettingResponse = getScanSetting(projectId); ScanSettingRequest scanSettingRequest = new ScanSettingRequest(); scanSettingRequest.setEngineConfigurationId(scanSettingResponse.getEngineConfiguration().getId());//todo check for null @@ -359,7 +362,7 @@ private long createScan(long projectId) throws CxClientException, IOException { } private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { - final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), CONTENT_TYPE_APPLICATION_JSON_V1, + final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), null, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); return cxID; diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index a04cbb32..22bb9a9f 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -9,8 +9,8 @@ import com.cx.restclient.exception.CxHTTPClientException; import com.cx.restclient.exception.CxTokenExpiredException; import com.cx.restclient.osa.dto.ClientType; -import org.apache.commons.lang3.StringUtils; import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; import org.apache.http.*; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; @@ -54,7 +54,6 @@ import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; @@ -67,7 +66,6 @@ import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; import static com.cx.restclient.httpClient.utils.HttpClientHelper.*; -import static org.apache.commons.lang3.StringUtils.isEmpty; /** @@ -432,7 +430,7 @@ public void patchRequest(String relPath, String contentType, HttpEntity entity, request(patch, contentType, entity, null, expectStatus, failedMsg, false, true); } - public void setTeamPathHeader(String teamPath){ + public void setTeamPathHeader(String teamPath) { this.teamPath = teamPath; } From 6e1078e45e2c4e29b202440ad7ce5b162854fb0e Mon Sep 17 00:00:00 2001 From: ilandn Date: Fri, 8 May 2020 11:15:47 -0500 Subject: [PATCH 235/473] bugid: 204546 - multipart request fix CR_by: n/a --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f2edcf8..3cf0d560 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ com.checkmarx cx-ws-fs-agent - 20.0.3 + 20.0.5 javax.xml.bind From f4b627a6f95c19c86e429f364133ad21e56275e4 Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 11:34:17 +0300 Subject: [PATCH 236/473] raise version to 2020.2.4.sca and add support for https as well for remote sources --- pom.xml | 2 +- src/main/java/com/cx/restclient/CxSASTClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3cf0d560..c9c9f645 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.3.SCA + 2020.2.4.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/CxSASTClient.java b/src/main/java/com/cx/restclient/CxSASTClient.java index 8dc4eab9..c043c4a3 100644 --- a/src/main/java/com/cx/restclient/CxSASTClient.java +++ b/src/main/java/com/cx/restclient/CxSASTClient.java @@ -362,7 +362,7 @@ private long createScan(long projectId) throws CxClientException, IOException { } private CxID createRemoteSourceRequest(long projectId, HttpEntity entity, String sourceType, boolean isSSH) throws IOException, CxClientException { - final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), null, + final CxID cxID = httpClient.postRequest(String.format(SAST_CREATE_REMOTE_SOURCE_SCAN, projectId, sourceType, isSSH ? "ssh" : ""), isSSH? null : CONTENT_TYPE_APPLICATION_JSON_V1, entity, CxID.class, 204, "create " + sourceType + " remote source scan setting"); return cxID; From 6112bab7713382c3d201f556977a6ac5208394c6 Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 16:18:43 +0300 Subject: [PATCH 237/473] raise version to 2020.2.4.sca and add support for https as well for remote sources --- pom.xml | 2 +- .../java/com/cx/restclient/httpClient/CxHttpClient.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c9c9f645..34b63fe6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.4.SCA + 2020.2.5.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 22bb9a9f..d78ad2dc 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -312,8 +312,12 @@ private String urlToJson(String url) { private String retrieveCookies() { List cookieList = cookieStore.getCookies(); String cookies = ""; + String name; + String value; for (Cookie cookie : cookieList) { - cookies += cookie.getName() + "=" + cookie.getValue() + ";"; + name = cookie.getName(); + value = cookie.getValue(); + cookies += name + "=" + value + ";"; } return cookies; From 56f17472106ff9daebf9fdc87c7540310412d880 Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 17:27:44 +0300 Subject: [PATCH 238/473] raise version to 2020.2.4.sca and add support for https as well for remote sources --- .../cx/restclient/httpClient/CxHttpClient.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index d78ad2dc..1a6e0628 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -62,6 +62,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static com.cx.restclient.common.CxPARAM.*; import static com.cx.restclient.httpClient.utils.ContentType.CONTENT_TYPE_APPLICATION_JSON; @@ -311,15 +312,15 @@ private String urlToJson(String url) { private String retrieveCookies() { List cookieList = cookieStore.getCookies(); + List cookieName = cookieList.stream().map(cookie -> cookie.getName()).collect(Collectors.toList()); + List cookieValue = cookieList.stream().map(cookie -> cookie.getValue()).collect(Collectors.toList()); String cookies = ""; - String name; - String value; - for (Cookie cookie : cookieList) { - name = cookie.getName(); - value = cookie.getValue(); - cookies += name + "=" + value + ";"; +/* for (Cookie cookie : cookieList) { + cookies += cookie.getName() + "=" + cookie.getValue() + ";"; + }*/ + for (int i = 0; i < cookieName.size(); i++) { + cookies += cookieName.get(i) + "=" + cookieValue.get(i) +';'; } - return cookies; } From d182e53efafb9cd29e198095b1e8891c965b5f2d Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 17:29:10 +0300 Subject: [PATCH 239/473] raise version to 2020.2.4.sca and add support for https as well for remote sources --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34b63fe6..5923b6d6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.5.SCA + 2020.2.6.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. From f54c8c4cf08e4390c25ecabb2b7e4bdbcccb7eb7 Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 17:43:02 +0300 Subject: [PATCH 240/473] implement cookie retrieve value in another way. --- .../com/cx/restclient/httpClient/CxHttpClient.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 1a6e0628..8810d398 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -314,14 +314,11 @@ private String retrieveCookies() { List cookieList = cookieStore.getCookies(); List cookieName = cookieList.stream().map(cookie -> cookie.getName()).collect(Collectors.toList()); List cookieValue = cookieList.stream().map(cookie -> cookie.getValue()).collect(Collectors.toList()); - String cookies = ""; -/* for (Cookie cookie : cookieList) { - cookies += cookie.getName() + "=" + cookie.getValue() + ";"; - }*/ - for (int i = 0; i < cookieName.size(); i++) { - cookies += cookieName.get(i) + "=" + cookieValue.get(i) +';'; - } - return cookies; + final String[] cookies = {""}; + cookieList.forEach(cookie -> { + cookies[0] += cookie.getName() + "=" + cookie.getValue() +';'; + }); + return cookies[0]; } public TokenLoginResponse generateToken(LoginSettings settings) throws IOException, CxClientException { From d09e781e2abf5f70892045d1fd8594fea4edbaaf Mon Sep 17 00:00:00 2001 From: Majd Date: Sun, 10 May 2020 17:53:02 +0300 Subject: [PATCH 241/473] remove lists --- src/main/java/com/cx/restclient/httpClient/CxHttpClient.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 8810d398..2c9a460f 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -312,8 +312,6 @@ private String urlToJson(String url) { private String retrieveCookies() { List cookieList = cookieStore.getCookies(); - List cookieName = cookieList.stream().map(cookie -> cookie.getName()).collect(Collectors.toList()); - List cookieValue = cookieList.stream().map(cookie -> cookie.getValue()).collect(Collectors.toList()); final String[] cookies = {""}; cookieList.forEach(cookie -> { cookies[0] += cookie.getName() + "=" + cookie.getValue() +';'; From 5828aadb1ae211feb85d02719a0c0d839808d93c Mon Sep 17 00:00:00 2001 From: Majd Date: Mon, 11 May 2020 10:58:46 +0300 Subject: [PATCH 242/473] use string builder to retrieve cookie and raise version to 2020.2.7.SCA --- pom.xml | 2 +- .../java/com/cx/restclient/httpClient/CxHttpClient.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 5923b6d6..f27fdb27 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.6.SCA + 2020.2.7.SCA jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 2c9a460f..cd7df837 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -312,11 +312,11 @@ private String urlToJson(String url) { private String retrieveCookies() { List cookieList = cookieStore.getCookies(); - final String[] cookies = {""}; + final StringBuilder builder = new StringBuilder(); cookieList.forEach(cookie -> { - cookies[0] += cookie.getName() + "=" + cookie.getValue() +';'; + builder.append(cookie.getName() + "=" + cookie.getValue() +';'); }); - return cookies[0]; + return builder.toString(); } public TokenLoginResponse generateToken(LoginSettings settings) throws IOException, CxClientException { From bbbee859b94875a16e980221d0cc7f15e6d7971b Mon Sep 17 00:00:00 2001 From: Majd Date: Mon, 11 May 2020 11:19:41 +0300 Subject: [PATCH 243/473] use string builder to retrieve cookie and raise version to 2020.2.7.SCA --- src/main/java/com/cx/restclient/httpClient/CxHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index cd7df837..3446e3c7 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -314,7 +314,7 @@ private String retrieveCookies() { List cookieList = cookieStore.getCookies(); final StringBuilder builder = new StringBuilder(); cookieList.forEach(cookie -> { - builder.append(cookie.getName() + "=" + cookie.getValue() +';'); + builder.append(cookie.getName()).append("=").append(cookie.getValue()).append(";"); }); return builder.toString(); } From 4ad8df8f38355a0d3256f1cbcd14dd47bd760f15 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 23 Apr 2020 14:29:30 +0300 Subject: [PATCH 244/473] Separated tests for SAST, OSA and SCA. Improved directory structure. --- .../configuration/ConnectionTest.java | 49 ------ .../connection/ProjectScanTests.java | 162 ------------------ .../CommonClientTest.java | 11 +- .../ConnectionTests.java | 2 +- .../GetTeamListTests.java | 2 +- .../cx/restclient/general/OsaScanTests.java | 55 ++++++ .../restclient/general/ProjectScanTests.java | 65 +++++++ .../cx/restclient/general/ScaScanTests.java | 65 +++++++ 8 files changed, 197 insertions(+), 214 deletions(-) delete mode 100644 src/test/java/com/cx/restclient/configuration/ConnectionTest.java delete mode 100644 src/test/java/com/cx/restclient/connection/ProjectScanTests.java rename src/test/java/com/cx/restclient/{connection => general}/CommonClientTest.java (62%) rename src/test/java/com/cx/restclient/{connection => general}/ConnectionTests.java (97%) rename src/test/java/com/cx/restclient/{connection => general}/GetTeamListTests.java (96%) create mode 100644 src/test/java/com/cx/restclient/general/OsaScanTests.java create mode 100644 src/test/java/com/cx/restclient/general/ProjectScanTests.java create mode 100644 src/test/java/com/cx/restclient/general/ScaScanTests.java diff --git a/src/test/java/com/cx/restclient/configuration/ConnectionTest.java b/src/test/java/com/cx/restclient/configuration/ConnectionTest.java deleted file mode 100644 index 6036a528..00000000 --- a/src/test/java/com/cx/restclient/configuration/ConnectionTest.java +++ /dev/null @@ -1,49 +0,0 @@ -//package com.cx.restclient.configuration; -// -//import com.cx.restclient.CxShragaClient; -//import com.cx.restclient.exception.CxClientException; -//import org.junit.Assert; -//import org.junit.Test; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.IOException; -//import java.util.Properties; -// -//public class ConnectionTest { -// -// private Logger log = LoggerFactory.getLogger(ConnectionTest.class.getName()); -// private static final String PROPERTIES_FILE = "config.properties"; -// private CxShragaClient client; -// private static Properties props; -// -//// @BeforeClass -//// public static void initTest() throws IOException { -//// props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); -//// } -// -// @Test -// public void ssoConnectionTest() { -// CxScanConfig config = initConfig(); -// try { -// client = new CxShragaClient(config, log); -// client.init(); -// } catch (IOException | CxClientException e) { -// e.printStackTrace(); -// log.error("Error running osa scan: " + e.getMessage()); -// Assert.fail(e.getMessage()); -// } -// } -// -// private CxScanConfig initConfig() { -// CxScanConfig config = new CxScanConfig(); -// config.setSastEnabled(true); -// config.setUseSSOLogin(true); -// config.setUrl("http://10.32.1.57"); -// config.setCxOrigin("common"); -// -// return config; -// } -// -// -//} diff --git a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java b/src/test/java/com/cx/restclient/connection/ProjectScanTests.java deleted file mode 100644 index 2831ac46..00000000 --- a/src/test/java/com/cx/restclient/connection/ProjectScanTests.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.cx.restclient.connection; - -import com.cx.restclient.CxShragaClient; -import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.DependencyScanResults; -import com.cx.restclient.dto.DependencyScannerType; -import com.cx.restclient.dto.ProxyConfig; -import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.sast.dto.SASTResults; -import com.cx.restclient.sca.dto.SCAConfig; -import com.cx.restclient.sca.dto.SCAResults; -import com.cx.utility.TestingUtils; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.net.MalformedURLException; - -@Ignore -public class ProjectScanTests extends CommonClientTest { - @Test - public void runOsaScan() throws MalformedURLException, CxClientException { - CxScanConfig config = initOsaConfig(); - CxShragaClient client = new CxShragaClient(config, log); - try { - client.init(); - client.createDependencyScan(); - DependencyScanResults results = client.waitForDependencyScanResults(); - Assert.assertNotNull(results); - Assert.assertNull(results.getScaResults()); - Assert.assertNotNull(results.getOsaResults()); - Assert.assertNotNull("Expected valid osa scan id", results.getOsaResults().getOsaScanId()); - } catch (Exception e) { - failOnException(e); - } - } - - @Test - public void runSastScan() throws MalformedURLException, CxClientException { - CxScanConfig config = initSastConfig(); - runSastScan(config); - } - - @Test - public void runSastScanWithProxy() throws MalformedURLException, CxClientException { - CxScanConfig config = initSastConfig(); - setProxy(config); - runSastScan(config); - } - - @Test - public void runScaScan() throws MalformedURLException, CxClientException { - CxScanConfig config = initScaConfig(); - runScaScan(config); - } - - @Test - public void runScaScanWithProxy() throws MalformedURLException, CxClientException { - CxScanConfig config = initScaConfig(); - setProxy(config); - runScaScan(config); - } - - private void setProxy(CxScanConfig config) { - ProxyConfig proxyConfig = new ProxyConfig(); - proxyConfig.setHost(props.getProperty("proxy.host")); - proxyConfig.setPort(Integer.parseInt(props.getProperty("proxy.port"))); - config.setProxyConfig(proxyConfig); - } - - private void runScaScan(CxScanConfig config) throws MalformedURLException, CxClientException { - CxShragaClient client = new CxShragaClient(config, log); - try { - client.init(); - client.createDependencyScan(); - DependencyScanResults results = client.waitForDependencyScanResults(); - Assert.assertNotNull(results); - Assert.assertNull(results.getOsaResults()); - - SCAResults scaResults = results.getScaResults(); - Assert.assertNotNull(scaResults); - Assert.assertNotNull(scaResults.getSummary()); - Assert.assertNotNull(scaResults.getScanId()); - Assert.assertNotNull(scaResults.getWebReportLink()); - } catch (Exception e) { - failOnException(e); - } - } - - private void runSastScan(CxScanConfig config) throws MalformedURLException, CxClientException { - CxShragaClient client = new CxShragaClient(config, log); - try { - client.init(); - client.createSASTScan(); - SASTResults results = client.waitForSASTResults(); - Assert.assertNotNull(results); - Assert.assertNotEquals("Expected valid SAST scan id", 0, results.getScanId()); - } catch (Exception e) { - failOnException(e); - } - } - - private CxScanConfig initSastConfig() { - CxScanConfig config = new CxScanConfig(); - config.setSastEnabled(true); - config.setReportsDir(new File("C:\\report")); - config.setSourceDir(props.getProperty("sastSource")); - config.setUsername(props.getProperty("username")); - config.setPassword(props.getProperty("password")); - config.setUrl(props.getProperty("serverUrl")); - config.setCxOrigin("common"); - config.setProjectName("sastOnlyScan"); - config.setPresetName("Default"); - config.setTeamPath("\\CxServer"); - config.setSynchronous(true); - config.setGeneratePDFReport(true); - config.setDependencyScannerType(DependencyScannerType.NONE); - config.setPresetName("Default"); -// config.setPresetId(7); - - return config; - } - - private CxScanConfig initOsaConfig() { - CxScanConfig config = new CxScanConfig(); - config.setDependencyScannerType(DependencyScannerType.OSA); - config.setSastEnabled(false); - config.setSourceDir(props.getProperty("dependencyScanSourceDir")); - config.setReportsDir(new File("C:\\report")); - config.setUrl(props.getProperty("serverUrl")); - config.setUsername(props.getProperty("username")); - config.setPassword(props.getProperty("password")); - - config.setCxOrigin("common"); - config.setProjectName("osaOnlyScan"); - config.setPresetName("Default"); - config.setTeamPath("\\CxServer"); - config.setSynchronous(true); - config.setGeneratePDFReport(true); - - config.setOsaRunInstall(true); - config.setOsaThresholdsEnabled(true); - config.setPublic(true); - - return config; - } - - private CxScanConfig initScaConfig() { - CxScanConfig config = new CxScanConfig(); - config.setDependencyScannerType(DependencyScannerType.SCA); - config.setSastEnabled(false); - config.setSourceDir(props.getProperty("dependencyScanSourceDir")); - config.setOsaThresholdsEnabled(true); - config.setProjectName("scaOnlyScan"); - - SCAConfig sca = TestingUtils.getScaConfig(props); - config.setScaConfig(sca); - - return config; - } -} diff --git a/src/test/java/com/cx/restclient/connection/CommonClientTest.java b/src/test/java/com/cx/restclient/general/CommonClientTest.java similarity index 62% rename from src/test/java/com/cx/restclient/connection/CommonClientTest.java rename to src/test/java/com/cx/restclient/general/CommonClientTest.java index 04ba087a..b97a8850 100644 --- a/src/test/java/com/cx/restclient/connection/CommonClientTest.java +++ b/src/test/java/com/cx/restclient/general/CommonClientTest.java @@ -1,5 +1,7 @@ -package com.cx.restclient.connection; +package com.cx.restclient.general; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.ProxyConfig; import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.BeforeClass; @@ -19,6 +21,13 @@ public static void initTest() throws IOException { props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); } + protected static void setProxy(CxScanConfig config) { + ProxyConfig proxyConfig = new ProxyConfig(); + proxyConfig.setHost(props.getProperty("proxy.host")); + proxyConfig.setPort(Integer.parseInt(props.getProperty("proxy.port"))); + config.setProxyConfig(proxyConfig); + } + void failOnException(Exception e) { log.error("Unexpected exception.", e); Assert.fail(e.getMessage()); diff --git a/src/test/java/com/cx/restclient/connection/ConnectionTests.java b/src/test/java/com/cx/restclient/general/ConnectionTests.java similarity index 97% rename from src/test/java/com/cx/restclient/connection/ConnectionTests.java rename to src/test/java/com/cx/restclient/general/ConnectionTests.java index f9a3efbc..4b91b27d 100644 --- a/src/test/java/com/cx/restclient/connection/ConnectionTests.java +++ b/src/test/java/com/cx/restclient/general/ConnectionTests.java @@ -1,4 +1,4 @@ -package com.cx.restclient.connection; +package com.cx.restclient.general; import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; diff --git a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java b/src/test/java/com/cx/restclient/general/GetTeamListTests.java similarity index 96% rename from src/test/java/com/cx/restclient/connection/GetTeamListTests.java rename to src/test/java/com/cx/restclient/general/GetTeamListTests.java index 7693f26a..5ed0a837 100644 --- a/src/test/java/com/cx/restclient/connection/GetTeamListTests.java +++ b/src/test/java/com/cx/restclient/general/GetTeamListTests.java @@ -1,4 +1,4 @@ -package com.cx.restclient.connection; +package com.cx.restclient.general; import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; diff --git a/src/test/java/com/cx/restclient/general/OsaScanTests.java b/src/test/java/com/cx/restclient/general/OsaScanTests.java new file mode 100644 index 00000000..d1044ab3 --- /dev/null +++ b/src/test/java/com/cx/restclient/general/OsaScanTests.java @@ -0,0 +1,55 @@ +package com.cx.restclient.general; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; +import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.exception.CxClientException; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.net.MalformedURLException; + +public class OsaScanTests extends CommonClientTest { + @Test + public void runOsaScan() throws MalformedURLException, CxClientException { + CxScanConfig config = initOsaConfig(); + CxShragaClient client = new CxShragaClient(config, log); + try { + client.init(); + client.createDependencyScan(); + DependencyScanResults results = client.waitForDependencyScanResults(); + Assert.assertNotNull(results); + Assert.assertNull(results.getScaResults()); + Assert.assertNotNull(results.getOsaResults()); + Assert.assertNotNull("Expected valid osa scan id", results.getOsaResults().getOsaScanId()); + } catch (Exception e) { + failOnException(e); + } + } + + private CxScanConfig initOsaConfig() { + CxScanConfig config = new CxScanConfig(); + config.setDependencyScannerType(DependencyScannerType.OSA); + config.setSastEnabled(false); + config.setSourceDir(props.getProperty("dependencyScanSourceDir")); + config.setReportsDir(new File("C:\\report")); + config.setUrl(props.getProperty("serverUrl")); + config.setUsername(props.getProperty("username")); + config.setPassword(props.getProperty("password")); + + config.setCxOrigin("common"); + config.setProjectName("osaOnlyScan"); + config.setPresetName("Default"); + config.setTeamPath("\\CxServer"); + config.setSynchronous(true); + config.setGeneratePDFReport(true); + + config.setOsaRunInstall(true); + config.setOsaThresholdsEnabled(true); + config.setPublic(true); + + return config; + } +} diff --git a/src/test/java/com/cx/restclient/general/ProjectScanTests.java b/src/test/java/com/cx/restclient/general/ProjectScanTests.java new file mode 100644 index 00000000..4683afba --- /dev/null +++ b/src/test/java/com/cx/restclient/general/ProjectScanTests.java @@ -0,0 +1,65 @@ +package com.cx.restclient.general; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; +import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ProxyConfig; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.sast.dto.SASTResults; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.net.MalformedURLException; + +@Ignore +public class ProjectScanTests extends CommonClientTest { + @Test + public void runSastScan() throws MalformedURLException, CxClientException { + CxScanConfig config = initSastConfig(); + runSastScan(config); + } + + @Test + public void runSastScanWithProxy() throws MalformedURLException, CxClientException { + CxScanConfig config = initSastConfig(); + setProxy(config); + runSastScan(config); + } + + private void runSastScan(CxScanConfig config) throws MalformedURLException, CxClientException { + CxShragaClient client = new CxShragaClient(config, log); + try { + client.init(); + client.createSASTScan(); + SASTResults results = client.waitForSASTResults(); + Assert.assertNotNull(results); + Assert.assertNotEquals("Expected valid SAST scan id", 0, results.getScanId()); + } catch (Exception e) { + failOnException(e); + } + } + + private CxScanConfig initSastConfig() { + CxScanConfig config = new CxScanConfig(); + config.setSastEnabled(true); + config.setReportsDir(new File("C:\\report")); + config.setSourceDir(props.getProperty("sastSource")); + config.setUsername(props.getProperty("username")); + config.setPassword(props.getProperty("password")); + config.setUrl(props.getProperty("serverUrl")); + config.setCxOrigin("common"); + config.setProjectName("sastOnlyScan"); + config.setPresetName("Default"); + config.setTeamPath("\\CxServer"); + config.setSynchronous(true); + config.setGeneratePDFReport(true); + config.setDependencyScannerType(DependencyScannerType.NONE); + config.setPresetName("Default"); +// config.setPresetId(7); + + return config; + } +} diff --git a/src/test/java/com/cx/restclient/general/ScaScanTests.java b/src/test/java/com/cx/restclient/general/ScaScanTests.java new file mode 100644 index 00000000..03e608e8 --- /dev/null +++ b/src/test/java/com/cx/restclient/general/ScaScanTests.java @@ -0,0 +1,65 @@ +package com.cx.restclient.general; + +import com.cx.restclient.CxShragaClient; +import com.cx.restclient.configuration.CxScanConfig; +import com.cx.restclient.dto.DependencyScanResults; +import com.cx.restclient.dto.DependencyScannerType; +import com.cx.restclient.dto.ProxyConfig; +import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.sca.dto.SCAConfig; +import com.cx.restclient.sca.dto.SCAResults; +import com.cx.utility.TestingUtils; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.net.MalformedURLException; + +@Ignore +public class ScaScanTests extends CommonClientTest { + @Test + public void runScaScan() throws MalformedURLException, CxClientException { + CxScanConfig config = initScaConfig(); + runScaScan(config); + } + + @Test + public void runScaScanWithProxy() throws MalformedURLException, CxClientException { + CxScanConfig config = initScaConfig(); + setProxy(config); + runScaScan(config); + } + + private void runScaScan(CxScanConfig config) throws MalformedURLException, CxClientException { + CxShragaClient client = new CxShragaClient(config, log); + try { + client.init(); + client.createDependencyScan(); + DependencyScanResults results = client.waitForDependencyScanResults(); + Assert.assertNotNull(results); + Assert.assertNull(results.getOsaResults()); + + SCAResults scaResults = results.getScaResults(); + Assert.assertNotNull(scaResults); + Assert.assertNotNull(scaResults.getSummary()); + Assert.assertNotNull(scaResults.getScanId()); + Assert.assertNotNull(scaResults.getWebReportLink()); + } catch (Exception e) { + failOnException(e); + } + } + + private CxScanConfig initScaConfig() { + CxScanConfig config = new CxScanConfig(); + config.setDependencyScannerType(DependencyScannerType.SCA); + config.setSastEnabled(false); + config.setSourceDir(props.getProperty("dependencyScanSourceDir")); + config.setOsaThresholdsEnabled(true); + config.setProjectName("scaOnlyScan"); + + SCAConfig sca = TestingUtils.getScaConfig(props); + config.setScaConfig(sca); + + return config; + } +} From b39ab2779e9c24cb4f14f026abffb845981b01a0 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 23 Apr 2020 17:07:16 +0300 Subject: [PATCH 245/473] Uploading zipped sources into SCA storage. --- .../java/com/cx/restclient/SCAClient.java | 130 +++++++++++------- .../sca/dto/RemoteRepositoryInfo.java | 16 +++ .../com/cx/restclient/sca/dto/SCAConfig.java | 55 +------- .../sca/dto/SourceLocationType.java | 6 + .../restclient/general/CommonClientTest.java | 2 +- ...ojectScanTests.java => SastScanTests.java} | 4 +- .../cx/restclient/general/ScaScanTests.java | 13 +- 7 files changed, 115 insertions(+), 111 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java rename src/test/java/com/cx/restclient/general/{ProjectScanTests.java => SastScanTests.java} (93%) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 1b8e68fd..23b5d9a2 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -15,15 +15,14 @@ import com.cx.restclient.sast.utils.zip.CxZipUtils; import com.cx.restclient.sca.SCAWaiter; import com.cx.restclient.sca.dto.*; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.entity.mime.content.InputStreamBody; -import org.apache.http.entity.mime.content.StringBody; import org.slf4j.Logger; import java.io.File; @@ -46,8 +45,6 @@ private static class UrlPaths { private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; - private static final String ZIP_UPLOAD = "/scan-runner/scans/zip"; - private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } @@ -61,7 +58,7 @@ private static class UrlPaths { private final Waiter waiter; private String scanId; - SCAClient(CxScanConfig config, Logger log) throws CxClientException { + SCAClient(CxScanConfig config, Logger log) { this.log = log; this.config = config; @@ -82,7 +79,7 @@ private static class UrlPaths { } @Override - public void init() throws CxClientException { + public void init() { try { login(); resolveProject(); @@ -92,16 +89,17 @@ public void init() throws CxClientException { } @Override - public String createScan(DependencyScanResults target) throws CxClientException { + public String createScan(DependencyScanResults target) { log.info("----------------------------------- Create CxSCA Scan:------------------------------------"); - PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); scanId = null; try { - String sourceDir = config.getEffectiveSourceDirForDependencyScan(); - File zipFile = CxZipUtils.getZippedSources(config, filter, sourceDir, log); - scanId = uploadZipFile(zipFile); - CxZipUtils.deleteZippedSources(zipFile, config, log); + SourceLocationType locationType = getScaConfig().getSourceLocationType(); + if (locationType == SourceLocationType.REMOTE_REPOSITORY) { + submitSourcesFromRemoteRepo(locationType); + } else if (locationType == SourceLocationType.LOCAL_DIRECTORY) { + submitSourcesFromLocalDir(); + } } catch (IOException e) { throw new CxClientException("Error creating CxSCA scan.", e); } @@ -109,8 +107,55 @@ public String createScan(DependencyScanResults target) throws CxClientException return scanId; } + private void submitSourcesFromRemoteRepo(SourceLocationType locationType) { + log.info("Using remote repository flow."); + RemoteRepositoryInfo repoInfo = getScaConfig().getRemoteRepositoryInfo(); + validate(repoInfo, locationType); + } + + private void validate(RemoteRepositoryInfo repoInfo, SourceLocationType locationType) { + if (repoInfo == null) { + String message = String.format( + "%s must be provided in CxSCA configuration when using source location of type %s.", + RemoteRepositoryInfo.class.getName(), + locationType.name()); + + throw new CxClientException(message); + } + } + + private void submitSourcesFromLocalDir() throws IOException { + log.info("Using local directory flow."); + PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); + String sourceDir = config.getEffectiveSourceDirForDependencyScan(); + File zipFile = CxZipUtils.getZippedSources(config, filter, sourceDir, log); + String uploadHandleUrl = uploadArchive(zipFile); + CxZipUtils.deleteZippedSources(zipFile, config, log); + startScanAfterUpload(); + } + + private void startScanAfterUpload() { + // TODO + } + + private String uploadArchive(File source) throws IOException { + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + InputStream input = new FileInputStream(source.getAbsoluteFile()); + InputStreamBody fileBody = new InputStreamBody(input, org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM, "anyfilename"); + builder.addPart("sources", fileBody); + HttpEntity entity = builder.build(); + + JsonNode response = httpClient.postRequest("/api/uploads", null, entity, JsonNode.class, HttpStatus.SC_OK, "upload ZIP file"); + String uploadHandlerUrl = response.get("url").textValue(); + log.debug("Upload handler URL: {}", uploadHandlerUrl); + + return uploadHandlerUrl; + } + @Override - public void waitForScanResults(DependencyScanResults target) throws CxClientException { + public void waitForScanResults(DependencyScanResults target) { log.info("------------------------------------Get CxSCA Results:-----------------------------------"); log.info("Waiting for CxSCA scan to finish"); @@ -125,7 +170,7 @@ public void waitForScanResults(DependencyScanResults target) throws CxClientExce } if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { - log.info("CxSCA scan results location: " + scaResult.getWebReportLink()); + log.info("CxSCA scan results location: {}", scaResult.getWebReportLink()); } target.setScaResults(scaResult); @@ -137,13 +182,13 @@ public DependencyScanResults getLatestScanResults() { return null; } - void testConnection() throws IOException, CxClientException { + void testConnection() throws IOException { // The calls below allow to check both access control and API connectivity. login(); getProjects(); } - private void login() throws IOException, CxClientException { + private void login() throws IOException { log.info("Logging into CxSCA."); SCAConfig scaConfig = getScaConfig(); @@ -157,18 +202,18 @@ private void login() throws IOException, CxClientException { httpClient.login(settings); } - private void resolveProject() throws IOException, CxClientException { + private void resolveProject() throws IOException { String projectName = config.getProjectName(); projectId = getProjectIdByName(projectName); if (projectId == null) { log.debug("Project not found, creating a new one."); projectId = createProject(projectName); } - log.debug("Using project ID: " + projectId); + log.debug("Using project ID: {}", projectId); } - private String getProjectIdByName(String name) throws IOException, CxClientException { - log.debug("Getting project by name: " + name); + private String getProjectIdByName(String name) throws IOException { + log.debug("Getting project by name: {}", name); if (StringUtils.isEmpty(name)) { throw new CxClientException("Non-empty project name must be provided."); @@ -183,16 +228,16 @@ private String getProjectIdByName(String name) throws IOException, CxClientExcep .orElse(null); } - private List getProjects() throws IOException, CxClientException { + private List getProjects() throws IOException { return (List) httpClient.getRequest(UrlPaths.PROJECTS, - ContentType.CONTENT_TYPE_APPLICATION_JSON, - Project.class, - HttpStatus.SC_OK, - "CxSCA projects", - true); + ContentType.CONTENT_TYPE_APPLICATION_JSON, + Project.class, + HttpStatus.SC_OK, + "CxSCA projects", + true); } - private String createProject(String name) throws CxClientException, IOException { + private String createProject(String name) throws IOException { CreateProjectRequest request = new CreateProjectRequest(); request.setName(name); @@ -208,28 +253,7 @@ private String createProject(String name) throws CxClientException, IOException return newProject.getId(); } - private String uploadZipFile(File zipFile) throws IOException, CxClientException { - log.info("Uploading zipped sources."); - - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - - InputStream input = new FileInputStream(zipFile.getAbsoluteFile()); - InputStreamBody fileBody = new InputStreamBody(input, org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM, "zippedSource"); - builder.addPart("zipFile", fileBody); - - ContentBody projectIdBody = new StringBody(projectId, org.apache.http.entity.ContentType.APPLICATION_FORM_URLENCODED); - builder.addPart("projectId", projectIdBody); - - HttpEntity entity = builder.build(); - - String scanId = httpClient.postRequest(UrlPaths.ZIP_UPLOAD, null, entity, String.class, HttpStatus.SC_CREATED, "upload ZIP file"); - log.debug("Scan ID: " + scanId); - - return scanId; - } - - private SCAResults retrieveScanResults() throws IOException, CxClientException { + private SCAResults retrieveScanResults() throws IOException { String reportId = getReportId(); SCAResults result = new SCAResults(); @@ -244,7 +268,7 @@ private SCAResults retrieveScanResults() throws IOException, CxClientException { } private String getWebReportLink(String reportId) { - String MESSAGE = "Unable to generate web report link. "; + final String MESSAGE = "Unable to generate web report link. "; String result = null; try { String webAppUrl = getScaConfig().getWebAppUrl(); @@ -266,7 +290,7 @@ private String getWebReportLink(String reportId) { return result; } - private String getReportId() throws IOException, CxClientException { + private String getReportId() throws IOException { log.debug("Getting report ID by scan ID: " + scanId); String path = String.format(UrlPaths.REPORT_ID, scanId); String reportId = httpClient.getRequest(path, @@ -279,7 +303,7 @@ private String getReportId() throws IOException, CxClientException { return reportId; } - private SCASummaryResults getSummaryReport(String reportId) throws IOException, CxClientException { + private SCASummaryResults getSummaryReport(String reportId) throws IOException { log.debug("Getting summary report."); String path = String.format(UrlPaths.SUMMARY_REPORT, reportId); @@ -310,7 +334,7 @@ private void printSummary(SCASummaryResults summary) { log.info(String.format("Total outdated packages: %d\n", summary.getTotalOutdatedPackages())); } - private SCAConfig getScaConfig() throws CxClientException { + private SCAConfig getScaConfig() { SCAConfig result = config.getScaConfig(); if (result == null) { throw new CxClientException("CxSCA scan configuration is missing."); diff --git a/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java b/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java new file mode 100644 index 00000000..c506e19e --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java @@ -0,0 +1,16 @@ +package com.cx.restclient.sca.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.net.URL; + +@Getter +@Setter +public class RemoteRepositoryInfo implements Serializable { + /** + * A URL for which 'git pull' is possible. + */ + private URL url; +} diff --git a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java index f2ab145f..e5f358e4 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java +++ b/src/main/java/com/cx/restclient/sca/dto/SCAConfig.java @@ -1,7 +1,12 @@ package com.cx.restclient.sca.dto; +import lombok.Getter; +import lombok.Setter; + import java.io.Serializable; +@Getter +@Setter public class SCAConfig implements Serializable { private String apiUrl; private String accessControlUrl; @@ -9,52 +14,6 @@ public class SCAConfig implements Serializable { private String password; private String tenant; private String webAppUrl; - - public String getApiUrl() { - return apiUrl; - } - - public void setApiUrl(String apiUrl) { - this.apiUrl = apiUrl; - } - - public void setAccessControlUrl(String accessControlUrl) { - this.accessControlUrl = accessControlUrl; - } - - public String getAccessControlUrl() { - return accessControlUrl; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getUsername() { - return username; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getPassword() { - return password; - } - - public void setTenant(String tenant) { - this.tenant = tenant; - } - - public String getTenant() { - return tenant; - } - - public String getWebAppUrl() { - return webAppUrl; - } - - public void setWebAppUrl(String webAppUrl) { - this.webAppUrl = webAppUrl; - } + private RemoteRepositoryInfo remoteRepositoryInfo; + private SourceLocationType sourceLocationType; } diff --git a/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java b/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java new file mode 100644 index 00000000..91b54714 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java @@ -0,0 +1,6 @@ +package com.cx.restclient.sca.dto; + +public enum SourceLocationType { + LOCAL_DIRECTORY, + REMOTE_REPOSITORY +} diff --git a/src/test/java/com/cx/restclient/general/CommonClientTest.java b/src/test/java/com/cx/restclient/general/CommonClientTest.java index b97a8850..85e44d83 100644 --- a/src/test/java/com/cx/restclient/general/CommonClientTest.java +++ b/src/test/java/com/cx/restclient/general/CommonClientTest.java @@ -18,7 +18,7 @@ public abstract class CommonClientTest { @BeforeClass public static void initTest() throws IOException { - props = TestingUtils.getProps(PROPERTIES_FILE, ProjectScanTests.class); + props = TestingUtils.getProps(PROPERTIES_FILE, SastScanTests.class); } protected static void setProxy(CxScanConfig config) { diff --git a/src/test/java/com/cx/restclient/general/ProjectScanTests.java b/src/test/java/com/cx/restclient/general/SastScanTests.java similarity index 93% rename from src/test/java/com/cx/restclient/general/ProjectScanTests.java rename to src/test/java/com/cx/restclient/general/SastScanTests.java index 4683afba..9e832e51 100644 --- a/src/test/java/com/cx/restclient/general/ProjectScanTests.java +++ b/src/test/java/com/cx/restclient/general/SastScanTests.java @@ -2,9 +2,7 @@ import com.cx.restclient.CxShragaClient; import com.cx.restclient.configuration.CxScanConfig; -import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; -import com.cx.restclient.dto.ProxyConfig; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sast.dto.SASTResults; import org.junit.Assert; @@ -15,7 +13,7 @@ import java.net.MalformedURLException; @Ignore -public class ProjectScanTests extends CommonClientTest { +public class SastScanTests extends CommonClientTest { @Test public void runSastScan() throws MalformedURLException, CxClientException { CxScanConfig config = initSastConfig(); diff --git a/src/test/java/com/cx/restclient/general/ScaScanTests.java b/src/test/java/com/cx/restclient/general/ScaScanTests.java index 03e608e8..d4a2353d 100644 --- a/src/test/java/com/cx/restclient/general/ScaScanTests.java +++ b/src/test/java/com/cx/restclient/general/ScaScanTests.java @@ -4,10 +4,10 @@ import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; -import com.cx.restclient.dto.ProxyConfig; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sca.dto.SCAResults; +import com.cx.restclient.sca.dto.SourceLocationType; import com.cx.utility.TestingUtils; import org.junit.Assert; import org.junit.Ignore; @@ -18,19 +18,20 @@ @Ignore public class ScaScanTests extends CommonClientTest { @Test - public void runScaScan() throws MalformedURLException, CxClientException { + public void scan_locaDirUpload() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); - runScaScan(config); + config.getScaConfig().setSourceLocationType(SourceLocationType.LOCAL_DIRECTORY); + scanUsing(config); } @Test public void runScaScanWithProxy() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); setProxy(config); - runScaScan(config); + scanUsing(config); } - private void runScaScan(CxScanConfig config) throws MalformedURLException, CxClientException { + private void scanUsing(CxScanConfig config) throws MalformedURLException, CxClientException { CxShragaClient client = new CxShragaClient(config, log); try { client.init(); @@ -55,7 +56,7 @@ private CxScanConfig initScaConfig() { config.setSastEnabled(false); config.setSourceDir(props.getProperty("dependencyScanSourceDir")); config.setOsaThresholdsEnabled(true); - config.setProjectName("scaOnlyScan"); + config.setProjectName("commonClient-test-scaOnlyScan"); SCAConfig sca = TestingUtils.getScaConfig(props); config.setScaConfig(sca); From 881c5da6c5cccd8e258c3a035cd4e961b27923df Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 26 Apr 2020 15:32:24 +0300 Subject: [PATCH 246/473] Implemented source archive upload. --- .../java/com/cx/restclient/SCAClient.java | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 23b5d9a2..1d4c20f9 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -19,16 +19,12 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; +import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.InputStreamBody; import org.slf4j.Logger; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -67,13 +63,7 @@ private static class UrlPaths { SCAConfig scaConfig = getScaConfig(); - httpClient = new CxHttpClient(scaConfig.getApiUrl(), - config.getCxOrigin(), - config.isDisableCertificateValidation(), - config.isUseSSOLogin(), - null, - config.getProxyConfig(), - log); + httpClient = createHttpClient(scaConfig.getApiUrl()); waiter = new SCAWaiter("CxSCA scan", pollInterval, maxRetries, httpClient, UrlPaths.SCAN_STATUS, log); } @@ -90,13 +80,13 @@ public void init() { @Override public String createScan(DependencyScanResults target) { - log.info("----------------------------------- Create CxSCA Scan:------------------------------------"); + log.info("----------------------------------- Creating CxSCA Scan:------------------------------------"); scanId = null; try { SourceLocationType locationType = getScaConfig().getSourceLocationType(); if (locationType == SourceLocationType.REMOTE_REPOSITORY) { - submitSourcesFromRemoteRepo(locationType); + submitSourcesFromRemoteRepo(); } else if (locationType == SourceLocationType.LOCAL_DIRECTORY) { submitSourcesFromLocalDir(); } @@ -107,18 +97,18 @@ public String createScan(DependencyScanResults target) { return scanId; } - private void submitSourcesFromRemoteRepo(SourceLocationType locationType) { + private void submitSourcesFromRemoteRepo() { log.info("Using remote repository flow."); RemoteRepositoryInfo repoInfo = getScaConfig().getRemoteRepositoryInfo(); - validate(repoInfo, locationType); + validateRemoteRepoConfig(repoInfo); } - private void validate(RemoteRepositoryInfo repoInfo, SourceLocationType locationType) { + private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { if (repoInfo == null) { String message = String.format( "%s must be provided in CxSCA configuration when using source location of type %s.", RemoteRepositoryInfo.class.getName(), - locationType.name()); + SourceLocationType.REMOTE_REPOSITORY.name()); throw new CxClientException(message); } @@ -126,32 +116,31 @@ private void validate(RemoteRepositoryInfo repoInfo, SourceLocationType location private void submitSourcesFromLocalDir() throws IOException { log.info("Using local directory flow."); + PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); String sourceDir = config.getEffectiveSourceDirForDependencyScan(); File zipFile = CxZipUtils.getZippedSources(config, filter, sourceDir, log); - String uploadHandleUrl = uploadArchive(zipFile); + + String uploadUrl = getSourcesUploadUrl(); + uploadArchive(zipFile, uploadUrl); CxZipUtils.deleteZippedSources(zipFile, config, log); - startScanAfterUpload(); } - private void startScanAfterUpload() { - // TODO + private String getSourcesUploadUrl() throws IOException { + JsonNode response = httpClient.postRequest("/api/uploads", null, null, JsonNode.class, HttpStatus.SC_OK, "get upload URL for sources"); + return response.get("url").asText(); } - private String uploadArchive(File source) throws IOException { - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + private void uploadArchive(File source, String uploadUrl) throws IOException { + log.info("Uploading the zipped sources."); - InputStream input = new FileInputStream(source.getAbsoluteFile()); - InputStreamBody fileBody = new InputStreamBody(input, org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM, "anyfilename"); - builder.addPart("sources", fileBody); - HttpEntity entity = builder.build(); + HttpEntity request = new FileEntity(source); - JsonNode response = httpClient.postRequest("/api/uploads", null, entity, JsonNode.class, HttpStatus.SC_OK, "upload ZIP file"); - String uploadHandlerUrl = response.get("url").textValue(); - log.debug("Upload handler URL: {}", uploadHandlerUrl); + CxHttpClient client = createHttpClient(uploadUrl); - return uploadHandlerUrl; + // Relative path is empty, because we use the whole upload URL as the base URL for the HTTP client. + // Content type is empty, because the server at uploadUrl throws an error if Content-Type is non-empty. + client.putRequest("", "", request, JsonNode.class, HttpStatus.SC_OK, "upload ZIP file"); } @Override @@ -320,7 +309,6 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException { return result; } - // This method is for demo purposes and probably should be replaced in the future. private void printSummary(SCASummaryResults summary) { log.info("\n----CxSCA risk report summary----"); log.info("Created on: " + summary.getCreatedOn()); @@ -331,7 +319,7 @@ private void printSummary(SCASummaryResults summary) { log.info("Risk report ID: " + summary.getRiskReportId()); log.info("Risk score: " + summary.getRiskScore()); log.info("Total packages: " + summary.getTotalPackages()); - log.info(String.format("Total outdated packages: %d\n", summary.getTotalOutdatedPackages())); + log.info(String.format("Total outdated packages: %d%n", summary.getTotalOutdatedPackages())); } private SCAConfig getScaConfig() { @@ -341,4 +329,14 @@ private SCAConfig getScaConfig() { } return result; } + + private CxHttpClient createHttpClient(String baseUrl) { + return new CxHttpClient(baseUrl, + config.getCxOrigin(), + config.isDisableCertificateValidation(), + config.isUseSSOLogin(), + null, + config.getProxyConfig(), + log); + } } From aab6b87260b4c6b69f16a6a98f1a5b6015ee05dd Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 26 Apr 2020 16:42:21 +0300 Subject: [PATCH 247/473] Starting the scan. --- .../java/com/cx/restclient/SCAClient.java | 57 ++++++++++++++----- .../httpClient/utils/HttpClientHelper.java | 17 ++++-- .../cx/restclient/sca/dto/ProjectToScan.java | 12 ++++ .../sca/dto/SourceLocationType.java | 11 +++- .../restclient/sca/dto/StartScanRequest.java | 10 ++++ .../cx/restclient/sca/dto/UploadHandler.java | 10 ++++ 6 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/StartScanRequest.java create mode 100644 src/main/java/com/cx/restclient/sca/dto/UploadHandler.java diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 1d4c20f9..000420de 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -34,6 +34,8 @@ * SCA - Software Composition Analysis - is the successor of OSA. */ public class SCAClient implements DependencyScanner { + + private static class UrlPaths { private static final String RISK_MANAGEMENT_API = "/risk-management/"; private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; @@ -41,6 +43,9 @@ private static class UrlPaths { private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; + public static final String GET_UPLOAD_URL_ENDPOINT = "/api/uploads"; + public static final String CREATE_SCAN_ENDPOINT = "/api/scans"; + private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } @@ -87,7 +92,7 @@ public String createScan(DependencyScanResults target) { SourceLocationType locationType = getScaConfig().getSourceLocationType(); if (locationType == SourceLocationType.REMOTE_REPOSITORY) { submitSourcesFromRemoteRepo(); - } else if (locationType == SourceLocationType.LOCAL_DIRECTORY) { + } else { submitSourcesFromLocalDir(); } } catch (IOException e) { @@ -103,17 +108,6 @@ private void submitSourcesFromRemoteRepo() { validateRemoteRepoConfig(repoInfo); } - private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { - if (repoInfo == null) { - String message = String.format( - "%s must be provided in CxSCA configuration when using source location of type %s.", - RemoteRepositoryInfo.class.getName(), - SourceLocationType.REMOTE_REPOSITORY.name()); - - throw new CxClientException(message); - } - } - private void submitSourcesFromLocalDir() throws IOException { log.info("Using local directory flow."); @@ -124,10 +118,47 @@ private void submitSourcesFromLocalDir() throws IOException { String uploadUrl = getSourcesUploadUrl(); uploadArchive(zipFile, uploadUrl); CxZipUtils.deleteZippedSources(zipFile, config, log); + + sendStartScanRequest(uploadUrl); + } + + private void sendStartScanRequest(String uploadUrl) throws IOException { + log.info("Sending a request to start scan."); + + UploadHandler handler = UploadHandler.builder() + .url(uploadUrl) + .build(); + + ProjectToScan project = ProjectToScan.builder() + .id(projectId) + .type(SourceLocationType.LOCAL_DIRECTORY.getApiValue()) + .handler(handler) + .build(); + + StartScanRequest request = StartScanRequest.builder() + .project(project) + .build(); + + StringEntity entity = HttpClientHelper.convertToStringEntity(request); + + httpClient.postRequest(UrlPaths.CREATE_SCAN_ENDPOINT, ContentType.CONTENT_TYPE_APPLICATION_JSON_V1, entity, String.class, HttpStatus.SC_CREATED, "start CxSCA scan"); + + log.info("Scan started successfully."); + } + + private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { + if (repoInfo == null) { + String message = String.format( + "%s must be provided in CxSCA configuration when using source location of type %s.", + RemoteRepositoryInfo.class.getName(), + SourceLocationType.REMOTE_REPOSITORY.name()); + + throw new CxClientException(message); + } } private String getSourcesUploadUrl() throws IOException { - JsonNode response = httpClient.postRequest("/api/uploads", null, null, JsonNode.class, HttpStatus.SC_OK, "get upload URL for sources"); + JsonNode response = httpClient.postRequest(UrlPaths.GET_UPLOAD_URL_ENDPOINT, null, null, JsonNode.class, HttpStatus.SC_OK, "get upload URL for sources"); return response.get("url").asText(); } diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index 03b4f8b9..e1e6b635 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -83,13 +83,20 @@ private static ObjectMapper getObjectMapper() { return result; } - public static void validateResponse(HttpResponse response, int status, String message) throws CxClientException { - if (response.getStatusLine().getStatusCode() != status) { + public static void validateResponse(HttpResponse response, int expectedStatus, String message) throws CxClientException { + int actualStatusCode = response.getStatusLine().getStatusCode(); + if (actualStatusCode != expectedStatus) { String responseBody = extractResponseBody(response); - responseBody = responseBody.replace("{", "").replace("}", "").replace(System.getProperty("line.separator"), " ").replace(" ", ""); - throw new CxHTTPClientException(response.getStatusLine().getStatusCode(), message + ": " + responseBody); - } + responseBody = responseBody.replace("{", "") + .replace("}", "") + .replace(System.getProperty("line.separator"), " ") + .replace(" ", ""); + + String exceptionMessage = String.format("Status code: %d, message: '%s', response body: %s", + actualStatusCode, message, responseBody); + throw new CxHTTPClientException(actualStatusCode, exceptionMessage); + } } public static String extractResponseBody(HttpResponse response) { diff --git a/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java b/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java new file mode 100644 index 00000000..78df7f5b --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java @@ -0,0 +1,12 @@ +package com.cx.restclient.sca.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ProjectToScan { + private String id; + private String type; + private UploadHandler handler; +} diff --git a/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java b/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java index 91b54714..cbf079fc 100644 --- a/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java +++ b/src/main/java/com/cx/restclient/sca/dto/SourceLocationType.java @@ -1,6 +1,13 @@ package com.cx.restclient.sca.dto; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter public enum SourceLocationType { - LOCAL_DIRECTORY, - REMOTE_REPOSITORY + LOCAL_DIRECTORY("upload"), + REMOTE_REPOSITORY("git"); + + private final String apiValue; } diff --git a/src/main/java/com/cx/restclient/sca/dto/StartScanRequest.java b/src/main/java/com/cx/restclient/sca/dto/StartScanRequest.java new file mode 100644 index 00000000..f800fc1c --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/StartScanRequest.java @@ -0,0 +1,10 @@ +package com.cx.restclient.sca.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class StartScanRequest { + private ProjectToScan project; +} diff --git a/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java b/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java new file mode 100644 index 00000000..cd692fc1 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java @@ -0,0 +1,10 @@ +package com.cx.restclient.sca.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class UploadHandler { + private String url; +} From 328fdbb839988b4720967cc97c1fe1908b6cee21 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 27 Apr 2020 15:41:58 +0300 Subject: [PATCH 248/473] Scanning a git repo by URL. Waiting until scan is finished. --- pom.xml | 6 + .../java/com/cx/restclient/SCAClient.java | 99 +++++++------ .../httpClient/utils/HttpClientHelper.java | 6 + .../java/com/cx/restclient/sca/SCAWaiter.java | 130 ++++++++++++------ .../restclient/sca/dto/ScanInfoResponse.java | 9 ++ .../sca/dto/ScanStatusResponse.java | 18 --- .../com/cx/restclient/sca/dto/StatusName.java | 6 +- 7 files changed, 156 insertions(+), 118 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java delete mode 100644 src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java diff --git a/pom.xml b/pom.xml index f27fdb27..cab230df 100644 --- a/pom.xml +++ b/pom.xml @@ -196,6 +196,12 @@ ${lombok.version} provided + + org.awaitility + awaitility + 4.0.2 + + jakarta.xml.bind diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 000420de..45a0f021 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -2,7 +2,6 @@ import com.cx.restclient.common.DependencyScanner; import com.cx.restclient.common.UrlUtils; -import com.cx.restclient.common.Waiter; import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.LoginSettings; @@ -16,8 +15,10 @@ import com.cx.restclient.sca.SCAWaiter; import com.cx.restclient.sca.dto.*; import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; @@ -35,16 +36,17 @@ */ public class SCAClient implements DependencyScanner { + public static class UrlPaths { + private UrlPaths() { } - private static class UrlPaths { private static final String RISK_MANAGEMENT_API = "/risk-management/"; private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; private static final String SUMMARY_REPORT = RISK_MANAGEMENT_API + "riskReports/%s/summary"; - private static final String SCAN_STATUS = RISK_MANAGEMENT_API + "scans/%s/status"; private static final String REPORT_ID = RISK_MANAGEMENT_API + "scans/%s/riskReportId"; - public static final String GET_UPLOAD_URL_ENDPOINT = "/api/uploads"; - public static final String CREATE_SCAN_ENDPOINT = "/api/scans"; + public static final String GET_UPLOAD_URL = "/api/uploads"; + public static final String CREATE_SCAN = "/api/scans"; + public static final String GET_SCAN = "/api/scans/%s"; private static final String WEB_REPORT = "/#/projects/%s/reports/%s"; } @@ -56,21 +58,15 @@ private static class UrlPaths { private final CxHttpClient httpClient; private String projectId; - private final Waiter waiter; private String scanId; SCAClient(CxScanConfig config, Logger log) { this.log = log; this.config = config; - int pollInterval = config.getOsaProgressInterval() != null ? config.getOsaProgressInterval() : 20; - int maxRetries = config.getConnectionRetries() != null ? config.getConnectionRetries() : 3; - SCAConfig scaConfig = getScaConfig(); httpClient = createHttpClient(scaConfig.getApiUrl()); - - waiter = new SCAWaiter("CxSCA scan", pollInterval, maxRetries, httpClient, UrlPaths.SCAN_STATUS, log); } @Override @@ -90,11 +86,14 @@ public String createScan(DependencyScanResults target) { scanId = null; try { SourceLocationType locationType = getScaConfig().getSourceLocationType(); + HttpResponse response; if (locationType == SourceLocationType.REMOTE_REPOSITORY) { - submitSourcesFromRemoteRepo(); + response = submitSourcesFromRemoteRepo(); } else { - submitSourcesFromLocalDir(); + response = submitSourcesFromLocalDir(); } + scanId = extractScanIdFrom(response); + log.info("Scan started successfully. Scan ID: {}", scanId); } catch (IOException e) { throw new CxClientException("Error creating CxSCA scan.", e); } @@ -102,36 +101,49 @@ public String createScan(DependencyScanResults target) { return scanId; } - private void submitSourcesFromRemoteRepo() { + private String extractScanIdFrom(HttpResponse response) { + if (response != null && response.getLastHeader("Location") != null) { + // Expecting a value like "/api/scans/1ecffa00-0e42-49b2-8755-388b9f6a9293" + String urlPathWithScanId = response.getLastHeader("Location").getValue(); + String lastPathSegment = FilenameUtils.getName(urlPathWithScanId); + if (StringUtils.isNotEmpty(lastPathSegment)) { + return lastPathSegment; + } + } + throw new CxClientException("Unable to get scan ID."); + } + + private HttpResponse submitSourcesFromRemoteRepo() throws IOException { log.info("Using remote repository flow."); RemoteRepositoryInfo repoInfo = getScaConfig().getRemoteRepositoryInfo(); validateRemoteRepoConfig(repoInfo); + return sendStartScanRequest(SourceLocationType.REMOTE_REPOSITORY, repoInfo.getUrl().toString()); } - private void submitSourcesFromLocalDir() throws IOException { + private HttpResponse submitSourcesFromLocalDir() throws IOException { log.info("Using local directory flow."); PathFilter filter = new PathFilter(config.getOsaFolderExclusions(), config.getOsaFilterPattern(), log); String sourceDir = config.getEffectiveSourceDirForDependencyScan(); File zipFile = CxZipUtils.getZippedSources(config, filter, sourceDir, log); - String uploadUrl = getSourcesUploadUrl(); - uploadArchive(zipFile, uploadUrl); + String uploadedArchiveUrl = getSourcesUploadUrl(); + uploadArchive(zipFile, uploadedArchiveUrl); CxZipUtils.deleteZippedSources(zipFile, config, log); - sendStartScanRequest(uploadUrl); + return sendStartScanRequest(SourceLocationType.LOCAL_DIRECTORY, uploadedArchiveUrl); } - private void sendStartScanRequest(String uploadUrl) throws IOException { + private HttpResponse sendStartScanRequest(SourceLocationType sourceLocation, String sourceUrl) throws IOException { log.info("Sending a request to start scan."); UploadHandler handler = UploadHandler.builder() - .url(uploadUrl) + .url(sourceUrl) .build(); ProjectToScan project = ProjectToScan.builder() .id(projectId) - .type(SourceLocationType.LOCAL_DIRECTORY.getApiValue()) + .type(sourceLocation.getApiValue()) .handler(handler) .build(); @@ -141,9 +153,8 @@ private void sendStartScanRequest(String uploadUrl) throws IOException { StringEntity entity = HttpClientHelper.convertToStringEntity(request); - httpClient.postRequest(UrlPaths.CREATE_SCAN_ENDPOINT, ContentType.CONTENT_TYPE_APPLICATION_JSON_V1, entity, String.class, HttpStatus.SC_CREATED, "start CxSCA scan"); - - log.info("Scan started successfully."); + return httpClient.postRequest(UrlPaths.CREATE_SCAN, ContentType.CONTENT_TYPE_APPLICATION_JSON, entity, + HttpResponse.class, HttpStatus.SC_CREATED, "start CxSCA scan"); } private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { @@ -158,7 +169,8 @@ private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { } private String getSourcesUploadUrl() throws IOException { - JsonNode response = httpClient.postRequest(UrlPaths.GET_UPLOAD_URL_ENDPOINT, null, null, JsonNode.class, HttpStatus.SC_OK, "get upload URL for sources"); + JsonNode response = httpClient.postRequest(UrlPaths.GET_UPLOAD_URL, null, null, JsonNode.class, + HttpStatus.SC_OK, "get upload URL for sources"); return response.get("url").asText(); } @@ -177,28 +189,17 @@ private void uploadArchive(File source, String uploadUrl) throws IOException { @Override public void waitForScanResults(DependencyScanResults target) { log.info("------------------------------------Get CxSCA Results:-----------------------------------"); - log.info("Waiting for CxSCA scan to finish"); - waiter.waitForTaskToFinish(scanId, this.config.getOsaScanTimeoutInMinutes(), log); - log.info("CxSCA scan finished successfully. Retrieving CxSCA scan results."); - SCAResults scaResult; - try { - scaResult = retrieveScanResults(); - } catch (IOException e) { - throw new CxClientException("Error retrieving CxSCA scan results.", e); - } - - if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { - log.info("CxSCA scan results location: {}", scaResult.getWebReportLink()); - } + SCAWaiter waiter = new SCAWaiter(httpClient, config); + waiter.waitForScanToComplete(scanId); - target.setScaResults(scaResult); + log.info("CxSCA scan finished."); } @Override public DependencyScanResults getLatestScanResults() { - // TODO + // TODO: implement when someone actually needs this. return null; } @@ -249,12 +250,8 @@ private String getProjectIdByName(String name) throws IOException { } private List getProjects() throws IOException { - return (List) httpClient.getRequest(UrlPaths.PROJECTS, - ContentType.CONTENT_TYPE_APPLICATION_JSON, - Project.class, - HttpStatus.SC_OK, - "CxSCA projects", - true); + return (List) httpClient.getRequest(UrlPaths.PROJECTS, ContentType.CONTENT_TYPE_APPLICATION_JSON, + Project.class, HttpStatus.SC_OK, "CxSCA projects", true); } private String createProject(String name) throws IOException { @@ -288,12 +285,12 @@ private SCAResults retrieveScanResults() throws IOException { } private String getWebReportLink(String reportId) { - final String MESSAGE = "Unable to generate web report link. "; + final String MESSAGE = "Unable to generate web report link."; String result = null; try { String webAppUrl = getScaConfig().getWebAppUrl(); if (StringUtils.isEmpty(webAppUrl)) { - log.warn(MESSAGE + "Web app URL is not specified."); + log.warn("{} Web app URL is not specified.", MESSAGE); } else { String encoding = StandardCharsets.UTF_8.name(); String path = String.format(UrlPaths.WEB_REPORT, @@ -303,7 +300,7 @@ private String getWebReportLink(String reportId) { result = UrlUtils.parseURLToString(webAppUrl, path); } } catch (MalformedURLException e) { - log.warn(MESSAGE + "Invalid web app URL.", e); + log.warn("{} Invalid web app URL.", MESSAGE, e); } catch (Exception e) { log.warn(MESSAGE, e); } @@ -311,7 +308,7 @@ private String getWebReportLink(String reportId) { } private String getReportId() throws IOException { - log.debug("Getting report ID by scan ID: " + scanId); + log.debug("Getting report ID by scan ID: {}", scanId); String path = String.format(UrlPaths.REPORT_ID, scanId); String reportId = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, @@ -319,7 +316,7 @@ private String getReportId() throws IOException { HttpStatus.SC_OK, "Risk report ID", false); - log.debug("Found report ID: " + reportId); + log.debug("Found report ID: {}", reportId); return reportId; } diff --git a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java index e1e6b635..ec82340f 100644 --- a/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java +++ b/src/main/java/com/cx/restclient/httpClient/utils/HttpClientHelper.java @@ -22,6 +22,12 @@ public abstract class HttpClientHelper { public static T convertToObject(HttpResponse response, Class responseType, boolean isCollection) throws IOException, CxClientException { + // If the caller is asking for the whole response, return the response (instead of just its entity), + // no matter if the entity is empty. + if (responseType != null && responseType.isAssignableFrom(response.getClass())) { + return (T)response; + } + //No content if (responseType == null || response.getEntity() == null || response.getEntity().getContentLength() == 0) { return null; diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index 61a01577..dd9095f4 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -1,70 +1,110 @@ package com.cx.restclient.sca; +import com.cx.restclient.SCAClient; import com.cx.restclient.common.ShragaUtils; -import com.cx.restclient.common.Waiter; +import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.exception.CxClientException; import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.httpClient.utils.ContentType; -import com.cx.restclient.sca.dto.ScanStatusResponse; +import com.cx.restclient.sca.dto.ScanInfoResponse; import com.cx.restclient.sca.dto.StatusName; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; import org.apache.http.HttpStatus; -import org.slf4j.Logger; +import org.awaitility.Awaitility; +import org.awaitility.core.ConditionTimeoutException; -import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; -public class SCAWaiter extends Waiter { +@RequiredArgsConstructor +@Slf4j +public class SCAWaiter { private final CxHttpClient httpClient; - private final String scanStatusUrlPath; - private final Logger log; - - public SCAWaiter(String scanType, int interval, int retry, CxHttpClient httpClient, String scanStatusUrlPath, Logger log) { - super(scanType, interval, retry); - this.httpClient = httpClient; - this.scanStatusUrlPath = scanStatusUrlPath; - this.log = log; - } + private final CxScanConfig config; + private long startTimestampSec; + + public void waitForScanToComplete(String scanId) { + startTimestampSec = System.currentTimeMillis() / 1000; + Duration timeout = getTimeout(config); + Duration pollInterval = getPollInterval(config); + + int maxErrorCount = getMaxErrorCount(config); + AtomicInteger errorCounter = new AtomicInteger(); + + String urlPath = String.format(SCAClient.UrlPaths.GET_SCAN, scanId); - @Override - public ScanStatusResponse getStatus(String scanId) throws CxClientException, IOException { - String path = String.format(scanStatusUrlPath, scanId); + try { + Awaitility.await() + .atMost(timeout) + .pollDelay(Duration.ZERO) + .pollInterval(pollInterval) + .until(() -> scanIsCompleted(urlPath, errorCounter, maxErrorCount)); + + } catch (ConditionTimeoutException e) { + String message = String.format( + "Failed to perform CxSCA scan. The scan has been automatically aborted: " + + "reached the user-specified timeout (%d minutes).", timeout.toMinutes()); + throw new CxClientException(message); + } + } - ScanStatusResponse response = httpClient.getRequest(path, - ContentType.CONTENT_TYPE_APPLICATION_JSON, - ScanStatusResponse.class, - HttpStatus.SC_OK, - "CxSCA scan status", - false); + private static Duration getTimeout(CxScanConfig config) { + Integer rawTimeout = config.getOsaScanTimeoutInMinutes(); + final int DEFAULT_TIMEOUT = 30; + rawTimeout = rawTimeout != null && rawTimeout > 0 ? rawTimeout : DEFAULT_TIMEOUT; + return Duration.ofMinutes(rawTimeout); + } - return response; + private static Duration getPollInterval(CxScanConfig config) { + int rawPollInterval = ObjectUtils.defaultIfNull(config.getOsaProgressInterval(), 20); + return Duration.ofSeconds(rawPollInterval); } - @Override - public void printProgress(ScanStatusResponse statusResponse) { - log.info(String.format("Waiting for CxSCA scan results. Elapsed time: %s. Status: %s.", - ShragaUtils.getTimestampSince(getStartTimeSec()), - statusResponse.getName().getValue())); + private static int getMaxErrorCount(CxScanConfig config) { + return ObjectUtils.defaultIfNull(config.getConnectionRetries(), 3); } - @Override - public ScanStatusResponse resolveStatus(ScanStatusResponse lastStatusResponse) throws CxClientException { - if (lastStatusResponse == null || lastStatusResponse.getName() == StatusName.FAILED) { - String details = null; - if (lastStatusResponse != null) { - details = String.format("Status: %s, message: \'%s\'", - lastStatusResponse.getName(), - lastStatusResponse.getMessage()); - } - throw new CxClientException("CxSCA scan cannot be completed. " + details); + private boolean scanIsCompleted(String path, AtomicInteger errorCounter, int maxErrorCount) { + boolean completedSuccessfully = false; + try { + ScanInfoResponse response = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, + ScanInfoResponse.class, HttpStatus.SC_OK, "CxSCA scan", false); + + completedSuccessfully = validateScanStatus(response); + } catch (Exception e) { + countError(errorCounter, maxErrorCount, e.getMessage()); } + return completedSuccessfully; + } - if (lastStatusResponse.getName() == StatusName.DONE) { - log.info("CxSCA scan finished."); + private void countError(AtomicInteger errorCounter, int maxErrorCount, String message) { + int currentErrorCount = errorCounter.incrementAndGet(); + int triesLeft = maxErrorCount - currentErrorCount; + if (triesLeft < 0) { + String fullMessage = String.format("Maximum number of errors was reached (%d), aborting.", maxErrorCount); + throw new CxClientException(fullMessage); + } else { + log.debug("Failed to get status from CxSCA. Retrying (tries left: {}). Error message: {}", triesLeft, message); } - return lastStatusResponse; } - @Override - public boolean isTaskInProgress(ScanStatusResponse statusResponse) { - return statusResponse != null && statusResponse.getName() == StatusName.SCANNING; + private boolean validateScanStatus(ScanInfoResponse response) { + if (response == null) { + throw new CxClientException("Empty response."); + } + + StatusName status = response.getStatus(); + String elapsedTimestamp = ShragaUtils.getTimestampSince(startTimestampSec); + log.info("Waiting for CxSCA scan results. Elapsed time: {}. Status: {}.", elapsedTimestamp, status.getValue()); + + boolean completedSuccessfully = false; + if (status == StatusName.FAILED) { + throw new CxClientException("CxSCA scan cannot be completed."); + } else if (status == StatusName.DONE) { + completedSuccessfully = true; + } + return completedSuccessfully; } } diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java b/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java new file mode 100644 index 00000000..96a12ae1 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java @@ -0,0 +1,9 @@ +package com.cx.restclient.sca.dto; + +import lombok.Getter; + +@Getter +public class ScanInfoResponse { + private String id; + private StatusName status; +} diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java b/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java deleted file mode 100644 index 01bc8391..00000000 --- a/src/main/java/com/cx/restclient/sca/dto/ScanStatusResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cx.restclient.sca.dto; - -public class ScanStatusResponse { - private StatusName name; - private String message; - - public StatusName getName() { - return name; - } - - public void setName(StatusName name) { - this.name = name; - } - - public String getMessage() { - return message; - } -} diff --git a/src/main/java/com/cx/restclient/sca/dto/StatusName.java b/src/main/java/com/cx/restclient/sca/dto/StatusName.java index 2ac058dc..0b048847 100644 --- a/src/main/java/com/cx/restclient/sca/dto/StatusName.java +++ b/src/main/java/com/cx/restclient/sca/dto/StatusName.java @@ -1,7 +1,9 @@ package com.cx.restclient.sca.dto; import com.fasterxml.jackson.annotation.JsonValue; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor public enum StatusName { FAILED("Failed"), DONE("Done"), @@ -13,8 +15,4 @@ public enum StatusName { public String getValue() { return value; } - - StatusName(String value) { - this.value = value; - } } From 1bbd7030b9da70f4e9ae60274342255417ea6277 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 28 Apr 2020 12:21:24 +0300 Subject: [PATCH 249/473] Updated scan status values after API changes. Removed a noisy log message. --- pom.xml | 5 +++++ .../cx/restclient/httpClient/CxHttpClient.java | 1 - .../java/com/cx/restclient/sca/SCAWaiter.java | 18 +++++++++++------- .../restclient/sca/dto/ScanInfoResponse.java | 2 +- .../com/cx/restclient/sca/dto/ScanStatus.java | 7 +++++++ .../com/cx/restclient/sca/dto/StatusName.java | 18 ------------------ .../cx/restclient/general/ScaScanTests.java | 16 ++++++++++++++++ 7 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/ScanStatus.java delete mode 100644 src/main/java/com/cx/restclient/sca/dto/StatusName.java diff --git a/pom.xml b/pom.xml index cab230df..452a89d8 100644 --- a/pom.xml +++ b/pom.xml @@ -201,6 +201,11 @@ awaitility 4.0.2 + + org.apache.commons + commons-lang3 + 3.10 + diff --git a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java index 3446e3c7..2f920a62 100644 --- a/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java +++ b/src/main/java/com/cx/restclient/httpClient/CxHttpClient.java @@ -446,7 +446,6 @@ private T request(HttpRequestBase httpMethod, String contentType, HttpEntity try { httpMethod.addHeader(ORIGIN_HEADER, cxOrigin); - log.debug("request setTeamPathHeader " + this.teamPath); httpMethod.addHeader(TEAM_PATH, this.teamPath); if (token != null) { httpMethod.addHeader(HttpHeaders.AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token()); diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index dd9095f4..b35c8ec5 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -7,9 +7,10 @@ import com.cx.restclient.httpClient.CxHttpClient; import com.cx.restclient.httpClient.utils.ContentType; import com.cx.restclient.sca.dto.ScanInfoResponse; -import com.cx.restclient.sca.dto.StatusName; +import com.cx.restclient.sca.dto.ScanStatus; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.http.HttpStatus; import org.awaitility.Awaitility; @@ -86,7 +87,7 @@ private void countError(AtomicInteger errorCounter, int maxErrorCount, String me String fullMessage = String.format("Maximum number of errors was reached (%d), aborting.", maxErrorCount); throw new CxClientException(fullMessage); } else { - log.debug("Failed to get status from CxSCA. Retrying (tries left: {}). Error message: {}", triesLeft, message); + log.info("Failed to get status from CxSCA. Retrying (tries left: {}). Error message: {}", triesLeft, message); } } @@ -95,15 +96,18 @@ private boolean validateScanStatus(ScanInfoResponse response) { throw new CxClientException("Empty response."); } - StatusName status = response.getStatus(); + String rawStatus = response.getStatus(); String elapsedTimestamp = ShragaUtils.getTimestampSince(startTimestampSec); - log.info("Waiting for CxSCA scan results. Elapsed time: {}. Status: {}.", elapsedTimestamp, status.getValue()); + log.info("Waiting for CxSCA scan results. Elapsed time: {}. Status: {}.", elapsedTimestamp, rawStatus); + ScanStatus status = EnumUtils.getEnumIgnoreCase(ScanStatus.class, rawStatus); boolean completedSuccessfully = false; - if (status == StatusName.FAILED) { - throw new CxClientException("CxSCA scan cannot be completed."); - } else if (status == StatusName.DONE) { + if (status == ScanStatus.COMPLETED) { completedSuccessfully = true; + } else if (status == ScanStatus.FAILED) { + throw new CxClientException("CxSCA scan cannot be completed."); + } else if (status == null) { + log.warn("Unknown status: {}", rawStatus); } return completedSuccessfully; } diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java b/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java index 96a12ae1..6fc1aabf 100644 --- a/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java +++ b/src/main/java/com/cx/restclient/sca/dto/ScanInfoResponse.java @@ -5,5 +5,5 @@ @Getter public class ScanInfoResponse { private String id; - private StatusName status; + private String status; } diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanStatus.java b/src/main/java/com/cx/restclient/sca/dto/ScanStatus.java new file mode 100644 index 00000000..0d76d67d --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/ScanStatus.java @@ -0,0 +1,7 @@ +package com.cx.restclient.sca.dto; + +public enum ScanStatus { + FAILED, + COMPLETED, + RUNNING +} diff --git a/src/main/java/com/cx/restclient/sca/dto/StatusName.java b/src/main/java/com/cx/restclient/sca/dto/StatusName.java deleted file mode 100644 index 0b048847..00000000 --- a/src/main/java/com/cx/restclient/sca/dto/StatusName.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cx.restclient.sca.dto; - -import com.fasterxml.jackson.annotation.JsonValue; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public enum StatusName { - FAILED("Failed"), - DONE("Done"), - SCANNING("Scanning"); - - private final String value; - - @JsonValue - public String getValue() { - return value; - } -} diff --git a/src/test/java/com/cx/restclient/general/ScaScanTests.java b/src/test/java/com/cx/restclient/general/ScaScanTests.java index d4a2353d..4f5a7c7d 100644 --- a/src/test/java/com/cx/restclient/general/ScaScanTests.java +++ b/src/test/java/com/cx/restclient/general/ScaScanTests.java @@ -5,6 +5,7 @@ import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.exception.CxClientException; +import com.cx.restclient.sca.dto.RemoteRepositoryInfo; import com.cx.restclient.sca.dto.SCAConfig; import com.cx.restclient.sca.dto.SCAResults; import com.cx.restclient.sca.dto.SourceLocationType; @@ -14,6 +15,7 @@ import org.junit.Test; import java.net.MalformedURLException; +import java.net.URL; @Ignore public class ScaScanTests extends CommonClientTest { @@ -24,6 +26,20 @@ public void scan_locaDirUpload() throws MalformedURLException, CxClientException scanUsing(config); } + @Test + public void scan_remotePublicRepo() throws MalformedURLException { + CxScanConfig config = initScaConfig(); + config.getScaConfig().setSourceLocationType(SourceLocationType.REMOTE_REPOSITORY); + RemoteRepositoryInfo repoInfo = new RemoteRepositoryInfo(); + + URL repoUrl = new URL(props.getProperty("sca.remotePublicRepoUrl")); + repoInfo.setUrl(repoUrl); + + config.getScaConfig().setRemoteRepositoryInfo(repoInfo); + + scanUsing(config); + } + @Test public void runScaScanWithProxy() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); From 21bf10a775fed7da9ac7845097225cb923e215aa Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Tue, 28 Apr 2020 14:21:36 +0300 Subject: [PATCH 250/473] Added URL-encoding, comments. Refactoring. --- .../java/com/cx/restclient/SCAClient.java | 87 ++++++++++++------- .../java/com/cx/restclient/sca/SCAWaiter.java | 11 ++- .../cx/restclient/sca/dto/ProjectToScan.java | 2 +- .../sca/dto/RemoteRepositoryInfo.java | 6 +- .../restclient/sca/dto/ScanStartHandler.java | 14 +++ .../cx/restclient/sca/dto/UploadHandler.java | 10 --- 6 files changed, 86 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/cx/restclient/sca/dto/ScanStartHandler.java delete mode 100644 src/main/java/com/cx/restclient/sca/dto/UploadHandler.java diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 45a0f021..7ce78251 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -36,8 +36,11 @@ */ public class SCAClient implements DependencyScanner { + public static final String ENCODING = StandardCharsets.UTF_8.name(); + public static class UrlPaths { - private UrlPaths() { } + private UrlPaths() { + } private static final String RISK_MANAGEMENT_API = "/risk-management/"; private static final String PROJECTS = RISK_MANAGEMENT_API + "projects"; @@ -79,6 +82,26 @@ public void init() { } } + /** + * Waits for SCA scan to finish, then gets scan results. + * + * @param target scan results will be written into this object + * ({@link com.cx.restclient.dto.DependencyScanResults#setScaResults}). + * @throws CxClientException in case of a network error, scan failure or when scan is aborted by timeout. + */ + @Override + public void waitForScanResults(DependencyScanResults target) { + log.info("------------------------------------Get CxSCA Results:-----------------------------------"); + + log.info("Waiting for CxSCA scan to finish"); + SCAWaiter waiter = new SCAWaiter(httpClient, config); + waiter.waitForScanToFinish(scanId); + log.info("CxSCA scan finished successfully. Retrieving CxSCA scan results."); + + SCAResults scaResult = retrieveScanResults(); + target.setScaResults(scaResult); + } + @Override public String createScan(DependencyScanResults target) { log.info("----------------------------------- Creating CxSCA Scan:------------------------------------"); @@ -137,7 +160,7 @@ private HttpResponse submitSourcesFromLocalDir() throws IOException { private HttpResponse sendStartScanRequest(SourceLocationType sourceLocation, String sourceUrl) throws IOException { log.info("Sending a request to start scan."); - UploadHandler handler = UploadHandler.builder() + ScanStartHandler handler = ScanStartHandler.builder() .url(sourceUrl) .build(); @@ -171,6 +194,11 @@ private void validateRemoteRepoConfig(RemoteRepositoryInfo repoInfo) { private String getSourcesUploadUrl() throws IOException { JsonNode response = httpClient.postRequest(UrlPaths.GET_UPLOAD_URL, null, null, JsonNode.class, HttpStatus.SC_OK, "get upload URL for sources"); + + if (response == null || response.get("url") == null) { + throw new CxClientException("Unable to get the upload URL."); + } + return response.get("url").asText(); } @@ -186,15 +214,10 @@ private void uploadArchive(File source, String uploadUrl) throws IOException { client.putRequest("", "", request, JsonNode.class, HttpStatus.SC_OK, "upload ZIP file"); } - @Override - public void waitForScanResults(DependencyScanResults target) { - log.info("------------------------------------Get CxSCA Results:-----------------------------------"); - log.info("Waiting for CxSCA scan to finish"); - - SCAWaiter waiter = new SCAWaiter(httpClient, config); - waiter.waitForScanToComplete(scanId); - - log.info("CxSCA scan finished."); + private void printWebReportLink(SCAResults scaResult) { + if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { + log.info("CxSCA scan results location: " + scaResult.getWebReportLink()); + } } @Override @@ -270,18 +293,24 @@ private String createProject(String name) throws IOException { return newProject.getId(); } - private SCAResults retrieveScanResults() throws IOException { - String reportId = getReportId(); + private SCAResults retrieveScanResults() { + try { + String reportId = getReportId(); - SCAResults result = new SCAResults(); - result.setScanId(scanId); + SCAResults result = new SCAResults(); + result.setScanId(scanId); - SCASummaryResults scanSummary = getSummaryReport(reportId); - result.setSummary(scanSummary); + SCASummaryResults scanSummary = getSummaryReport(reportId); + result.setSummary(scanSummary); + printSummary(scanSummary); - String reportLink = getWebReportLink(reportId); - result.setWebReportLink(reportLink); - return result; + String reportLink = getWebReportLink(reportId); + result.setWebReportLink(reportLink); + printWebReportLink(result); + return result; + } catch (IOException e) { + throw new CxClientException("Error retrieving CxSCA scan results.", e); + } } private String getWebReportLink(String reportId) { @@ -292,24 +321,25 @@ private String getWebReportLink(String reportId) { if (StringUtils.isEmpty(webAppUrl)) { log.warn("{} Web app URL is not specified.", MESSAGE); } else { - String encoding = StandardCharsets.UTF_8.name(); String path = String.format(UrlPaths.WEB_REPORT, - URLEncoder.encode(projectId, encoding), - URLEncoder.encode(reportId, encoding)); + URLEncoder.encode(projectId, ENCODING), + URLEncoder.encode(reportId, ENCODING)); result = UrlUtils.parseURLToString(webAppUrl, path); } } catch (MalformedURLException e) { - log.warn("{} Invalid web app URL.", MESSAGE, e); + log.warn("Unable to generate web report link: invalid web app URL.", e); } catch (Exception e) { - log.warn(MESSAGE, e); + log.warn("Unable to generate web report link: general error.", e); } return result; } private String getReportId() throws IOException { log.debug("Getting report ID by scan ID: {}", scanId); - String path = String.format(UrlPaths.REPORT_ID, scanId); + String path = String.format(UrlPaths.REPORT_ID, + URLEncoder.encode(scanId, ENCODING)); + String reportId = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, String.class, @@ -323,7 +353,8 @@ private String getReportId() throws IOException { private SCASummaryResults getSummaryReport(String reportId) throws IOException { log.debug("Getting summary report."); - String path = String.format(UrlPaths.SUMMARY_REPORT, reportId); + String path = String.format(UrlPaths.SUMMARY_REPORT, + URLEncoder.encode(reportId, ENCODING)); SCASummaryResults result = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, @@ -332,8 +363,6 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException { "CxSCA report summary", false); - printSummary(result); - return result; } diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index b35c8ec5..b7914bff 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -16,6 +16,8 @@ import org.awaitility.Awaitility; import org.awaitility.core.ConditionTimeoutException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; @@ -26,7 +28,7 @@ public class SCAWaiter { private final CxScanConfig config; private long startTimestampSec; - public void waitForScanToComplete(String scanId) { + public void waitForScanToFinish(String scanId) { startTimestampSec = System.currentTimeMillis() / 1000; Duration timeout = getTimeout(config); Duration pollInterval = getPollInterval(config); @@ -34,9 +36,10 @@ public void waitForScanToComplete(String scanId) { int maxErrorCount = getMaxErrorCount(config); AtomicInteger errorCounter = new AtomicInteger(); - String urlPath = String.format(SCAClient.UrlPaths.GET_SCAN, scanId); - try { + String urlPath = String.format(SCAClient.UrlPaths.GET_SCAN, + URLEncoder.encode(scanId, SCAClient.ENCODING)); + Awaitility.await() .atMost(timeout) .pollDelay(Duration.ZERO) @@ -48,6 +51,8 @@ public void waitForScanToComplete(String scanId) { "Failed to perform CxSCA scan. The scan has been automatically aborted: " + "reached the user-specified timeout (%d minutes).", timeout.toMinutes()); throw new CxClientException(message); + } catch (UnsupportedEncodingException e) { + log.error("Unexpected error.", e); } } diff --git a/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java b/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java index 78df7f5b..bd9339bb 100644 --- a/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java +++ b/src/main/java/com/cx/restclient/sca/dto/ProjectToScan.java @@ -8,5 +8,5 @@ public class ProjectToScan { private String id; private String type; - private UploadHandler handler; + private ScanStartHandler handler; } diff --git a/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java b/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java index c506e19e..a28211d2 100644 --- a/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java +++ b/src/main/java/com/cx/restclient/sca/dto/RemoteRepositoryInfo.java @@ -6,11 +6,15 @@ import java.io.Serializable; import java.net.URL; +/** + * Instructs SCA which repository should be scanned. + * In the future this class may be expanded to include repo credentials and commit/branch/tag reference. + */ @Getter @Setter public class RemoteRepositoryInfo implements Serializable { /** - * A URL for which 'git pull' is possible. + * A URL for which 'git clone' is possible. */ private URL url; } diff --git a/src/main/java/com/cx/restclient/sca/dto/ScanStartHandler.java b/src/main/java/com/cx/restclient/sca/dto/ScanStartHandler.java new file mode 100644 index 00000000..5dbf9312 --- /dev/null +++ b/src/main/java/com/cx/restclient/sca/dto/ScanStartHandler.java @@ -0,0 +1,14 @@ +package com.cx.restclient.sca.dto; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class ScanStartHandler { + /** + * For local directory scan - the URL where the zipped directory has been uploaded. + * For remote repo scan - a URL for which 'git clone' is possible. + */ + private String url; +} diff --git a/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java b/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java deleted file mode 100644 index cd692fc1..00000000 --- a/src/main/java/com/cx/restclient/sca/dto/UploadHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.cx.restclient.sca.dto; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class UploadHandler { - private String url; -} From 3fe0edebb6575214a92c5700a94a288634d1e722 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 30 Apr 2020 10:47:14 +0300 Subject: [PATCH 251/473] Fixed several SonarLint issues. --- .../java/com/cx/restclient/SCAClient.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 7ce78251..974e87ff 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -216,7 +216,7 @@ private void uploadArchive(File source, String uploadUrl) throws IOException { private void printWebReportLink(SCAResults scaResult) { if (!StringUtils.isEmpty(scaResult.getWebReportLink())) { - log.info("CxSCA scan results location: " + scaResult.getWebReportLink()); + log.info("CxSCA scan results location: {}", scaResult.getWebReportLink()); } } @@ -356,27 +356,27 @@ private SCASummaryResults getSummaryReport(String reportId) throws IOException { String path = String.format(UrlPaths.SUMMARY_REPORT, URLEncoder.encode(reportId, ENCODING)); - SCASummaryResults result = httpClient.getRequest(path, + return httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, SCASummaryResults.class, HttpStatus.SC_OK, "CxSCA report summary", false); - - return result; } private void printSummary(SCASummaryResults summary) { - log.info("\n----CxSCA risk report summary----"); - log.info("Created on: " + summary.getCreatedOn()); - log.info("Direct packages: " + summary.getDirectPackages()); - log.info("High vulnerabilities: " + summary.getHighVulnerabilityCount()); - log.info("Medium vulnerabilities: " + summary.getMediumVulnerabilityCount()); - log.info("Low vulnerabilities: " + summary.getLowVulnerabilityCount()); - log.info("Risk report ID: " + summary.getRiskReportId()); - log.info("Risk score: " + summary.getRiskScore()); - log.info("Total packages: " + summary.getTotalPackages()); - log.info(String.format("Total outdated packages: %d%n", summary.getTotalOutdatedPackages())); + if (log.isInfoEnabled()) { + log.info("\n----CxSCA risk report summary----"); + log.info("Created on: {}", summary.getCreatedOn()); + log.info("Direct packages: {}", summary.getDirectPackages()); + log.info("High vulnerabilities: {}", summary.getHighVulnerabilityCount()); + log.info("Medium vulnerabilities: {}", summary.getMediumVulnerabilityCount()); + log.info("Low vulnerabilities: {}", summary.getLowVulnerabilityCount()); + log.info("Risk report ID: {}", summary.getRiskReportId()); + log.info("Risk score: {}", summary.getRiskScore()); + log.info("Total packages: {}", summary.getTotalPackages()); + log.info(String.format("Total outdated packages: %d%n", summary.getTotalOutdatedPackages())); + } } private SCAConfig getScaConfig() { From 437c795d8551378a2a7beb86f2f7e77407e1b083 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Thu, 30 Apr 2020 14:37:30 +0300 Subject: [PATCH 252/473] Updated logging, version, improved tests. --- pom.xml | 2 +- src/main/java/com/cx/restclient/SCAClient.java | 10 ++++++---- .../java/com/cx/restclient/general/ScaScanTests.java | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 452a89d8..228abb3e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.checkmarx cx-client-common - 2020.2.7.SCA + 2020.1.13.SCA-SNAPSHOT jar Checkmarx Client Enables web services access Checkmarx SAST and OSA scan. diff --git a/src/main/java/com/cx/restclient/SCAClient.java b/src/main/java/com/cx/restclient/SCAClient.java index 974e87ff..87d2272f 100644 --- a/src/main/java/com/cx/restclient/SCAClient.java +++ b/src/main/java/com/cx/restclient/SCAClient.java @@ -248,17 +248,19 @@ private void login() throws IOException { private void resolveProject() throws IOException { String projectName = config.getProjectName(); + log.info("Getting project by name: '{}'", projectName); projectId = getProjectIdByName(projectName); if (projectId == null) { - log.debug("Project not found, creating a new one."); + log.info("Project not found, creating a new one."); projectId = createProject(projectName); + log.info("Created a project with ID {}", projectId); + } + else { + log.info("Project already exists with ID {}", projectId); } - log.debug("Using project ID: {}", projectId); } private String getProjectIdByName(String name) throws IOException { - log.debug("Getting project by name: {}", name); - if (StringUtils.isEmpty(name)) { throw new CxClientException("Non-empty project name must be provided."); } diff --git a/src/test/java/com/cx/restclient/general/ScaScanTests.java b/src/test/java/com/cx/restclient/general/ScaScanTests.java index 4f5a7c7d..2ac02f8c 100644 --- a/src/test/java/com/cx/restclient/general/ScaScanTests.java +++ b/src/test/java/com/cx/restclient/general/ScaScanTests.java @@ -20,8 +20,10 @@ @Ignore public class ScaScanTests extends CommonClientTest { @Test - public void scan_locaDirUpload() throws MalformedURLException, CxClientException { + public void scan_localDirUpload() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); + config.setSourceDir(props.getProperty("dependencyScanSourceDir")); + config.setOsaThresholdsEnabled(true); config.getScaConfig().setSourceLocationType(SourceLocationType.LOCAL_DIRECTORY); scanUsing(config); } @@ -70,9 +72,7 @@ private CxScanConfig initScaConfig() { CxScanConfig config = new CxScanConfig(); config.setDependencyScannerType(DependencyScannerType.SCA); config.setSastEnabled(false); - config.setSourceDir(props.getProperty("dependencyScanSourceDir")); - config.setOsaThresholdsEnabled(true); - config.setProjectName("commonClient-test-scaOnlyScan"); + config.setProjectName(props.getProperty("sca.projectName")); SCAConfig sca = TestingUtils.getScaConfig(props); config.setScaConfig(sca); From 3338c95c4a832452a029a6838828247f5ab09b3c Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Sun, 3 May 2020 16:11:30 +0300 Subject: [PATCH 253/473] Fixed error handling while waiting for scan to complete. Improved logging. --- .../java/com/cx/restclient/sca/SCAWaiter.java | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/cx/restclient/sca/SCAWaiter.java b/src/main/java/com/cx/restclient/sca/SCAWaiter.java index b7914bff..ff4fa0db 100644 --- a/src/main/java/com/cx/restclient/sca/SCAWaiter.java +++ b/src/main/java/com/cx/restclient/sca/SCAWaiter.java @@ -73,14 +73,37 @@ private static int getMaxErrorCount(CxScanConfig config) { } private boolean scanIsCompleted(String path, AtomicInteger errorCounter, int maxErrorCount) { - boolean completedSuccessfully = false; + ScanInfoResponse response = null; + String errorMessage = null; try { - ScanInfoResponse response = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, + response = httpClient.getRequest(path, ContentType.CONTENT_TYPE_APPLICATION_JSON, ScanInfoResponse.class, HttpStatus.SC_OK, "CxSCA scan", false); - - completedSuccessfully = validateScanStatus(response); } catch (Exception e) { - countError(errorCounter, maxErrorCount, e.getMessage()); + errorMessage = e.getMessage(); + } + + boolean completedSuccessfully = false; + if (response == null) { + // A network error is likely to have occurred -> retry. + countError(errorCounter, maxErrorCount, errorMessage); + } else { + ScanStatus status = extractScanStatusFrom(response); + completedSuccessfully = handleScanStatus(status); + } + + return completedSuccessfully; + } + + private boolean handleScanStatus(ScanStatus status) { + boolean completedSuccessfully = false; + if (status == ScanStatus.COMPLETED) { + completedSuccessfully = true; + } else if (status == ScanStatus.FAILED) { + // Scan has failed on the back end, no need to retry. + throw new CxClientException(String.format("Scan status is %s, aborting.", status)); + } + else if (status == null) { + log.warn("Unknown status."); } return completedSuccessfully; } @@ -92,28 +115,19 @@ private void countError(AtomicInteger errorCounter, int maxErrorCount, String me String fullMessage = String.format("Maximum number of errors was reached (%d), aborting.", maxErrorCount); throw new CxClientException(fullMessage); } else { - log.info("Failed to get status from CxSCA. Retrying (tries left: {}). Error message: {}", triesLeft, message); + String note = (triesLeft == 0 ? "last attempt" : String.format("tries left: %d", triesLeft)); + log.info("Failed to get status from CxSCA with the message: {}. Retrying ({})", message, note); } } - private boolean validateScanStatus(ScanInfoResponse response) { - if (response == null) { - throw new CxClientException("Empty response."); - } - + private ScanStatus extractScanStatusFrom(ScanInfoResponse response) { String rawStatus = response.getStatus(); String elapsedTimestamp = ShragaUtils.getTimestampSince(startTimestampSec); log.info("Waiting for CxSCA scan results. Elapsed time: {}. Status: {}.", elapsedTimestamp, rawStatus); ScanStatus status = EnumUtils.getEnumIgnoreCase(ScanStatus.class, rawStatus); - - boolean completedSuccessfully = false; - if (status == ScanStatus.COMPLETED) { - completedSuccessfully = true; - } else if (status == ScanStatus.FAILED) { - throw new CxClientException("CxSCA scan cannot be completed."); - } else if (status == null) { - log.warn("Unknown status: {}", rawStatus); + if (status == null) { + log.warn("Unknown status: '{}'", rawStatus); } - return completedSuccessfully; + return status; } } From 642eaa5fe79bf853de439e330aa342433c7bd108 Mon Sep 17 00:00:00 2001 From: AlexeyK Date: Mon, 4 May 2020 13:26:50 +0300 Subject: [PATCH 254/473] Using resources in SCA directory upload test. --- .../restclient/general/CommonClientTest.java | 7 +- .../cx/restclient/general/ScaScanTests.java | 175 +++++++++++++++--- src/test/resources/sources-to-scan.zip | Bin 0 -> 3833042 bytes 3 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/sources-to-scan.zip diff --git a/src/test/java/com/cx/restclient/general/CommonClientTest.java b/src/test/java/com/cx/restclient/general/CommonClientTest.java index 85e44d83..016e21bf 100644 --- a/src/test/java/com/cx/restclient/general/CommonClientTest.java +++ b/src/test/java/com/cx/restclient/general/CommonClientTest.java @@ -3,17 +3,16 @@ import com.cx.restclient.configuration.CxScanConfig; import com.cx.restclient.dto.ProxyConfig; import com.cx.utility.TestingUtils; +import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.BeforeClass; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Properties; +@Slf4j public abstract class CommonClientTest { private static final String PROPERTIES_FILE = "config.properties"; - Logger log = LoggerFactory.getLogger(ConnectionTests.class.getName()); static Properties props; @BeforeClass @@ -29,7 +28,7 @@ protected static void setProxy(CxScanConfig config) { } void failOnException(Exception e) { - log.error("Unexpected exception.", e); + log.error("Unexpected exception during test.", e); Assert.fail(e.getMessage()); } } diff --git a/src/test/java/com/cx/restclient/general/ScaScanTests.java b/src/test/java/com/cx/restclient/general/ScaScanTests.java index 2ac02f8c..5431db2a 100644 --- a/src/test/java/com/cx/restclient/general/ScaScanTests.java +++ b/src/test/java/com/cx/restclient/general/ScaScanTests.java @@ -5,27 +5,72 @@ import com.cx.restclient.dto.DependencyScanResults; import com.cx.restclient.dto.DependencyScannerType; import com.cx.restclient.exception.CxClientException; -import com.cx.restclient.sca.dto.RemoteRepositoryInfo; -import com.cx.restclient.sca.dto.SCAConfig; -import com.cx.restclient.sca.dto.SCAResults; -import com.cx.restclient.sca.dto.SourceLocationType; +import com.cx.restclient.sca.dto.*; import com.cx.utility.TestingUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; -@Ignore +@Slf4j public class ScaScanTests extends CommonClientTest { + + // Storing the test project as an archive to avoid cluttering the current project + // and also to prevent false positives during a vulnerability scan of the current project. + public static final String PACKED_SOURCES_TO_SCAN = "sources-to-scan.zip"; + @Test - public void scan_localDirUpload() throws MalformedURLException, CxClientException { + public void scan_localDirUpload() throws IOException, CxClientException { CxScanConfig config = initScaConfig(); - config.setSourceDir(props.getProperty("dependencyScanSourceDir")); config.setOsaThresholdsEnabled(true); config.getScaConfig().setSourceLocationType(SourceLocationType.LOCAL_DIRECTORY); - scanUsing(config); + + Path sourcesDir = null; + try { + sourcesDir = extractTestProjectFromResources(); + config.setSourceDir(sourcesDir.toString()); + + DependencyScanResults scanResults = scanUsing(config); + + checkCommonAssertions(scanResults); + checkDirectoryUploadAssertions(scanResults); + } finally { + deleteDir(sourcesDir); + } + } + + private void checkDirectoryUploadAssertions(DependencyScanResults scanResults) { + if (scanResults == null || + scanResults.getScaResults() == null || + scanResults.getScaResults().getSummary() == null) { + Assert.fail("Unable to find summary."); + } + + SCASummaryResults summary = scanResults.getScaResults().getSummary(); + Assert.assertTrue("SCA hasn't found any packages.", summary.getTotalPackages() > 0); + + boolean anyVulnerabilitiesDetected = summary.getHighVulnerabilityCount() > 0 || + summary.getMediumVulnerabilityCount() > 0 || + summary.getLowVulnerabilityCount() > 0; + Assert.assertTrue("No vulnerabilities were detected.", anyVulnerabilitiesDetected); } @Test @@ -39,36 +84,124 @@ public void scan_remotePublicRepo() throws MalformedURLException { config.getScaConfig().setRemoteRepositoryInfo(repoInfo); - scanUsing(config); + DependencyScanResults scanResults = scanUsing(config); + checkCommonAssertions(scanResults); + } @Test + @Ignore("Needs specific network configuration with a proxy.") public void runScaScanWithProxy() throws MalformedURLException, CxClientException { CxScanConfig config = initScaConfig(); setProxy(config); - scanUsing(config); + DependencyScanResults scanResults = scanUsing(config); + checkCommonAssertions(scanResults); + } + + private Path extractTestProjectFromResources() { + InputStream testProjectStream = getTestProjectStream(); + Path tempDirectory = createTempDirectory(); + extractResourceToDir(testProjectStream, tempDirectory); + return tempDirectory; + } + + private void extractResourceToDir(InputStream source, Path targetDir) { + log.info("Unpacking sources into the temp dir."); + int fileCount = 0; + try (ArchiveInputStream inputStream = new ArchiveStreamFactory().createArchiveInputStream(source)) { + ArchiveEntry entry; + while ((entry = inputStream.getNextEntry()) != null) { + if (!inputStream.canReadEntryData(entry)) { + throw new IOException(String.format("Unable to read entry: %s", entry)); + } + Path fullTargetPath = targetDir.resolve(entry.getName()); + File targetFile = fullTargetPath.toFile(); + if (entry.isDirectory()) { + extractDirectory(targetFile); + } else { + extractFile(inputStream, targetFile); + fileCount++; + } + } + } catch (IOException | ArchiveException e) { + failOnException(e); + } + log.info("Files extracted: {}", fileCount); + } + + private static void extractFile(ArchiveInputStream inputStream, File targetFile) throws IOException { + File parent = targetFile.getParentFile(); + extractDirectory(parent); + try (OutputStream outputStream = Files.newOutputStream(targetFile.toPath())) { + IOUtils.copy(inputStream, outputStream); + } + } + + private static void extractDirectory(File targetFile) throws IOException { + if (!targetFile.isDirectory() && !targetFile.mkdirs()) { + throw new IOException(String.format("Failed to create directory %s", targetFile)); + } + } + + private static Path createTempDirectory() { + String systemTempDir = FileUtils.getTempDirectoryPath(); + String subdir = String.format("common-client-tests-%s", UUID.randomUUID()); + Path result = Paths.get(systemTempDir, subdir); + + log.info("Creating a temp dir: {}", result); + boolean success = result.toFile().mkdir(); + if (!success) { + Assert.fail("Failed to create temp dir."); + } + return result; + } + + private static void deleteDir(Path directory) { + if (directory == null) { + return; + } + + log.info("Deleting '{}'", directory); + try { + FileUtils.deleteDirectory(directory.toFile()); + } catch (IOException e) { + log.warn("Failed to delete temp dir.", e); + } } - private void scanUsing(CxScanConfig config) throws MalformedURLException, CxClientException { + private static InputStream getTestProjectStream() { + String srcResourceName = ScaScanTests.PACKED_SOURCES_TO_SCAN; + log.info("Getting resource stream from '{}'", srcResourceName); + return Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(srcResourceName); + } + + private DependencyScanResults scanUsing(CxScanConfig config) throws MalformedURLException, CxClientException { CxShragaClient client = new CxShragaClient(config, log); + DependencyScanResults results = null; try { client.init(); client.createDependencyScan(); - DependencyScanResults results = client.waitForDependencyScanResults(); - Assert.assertNotNull(results); - Assert.assertNull(results.getOsaResults()); - - SCAResults scaResults = results.getScaResults(); - Assert.assertNotNull(scaResults); - Assert.assertNotNull(scaResults.getSummary()); - Assert.assertNotNull(scaResults.getScanId()); - Assert.assertNotNull(scaResults.getWebReportLink()); + results = client.waitForDependencyScanResults(); } catch (Exception e) { failOnException(e); } + return results; + } + + private void checkCommonAssertions(DependencyScanResults results) { + Assert.assertNotNull("Scan results are null.", results); + Assert.assertNull("OSA results are not null.", results.getOsaResults()); + + SCAResults scaResults = results.getScaResults(); + Assert.assertNotNull("SCA results are null", scaResults); + Assert.assertNotNull("SCA summary is null", scaResults.getSummary()); + Assert.assertTrue("Scan ID is empty", StringUtils.isNotEmpty(scaResults.getScanId())); + Assert.assertTrue("Web report link is empty", StringUtils.isNotEmpty(scaResults.getWebReportLink())); } - private CxScanConfig initScaConfig() { + private static CxScanConfig initScaConfig() { CxScanConfig config = new CxScanConfig(); config.setDependencyScannerType(DependencyScannerType.SCA); config.setSastEnabled(false); diff --git a/src/test/resources/sources-to-scan.zip b/src/test/resources/sources-to-scan.zip new file mode 100644 index 0000000000000000000000000000000000000000..10d7b8938035b63e029a74d93d2c43a90cd15e94 GIT binary patch literal 3833042 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

    A%?Z)xaOt$&H8oXoRMwE|Bq!DT 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%0Ae8?4%^aXlD{O^ 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%0DnfGj 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