diff --git a/docs/Bug-Trackers-and-Feedback-Channels.md b/docs/Bug-Trackers-and-Feedback-Channels.md
index 05394152a..2d54f01bb 100644
--- a/docs/Bug-Trackers-and-Feedback-Channels.md
+++ b/docs/Bug-Trackers-and-Feedback-Channels.md
@@ -497,7 +497,7 @@ azure:
## GitLab Issues
* GitLab Issues leverages the same configuration as specified for WebHook listeners → API token (**token**) and valid urls are required
-```
+```yaml
gitlab:
webhook-token: XXXX
token: xxx
@@ -505,6 +505,40 @@ gitlab:
api-url: https://gitlab.com/api/v4/
false-positive-label: false-positive
block-merge: true
+ fields:
+ - type: result
+ name: application
+ - type: result
+ name: project
+```
+#### Fields
+Every value mentioned in fields will be added in Labels.
+* **type**
+ * **cx-scan**: Used to map specific Checkmarx Scan Custom Field values
+ * **cx-sca**: Used to map specific Checkmarx SCA Scan tags values
+ * **static**: Used for static values (specifically requires a default-value to be provided)
+ * **result**: Used to map known values from Checkmarx results or repository/scan request details. Refer to the Result values below.
+
+* **name**: If cx-scan or cx-sca reflects the type, it is the name of the custom field within Checkmarx SAST or key of tag in case o SCA
+* **default-value** Static value if no value can be determined for the respective field (Optional)
+* If **result** is provided as type, the name must be one of the following:
+
+```
+application - Command line option --app
+project - Command line option --cx-project
+namespace - Command line option --namespace
+repo-name - Command line option --repo-name
+repo-url - Command line option --repo-url
+branch - Command line option --branch
+severity - Severity of issue in Checkmarx
+category - Category of issue in Checkmarx
+cwe - CWE of issue in Checkmarx
+recommendation - Recommendation details based on Mitre/Custom Wiki
+loc - csv of lines of code
+issue-link - Direct link to issue within Checkmarx
+filename - Filename provided by Checkmarx issue
+language - Language provided by Checkmarx issue
+similarity-id - Cx Similarity ID
```
[[/Images/bug2.png|Screenshot of GitLab issue]]
@@ -591,6 +625,40 @@ github:
api-url: https://api.github.com/repos/
false-positive-label: false-positive
block-merge: true
+ fields:
+ - type: result
+ name: application
+ - type: result
+ name: project
+```
+#### Fields
+Every value mentioned in fields will be added in Labels.
+* **type**
+ * **cx-scan**: Used to map specific Checkmarx Scan Custom Field values
+ * **cx-sca**: Used to map specific Checkmarx SCA Scan tags values
+ * **static**: Used for static values (specifically requires a default-value to be provided)
+ * **result**: Used to map known values from Checkmarx results or repository/scan request details. Refer to the Result values below.
+
+* **name**: If cx-scan or cx-sca reflects the type, it is the name of the custom field within Checkmarx SAST or key of tag in case o SCA
+* **default-value** Static value if no value can be determined for the respective field (Optional)
+* If **result** is provided as type, the name must be one of the following:
+
+```
+application - Command line option --app
+project - Command line option --cx-project
+namespace - Command line option --namespace
+repo-name - Command line option --repo-name
+repo-url - Command line option --repo-url
+branch - Command line option --branch
+severity - Severity of issue in Checkmarx
+category - Category of issue in Checkmarx
+cwe - CWE of issue in Checkmarx
+recommendation - Recommendation details based on Mitre/Custom Wiki
+loc - csv of lines of code
+issue-link - Direct link to issue within Checkmarx
+filename - Filename provided by Checkmarx issue
+language - Language provided by Checkmarx issue
+similarity-id - Cx Similarity ID
```
[[/Images/bug4.png|Screenshot of GitHub issue]]
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 6f5851c2c..8619534b7 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -838,6 +838,11 @@ github:
max-delay :
comment-update: false
zero-vulnerability-summary: true
+ fields:
+ - type: result
+ name: application
+ - type: result
+ name: project
```
| Configuration | Default | Description |
@@ -853,8 +858,10 @@ github:
| `max-delay` | | When Secondary rate limit is hit, it will delay each API call for issue creation(Mininum value should be 3) |
| `comment-update` | true | if false, will create a new comment for every scan |
| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. |
+| `fields` | | Refer page: [Bug-Trackers-and-Feedback-Channels Chapter Github Fields](https://github.com/checkmarx-ltd/cx-flow/wiki/Bug-Trackers-and-Feedback-Channels#githubfields) |
**Note**: A service account is required with access to the repositories that will be scanned, pull requests that will be commented on, and GitHub issues that will be created/updated.
+
### GitLab
```yaml
gitlab:
@@ -866,6 +873,11 @@ gitlab:
block-merge: true
comment-update: false
zero-vulnerability-summary: true
+ fields:
+ - type: result
+ name: application
+ - type: result
+ name: project
```
| Configuration | Default | Description |
@@ -879,6 +891,7 @@ gitlab:
| `scan-submitted-comment` | true | Comment on Merge Request with "Scan submitted (or not submitted) to Checkmarx ...". |
| `comment-update` | true | if false, will create a new comment for every scan |
| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. |
+| `fields` | | Refer page: [Bug-Trackers-and-Feedback-Channels Chapter Gitlab Fields](https://github.com/checkmarx-ltd/cx-flow/wiki/Bug-Trackers-and-Feedback-Channels#gitlabfields) |
**Note**: A service account is required with access to the repositories that are going to be scanned, pull requests that are commented on, and GitLab issues that are created/updated.
diff --git a/src/main/java/com/checkmarx/flow/config/GitHubProperties.java b/src/main/java/com/checkmarx/flow/config/GitHubProperties.java
index 94965345f..97e0636d0 100644
--- a/src/main/java/com/checkmarx/flow/config/GitHubProperties.java
+++ b/src/main/java/com/checkmarx/flow/config/GitHubProperties.java
@@ -1,11 +1,14 @@
package com.checkmarx.flow.config;
+import com.checkmarx.flow.dto.Field;
+import com.checkmarx.flow.dto.LabelField;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
+import java.util.List;
import java.util.Map;
@Component
@@ -40,6 +43,13 @@ public class GitHubProperties extends RepoProperties {
@Setter
private Map issueslabel;
+ @Getter
+ @Setter
+ private boolean commentUpdate =true;
+ @Getter
+ @Setter
+ private List fields;
+
public String getMergeNoteUri(String namespace, String repo, String mergeId){
String format = "%s/%s/%s/issues/%s/comments";
diff --git a/src/main/java/com/checkmarx/flow/config/GitLabProperties.java b/src/main/java/com/checkmarx/flow/config/GitLabProperties.java
index b6cff313a..44cf2953a 100644
--- a/src/main/java/com/checkmarx/flow/config/GitLabProperties.java
+++ b/src/main/java/com/checkmarx/flow/config/GitLabProperties.java
@@ -1,11 +1,13 @@
package com.checkmarx.flow.config;
+import com.checkmarx.flow.dto.LabelField;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
+import java.util.List;
import java.util.Map;
@Component
@@ -30,7 +32,9 @@ public class GitLabProperties extends RepoProperties {
@Getter
@Setter
private boolean commentUpdate =true;
-
+ @Getter
+ @Setter
+ private List fields;
public String getGitUri(String namespace, String repo){
diff --git a/src/main/java/com/checkmarx/flow/custom/GitHubIssueTracker.java b/src/main/java/com/checkmarx/flow/custom/GitHubIssueTracker.java
index 8cf28c4f5..d4c1ee2c1 100644
--- a/src/main/java/com/checkmarx/flow/custom/GitHubIssueTracker.java
+++ b/src/main/java/com/checkmarx/flow/custom/GitHubIssueTracker.java
@@ -4,7 +4,9 @@
import com.checkmarx.flow.config.FlowProperties;
import com.checkmarx.flow.config.GitHubProperties;
import com.checkmarx.flow.config.ScmConfigOverrider;
+import com.checkmarx.flow.constants.FlowConstants;
import com.checkmarx.flow.dto.Issue;
+import com.checkmarx.flow.dto.LabelField;
import com.checkmarx.flow.dto.ScanRequest;
import com.checkmarx.flow.dto.github.LabelsItem;
import com.checkmarx.flow.exception.MachinaException;
@@ -13,6 +15,7 @@
import com.checkmarx.flow.utils.HTMLHelper;
import com.checkmarx.flow.utils.ScanUtils;
import com.checkmarx.sdk.dto.ScanResults;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.ThreadUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONException;
@@ -26,7 +29,10 @@
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
+import java.util.stream.Collectors;
@Service("GitHub")
public class GitHubIssueTracker implements IssueTracker {
@@ -287,7 +293,7 @@ private JSONObject getJSONUpdateIssue(ScanResults.XIssue resultIssue, ScanReques
String fileUrl = ScanUtils.getFileUrl(request, resultIssue.getFilename());
String body = HTMLHelper.getMDBody(resultIssue, request.getBranch(), fileUrl, flowProperties,properties.getMaxDescriptionLength());
String title = getXIssueKey(resultIssue, request);
- String[] label = getString(resultIssue);
+ String[] label = getString(request,resultIssue);
try {
@@ -314,7 +320,7 @@ private JSONObject getJSONCreateIssue(ScanResults.XIssue resultIssue, ScanReques
String fileUrl = ScanUtils.getFileUrl(request, resultIssue.getFilename());
String body = HTMLHelper.getMDBody(resultIssue, request.getBranch(), fileUrl, flowProperties,properties.getMaxDescriptionLength());
String title = HTMLHelper.getScanRequestIssueKeyWithDefaultProductValue(request, this, resultIssue);
- String[] label = getString(resultIssue);
+ String[] label = getString(request,resultIssue);
try {
requestBody.put("title", title);
@@ -329,17 +335,297 @@ private JSONObject getJSONCreateIssue(ScanResults.XIssue resultIssue, ScanReques
return requestBody;
}
- private String[] getString(ScanResults.XIssue resultIssue) {
+ private String[] getString(ScanRequest request, ScanResults.XIssue issue) {
+ List value = new ArrayList<>();
String[] strArray = new String[]{};
try {
- Map findingsPerSeverity = properties.getIssueslabel();
- for (Map.Entry entry : findingsPerSeverity.entrySet()) {
- if(resultIssue.getSeverity().equalsIgnoreCase(entry.getKey().toString()) || resultIssue.getSeverity().toLowerCase(Locale.ROOT).contains(entry.getKey().toString().toLowerCase(Locale.ROOT))){
+ if(properties.getIssueslabel()!=null){
+ Map findingsPerSeverity = properties.getIssueslabel();
+ for (Map.Entry entry : findingsPerSeverity.entrySet()) {
+ if (issue.getSeverity().equalsIgnoreCase(entry.getKey().toString()) || issue.getSeverity().toLowerCase(Locale.ROOT).contains(entry.getKey().toString().toLowerCase(Locale.ROOT))) {
//converting using String.split() method with whitespace as a delimiter
- strArray = entry.getValue().split(",");
- break;
+ value=Arrays.stream(entry.getValue().split(",")).collect(Collectors.toList());
+ break;
+ }
}
}
+
+ //adding fields in labels
+ for (LabelField f : properties.getFields()) {
+ String fieldType = f.getType();
+ String fieldName;
+ if (ScanUtils.empty(fieldType)) {
+ log.warn("Field type not supplied. Using 'result' by default.");
+ // use default = result
+ fieldType = "result";
+ }
+ Map addDetails = null;
+ Map scanCustomFields = null;
+ String scanScaTags = null;
+ String scanCustomFieldsValue = null;
+ if (Objects.nonNull(issue.getAdditionalDetails()) && Objects.nonNull((Map) issue.getAdditionalDetails().get("scanCustomFields"))) {
+ addDetails = issue.getAdditionalDetails();
+ scanCustomFields = (Map) addDetails.get("scanCustomFields");
+ scanCustomFieldsValue = scanCustomFields.get(f.getName());
+ }
+ if (Objects.nonNull(issue.getScaDetails()) && Objects.nonNull(issue.getScaDetails().get(0).getScanTags())) {
+ scanScaTags = (String) issue.getScaDetails().get(0).getScanTags().get(f.getName());
+ }
+
+ switch (fieldType) {
+ case "cx-scan"://cx-scan, cx-sca, sca-results, static, result
+ log.debug("Checkmarx scan custom field {}", f.getName());
+ if (scanCustomFieldsValue != null) {
+ log.debug("Checkmarx scan custom field");
+ value.add(f.getName() + ":" + scanCustomFieldsValue);
+ log.debug("Cx Scan Field value: {}", value);
+ if (ScanUtils.empty(value) && !ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug("default Value is {}", value);
+ }
+ } else {
+ log.debug("No value found for {}", f.getName());
+ value.add("");
+ }
+ break;
+ case "cx-sca":
+ log.debug("SCA scan Tags Key name {}", f.getName());
+ if (scanScaTags != null) {
+ value.add(f.getName() + ":" + scanScaTags);
+ log.debug("SCA scan Field value: {}", value);
+ if (ScanUtils.empty(value) && !ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug(" default Value is {}", value);
+ }
+ } else {
+ log.debug("No value found for {}", f.getName());
+ value.add("");
+ }
+ break;
+ case "sca-results":
+ if (issue.getScaDetails() == null) {
+ log.debug("Sca details not available");
+ break;
+ }
+ fieldName = f.getName();
+ switch (fieldName) {
+ case "package-name":
+ log.debug("package-name: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getId());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getId());
+ break;
+ case "current-version":
+ log.debug("current-version: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getVersion());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getVersion());
+ break;
+ case "fixed-version":
+ log.debug("fixed-version: {}", issue.getScaDetails().get(0).getFinding().getFixResolutionText());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getFinding().getFixResolutionText());
+ break;
+ case "newest-version":
+ log.debug(issue.getScaDetails().get(0).getVulnerabilityPackage().getNewestVersion());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getNewestVersion());
+ break;
+ case "locations":
+ List locations = issue.getScaDetails().get(0).getVulnerabilityPackage().getLocations();
+ String location = null;
+ for (String l : locations
+ ) {
+ location = l + ",";
+ }
+ log.debug("locations: {}", location);
+ assert location != null;
+ value.add(f.getName() + ":" + location.substring(0, location.length() - 1));
+ break;
+ case "dev-dependency":
+ log.debug("dev-dependency: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDevelopmentDependency());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDevelopmentDependency()).toUpperCase());
+ break;
+ case "direct-dependency":
+ log.debug("direct-dependency: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDirectDependency());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDirectDependency()).toUpperCase());
+ break;
+ case "risk-score":
+ log.debug("risk score: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getRiskScore());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().getRiskScore()));
+ break;
+ case "outdated":
+ log.debug("outdated: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isOutdated());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isOutdated()).toUpperCase());
+ break;
+ case "violates-policy":
+ log.debug("Violates-Policy: {}", issue.getScaDetails().get(0).getFinding().isViolatingPolicy());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getFinding().isViolatingPolicy()).toUpperCase());
+
+ }
+ break;
+ case "static":
+ log.debug("Static value {} - {}", f.getName(), f.getDefaultValue());
+ value.add(f.getDefaultValue());
+ break;
+ default: //result
+ fieldName = f.getName();
+ if (fieldName == null) {
+ log.warn("Field name not supplied. Skipping.");
+ /* there is no default, move on to the next field */
+ continue;
+ }
+ /*known values we can use*/
+ switch (fieldName) {
+ case "application":
+ log.debug("application: {}", request.getApplication());
+ if(request.getApplication()!=null){
+ value.add(f.getName() + ":" + request.getApplication());
+ }else{
+ value.add(f.getName() + ":" +" ");
+ }
+ break;
+ case "project":
+ log.debug("project: {}", request.getProject());
+ value.add(f.getName() + ":" + request.getProject());
+ break;
+ case "namespace":
+ log.debug("namespace: {}", request.getNamespace());
+ value.add(f.getName() + ":" + request.getNamespace());
+ break;
+ case "repo-name":
+ log.debug("repo-name: {}", request.getRepoName());
+ value.add(f.getName() + ":" + request.getRepoName());
+ break;
+ case "repo-url":
+ log.debug("repo-url: {}", request.getRepoUrl());
+ value.add(f.getName() + ":" + request.getRepoUrl());
+ break;
+ case "branch":
+ log.debug("branch: {}", request.getBranch());
+ value.add(f.getName() + ":" + request.getBranch());
+ break;
+ case "severity":
+ if (issue.getScaDetails() != null) {
+ log.debug("severity: {}", issue.getScaDetails().get(0).getFinding().getSeverity());
+ value.add(f.getName() + ":" + ScanUtils.toProperCase(String.valueOf(issue.getScaDetails().get(0).getFinding().getSeverity())));
+ } else {
+ log.debug("severity: {}", issue.getSeverity());
+ value.add(f.getName() + ":" + ScanUtils.toProperCase(issue.getSeverity()));
+ }
+ break;
+ case "category":
+ log.debug("category: {}", issue.getVulnerability());
+ value.add(f.getName() + ":" + issue.getVulnerability());
+ break;
+ case "cwe":
+ log.debug("cwe: {}", issue.getCwe());
+ value.add(f.getName() + ":" + issue.getCwe());
+ break;
+ case "cve":
+ if (issue.getScaDetails() != null) {
+ log.debug("cve: {}", issue.getScaDetails().get(0).getFinding().getId());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getFinding().getId());
+ } else {
+ log.debug("cve: {}", issue.getCve());
+ value.add(f.getName() + ":" + issue.getCve());
+ }
+ break;
+ case "system-date":
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ LocalDateTime now = LocalDateTime.now();
+ value.add(f.getName() + ":" + dtf.format(now));
+ log.debug("system date: {}", value);
+ break;
+ case "recommendation":
+ StringBuilder recommendation = new StringBuilder();
+ if (issue.getLink() != null && !issue.getLink().isEmpty()) {
+ recommendation.append("Checkmarx Link: ").append(issue.getLink()).append(HTMLHelper.CRLF);
+ }
+ if (!ScanUtils.anyEmpty(flowProperties.getMitreUrl(), issue.getCwe())) {
+ recommendation.append("Mitre Details: ").append(String.format(flowProperties.getMitreUrl(), issue.getCwe())).append(HTMLHelper.CRLF);
+ }
+ if (!ScanUtils.empty(flowProperties.getWikiUrl())) {
+ recommendation.append("Guidance: ").append(flowProperties.getWikiUrl()).append(HTMLHelper.CRLF);
+ }
+ value.add(f.getName() + ":" + recommendation.toString());
+ break;
+ case "loc":
+ if (issue.getDetails() != null) {
+ List lines = issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ if (!lines.isEmpty()) {
+ Collections.sort(lines);
+ value.add(f.getName() + ":" + StringUtils.join(lines, ","));
+ log.debug("loc: {}", value);
+ }
+ }
+ break;
+ case "not-exploitable":
+ List fpLines;
+ if (issue.getDetails() != null) {
+ fpLines = issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && x.getValue().isFalsePositive())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ if (!fpLines.isEmpty()) {
+ Collections.sort(fpLines);
+ value.add(f.getName() + ":" + StringUtils.join(fpLines, ","));
+ log.debug("loc: {}", value);
+ }
+ }
+ break;
+ case "site":
+ log.debug("site: {}", request.getSite());
+ value.add(f.getName() + ":" + request.getSite());
+ break;
+ case "issue-link":
+ if (issue.getScaDetails() != null) {
+ log.debug("issue-link: {}", issue.getScaDetails().get(0).getVulnerabilityLink());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityLink());
+ } else {
+ log.debug("issue-link: {}", issue.getLink());
+ value.add(f.getName() + ":" + issue.getLink());
+ }
+ break;
+ case "filename":
+ log.debug("filename: {}", issue.getFilename());
+ value.add(f.getName() + ":" + issue.getFilename());
+ break;
+ case "language":
+ log.debug("language: {}", issue.getLanguage());
+ value.add(f.getName() + ":" + issue.getLanguage());
+ break;
+ case "similarity-id":
+ log.debug("similarity-id: {}", issue.getSimilarityId());
+ value.add(f.getName() + ":" + issue.getSimilarityId());
+ break;
+ case "comment":
+ StringBuilder comments = new StringBuilder();
+ String commentFmt = "[Line %s]: [%s]".concat(HTMLHelper.CRLF);
+ if (issue.getDetails() != null) {
+ issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && x.getValue().getComment() != null && !x.getValue().getComment().isEmpty())
+ .forEach(c -> comments.append(String.format(commentFmt, c.getKey(), c.getValue().getComment())));
+ value.add(f.getName() + ":" + comments.toString());
+ }
+ break;
+ default:
+ log.warn("field value for {} not found", f.getName());
+ value.add("");
+ }
+ /*If the value is missing, check if a default value was specified*/
+ if (ScanUtils.empty(value)) {
+ log.debug("Value is empty, defaulting to configured default (if applicable)");
+ if (!ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug("Default value is {}", value);
+ }
+ }
+ break;
+ }
+ }
+ strArray = value.toArray(new String[0]);
+
} catch (Exception e) {
return strArray;
}
diff --git a/src/main/java/com/checkmarx/flow/custom/GitLabIssueTracker.java b/src/main/java/com/checkmarx/flow/custom/GitLabIssueTracker.java
index 6d35adfe4..f28cc2092 100644
--- a/src/main/java/com/checkmarx/flow/custom/GitLabIssueTracker.java
+++ b/src/main/java/com/checkmarx/flow/custom/GitLabIssueTracker.java
@@ -5,12 +5,14 @@
import com.checkmarx.flow.config.GitLabProperties;
import com.checkmarx.flow.config.ScmConfigOverrider;
import com.checkmarx.flow.dto.Issue;
+import com.checkmarx.flow.dto.LabelField;
import com.checkmarx.flow.dto.ScanRequest;
import com.checkmarx.flow.dto.gitlab.Note;
import com.checkmarx.flow.exception.MachinaException;
import com.checkmarx.flow.utils.HTMLHelper;
import com.checkmarx.flow.utils.ScanUtils;
import com.checkmarx.sdk.dto.ScanResults;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONArray;
import org.json.JSONException;
@@ -25,7 +27,10 @@
import java.net.URI;
import java.net.URISyntaxException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
+import java.util.stream.Collectors;
@Service("GitLab")
public class GitLabIssueTracker implements IssueTracker {
@@ -363,7 +368,7 @@ private JSONObject getJSONUpdateIssue(ScanResults.XIssue resultIssue, ScanReques
String fileUrl = getFileUrl(request, resultIssue.getFilename());
String body = HTMLHelper.getMDBody(resultIssue, request.getBranch(), fileUrl, flowProperties, max_desc_length);
String title = getXIssueKey(resultIssue, request);
- String label = getString(resultIssue);
+ String label = getString(request,resultIssue);
try {
requestBody.put("title", title);
@@ -388,7 +393,7 @@ private JSONObject getJSONCreateIssue(ScanResults.XIssue resultIssue, ScanReques
String fileUrl = getFileUrl(request, resultIssue.getFilename());
String body = HTMLHelper.getMDBody(resultIssue, request.getBranch(), fileUrl, flowProperties, max_desc_length);
String title = HTMLHelper.getScanRequestIssueKeyWithDefaultProductValue(request, this, resultIssue);
- String label = getString(resultIssue);
+ String label = getString(request,resultIssue);
try {
requestBody.put("title", title);
@@ -402,16 +407,302 @@ private JSONObject getJSONCreateIssue(ScanResults.XIssue resultIssue, ScanReques
return requestBody;
}
- private String getString(ScanResults.XIssue resultIssue) {
+ private String getString(ScanRequest request,ScanResults.XIssue issue) {
String label = "NA";
+ List value = new ArrayList<>();
try {
- Map findingsPerSeverity = properties.getIssueslabel();
- for (Map.Entry entry : findingsPerSeverity.entrySet()) {
- if (resultIssue.getSeverity().equalsIgnoreCase(entry.getKey().toString()) || resultIssue.getSeverity().toLowerCase(Locale.ROOT).contains(entry.getKey().toString().toLowerCase(Locale.ROOT))) {
- label = entry.getValue();
- break;
+ if(properties.getIssueslabel()!=null)
+ {
+ Map findingsPerSeverity = properties.getIssueslabel();
+ for (Map.Entry entry : findingsPerSeverity.entrySet()) {
+ if (issue.getSeverity().equalsIgnoreCase(entry.getKey().toString()) || issue.getSeverity().toLowerCase(Locale.ROOT).contains(entry.getKey().toString().toLowerCase(Locale.ROOT))) {
+ label = entry.getValue();
+ break;
+ }
+ }
+ }
+
+ for (LabelField f : properties.getFields()) {
+ String fieldType = f.getType();
+ String fieldName;
+ if (ScanUtils.empty(fieldType)) {
+ log.warn("Field type not supplied. Using 'result' by default.");
+ // use default = result
+ fieldType = "result";
+ }
+ Map addDetails = null;
+ Map scanCustomFields = null;
+ String scanScaTags = null;
+ String scanCustomFieldsValue = null;
+ if (Objects.nonNull(issue.getAdditionalDetails()) && Objects.nonNull((Map) issue.getAdditionalDetails().get("scanCustomFields"))) {
+ addDetails = issue.getAdditionalDetails();
+ scanCustomFields = (Map) addDetails.get("scanCustomFields");
+ scanCustomFieldsValue = scanCustomFields.get(f.getName());
+ }
+ if (Objects.nonNull(issue.getScaDetails()) && Objects.nonNull(issue.getScaDetails().get(0).getScanTags())) {
+ scanScaTags = (String) issue.getScaDetails().get(0).getScanTags().get(f.getName());
+ }
+
+ switch (fieldType) {
+ case "cx-scan"://cx-scan, cx-sca, sca-results, static, result
+ log.debug("Checkmarx scan custom field {}", f.getName());
+ if (scanCustomFieldsValue != null) {
+ log.debug("Checkmarx scan custom field");
+ value.add(f.getName() + ":" + scanCustomFieldsValue);
+ log.debug("Cx Scan Field value: {}", value);
+ if (ScanUtils.empty(value) && !ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug("default Value is {}", value);
+ }
+ } else {
+ log.debug("No value found for {}", f.getName());
+ value.add("");
+ }
+ break;
+ case "cx-sca":
+ log.debug("SCA scan Tags Key name {}", f.getName());
+ if (scanScaTags != null) {
+ value.add(f.getName() + ":" + scanScaTags);
+ log.debug("SCA scan Field value: {}", value);
+ if (ScanUtils.empty(value) && !ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug(" default Value is {}", value);
+ }
+ } else {
+ log.debug("No value found for {}", f.getName());
+ value.add("");
+ }
+ break;
+ case "sca-results":
+ if (issue.getScaDetails() == null) {
+ log.debug("Sca details not available");
+ break;
+ }
+ fieldName = f.getName();
+ switch (fieldName) {
+ case "package-name":
+ log.debug("package-name: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getId());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getId());
+ break;
+ case "current-version":
+ log.debug("current-version: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getVersion());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getVersion());
+ break;
+ case "fixed-version":
+ log.debug("fixed-version: {}", issue.getScaDetails().get(0).getFinding().getFixResolutionText());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getFinding().getFixResolutionText());
+ break;
+ case "newest-version":
+ log.debug(issue.getScaDetails().get(0).getVulnerabilityPackage().getNewestVersion());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityPackage().getNewestVersion());
+ break;
+ case "locations":
+ List locations = issue.getScaDetails().get(0).getVulnerabilityPackage().getLocations();
+ String location = null;
+ for (String l : locations
+ ) {
+ location = l + ",";
+ }
+ log.debug("locations: {}", location);
+ assert location != null;
+ value.add(f.getName() + ":" + location.substring(0, location.length() - 1));
+ break;
+ case "dev-dependency":
+ log.debug("dev-dependency: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDevelopmentDependency());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDevelopmentDependency()).toUpperCase());
+ break;
+ case "direct-dependency":
+ log.debug("direct-dependency: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDirectDependency());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isIsDirectDependency()).toUpperCase());
+ break;
+ case "risk-score":
+ log.debug("risk score: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().getRiskScore());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().getRiskScore()));
+ break;
+ case "outdated":
+ log.debug("outdated: {}", issue.getScaDetails().get(0).getVulnerabilityPackage().isOutdated());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getVulnerabilityPackage().isOutdated()).toUpperCase());
+ break;
+ case "violates-policy":
+ log.debug("Violates-Policy: {}", issue.getScaDetails().get(0).getFinding().isViolatingPolicy());
+ value.add(f.getName() + ":" + String.valueOf(issue.getScaDetails().get(0).getFinding().isViolatingPolicy()).toUpperCase());
+
+ }
+ break;
+ case "static":
+ log.debug("Static value {} - {}", f.getName(), f.getDefaultValue());
+ value.add(f.getDefaultValue());
+ break;
+ default: //result
+ fieldName = f.getName();
+ if (fieldName == null) {
+ log.warn("Field name not supplied. Skipping.");
+ /* there is no default, move on to the next field */
+ continue;
+ }
+ /*known values we can use*/
+ switch (fieldName) {
+ case "application":
+ log.debug("application: {}", request.getApplication());
+ if(request.getApplication()!=null){
+ value.add(f.getName() + ":" + request.getApplication());
+ }else{
+ value.add(f.getName() + ":" +" ");
+ }
+ break;
+ case "project":
+ log.debug("project: {}", request.getProject());
+ value.add(f.getName() + ":" + request.getProject());
+ break;
+ case "namespace":
+ log.debug("namespace: {}", request.getNamespace());
+ value.add(f.getName() + ":" + request.getNamespace());
+ break;
+ case "repo-name":
+ log.debug("repo-name: {}", request.getRepoName());
+ value.add(f.getName() + ":" + request.getRepoName());
+ break;
+ case "repo-url":
+ log.debug("repo-url: {}", request.getRepoUrl());
+ value.add(f.getName() + ":" + request.getRepoUrl());
+ break;
+ case "branch":
+ log.debug("branch: {}", request.getBranch());
+ value.add(f.getName() + ":" + request.getBranch());
+ break;
+ case "severity":
+ if (issue.getScaDetails() != null) {
+ log.debug("severity: {}", issue.getScaDetails().get(0).getFinding().getSeverity());
+ value.add(f.getName() + ":" + ScanUtils.toProperCase(String.valueOf(issue.getScaDetails().get(0).getFinding().getSeverity())));
+ } else {
+ log.debug("severity: {}", issue.getSeverity());
+ value.add(f.getName() + ":" + ScanUtils.toProperCase(issue.getSeverity()));
+ }
+ break;
+ case "category":
+ log.debug("category: {}", issue.getVulnerability());
+ value.add(f.getName() + ":" + issue.getVulnerability());
+ break;
+ case "cwe":
+ log.debug("cwe: {}", issue.getCwe());
+ value.add(f.getName() + ":" + issue.getCwe());
+ break;
+ case "cve":
+ if (issue.getScaDetails() != null) {
+ log.debug("cve: {}", issue.getScaDetails().get(0).getFinding().getId());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getFinding().getId());
+ } else {
+ log.debug("cve: {}", issue.getCve());
+ value.add(f.getName() + ":" + issue.getCve());
+ }
+ break;
+ case "system-date":
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ LocalDateTime now = LocalDateTime.now();
+ value.add(f.getName() + ":" + dtf.format(now));
+ log.debug("system date: {}", value);
+ break;
+ case "recommendation":
+ StringBuilder recommendation = new StringBuilder();
+ if (issue.getLink() != null && !issue.getLink().isEmpty()) {
+ recommendation.append("Checkmarx Link: ").append(issue.getLink()).append(HTMLHelper.CRLF);
+ }
+ if (!ScanUtils.anyEmpty(flowProperties.getMitreUrl(), issue.getCwe())) {
+ recommendation.append("Mitre Details: ").append(String.format(flowProperties.getMitreUrl(), issue.getCwe())).append(HTMLHelper.CRLF);
+ }
+ if (!ScanUtils.empty(flowProperties.getWikiUrl())) {
+ recommendation.append("Guidance: ").append(flowProperties.getWikiUrl()).append(HTMLHelper.CRLF);
+ }
+ value.add(f.getName() + ":" + recommendation.toString());
+ break;
+ case "loc":
+ if (issue.getDetails() != null) {
+ List lines = issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ if (!lines.isEmpty()) {
+ Collections.sort(lines);
+ value.add(f.getName() + ":" + StringUtils.join(lines, ","));
+ log.debug("loc: {}", value);
+ }
+ }
+ break;
+ case "not-exploitable":
+ List fpLines;
+ if (issue.getDetails() != null) {
+ fpLines = issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && x.getValue().isFalsePositive())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ if (!fpLines.isEmpty()) {
+ Collections.sort(fpLines);
+ value.add(f.getName() + ":" + StringUtils.join(fpLines, ","));
+ log.debug("loc: {}", value);
+ }
+ }
+ break;
+ case "site":
+ log.debug("site: {}", request.getSite());
+ value.add(f.getName() + ":" + request.getSite());
+ break;
+ case "issue-link":
+ if (issue.getScaDetails() != null) {
+ log.debug("issue-link: {}", issue.getScaDetails().get(0).getVulnerabilityLink());
+ value.add(f.getName() + ":" + issue.getScaDetails().get(0).getVulnerabilityLink());
+ } else {
+ log.debug("issue-link: {}", issue.getLink());
+ value.add(f.getName() + ":" + issue.getLink());
+ }
+ break;
+ case "filename":
+ log.debug("filename: {}", issue.getFilename());
+ value.add(f.getName() + ":" + issue.getFilename());
+ break;
+ case "language":
+ log.debug("language: {}", issue.getLanguage());
+ value.add(f.getName() + ":" + issue.getLanguage());
+ break;
+ case "similarity-id":
+ log.debug("similarity-id: {}", issue.getSimilarityId());
+ value.add(f.getName() + ":" + issue.getSimilarityId());
+ break;
+ case "comment":
+ StringBuilder comments = new StringBuilder();
+ String commentFmt = "[Line %s]: [%s]".concat(HTMLHelper.CRLF);
+ if (issue.getDetails() != null) {
+ issue.getDetails().entrySet()
+ .stream()
+ .filter(x -> x.getKey() != null && x.getValue() != null && x.getValue().getComment() != null && !x.getValue().getComment().isEmpty())
+ .forEach(c -> comments.append(String.format(commentFmt, c.getKey(), c.getValue().getComment())));
+ value.add(f.getName() + ":" + comments.toString());
+ }
+ break;
+ default:
+ log.warn("field value for {} not found", f.getName());
+ value.add("");
+ }
+ /*If the value is missing, check if a default value was specified*/
+ if (ScanUtils.empty(value)) {
+ log.debug("Value is empty, defaulting to configured default (if applicable)");
+ if (!ScanUtils.empty(f.getDefaultValue())) {
+ value.add(f.getName() + ":" + f.getDefaultValue());
+ log.debug("Default value is {}", value);
+ }
+ }
+ break;
}
}
+
+
+ if(properties.getIssueslabel()==null && !value.isEmpty())
+ {
+ label = String.join(",", value);
+ }else{
+ label = label + "," + String.join(",", value);
+ }
} catch (Exception e) {
return label;
}
diff --git a/src/main/java/com/checkmarx/flow/dto/LabelField.java b/src/main/java/com/checkmarx/flow/dto/LabelField.java
new file mode 100644
index 000000000..f7c9674c5
--- /dev/null
+++ b/src/main/java/com/checkmarx/flow/dto/LabelField.java
@@ -0,0 +1,12 @@
+package com.checkmarx.flow.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class LabelField {
+ private String type;
+ private String name;
+ private String defaultValue;
+}