diff --git a/src/main/java/com/jfrog/ide/common/configuration/AuditConfig.java b/src/main/java/com/jfrog/ide/common/configuration/AuditConfig.java new file mode 100644 index 00000000..a930b2ca --- /dev/null +++ b/src/main/java/com/jfrog/ide/common/configuration/AuditConfig.java @@ -0,0 +1,54 @@ +package com.jfrog.ide.common.configuration; + +import lombok.Getter; + +import java.util.List; +import java.util.Map; + +@Getter +public class AuditConfig { + // Getters + private final List scannedDirectories; + private final String serverId; + private final List excludedPattern; + private final Map envVars; + + private AuditConfig(Builder builder) { + this.scannedDirectories = builder.scannedDirectories; + this.serverId = builder.serverId; + this.excludedPattern = builder.excludedPattern; + this.envVars = builder.envVars; + } + + public static class Builder { + private List scannedDirectories; + private String serverId; + private List excludedPattern; + private Map envVars; + + public Builder serverId(String serverId) { + this.serverId = serverId; + return this; + } + + public Builder scannedDirectories(List scannedDirectories) { + this.scannedDirectories = scannedDirectories; + return this; + } + + public Builder excludedPattern(List excludedPattern) { + this.excludedPattern = excludedPattern; + return this; + } + + public Builder envVars(Map envVars) { + this.envVars = envVars; + return this; + } + + public AuditConfig build() { + return new AuditConfig(this); + } + } + +} diff --git a/src/main/java/com/jfrog/ide/common/configuration/JfrogCliDriver.java b/src/main/java/com/jfrog/ide/common/configuration/JfrogCliDriver.java index 5b215b2d..ab19ab3e 100644 --- a/src/main/java/com/jfrog/ide/common/configuration/JfrogCliDriver.java +++ b/src/main/java/com/jfrog/ide/common/configuration/JfrogCliDriver.java @@ -182,17 +182,36 @@ public CommandResults addCliServerConfig(String xrayUrl, String artifactoryUrl, } } - public CommandResults runCliAudit(File workingDirectory, List scannedDirectories, String serverId, List extraArgs, Map envVars) throws Exception { + public CommandResults runCliAudit(File workingDirectory, List scannedDirectories, String serverId, Map envVars) throws Exception { + AuditConfig config = new AuditConfig.Builder() + .scannedDirectories(scannedDirectories) + .serverId(serverId) + .envVars(envVars) + .build(); + return runCliAudit(workingDirectory, config); + } + + public CommandResults runCliAudit(File workingDirectory, AuditConfig config) throws Exception { List args = new ArrayList<>(); args.add("audit"); - if (scannedDirectories != null && !scannedDirectories.isEmpty()) { - String workingDirsString = scannedDirectories.size() > 1 ? String.join(", ", scannedDirectories) : scannedDirectories.get(0); - args.add("--working-dirs=" + workingDirsString); + + if (config.getScannedDirectories() != null && !config.getScannedDirectories().isEmpty()) { + String workingDirsString = config.getScannedDirectories().size() > 1 ? + String.join(", ", config.getScannedDirectories()) : + config.getScannedDirectories().get(0); + args.add("--working-dirs=" + quoteArgumentForUnix(workingDirsString)); } - args.add("--server-id=" + serverId); + + args.add("--server-id=" + config.getServerId()); args.add("--format=sarif"); + + if (config.getExcludedPattern() != null && !config.getExcludedPattern().isEmpty()) { + String excludedPatterns = String.join(",", config.getExcludedPattern()); + args.add("--exclusions=" + quoteArgumentForUnix(excludedPatterns)); + } + try { - return runCommand(workingDirectory, envVars, args.toArray(new String[0]), extraArgs != null ? extraArgs : Collections.emptyList(), null, log); + return runCommand(workingDirectory, config.getEnvVars(), args.toArray(new String[0]), Collections.emptyList(), null, log); } catch (IOException | InterruptedException e) { throw new Exception("Failed to run JF audit. Reason: " + e.getMessage(), e); } @@ -218,4 +237,9 @@ private void addDefaultEnvVars(Map env) { env.put("JFROG_CLI_AVOID_NEW_VERSION_WARNING", "true"); } } + + private String quoteArgumentForUnix(String commaSeparatedValues) { + // macOS/Linux: add quotes around the comma-separated values + return SystemUtils.IS_OS_WINDOWS ? commaSeparatedValues : "\"" + commaSeparatedValues + "\""; + } } diff --git a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java index d320103d..4fbe7ca1 100644 --- a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java +++ b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java @@ -1,8 +1,15 @@ package com.jfrog.ide.common.configuration; +import com.jfrog.ide.common.nodes.FileIssueNode; +import com.jfrog.ide.common.nodes.FileTreeNode; +import com.jfrog.ide.common.nodes.subentities.Severity; +import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType; +import com.jfrog.ide.common.parse.SarifParser; import org.apache.commons.lang3.SystemUtils; import org.jfrog.build.api.util.NullLog; import org.jfrog.build.extractor.executor.CommandResults; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -19,7 +26,6 @@ import java.text.SimpleDateFormat; import java.util.*; -import static java.util.Collections.singletonList; import static org.testng.Assert.*; /** @@ -40,6 +46,8 @@ public class JfrogCliDriverTest { private final String XRAY_URL = SERVER_URL + "xray/"; private String testServerId; private File tempDir; + private final SarifParser parser = new SarifParser(new NullLog()); + private final Logger logger = LoggerFactory.getLogger(JfrogCliDriverTest.class); @SuppressWarnings("unused") @Test() @@ -82,6 +90,8 @@ private void configJfrogCli(Boolean skipDownload) { fail(e.getMessage(), e); } testEnv.put("JFROG_CLI_HOME_DIR", tempDir.getAbsolutePath()); + testEnv.put("JFROG_CLI_LOG_LEVEL", "DEBUG"); + testEnv.put("CI", "true"); jfrogCliDriver = new JfrogCliDriver(testEnv, tempDir.getAbsolutePath() + File.separator, new NullLog()); } @@ -152,7 +162,6 @@ public void testAddCliServerConfig_withUsernameAndPassword() { CommandResults response = jfrogCliDriver.addCliServerConfig(XRAY_URL, ARTIFACTORY_URL, testServerId, USER_NAME, PASSWORD, null, tempDir, testEnv); JfrogCliServerConfig serverConfig = jfrogCliDriver.getServerConfig(tempDir, Collections.emptyList(), testEnv); assertTrue(response.isOk()); - assertTrue(response.getErr().isBlank()); assertNotNull(serverConfig); assertEquals(serverConfig.getUsername(), USER_NAME); assertEquals(serverConfig.getPassword(), PASSWORD); @@ -169,7 +178,6 @@ public void testAddCliServerConfig_withAccessToken() { CommandResults response = jfrogCliDriver.addCliServerConfig(XRAY_URL, ARTIFACTORY_URL, testServerId, null, null, ACCESS_TOKEN, tempDir, testEnv); JfrogCliServerConfig serverConfig = jfrogCliDriver.getServerConfig(tempDir, Collections.emptyList(), testEnv); assertTrue(response.isOk()); - assertTrue(response.getErr().isBlank()); assertNotNull(serverConfig); assertEquals(serverConfig.getAccessToken(), ACCESS_TOKEN); assertEquals(serverConfig.getArtifactoryUrl(), ARTIFACTORY_URL); @@ -195,13 +203,23 @@ public void testAddServerConfig_withBadCredentials() { @Test public void testRunAudit_NpmProject() { - String projectToCheck = "npm"; try { Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/npm"); CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), - singletonList(projectToCheck), testServerId, null, testEnv); - //TODO: check real values after the sarif parser is added + null, testServerId, testEnv); assertEquals(response.getExitValue(),0); + logger.info("Audit debug logs: \n" + response.getErr()); + logger.info("Audit response: \n" + response.getRes()); + List findings = parser.parse(response.getRes()); + assertNotNull(findings); + assertFalse(findings.isEmpty(), "Expected findings in SARIF output for npm project"); + // Verify the findings + assertEquals(findings.size(), 1, "Expected exactly one file with findings"); + FileTreeNode node = findings.get(0); + assertEquals(node.getChildren().size(), 1, "Expected exactly one vulnerabilities"); + FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); + assertEquals(issue.getSeverity(), Severity.High, "Expected severity to be HIGH"); + assertEquals(issue.getReporterType(), SourceCodeScanType.SCA, "Expected reporter type to be SCA"); } catch (Exception e) { fail(e.getMessage(), e); } @@ -213,9 +231,20 @@ public void testRunAudit_MultiMavenProject() { try { Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/maven-example"); CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), - projectsToCheck, testServerId, null, testEnv); - //TODO: check real values after the sarif parser is added + projectsToCheck, testServerId, testEnv); assertEquals(response.getExitValue(), 0); + logger.info("Audit debug logs: \n" + response.getErr()); + logger.info("Audit response: \n" + response.getRes()); + List findings = parser.parse(response.getRes()); + assertNotNull(findings); + assertFalse(findings.isEmpty(), "Expected findings in SARIF output for multi-maven project"); + // Verify the findings + assertEquals(findings.size(), 1, "Expected exactly one file with findings"); + FileTreeNode node = findings.get(0); + assertEquals(node.getChildren().size(), 3, "Expected exactly three vulnerabilities"); + assertEquals(node.getSeverity(), Severity.High, "Expected severity to be HIGH"); + FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); + assertEquals(issue.getReporterType(), SourceCodeScanType.SCA, "Expected reporter type to be SCA"); } catch (Exception e) { fail(e.getMessage(), e); } @@ -225,6 +254,35 @@ private String createServerId() { return "ide-plugins-common-test-server-" + timeStampFormat.format(System.currentTimeMillis()); } + @Test + public void testRunAudit_WithExcludedPattern() { + try { + Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/maven-example"); + AuditConfig config = new AuditConfig.Builder() + .serverId(testServerId) + .excludedPattern(new ArrayList<>(List.of("*multi3*"))) + .serverId(testServerId) + .envVars(testEnv) + .build(); + CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), config); + assertEquals(response.getExitValue(), 0); + logger.info("Audit debug logs: \n" + response.getErr()); + logger.info("Audit response: \n" + response.getRes()); + List findings = parser.parse(response.getRes()); + assertNotNull(findings); + assertFalse(findings.isEmpty(), "Expected findings in SARIF output for multi-maven project"); + // Verify the findings + assertEquals(findings.size(), 1, "Expected exactly one file with findings"); + FileTreeNode node = findings.get(0); + assertEquals(node.getChildren().size(), 3, "Expected exactly three vulnerabilities"); + assertEquals(node.getSeverity(), Severity.High, "Expected severity to be HIGH"); + FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); + assertEquals(issue.getReporterType(), SourceCodeScanType.SCA, "Expected reporter type to be SCA"); + } catch (Exception e) { + fail(e.getMessage(), e); + } + } + @AfterMethod public void cleanUp(Method method) { try { @@ -236,4 +294,4 @@ public void cleanUp(Method method) { fail(e.getMessage(), e); } } -} \ No newline at end of file +}