From 36ff4fefee25edd5ddf7e1d1e39875a9c1d9a771 Mon Sep 17 00:00:00 2001 From: Alexey K Date: Wed, 12 Aug 2020 15:41:33 +0300 Subject: [PATCH] Fixed: CxFlow tries do delete a wrong SAST project when the project name was defined in config-as-code (#384) * Added the isUseConfigAsCodeFromDefaultBranch property to determine effective branch for getting config-as-code. * Don't allow to delete SAST project if the corresponding branch is protected. * Added test scenarios for config-as-code and SAST project deletion functionality. --- .../flow/config/GitHubProperties.java | 6 +- .../checkmarx/flow/config/RepoProperties.java | 9 +- .../flow/controller/GitHubController.java | 10 +- .../checkmarx/flow/service/GitHubService.java | 133 +++++++---- .../checkmarx/flow/service/HelperService.java | 159 ++++++------- .../checkmarx/flow/service/RepoService.java | 4 - .../checkmarx/flow/service/SastScanner.java | 29 ++- .../deletebranch/DeleteBranchRunner.java | 2 +- .../deletebranch/DeleteBranchSteps.java | 221 ++++++++---------- .../cxconfig/ConfigAsCodeBranchSteps.java | 109 +++++++++ .../integration/cxconfig/CxConfigSteps.java | 77 +++--- .../componentTests/deletBranch.feature | 32 --- .../componentTests/delete-branch.feature | 33 +++ .../integrationTests/cxconfig.feature | 11 +- 14 files changed, 503 insertions(+), 332 deletions(-) create mode 100644 src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/ConfigAsCodeBranchSteps.java delete mode 100644 src/test/resources/cucumber/features/componentTests/deletBranch.feature create mode 100644 src/test/resources/cucumber/features/componentTests/delete-branch.feature diff --git a/src/main/java/com/checkmarx/flow/config/GitHubProperties.java b/src/main/java/com/checkmarx/flow/config/GitHubProperties.java index 7e89f802c..253374e8e 100644 --- a/src/main/java/com/checkmarx/flow/config/GitHubProperties.java +++ b/src/main/java/com/checkmarx/flow/config/GitHubProperties.java @@ -1,5 +1,7 @@ package com.checkmarx.flow.config; +import lombok.Getter; +import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; @@ -8,6 +10,9 @@ @ConfigurationProperties(prefix = "github") @Validated public class GitHubProperties extends RepoProperties { + @Getter + @Setter + private boolean useConfigAsCodeFromDefaultBranch; public String getMergeNoteUri(String namespace, String repo, String mergeId){ String format = "%s/%s/%s/issues/%s/comments"; @@ -20,5 +25,4 @@ public String getGitUri(String namespace, String repo){ return String.format(format, getUrl(), namespace, repo); //sample: https://github.com/namespace/repo.git } - } diff --git a/src/main/java/com/checkmarx/flow/config/RepoProperties.java b/src/main/java/com/checkmarx/flow/config/RepoProperties.java index 37400b691..c311b175b 100644 --- a/src/main/java/com/checkmarx/flow/config/RepoProperties.java +++ b/src/main/java/com/checkmarx/flow/config/RepoProperties.java @@ -1,5 +1,7 @@ package com.checkmarx.flow.config; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.lang3.StringUtils; import javax.annotation.PostConstruct; @@ -12,6 +14,7 @@ public class RepoProperties { private String apiUrl; private String falsePositiveLabel = "false-positive"; private String configAsCode = "cx.config"; + private String openTransition = "open"; private String closeTransition = "closed"; private String filePath = "."; @@ -173,10 +176,8 @@ public void setCxSummaryHeader(String cxSummaryHeader) { @PostConstruct private void postConstruct() { - if(this.apiUrl != null){ - if(this.apiUrl.endsWith("/")){ - this.setApiUrl(StringUtils.chop(apiUrl)); - } + if(apiUrl != null && apiUrl.endsWith("/")){ + setApiUrl(StringUtils.chop(apiUrl)); } } } diff --git a/src/main/java/com/checkmarx/flow/controller/GitHubController.java b/src/main/java/com/checkmarx/flow/controller/GitHubController.java index 83d5702d3..dd309483c 100644 --- a/src/main/java/com/checkmarx/flow/controller/GitHubController.java +++ b/src/main/java/com/checkmarx/flow/controller/GitHubController.java @@ -419,21 +419,25 @@ public ResponseEntity deleteBranchRequest( .repoUrl(repository.getCloneUrl()) .repoType(ScanRequest.Repository.NA) .branch(currentBranch) + .defaultBranch(repository.getDefaultBranch()) .refs(event.getRef()) .build(); request.setScanPresetOverride(false); + CxConfig cxConfig = gitHubService.getCxConfigOverride(request); + request = configOverrider.overrideScanRequestProperties(cxConfig, request); + //deletes a project which is not in the middle of a scan, otherwise it will not be deleted sastScanner.deleteProject(request); - log.info("Process of delete branch has finished successfully"); + final String MESSAGE = "Branch deletion event was handled successfully."; + log.info(MESSAGE); return ResponseEntity.status(HttpStatus.OK).body(EventResponse.builder() - .message("Delete Branch Successfully finished") + .message(MESSAGE) .success(true) .build()); - } diff --git a/src/main/java/com/checkmarx/flow/service/GitHubService.java b/src/main/java/com/checkmarx/flow/service/GitHubService.java index c0075f3b9..3c9cfaccc 100644 --- a/src/main/java/com/checkmarx/flow/service/GitHubService.java +++ b/src/main/java/com/checkmarx/flow/service/GitHubService.java @@ -1,33 +1,26 @@ package com.checkmarx.flow.service; -import com.checkmarx.flow.config.FindingSeverity; -import com.checkmarx.flow.config.FlowProperties; -import com.checkmarx.flow.config.GitHubProperties; +import com.checkmarx.flow.config.*; import com.checkmarx.flow.dto.*; import com.checkmarx.flow.dto.github.Content; +import com.checkmarx.flow.dto.report.AnalyticsReport; import com.checkmarx.flow.dto.report.PullRequestReport; import com.checkmarx.flow.exception.GitHubClientRunTimeException; import com.checkmarx.flow.utils.HTMLHelper; import com.checkmarx.flow.utils.ScanUtils; import com.checkmarx.sdk.dto.CxConfig; import com.checkmarx.sdk.dto.ScanResults; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.*; import org.springframework.web.util.DefaultUriBuilderFactory; import java.io.IOException; @@ -36,9 +29,8 @@ import java.util.*; @Service +@Slf4j public class GitHubService extends RepoService { - private static final Logger log = LoggerFactory.getLogger(GitHubService.class); - private static final String HTTP_BODY_IS_NULL = "HTTP Body is null for content api "; private static final String CONTENT_NOT_FOUND_IN_RESPONSE = "Content not found in JSON response"; private static final String STATUSES_URL_KEY = "statuses_url"; @@ -196,9 +188,8 @@ private void statusExchange(ScanRequest request, HttpEntity httpEntity, Strin } void endBlockMerge(ScanRequest request, ScanResults results, ScanDetails scanDetails) { + logPullRequestWithScaResults(request, results); - logPullRequestWithScaResutls(request, results); - if (properties.isBlockMerge()) { String statusApiUrl = request.getAdditionalMetadata(STATUSES_URL_KEY); if (ScanUtils.empty(statusApiUrl)) { @@ -207,9 +198,9 @@ void endBlockMerge(ScanRequest request, ScanResults results, ScanDetails scanDet } PullRequestReport report = new PullRequestReport(scanDetails, request); - + HttpEntity httpEntity = getStatusRequestEntity(results, report); - + logPullRequestWithSastOsa(results, report); log.debug("Updating pull request status: {}", statusApiUrl); @@ -239,7 +230,6 @@ private boolean hasSastOsaScan(ScanResults results) { } private void logPullRequestWithSastOsa(ScanResults results, PullRequestReport report) { - //Report pull request only if there was SAST/OSA scan //Otherwise it would be only SCA if(hasSastOsaScan(results)) { @@ -247,18 +237,15 @@ private void logPullRequestWithSastOsa(ScanResults results, PullRequestReport re } } - private void logPullRequestWithScaResutls(ScanRequest request, ScanResults results) { + private void logPullRequestWithScaResults(ScanRequest request, ScanResults results) { if(results.getScaResults() != null ) { - PullRequestReport report = new PullRequestReport(results.getScaResults().getScanId(), request, PullRequestReport.SCA); + PullRequestReport report = new PullRequestReport(results.getScaResults().getScanId(), request, AnalyticsReport.SCA); report.setFindingsPerSeveritySca(results); report.setPullRequestResult(OperationResult.successful()); report.log(); } - } - - private HttpEntity getStatusRequestEntity(ScanResults results, PullRequestReport pullRequestReport) { String statusForApi = MERGE_SUCCESS; @@ -367,7 +354,10 @@ private Sources getRepoLanguagePercentages(ScanRequest request) { } for (Map.Entry entry : langs.entrySet()){ Long bytes = entry.getValue(); - double percentage = (Double.valueOf(bytes) / Double.valueOf(total) * 100); + double percentage = 0; + if (total != 0L) { + percentage = (Double.valueOf(bytes) / (double) total * 100); + } langsPercent.put(entry.getKey(), (int) percentage); } sources.setLanguageStats(langsPercent); @@ -427,7 +417,7 @@ public CxConfig getCxConfigOverride(ScanRequest request) { CxConfig result = null; if (StringUtils.isNotBlank(properties.getConfigAsCode())) { try { - result = loadCxConfigFromGitHub(properties.getConfigAsCode(), request); + result = loadConfigAsCode(properties.getConfigAsCode(), request); } catch (NullPointerException e) { log.warn(CONTENT_NOT_FOUND_IN_RESPONSE); } catch (HttpClientErrorException.NotFound e) { @@ -439,31 +429,84 @@ public CxConfig getCxConfigOverride(ScanRequest request) { return result; } - private CxConfig loadCxConfigFromGitHub(String filename, ScanRequest request) { - HttpHeaders headers = createAuthHeaders(); - String urlTemplate = properties.getApiUrl().concat(FILE_CONTENT); - ResponseEntity response = restTemplate.exchange( - urlTemplate, - HttpMethod.GET, - new HttpEntity<>(headers), - String.class, - request.getNamespace(), - request.getRepoName(), - filename, - request.getBranch() - ); - if (response.getBody() == null) { + private CxConfig loadConfigAsCode(String filename, ScanRequest request) { + CxConfig result = null; + + String effectiveBranch = determineConfigAsCodeBranch(request); + String fileContent = downloadFileContent(filename, request, effectiveBranch); + if (fileContent == null) { log.warn(HTTP_BODY_IS_NULL); - return null; } else { - JSONObject json = new JSONObject(response.getBody()); + JSONObject json = new JSONObject(fileContent); String content = json.getString("content"); if (ScanUtils.empty(content)) { log.warn(CONTENT_NOT_FOUND_IN_RESPONSE); - return null; + } else { + String decodedContent = new String(Base64.decodeBase64(content.trim())); + result = com.checkmarx.sdk.utils.ScanUtils.getConfigAsCode(decodedContent); } - String decodedContent = new String(Base64.decodeBase64(content.trim())); - return com.checkmarx.sdk.utils.ScanUtils.getConfigAsCode(decodedContent); } + + return result; + } + + private String downloadFileContent(String filename, ScanRequest request, String branch) { + ResponseEntity response = null; + + if (StringUtils.isNotEmpty(branch)) { + HttpHeaders headers = createAuthHeaders(); + String urlTemplate = properties.getApiUrl().concat(FILE_CONTENT); + + response = restTemplate.exchange( + urlTemplate, + HttpMethod.GET, + new HttpEntity<>(headers), + String.class, + request.getNamespace(), + request.getRepoName(), + filename, + branch); + } else { + log.warn("Unable to load config-as-code."); + } + + return response != null ? response.getBody() : null; + } + + private String determineConfigAsCodeBranch(ScanRequest request) { + String result; + log.debug("Determining a branch to get config-as-code from."); + if (properties.isUseConfigAsCodeFromDefaultBranch()) { + result = tryGetDefaultBranch(request); + } else { + result = tryGetCurrentBranch(request); + } + return result; + } + + private static String tryGetCurrentBranch(ScanRequest request) { + String result = null; + if (StringUtils.isNotEmpty(request.getBranch())) { + result = request.getBranch(); + log.debug("Using the current branch ({}) to get config-as-code.", result); + } + else { + log.warn("Tried to use current branch to get config-as-code. " + + "However, current branch couldn't be determined."); + } + return result; + } + + private static String tryGetDefaultBranch(ScanRequest request) { + String result = null; + if (StringUtils.isNotEmpty(request.getDefaultBranch())) { + result = request.getDefaultBranch(); + log.debug("Using the default branch ({}) to get config-as-code.", result); + } + else { + log.warn("Configuration indicates that default branch must be used to get config-as-code. " + + "However, default branch couldn't be determined."); + } + return result; } } diff --git a/src/main/java/com/checkmarx/flow/service/HelperService.java b/src/main/java/com/checkmarx/flow/service/HelperService.java index df3b0003c..7d3d2b1a9 100644 --- a/src/main/java/com/checkmarx/flow/service/HelperService.java +++ b/src/main/java/com/checkmarx/flow/service/HelperService.java @@ -8,7 +8,9 @@ import com.checkmarx.sdk.config.Constants; import com.checkmarx.sdk.config.CxProperties; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.springframework.stereotype.Service; @@ -56,93 +58,105 @@ public void loadCxProfiles() { } } - public boolean isBranch2Scan(ScanRequest request, List branches){ + public boolean isBranch2Scan(ScanRequest request, List branches) { + String branchToCheck = getBranchToCheck(request); + + // If script is provided, it is highest priority String scriptFile = properties.getBranchScript(); - String branch = request.getBranch(); - String targetBranch = request.getMergeTargetBranch(); - if(!ScanUtils.empty(targetBranch)){ //if targetBranch is set, it is a merge request - branch = targetBranch; - } - //note: if script is provided, it is highest priority - if(!ScanUtils.empty(scriptFile)){ - log.info("executing external script to determine if branch should be scanned ({})", scriptFile); - try { - String script = getStringFromFile(scriptFile); - HashMap bindings = new HashMap<>(); - bindings.put(REQUEST, request); - bindings.put("branches", branches); - Object result = scriptService.runScript(script, bindings); - if (result instanceof Boolean) { - return ((boolean) result); - } - }catch (IOException e){ - log.error("Error reading script file {}", scriptFile, e); + if (!ScanUtils.empty(scriptFile)) { + Object branchShouldBeScanned = executeBranchScript(scriptFile, request, branches); + if (branchShouldBeScanned instanceof Boolean) { + return ((boolean) branchShouldBeScanned); } } - /*Override branches if provided in the request*/ - if(request.getActiveBranches() != null && !request.getActiveBranches().isEmpty()){ + + // Override branches if provided in the request + if (CollectionUtils.isNotEmpty(request.getActiveBranches())) { branches = request.getActiveBranches(); } - //If the script fails above, default to base property check functionality (regex list) - for( String b: branches){ - if(strMatches(b, branch)) return true; - } - if (branches.isEmpty() && branch.equalsIgnoreCase(request.getDefaultBranch())) - { - log.info("Scanning default branch - {}", request.getDefaultBranch()); + // If the script fails above, default to base property check functionality (regex list) + if (isBranchProtected(branchToCheck, branches, request)) { return true; } - log.info("Branch {} did not meet the scanning criteria [{}]", branch, branches); + + log.info("Branch {} did not meet the scanning criteria [{}]", branchToCheck, branches); return false; } - public String getCxTeam(ScanRequest request){ - String scriptFile = cxProperties.getTeamScript(); - String team = request.getTeam(); - //note: if script is provided, it is highest priority - if(!ScanUtils.empty(scriptFile)){ - log.info("executing external script to determine the Team in Checkmarx to be used ({})", scriptFile); - try { - String script = getStringFromFile(scriptFile); - HashMap bindings = new HashMap<>(); - bindings.put(REQUEST, request); - Object result = scriptService.runScript(script, bindings); - if (result instanceof String) { - return ((String) result); - } - }catch (IOException e){ - log.error("Error reading script file for checkmarx team {}", scriptFile, e); - } + public boolean isBranchProtected(String branchToCheck, List protectedBranchPatterns, ScanRequest request) { + boolean result; + if (protectedBranchPatterns.isEmpty() && branchToCheck.equalsIgnoreCase(request.getDefaultBranch())) { + result = true; + log.info("Scanning default branch - {}", request.getDefaultBranch()); + } else { + result = protectedBranchPatterns.stream().anyMatch(aBranch -> strMatches(aBranch, branchToCheck)); } - else if(!ScanUtils.empty(team)){ - return team; + return result; + } + + private Object executeBranchScript(String scriptFile, ScanRequest request, List branches) { + Object result = null; + log.info("executing external script to determine if branch should be scanned ({})", scriptFile); + try { + String script = getStringFromFile(scriptFile); + HashMap bindings = new HashMap<>(); + bindings.put(REQUEST, request); + bindings.put("branches", branches); + result = scriptService.runScript(script, bindings); + } catch (IOException e) { + log.error("Error reading script file {}", scriptFile, e); + } + return result; + } + + private static String getBranchToCheck(ScanRequest request) { + String result = request.getBranch(); + String targetBranch = request.getMergeTargetBranch(); + if (StringUtils.isNotEmpty(targetBranch)) { //if targetBranch is set, it is a merge request + result = targetBranch; } - return null; //null will indicate no override of team will take place + return result; + } + + public String getCxTeam(ScanRequest request) { + String scriptFile = cxProperties.getTeamScript(); + String team = request.getTeam(); + return getEffectiveEntityName(request, scriptFile, team, "team"); } - public String getCxProject(ScanRequest request){ + public String getCxProject(ScanRequest request) { String scriptFile = cxProperties.getProjectScript(); String project = request.getProject(); + return getEffectiveEntityName(request, scriptFile, project, "project"); + } + + private String getEffectiveEntityName(ScanRequest request, String scriptFile, String defaultName, String entity) { + String result = null; //note: if script is provided, it is highest priority - if(!ScanUtils.empty(scriptFile)){ - log.info("executing external script to determine the Project in Checkmarx to be used ({})", scriptFile); - try { - String script = getStringFromFile(scriptFile); - HashMap bindings = new HashMap<>(); - bindings.put(REQUEST, request); - Object result = scriptService.runScript(script, bindings); - if (result instanceof String) { - return ((String) result); - } - }catch (IOException e){ - log.error("Error reading script file for checkmarx project {}", scriptFile, e); - } + if (!ScanUtils.empty(scriptFile)) { + result = getScriptExecutionResult(request, scriptFile, entity); + } else if (!ScanUtils.empty(defaultName)) { + result = defaultName; } - else if(!ScanUtils.empty(project)){ - return project; + return result; //null will indicate no override will take place + } + + private String getScriptExecutionResult(ScanRequest request, String scriptFile, String entity) { + String result = null; + log.info("executing external script to determine the {} in Checkmarx to be used ({})", entity, scriptFile); + try { + String script = getStringFromFile(scriptFile); + HashMap bindings = new HashMap<>(); + bindings.put(REQUEST, request); + Object rawResult = scriptService.runScript(script, bindings); + if (rawResult instanceof String) { + result = ((String) rawResult); + } + } catch (IOException e) { + log.error("Error reading script file for Checkmarx {} {}", entity, scriptFile, e); } - return null; //null will indicate no override of team will take place + return result; } public String getShortUid(ScanRequest request){ @@ -161,8 +175,6 @@ public String getStringFromFile(String path) throws IOException { /** * Determine what preset to use based on Sources and Profile mappings - * @param sources - * @return */ public String getPresetFromSources(Sources sources){ if(sources == null || profiles == null || sources.getLanguageStats() == null || sources.getSources() == null){ @@ -225,9 +237,6 @@ else if(languages == null || languages.isEmpty() ){ /** * Go through each possible pattern and determine if a match exists within the Sources list - * @param sources - * @param regex - * @return */ private boolean checkFileRegex(List sources, List regex){ if(sources == null || sources.isEmpty() || regex == null || regex.isEmpty()){ @@ -243,9 +252,6 @@ private boolean checkFileRegex(List sources, List regex) /** * Go through list of Sources (file names/paths) and determine if a match exists with a pattern - * @param sources - * @param patternStr - * @return */ private boolean strListMatches(List sources, String patternStr){ for(Sources.Source s: sources) { @@ -258,9 +264,6 @@ private boolean strListMatches(List sources, String patternStr){ /** * Regex String match - * @param patternStr - * @param str - * @return */ private boolean strMatches(String patternStr, String str){ Pattern pattern = Pattern.compile(patternStr); diff --git a/src/main/java/com/checkmarx/flow/service/RepoService.java b/src/main/java/com/checkmarx/flow/service/RepoService.java index d2b9f56aa..618b3e93f 100644 --- a/src/main/java/com/checkmarx/flow/service/RepoService.java +++ b/src/main/java/com/checkmarx/flow/service/RepoService.java @@ -3,15 +3,11 @@ import com.checkmarx.flow.dto.ScanRequest; import com.checkmarx.flow.dto.Sources; import com.checkmarx.sdk.dto.CxConfig; -import com.checkmarx.sdk.exception.CheckmarxException; -import com.checkmarx.sdk.utils.ScanUtils; public abstract class RepoService { - public abstract Sources getRepoContent(ScanRequest request); public CxConfig getCxConfigOverride(ScanRequest request) { return null; } - } diff --git a/src/main/java/com/checkmarx/flow/service/SastScanner.java b/src/main/java/com/checkmarx/flow/service/SastScanner.java index 4108254c4..0bd35fed6 100644 --- a/src/main/java/com/checkmarx/flow/service/SastScanner.java +++ b/src/main/java/com/checkmarx/flow/service/SastScanner.java @@ -106,9 +106,9 @@ public ScanResults scan(ScanRequest scanRequest) { //the repository is unavailable - can happen for a push event of a deleted branch - nothing to do //the error message is printed when the exception is thrown - //usually should occur during push event occuring on delete branch + //usually should occur during push event occurring on delete branch //therefore need to eliminate the scan process but do not want to create - //an error stuck trace in the log + //an error stack trace in the log return getEmptyScanResults(); } catch (Exception e) { @@ -138,7 +138,7 @@ public ScanResults scanCli(ScanRequest request, String scanType, File... files) cxBatch(request); break; default: - log.warn("SastScanner does not support scanType of {}, ignoring."); + log.warn("SastScanner does not support scanType of {}, ignoring.", scanType); break; } } catch (ExitThrowable e) { @@ -307,9 +307,7 @@ public void cxBatch(ScanRequest originalRequest) throws ExitThrowable { } public void deleteProject(ScanRequest request) { - try { - String ownerId = scanRequestConverter.determineTeamAndOwnerID(request); String projectName = projectNameGenerator.determineProjectName(request); @@ -317,15 +315,32 @@ public void deleteProject(ScanRequest request) { Integer projectId = scanRequestConverter.determinePresetAndProjectId(request, ownerId); - if (projectId != UNKNOWN_INT) { + if (canDeleteProject(projectId, request)) { cxService.deleteProject(projectId); } - } catch (CheckmarxException e) { log.error("Error delete branch " + e.getMessage()); } } + private boolean canDeleteProject(Integer projectId, ScanRequest request) { + boolean result = false; + if (projectId == null || projectId == UNKNOWN_INT) { + log.warn("{} project with the provided name is not found, nothing to delete.", SCAN_TYPE); + } else { + boolean branchIsProtected = helperService.isBranchProtected(request.getBranch(), + flowProperties.getBranches(), + request); + + if (branchIsProtected) { + log.info("Unable to delete {} project, because the corresponding repo branch is protected.", SCAN_TYPE); + } else { + result = true; + } + } + return result; + } + private String treatFailure(ScanRequest request, File cxFile, Integer scanId, Exception e) { String extendedMessage = ExceptionUtils.getMessage(e); log.error(extendedMessage, e); diff --git a/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchRunner.java b/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchRunner.java index 7052c001e..761789e42 100644 --- a/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchRunner.java +++ b/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchRunner.java @@ -6,7 +6,7 @@ @RunWith(Cucumber.class) @CucumberOptions( - features = "src/test/resources/cucumber/features/componentTests/deletBranch.feature", + features = "src/test/resources/cucumber/features/componentTests/delete-branch.feature", tags = "not @Skip") public class DeleteBranchRunner { } diff --git a/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchSteps.java b/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchSteps.java index 215db6bc8..63f4b9dd3 100644 --- a/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchSteps.java +++ b/src/test/java/com/checkmarx/flow/cucumber/component/deletebranch/DeleteBranchSteps.java @@ -3,37 +3,28 @@ import com.checkmarx.flow.CxFlowApplication; import com.checkmarx.flow.config.FlowProperties; import com.checkmarx.flow.config.GitHubProperties; - -import com.checkmarx.flow.exception.MachinaException; -import com.checkmarx.flow.sastscanning.ScanRequestConverter; -import com.checkmarx.sdk.exception.CheckmarxException; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - import com.checkmarx.flow.controller.GitHubController; - import com.checkmarx.flow.dto.github.*; +import com.checkmarx.flow.sastscanning.ScanRequestConverter; import com.checkmarx.flow.service.*; import com.checkmarx.sdk.config.Constants; import com.checkmarx.sdk.config.CxProperties; - +import com.checkmarx.sdk.exception.CheckmarxException; import com.checkmarx.sdk.service.CxClient; import com.fasterxml.jackson.core.JsonProcessingException; - import com.fasterxml.jackson.databind.ObjectMapper; import io.cucumber.java.Before; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; +import io.cucumber.java.en.*; import lombok.extern.slf4j.Slf4j; - +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.boot.test.context.SpringBootTest; - import java.util.LinkedList; import java.util.List; -import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockito.Mockito.*; @SpringBootTest(classes = {CxFlowApplication.class}) @@ -44,12 +35,12 @@ public class DeleteBranchSteps { private static final String TEAM = "SOME_TEAM"; private static final String PROJECT_NAME = "VB_3845-test1"; private static final String PRESET = "Default Preset"; - private static final String BRANCH_STR = "branch"; - public static final String GITHUB_USER = "cxflowtestuser"; + private static final String BRANCH_REF_TYPE = "branch"; + private static final String GITHUB_USER = "cxflowtestuser"; private final CxClient cxClientMock; private final GitHubService gitHubService; private GitHubController gitHubControllerSpy; - private final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); private final FlowProperties flowProperties; private final CxProperties cxProperties; @@ -59,11 +50,10 @@ public class DeleteBranchSteps { private final ConfigurationOverrider configOverrider; private String branch; - - private Boolean deleteCalled = null; + + private Boolean deleteCalled; private String repoName; - private int actualProjectId; private String trigger; private String calculatedProjectName; @@ -93,13 +83,12 @@ private void initGitHubProperties() { this.gitHubProperties.setUrl("https://github.com/" + GITHUB_USER + "/" + repoName); this.gitHubProperties.setWebhookToken("1234"); this.gitHubProperties.setApiUrl("https://api.github.com/repos"); - } @Before("@DeleteBranchFeature") public void prepareServices() { deleteCalled = Boolean.FALSE; - actualProjectId = Constants.UNKNOWN_INT; + trigger = BRANCH_REF_TYPE; initCxClientMock(); initHelperServiceMock(); initServices(); @@ -110,97 +99,88 @@ private void initCxClientMock() { ScanResultsAnswerer answerer = new ScanResultsAnswerer(); doAnswer(answerer).when(cxClientMock).deleteProject(any()); try { - when(cxClientMock.getTeamId(anyString(),anyString())).thenReturn(TEAM); + when(cxClientMock.getTeamId(anyString(), anyString())).thenReturn(TEAM); when(cxClientMock.getTeamId(anyString())).thenReturn(TEAM); } catch (CheckmarxException e) { fail(e.getMessage()); } } - private class ScanResultsAnswerer implements Answer { - + private class ScanResultsAnswerer implements Answer { @Override public Object answer(InvocationOnMock invocation) { - actualProjectId = invocation.getArgument(0); deleteCalled = true; return null; } } - @Given("GitHub repoName is {string}") - public void setRepoName(String repoName){ + @Given("GitHub repo name is {string}") + public void setRepoName(String repoName) { this.repoName = repoName; initGitHubProperties(); } - @And("SAST delete API will be called for project {string}") - public void validateProjectName(String projectName){ - assertEquals(calculatedProjectName, projectName); - } - - @And("GitHub webhook is configured for delete branch or tag") - public void nothingToImpl(){} - @And("no exception will be thrown") - public void validateNoException(){} - - @And("github trigger can be branch or tag {string}") - public void setTrigger(String trigger){ - this.trigger = trigger; + public void validateNoException() { + // If we have arrived here, no exception was thrown. } - - @And("CxFlow will call the SAST delete API only if trigger is branch") - public void callDelete(){ - - buildDeleteRequest(trigger); - if (trigger.equals(BRANCH_STR)) { - assertEquals(Boolean.TRUE, deleteCalled); - assertEquals(EXISTING_PROJECT_ID, actualProjectId); - } else { - assertEquals(Boolean.FALSE, deleteCalled); - assertEquals(Constants.UNKNOWN_INT, actualProjectId); - } + @And("GitHub trigger is {string}") + public void setTrigger(String trigger) { + this.trigger = trigger; } - - @And("github branch is {string} and it is set {string} application.yml") - public void setBranch(String branch, String set_in_app){ - LinkedList branches = new LinkedList<>(); - if(Boolean.parseBoolean(set_in_app)){ - branches.add(branch); - flowProperties.setBranches(branches); - }else{ - flowProperties.setBranches(branches); - } + + @And("GitHub branch is {string}") + public void setBranch(String branch) { this.branch = branch; } - + @And("a project {string} {string} in SAST") - public void setProjectId(String projectName, String exists){ + public void setProjectId(String projectName, String exists) { int projectId; - if(Boolean.parseBoolean(exists)){ + if (exists.equals("exists") || Boolean.parseBoolean(exists)) { projectId = EXISTING_PROJECT_ID; - }else{ + } else { projectId = Constants.UNKNOWN_INT; } - when(cxClientMock.getProjectId(anyString(),anyString())).thenReturn(projectId); + when(cxClientMock.getProjectId(anyString(), any())).thenReturn(projectId); + } + + @And("the {string} branch is {string} as determined by application.yml") + public void theBranchIsSpecifiedAsProtected(String branch, String protectedOrNot) { + List protectedBranches = flowProperties.getBranches(); + if (Boolean.parseBoolean(protectedOrNot)) { + protectedBranches.add(branch); + } else { + protectedBranches.remove(branch); + } } - - @And("CxFlow will call or not call the SAST delete API based on the fact whether the project {string} or not in SAST") - public void checkIfDeleteMethodIsCalled(String methodCalled) - { - buildDeleteRequest(BRANCH_STR); - - if(Boolean.parseBoolean(methodCalled)){ + + @When("GitHub notifies cxFlow that a {string} branch/ref was deleted") + public void githubNotifiesCxFlowThatABranchWasDeleted(String deletedBranch) { + branch = deletedBranch; + sendDeleteEvent(); + } + + @Then("CxFlow will {string} the SAST delete API for the {string} project") + public void cxflowWillNotCallTheSASTDeleteAPI(String willCall, String projectName) { + boolean expectingCall = Boolean.parseBoolean(willCall); + verifyDeleteApiCall(expectingCall); + + if (expectingCall) { + assertEquals("Wrong project name in SAST delete API call.", projectName, calculatedProjectName); + } + } + + private void verifyDeleteApiCall(boolean expectingCall) { + if (expectingCall) { assertEquals(Boolean.TRUE, deleteCalled); - assertEquals(EXISTING_PROJECT_ID,actualProjectId ); - }else{ + } else { assertEquals(Boolean.FALSE, deleteCalled); - assertEquals(Constants.UNKNOWN_INT,actualProjectId); } } - - public void buildDeleteRequest(String refType) { + + private void sendDeleteEvent() { DeleteEvent deleteEvent = new DeleteEvent(); Repository repo = new Repository(); repo.setName(repoName); @@ -211,49 +191,58 @@ public void buildDeleteRequest(String refType) { owner.setLogin(GITHUB_USER); repo.setOwner(owner); deleteEvent.setRepository(repo); - deleteEvent.setRefType(refType); + deleteEvent.setRefType(trigger); deleteEvent.setRef(branch); - + try { String deleteEventStr = mapper.writeValueAsString(deleteEvent); gitHubControllerSpy.deleteBranchRequest( - deleteEventStr,"SIGNATURE", "CX", null, null, TEAM ); + deleteEventStr, "SIGNATURE", "CX", null, null, TEAM); } catch (JsonProcessingException e) { fail("Unable to parse " + deleteEvent.toString()); } } - private void initHelperServiceMock() { - when(helperService.getShortUid()).thenReturn("123456"); - when(helperService.getCxTeam(any())).thenReturn(TEAM); - when(helperService.getCxProject(any())).thenReturn(PROJECT_NAME); - when(helperService.getPresetFromSources(any())).thenReturn(PRESET); - + + private void initHelperServiceMock() { + when(helperService.getShortUid()).thenReturn("123456"); + when(helperService.getCxTeam(any())).thenReturn(TEAM); + when(helperService.getCxProject(any())).thenReturn(PROJECT_NAME); + when(helperService.getPresetFromSources(any())).thenReturn(PRESET); + when(helperService.isBranchProtected(anyString(), anyList(), any())).thenCallRealMethod(); } private void initMockGitHubController() { doNothing().when(gitHubControllerSpy).verifyHmacSignature(any(), any()); - } - - private void initServices() { + private void initServices() { ProjectNameGenerator projectNameGeneratorSpy = spy(new ProjectNameGenerator(helperService, cxProperties, null)); + initProjectNameGeneratorSpy(projectNameGeneratorSpy); - try { - initProjectNameGeneratorSpy(projectNameGeneratorSpy); - } catch (MachinaException e) { - fail(e.getMessage()); - } - - ScanRequestConverter scanRequestConverter = new ScanRequestConverter(helperService, cxProperties, cxClientMock, flowProperties, gitHubService, null); - SastScanner sastScanner = new SastScanner(null, cxClientMock, helperService, cxProperties, flowProperties, null, null, scanRequestConverter, null, projectNameGeneratorSpy); - List scanners= new LinkedList<>(); + ScanRequestConverter scanRequestConverter = new ScanRequestConverter(helperService, + cxProperties, + cxClientMock, + flowProperties, + gitHubService, + null); + + SastScanner sastScanner = new SastScanner(null, + cxClientMock, + helperService, + cxProperties, + flowProperties, + null, + null, + scanRequestConverter, + null, + projectNameGeneratorSpy); + List scanners = new LinkedList<>(); scanners.add(sastScanner); - + FlowService flowServiceSpy = spy(new FlowService(scanners, projectNameGeneratorSpy, null)); - + //gitHubControllerSpy is a spy which will run real methods. //It will connect to a real github repository to read a real cx.config file //And thus it will work with real gitHubService @@ -267,25 +256,19 @@ private void initServices() { sastScanner, filterFactory, configOverrider)); - - } - private void initProjectNameGeneratorSpy(ProjectNameGenerator projectNameGenerator) throws MachinaException { - ProjectNameGeneratorAnswered answered = new ProjectNameGeneratorAnswered(); - doAnswer(answered).when(projectNameGenerator).determineProjectName(any()); } - private class ProjectNameGeneratorAnswered implements Answer { + private void initProjectNameGeneratorSpy(ProjectNameGenerator projectNameGenerator) { + doAnswer(this::interceptProjectName).when(projectNameGenerator).determineProjectName(any()); + } - @Override - public Object answer(InvocationOnMock invocation) { - try { - calculatedProjectName = (String)invocation.callRealMethod(); - } catch (Throwable throwable) { - fail(throwable.getMessage()); - } - - return calculatedProjectName; + public Object interceptProjectName(InvocationOnMock invocation) { + try { + calculatedProjectName = (String) invocation.callRealMethod(); + } catch (Throwable throwable) { + fail(throwable.getMessage()); } + return null; } } diff --git a/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/ConfigAsCodeBranchSteps.java b/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/ConfigAsCodeBranchSteps.java new file mode 100644 index 000000000..58e2b4827 --- /dev/null +++ b/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/ConfigAsCodeBranchSteps.java @@ -0,0 +1,109 @@ +package com.checkmarx.flow.cucumber.integration.cxconfig; + +import com.checkmarx.flow.config.FlowProperties; +import com.checkmarx.flow.config.GitHubProperties; +import com.checkmarx.flow.controller.GitHubController; +import com.checkmarx.flow.dto.github.PullEvent; +import com.checkmarx.flow.service.*; +import com.checkmarx.sdk.config.CxProperties; +import io.cucumber.java.en.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.springframework.http.*; +import org.springframework.web.client.RestTemplate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@Slf4j +@RequiredArgsConstructor +public class ConfigAsCodeBranchSteps { + private static final int BRANCH_ARGUMENT_INDEX = 7; + + private final GitHubProperties gitHubProperties; + private final FlowProperties flowProperties; + private final CxProperties cxProperties; + private final HelperService helperService; + private final FilterFactory filterFactory; + private final ConfigurationOverrider configOverrider; + + private String defaultBranch; + private String actualBranch; + + @Given("use-config-as-code-from-default-branch property in application.yml is set to {string}") + public void useConfigAsCodeFromDefaultBranch(String useDefaultBranch) { + boolean parsedPropValue = Boolean.parseBoolean(useDefaultBranch); + gitHubProperties.setUseConfigAsCodeFromDefaultBranch(parsedPropValue); + } + + @And("GitHub repo default branch is {string}") + public void githubRepoDefaultBranchIs(String defaultBranch) { + this.defaultBranch = defaultBranch; + } + + @When("GitHub notifies CxFlow that a pull request was created for the {string} branch") + public void githubNotifiesCxFlow(String srcBranch) { + log.info("Creating RestTemplate mock."); + RestTemplate restTemplateMock = mock(RestTemplate.class); + when(gettingFileFromRepo(restTemplateMock)).thenAnswer(this::interceptConfigAsCodeBranch); + + PullEvent pullEvent = CxConfigSteps.createPullEventDto(srcBranch, defaultBranch, gitHubProperties); + + GitHubController controllerSpy = getGitHubControllerSpy(restTemplateMock); + CxConfigSteps.sendPullRequest(pullEvent, controllerSpy, srcBranch); + } + + private GitHubController getGitHubControllerSpy(RestTemplate restTemplateMock) { + log.info("Creating GitHub controller spy."); + + // Don't start automation. + FlowService flowServiceMock = mock(FlowService.class); + + GitHubService gitHubService = new GitHubService(restTemplateMock, gitHubProperties, flowProperties, null); + + GitHubController gitHubControllerSpy = Mockito.spy(new GitHubController(gitHubProperties, + flowProperties, + cxProperties, + null, + flowServiceMock, + helperService, + gitHubService, + null, + filterFactory, + configOverrider)); + doNothing().when(gitHubControllerSpy).verifyHmacSignature(any(), any()); + + return gitHubControllerSpy; + } + + @Then("CxFlow should get config-as-code from the {string} branch") + public void cxflowShouldGetConfigAsCodeFromTheBranch(String expectedBranch) { + assertEquals(expectedBranch, + actualBranch, + "CxFlow has tried to get config-as-code file from an incorrect branch."); + } + + private static ResponseEntity gettingFileFromRepo(RestTemplate restTemplateMock) { + return restTemplateMock.exchange(anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + ArgumentMatchers.>any(), + anyString(), + anyString(), + anyString(), + anyString()); // expecting branch name here + } + + private Object interceptConfigAsCodeBranch(InvocationOnMock invocation) { + assertEquals(BRANCH_ARGUMENT_INDEX + 1, + invocation.getArguments().length, + "Unexpected argument count for the restTemplate call."); + + actualBranch = invocation.getArgument(BRANCH_ARGUMENT_INDEX); + return null; + } +} diff --git a/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/CxConfigSteps.java b/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/CxConfigSteps.java index cd939afad..b21d6b77d 100644 --- a/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/CxConfigSteps.java +++ b/src/test/java/com/checkmarx/flow/cucumber/integration/cxconfig/CxConfigSteps.java @@ -23,9 +23,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.cucumber.java.Before; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; +import io.cucumber.java.en.*; import lombok.extern.slf4j.Slf4j; import org.eclipse.jgit.util.StringUtils; import org.junit.Assert; @@ -58,11 +56,11 @@ public class CxConfigSteps { public static final String SQL_INJECTION = "SQL_INJECTION"; public static final String CWE_79 = "79"; public static final String CWE_89 = "89"; + private static final ObjectMapper mapper = new ObjectMapper(); private final CxClient cxClientMock; private final GitHubService gitHubService; private GitHubController gitHubControllerSpy; - private final ObjectMapper mapper = new ObjectMapper(); private final ThresholdValidator thresholdValidator; private final FlowProperties flowProperties; private final CxProperties cxProperties; @@ -79,6 +77,7 @@ public class CxConfigSteps { private final FlowService flowService; private String branch; + private ScanRequest request; private final JiraProperties jiraProperties; @@ -132,30 +131,53 @@ private void initFlowProperties() { flowProperties.setFilterStatus(null); } - @Given("github branch is {string} and threshods section is not set application.yml") - public void setBranchAndCreatePullReqeust(String branch) { + @Given("github branch is {string} and thresholds section is not set application.yml") + public void setBranchAndSendPullRequest(String branch) { this.branch = branch; - buildPullRequest(); + PullEvent pullEvent = createPullEventDto(branch, null, gitHubProperties); + sendPullRequest(pullEvent, gitHubControllerSpy, branch); } @And("github branch is {string} with cx.config") public void setBranchAppSet(String branch) { - setBranchAndCreatePullReqeust(branch); + setBranchAndSendPullRequest(branch); } @Given("github branch is {string} with invalid cx.config") public void setBranchInvalid(String branch) { //set filter from application.yml setCurrentFilter("severity"); - setBranchAndCreatePullReqeust(branch); + setBranchAndSendPullRequest(branch); } - public void buildPullRequest() { + static void sendPullRequest(PullEvent pullEvent, GitHubController gitHubController, String sourceBranch) { + log.info("Sending pull request event to controller."); + try { + String pullEventStr = mapper.writeValueAsString(pullEvent); + + ControllerRequest request = ControllerRequest.builder() + .branch(Collections.singletonList(sourceBranch)) + .application("VB") + .team("\\CxServer\\SP") + .assignee("") + .preset("default") + .build(); + + gitHubController.pullRequest(pullEventStr, "SIGNATURE", "CX", request); + + } catch (JsonProcessingException e) { + fail("Unable to parse " + pullEvent.toString()); + } + } + + static PullEvent createPullEventDto(String sourceBranch, String defaultBranch, GitHubProperties gitHubProperties) { + log.info("Creating pull event DTO."); PullEvent pullEvent = new PullEvent(); Repository repo = new Repository(); repo.setName("CxConfigTests"); - repo.setCloneUrl(gitHubProperties.getUrl()); + repo.setDefaultBranch(defaultBranch); + Owner owner = new Owner(); owner.setName(""); owner.setLogin("cxflowtestuser"); @@ -165,30 +187,14 @@ public void buildPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setIssueUrl(""); Head headBranch = new Head(); - headBranch.setRef(branch); + headBranch.setRef(sourceBranch); pullRequest.setHead(headBranch); pullRequest.setBase(new Base()); pullRequest.setStatusesUrl(""); pullEvent.setPullRequest(pullRequest); - - try { - String pullEventStr = mapper.writeValueAsString(pullEvent); - - ControllerRequest request = ControllerRequest.builder() - .branch(Collections.singletonList(branch)) - .application("VB") - .team("\\CxServer\\SP") - .assignee("") - .preset("default") - .build(); - - gitHubControllerSpy.pullRequest(pullEventStr, "SIGNATURE", "CX", request); - - } catch (JsonProcessingException e) { - fail("Unable to parse " + pullEvent.toString()); - } + return pullEvent; } @Given("application.xml contains high thresholds {string} medium thresholds {string} and low thresholds {string}") @@ -482,7 +488,6 @@ private ResponseEntity createResponseForGetComments() { return new ResponseEntity<>("{}", HttpStatus.OK); } - private void initCxClientMock() { try { ScanResultsAnswerer answerer = new ScanResultsAnswerer(); @@ -492,7 +497,6 @@ private void initCxClientMock() { } } - /** * Returns scan results as if they were produced by SAST. */ @@ -503,7 +507,6 @@ public ScanResults answer(InvocationOnMock invocation) { } } - private void initHelperServiceMock() { HelperServiceAnswerer answerer = new HelperServiceAnswerer(); when(helperService.isBranch2Scan(any(), anyList())).thenAnswer(answerer); @@ -516,9 +519,9 @@ private void initMockGitHubController() { private void initServices() { - //gitHubControllerSpy is a spy which will run real methods. - //It will connect to a real github repository toread a real cx.config file - //And thus it will work with real gitHubService + // gitHubControllerSpy is a spy which will run real methods. + // It will connect to a real github repository to read a real cx.config file + // And thus it will work with real gitHubService this.gitHubControllerSpy = spy(new GitHubController(gitHubProperties, flowProperties, cxProperties, @@ -530,8 +533,8 @@ private void initServices() { filterFactory, configOverrider)); - //results service will be a Mock and will work with gitHubService Mock - //and will not not connect to any external + // results service will be a Mock and will work with gitHubService Mock + // and will not connect to any external service. initResultsServiceMock(); } diff --git a/src/test/resources/cucumber/features/componentTests/deletBranch.feature b/src/test/resources/cucumber/features/componentTests/deletBranch.feature deleted file mode 100644 index 7d27339f0..000000000 --- a/src/test/resources/cucumber/features/componentTests/deletBranch.feature +++ /dev/null @@ -1,32 +0,0 @@ -@DeleteBranchFeature @ComponentTest -Feature: CxFlow should delete SAST project when corresponding GitHub branch is deleted - - Scenario Outline: CxFlow SAST project when GitHub branch is deleted - Given GitHub repoName is "" - And GitHub webhook is configured for delete branch or tag - And github branch is "" and it is set "" application.yml - And a project "" "" in SAST - Then CxFlow will call or not call the SAST delete API based on the fact whether the project "" or not in SAST - And SAST delete API will be called for project "" - And no exception will be thrown - - Examples: - | repoName | branch | projectName | exists | set_in_app | - | VB_3845 | test1 | VB_3845-test1 | true | false | - | VB_3845 | test1 | VB_3845-test1 | true | true | - | VB_3845 | test1 | VB_3845-test1 | false | false | - - Scenario Outline: Github triggers delete event both when branch and tag are deleted, - but CxFlow will call SAST delete API only when branch is deleted - Given GitHub repoName is "" - And GitHub webhook is configured for delete branch or tag - And github trigger can be branch or tag "" - And a project "" "" in SAST - Then CxFlow will call the SAST delete API only if trigger is branch - And no exception will be thrown - - Examples: - | repoName | trigger | exists | - | VB_3845 | branch | true | - | VB_3845 | tag | true | - \ No newline at end of file diff --git a/src/test/resources/cucumber/features/componentTests/delete-branch.feature b/src/test/resources/cucumber/features/componentTests/delete-branch.feature new file mode 100644 index 000000000..693f49e5c --- /dev/null +++ b/src/test/resources/cucumber/features/componentTests/delete-branch.feature @@ -0,0 +1,33 @@ +@DeleteBranchFeature @ComponentTest +Feature: CxFlow should delete SAST project when corresponding GitHub branch is deleted + + Scenario Outline: CxFlow deletes SAST project when a non-protected GitHub branch is deleted + Given GitHub repo name is "VB_3845" + And GitHub branch is "test1" + And the "test1" branch is "" as determined by application.yml + And a project "VB_3845-test1" "" in SAST + When GitHub notifies cxFlow that a "test1" branch was deleted + Then CxFlow will "" the SAST delete API for the "VB_3845-test1" project + And no exception will be thrown + + Examples: + | exists | protected | call | + | true | true | false | + | true | false | true | + | false | true | false | + | false | false | false | + + Scenario Outline: Github triggers delete event both when branch and tag are deleted, + but CxFlow will call SAST delete API only when branch is deleted + Given GitHub repo name is "VB_3845" + And GitHub trigger is "" + And the "test2" branch is "not protected" as determined by application.yml + And a project "VB_3845-test2" "exists" in SAST + When GitHub notifies cxFlow that a "test2" ref was deleted + Then CxFlow will "" the SAST delete API for the "VB_3845-test2" project + And no exception will be thrown + + Examples: + | trigger | call | + | branch | true | + | tag | false | \ No newline at end of file diff --git a/src/test/resources/cucumber/features/integrationTests/cxconfig.feature b/src/test/resources/cucumber/features/integrationTests/cxconfig.feature index 19393353b..dd751b63d 100644 --- a/src/test/resources/cucumber/features/integrationTests/cxconfig.feature +++ b/src/test/resources/cucumber/features/integrationTests/cxconfig.feature @@ -4,7 +4,7 @@ Feature: CxFlow should read configuration from cx.config file in the root of rep @Thresholds Scenario Outline: CxFlow should approve or fail GitHub pull request, depending on whether threshold is exceeded in cx.config GitHub notifies CxFlow that a pull request was created. CxFlow then executes a SAST scan. - Given github branch is "" and threshods section is not set application.yml + Given github branch is "" and thresholds section is not set application.yml And SAST detects findings of "High" severity And findings of "Medium" severity And findings of "Low" severity @@ -104,3 +104,12 @@ Feature: CxFlow should read configuration from cx.config file in the root of rep | filterSeverity | | filterScore | + Scenario Outline: CxFlow should use config-as-code from a correct branch + Given use-config-as-code-from-default-branch property in application.yml is set to "" + And GitHub repo default branch is "master" + When GitHub notifies CxFlow that a pull request was created for the "test1" branch + Then CxFlow should get config-as-code from the "" branch + Examples: + | use default | branch | + | true | master | + | false | test1 | \ No newline at end of file