From ba87b952e236cd6e2c32e50d2c4d2bf6d0c564ce Mon Sep 17 00:00:00 2001 From: "CORP\\mmrzik" Date: Tue, 5 Dec 2023 15:09:43 +0100 Subject: [PATCH] #103: test interaction and getVersionRangeFromInterval --- .../tools/ide/tool/GlobalToolCommandlet.java | 6 +- .../tools/ide/tool/LocalToolCommandlet.java | 6 +- .../tools/ide/tool/ToolCommandlet.java | 74 +++--- .../model/file/json/UrlSecurityJsonFile.java | 120 +++------ .../ide/url/updater/AbstractUrlUpdater.java | 23 +- .../tools/ide/url/updater/UpdateManager.java | 23 +- .../ide/context/AbstractIdeContextTest.java | 6 +- .../tools/ide/tool/ToolCommandletTest.java | 238 ++++++++++++++++++ pom.xml | 2 +- .../{Main.java => BuildSecurityJsonFile.java} | 217 ++++++++++------ .../devonfw/tools/security/UrlAnalyzer.java | 12 +- .../security/BuildSecurityJsonFileTest.java | 107 ++++++++ 12 files changed, 606 insertions(+), 228 deletions(-) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/tool/ToolCommandletTest.java rename security/src/main/java/com/devonfw/tools/security/{Main.java => BuildSecurityJsonFile.java} (56%) create mode 100644 security/src/test/java/com/devonfw/tools/security/BuildSecurityJsonFileTest.java diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java index 8c83a16a1..b4ae9822c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java @@ -47,7 +47,11 @@ protected boolean doInstall(boolean silent) { String edition = getEdition(); ToolRepository toolRepository = this.context.getDefaultToolRepository(); VersionIdentifier configuredVersion = getConfiguredVersion(); - VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion); + + VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion); + System.out.println("Selected version: " + selectedVersion); + + VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, edition, selectedVersion); // download and install the global tool FileAccess fileAccess = this.context.getFileAccess(); Path target = toolRepository.download(this.tool, edition, resolvedVersion); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java index 36e6c8566..a77363e49 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/LocalToolCommandlet.java @@ -60,13 +60,15 @@ public Path getToolBinPath() { protected boolean doInstall(boolean silent) { VersionIdentifier configuredVersion = getConfiguredVersion(); - // install configured version of our tool in the software repository if not already installed - ToolInstallation installation = installInRepo(configuredVersion); VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion); System.out.println("Selected version: " + selectedVersion); + // install configured version of our tool in the software repository if not already installed + ToolInstallation installation = installInRepo(selectedVersion); + + // check if we already have this version installed (linked) locally in IDE_HOME/software VersionIdentifier installedVersion = getInstalledVersion(); VersionIdentifier resolvedVersion = installation.resolvedVersion(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index abb5b1028..b64636914 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -16,15 +16,13 @@ import com.devonfw.tools.ide.environment.EnvironmentVariablesType; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.TarCompression; -import com.devonfw.tools.ide.json.mapping.JsonMapping; import com.devonfw.tools.ide.os.MacOsHelper; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.property.StringListProperty; -import com.devonfw.tools.ide.url.model.file.UrlSecurityFile; +import com.devonfw.tools.ide.url.model.file.json.UrlSecurityJsonFile; import com.devonfw.tools.ide.util.FilenameUtil; import com.devonfw.tools.ide.version.VersionIdentifier; -import com.fasterxml.jackson.databind.ObjectMapper; /** * {@link Commandlet} for a tool integrated into the IDE. @@ -68,7 +66,6 @@ public String getName() { } /** - * * @return the name of the binary */ protected String getBinaryName() { @@ -92,7 +89,7 @@ public void run() { * Ensures the tool is installed and then runs this tool with the given arguments. * * @param toolVersion the explicit version (pattern) to run. Typically {@code null} to ensure the configured version - * is installed and use that one. Otherwise the specified version will be installed in the software repository + * is installed and use that one. Otherwise, the specified version will be installed in the software repository * without touching and IDE installation and used to run. * @param args the commandline arguments to run the tool. */ @@ -174,7 +171,7 @@ public boolean install(boolean silent) { return doInstall(silent); } - protected String question(String question, String... options) { + protected String securityRiskInteractionQuestion(String question, String... options) { question += " Do you want to"; for (int i = 0; i < options.length - 1; i++) { @@ -184,18 +181,27 @@ protected String question(String question, String... options) { return this.context.question(question, options); } + /** + * Checks if the given {@link VersionIdentifier} has a matching security warning in the {@link UrlSecurityJsonFile}. + * + * @param configuredVersion the {@link VersionIdentifier} to be checked. + * @return the {@link VersionIdentifier} to be used for installation. If the configured version is safe or there are + * no save versions the potentially unresolved configured version is simply returned. Otherwise, a resolved version is + * returned. + */ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configuredVersion) { - // TODO vielleicht security file auch neu als json file wenn 1.2 > 2.9 nicht ausreicht - // TODO webpage:\nhttps://github.com/devonfw/ide/blob/master/documentation/vulnerabilities.asciidoc\n\n"; + // TODO maybe instead of returning current return configuredVersion if the users chooses "stay" - // TODO if no version is save, find a version that has lowest security risk, or suggest multiple ones, such that the - // user can choose + // TODO webpage:\nhttps://github.com/devonfw/ide/blob/master/documentation/vulnerabilities.asciidoc\n\n"; - UrlSecurityFile securityFile = this.context.getUrls().getEdition(this.tool, this.getEdition()).getSecurityFile(); - ObjectMapper mapper = JsonMapping.create(); + UrlSecurityJsonFile securityFile = this.context.getUrls().getEdition(this.tool, this.getEdition()) + .getSecurityJsonFile(); VersionIdentifier current = this.context.getUrls().getVersion(this.tool, this.getEdition(), configuredVersion); + // TODO oder doch eher sowas wie VersionIdentifier resolvedVersion = toolRepository.resolveVersion(this.tool, + // edition, selectedVersion); sollte immer das selbe ergeben + if (!securityFile.contains(current)) { return configuredVersion; } @@ -203,12 +209,8 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured List allVersions = this.context.getUrls().getSortedVersions(this.tool, this.getEdition()); VersionIdentifier latest = allVersions.get(0); - // currentVersion = VersionIdentifier.of("4.9.52"); - int currentVersionIndex = allVersions.indexOf(current); - // VersionIdentifier nextVersion = currentVersionIndex == 0 ? null : - // allVersions.get(allVersions.indexOf(currentVersion) - 1); VersionIdentifier nextSafe = null; for (int i = currentVersionIndex - 1; i >= 0; i--) { if (!securityFile.contains(allVersions.get(i))) { @@ -223,48 +225,50 @@ protected VersionIdentifier securityRiskInteraction(VersionIdentifier configured break; } } - VersionIdentifier previousSafe = null; - for (int i = currentVersionIndex + 1; i < allVersions.size(); i++) { - if (!securityFile.contains(allVersions.get(i))) { - previousSafe = allVersions.get(i); - break; - } - } String currentIsUnsafe = "Currently, version " + current + " of " + this.getName() + " is installed, " - + "which is has a vulnerability:\n" + " TODOODODO" + "\n\n"; + + "which is has a vulnerability:\n" + " TODO list vulnerability" + "\n\n (See also " + securityFile.getPath() + + ")"; - String stay = "stay with the current unsafe version (" + current + ")"; + String stay = "stay with the current unsafe version (" + current + ")"; String installLatestSafe = "install the latest safe version (" + latestSafe + ")"; String installSafeLatest = "install the (safe) latest version (" + latestSafe + ")"; String installNextSafe = "install the next safe version (" + nextSafe + ")"; + // I don't need to offer "install latest which is unsafe" as option since the user can set to the latest and choose "stay" + + if (latestSafe == null) { + this.context.warning(currentIsUnsafe + "There is no safe version available."); + return configuredVersion; + } if (current.equals(latest)) { - String answer = question(currentIsUnsafe, stay, installLatestSafe); + String answer = securityRiskInteractionQuestion(currentIsUnsafe + "There are no updates available.", stay, + installLatestSafe); return answer.startsWith(stay) ? current : latestSafe; } else if (nextSafe == null) { - // TODO also allow selection of next or previous version, even if they are unsafe? - String answer = question(currentIsUnsafe + " All newer versions are also not safe.", stay, installLatestSafe); + String answer = securityRiskInteractionQuestion(currentIsUnsafe + " All newer versions are also not safe.", stay, + installLatestSafe); return answer.startsWith(stay) ? current : latestSafe; } else if (nextSafe.equals(latest)) { - String answer = question(currentIsUnsafe + " Of the newer versions, only the latest is safe.", stay, - installSafeLatest); + String answer = securityRiskInteractionQuestion( + currentIsUnsafe + " Of the newer versions, only the latest is safe.", stay, installSafeLatest); return answer.startsWith(stay) ? current : latestSafe; } else if (nextSafe.equals(latestSafe)) { - String answer = question(currentIsUnsafe + " Of the newer versions, only the version " + nextSafe + " is safe.", - stay, "Install the safe version (" + nextSafe + ")"); + String answer = securityRiskInteractionQuestion( + currentIsUnsafe + " Of the newer versions, only the version " + nextSafe + + " is safe, Which is not the latest.", stay, "Install the safe version (" + nextSafe + ")"); return answer.startsWith(stay) ? current : nextSafe; } else { - if (latest.equals(latestSafe)) { - String answer = question(currentIsUnsafe, stay, installNextSafe, installSafeLatest); + if (latestSafe.equals(latest)) { + String answer = securityRiskInteractionQuestion(currentIsUnsafe, stay, installNextSafe, installSafeLatest); return answer.startsWith(stay) ? current : answer.startsWith(installNextSafe) ? nextSafe : latestSafe; } else { - String answer = question(currentIsUnsafe, stay, installNextSafe, installLatestSafe); + String answer = securityRiskInteractionQuestion(currentIsUnsafe, stay, installNextSafe, installLatestSafe); return answer.startsWith(stay) ? current : answer.startsWith(installNextSafe) ? nextSafe : latestSafe; } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java b/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java index d7305ca4a..d400e9f1f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlSecurityJsonFile.java @@ -2,10 +2,10 @@ import java.io.BufferedWriter; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,7 +29,7 @@ public class UrlSecurityJsonFile extends AbstractUrlFile { private static final Logger LOG = LoggerFactory.getLogger(UrlSecurityJsonFile.class); - List matches; + Set warnings; /** * The constructor. @@ -39,63 +39,62 @@ public class UrlSecurityJsonFile extends AbstractUrlFile { public UrlSecurityJsonFile(UrlEdition parent) { super(parent, FILENAME_SECURITY); - this.matches = new ArrayList<>(); + this.warnings = new HashSet<>(); } - public boolean addSecurityMatch(VersionRange versionRange, double severity, String severityVersion, String cveName, + /*** + * Adds a new security warning to the security json file. + * + * @param versionRange the version range, specifying the versions of the tool to which the security risk applies + * @param severity the severity of the security risk. + * @param severityVersion Indicating from which version the {@code severity} was obtained. As of December 2023, this + * is either v2 or v3. + * @param cveName the name of the CVE (Common Vulnerabilities and Exposures). + * @param description the description of the CVE. + * @param nistUrl the url to the CVE on the NIST website. + * @param referenceUrl the urls where additional information about the CVE can be found. + * @return {@code true} if the security match was added, {@code false} if it was already present. + */ + public boolean addSecurityWarning(VersionRange versionRange, BigDecimal severity, String severityVersion, String cveName, String description, String nistUrl, List referenceUrl) { - UrlSecurityWarning newWarning = new UrlSecurityWarning(severity, severityVersion, cveName, description, nistUrl, + UrlSecurityWarning newWarning = new UrlSecurityWarning(versionRange, severity, severityVersion, cveName, description, nistUrl, referenceUrl); - for (UrlSecurityMatch match : matches) { - if (match.getVersionRange().equals(versionRange)) { - boolean added = match.addWarning(newWarning); - this.modified = this.modified || added; - return added; - } - } - UrlSecurityMatch newMatch = new UrlSecurityMatch(versionRange); - newMatch.addWarning(newWarning); - this.modified = true; - return matches.add(newMatch); - } - - public boolean removeSecurityMatch(VersionRange versionRange) { - - for (UrlSecurityMatch match : matches) { - if (match.getVersionRange().equals(versionRange)) { - boolean removed = matches.remove(match); - this.modified = this.modified || removed; - return removed; - } - } - return false; + boolean added = warnings.add(newWarning); + this.modified = this.modified || added; + return added; } + /*** + * For a given version, returns whether there is a security risk by locking at the warnings in the security json file. + * + * @param version the version to check for security risks. + * @return {@code true} if there is a security risk for the given version, {@code false} otherwise. + */ public boolean contains(VersionIdentifier version) { - for (UrlSecurityMatch match : matches) { - if (match.getVersionRange().contains(version)) { + for (UrlSecurityWarning warning : this.warnings) { + if (warning.versionRange().contains(version)) { return true; } } return false; } - public Set getSecurityWarnings(VersionIdentifier version) { + public Set getMatchingSecurityWarnings(VersionIdentifier version) { - Set warnings = new HashSet<>(); - for (UrlSecurityMatch match : matches) { - if (match.getVersionRange().contains(version)) { - warnings.addAll(match.getWarnings()); + Set matchedWarnings = new HashSet<>(); + for (UrlSecurityWarning warning : this.warnings) { + if (warning.versionRange().contains(version)) { + matchedWarnings.add(warning); } } - return warnings; + return matchedWarnings; } - public void clearSecurityMatches() { + public void clearSecurityWarnings() { - this.matches.clear(); + this.warnings.clear(); } @Override @@ -106,7 +105,7 @@ protected void doLoad() { } ObjectMapper mapper = JsonMapping.create(); try { - matches = mapper.readValue(getPath().toFile(), new TypeReference>() { + warnings = mapper.readValue(getPath().toFile(), new TypeReference>() { }); } catch (IOException e) { throw new IllegalStateException("The UrlSecurityJsonFile " + getPath() + " could not be parsed.", e); @@ -119,13 +118,13 @@ protected void doSave() { Path path = getPath(); ObjectMapper mapper = JsonMapping.create(); - if (this.matches.isEmpty() && !Files.exists(path)) { + if (this.warnings.isEmpty() && !Files.exists(path)) { return; } String jsonString; try { - jsonString = mapper.writeValueAsString(matches); + jsonString = mapper.writeValueAsString(warnings); } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -139,43 +138,6 @@ protected void doSave() { } } -class UrlSecurityMatch { - private final VersionRange versionRange; - - private final Set warnings; - - public UrlSecurityMatch() { - - // this constructor is needed for jackson deserialization - this.versionRange = null; - this.warnings = new HashSet<>(); - } - - public UrlSecurityMatch(VersionRange versionRange) { - - this.versionRange = versionRange; - this.warnings = new HashSet<>(); - } - - public VersionRange getVersionRange() { - - return versionRange; - } - - public Set getWarnings() { - - return warnings; - } - - public boolean addWarning(UrlSecurityWarning warning) { - - return this.warnings.add(warning); - } - -} - -// severity could be java.math.BigDecimal; instead of double (unsing BigDecimal("123.4").setScale(1, -// BigDecimal.ROUND_HALF_UP);) -record UrlSecurityWarning(double severity, String severityVersion, String cveName, String description, String nistUrl, +record UrlSecurityWarning(VersionRange versionRange, BigDecimal severity, String severityVersion, String cveName, String description, String nistUrl, List referenceUrl) { }; \ No newline at end of file diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java index 7874bc47f..1fdac6c71 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/AbstractUrlUpdater.java @@ -97,22 +97,35 @@ protected final String getToolWithEdition() { return tool + "/" + edition; } - protected String getCpeVendor() { + /*** + * + * @return the vendor of the tool as specified in the CPE (Common Platform Enumeration) + */ + public String getCpeVendor() { return null; } - - protected String getCpeProduct() { + /*** + * @return the product name of the tool as specified in the CPE (Common Platform Enumeration) + */ + public String getCpeProduct() { return null; } - protected String getCpeEdition() { + /*** + * @return the edition of the tool as specified in the CPE (Common Platform Enumeration) + */ + public String getCpeEdition() { return null; } - protected String mapUrlVersionToCpeVersion(String version) { + /*** + * @return maps the version as specified by the directory name in the url repository to the version as specified in + * the CPE (Common Platform Enumeration). + */ + public String mapUrlVersionToCpeVersion(String version) { return version; } diff --git a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index 708e63304..7d7429f8b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -96,28 +96,9 @@ public void updateAll() { } } - public String getCpeVendor(String tool) { + public AbstractUrlUpdater getUrlUpdater(String tool) { - return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() - .map(AbstractUrlUpdater::getCpeVendor).orElse(null); - } - - public String getCpeProduct(String tool) { - - return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() - .map(AbstractUrlUpdater::getCpeProduct).orElse(null); - } - - public String getCpeEdition(String tool) { - - return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() - .map(AbstractUrlUpdater::getCpeEdition).orElse(null); - } - - public String mapUrlVersionToCpeVersion(String tool, String urlVersion) { - - return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst() - .map(updater -> updater.mapUrlVersionToCpeVersion(urlVersion)).orElse(null); + return updaters.stream().filter(updater -> updater.getTool().equals(tool)).findFirst().orElse(null); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java index ba671cff3..d92658ac7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java @@ -60,7 +60,7 @@ protected static IdeTestContext newContext(String projectName, String projectPat * in that project. * @return the {@link IdeTestContext} pointing to that project. */ - protected static IdeTestContext newContext(String projectName, String projectPath, boolean copyForMutation) { + protected static IdeTestContext newContext(String projectName, String projectPath, boolean copyForMutation, String ... answers) { Path sourceDir = PATH_PROJECTS.resolve(projectName); Path userDir = sourceDir; @@ -77,9 +77,9 @@ protected static IdeTestContext newContext(String projectName, String projectPat fileAccess.copy(sourceDir, projectDir, FileCopyMode.COPY_TREE_OVERRIDE_TREE); fileAccess.copy(PATH_PROJECTS.resolve(IdeContext.FOLDER_IDE), PATH_PROJECTS_COPY.resolve(IdeContext.FOLDER_IDE), FileCopyMode.COPY_TREE_OVERRIDE_TREE); - context = new IdeTestContext(projectDir.resolve(projectPath)); + context = new IdeTestContext(projectDir.resolve(projectPath), answers); } else { - context = new IdeTestContext(userDir); + context = new IdeTestContext(userDir, answers); } return context; } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/ToolCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/ToolCommandletTest.java new file mode 100644 index 000000000..da3da87e7 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/ToolCommandletTest.java @@ -0,0 +1,238 @@ +package com.devonfw.tools.ide.tool; + +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.url.model.file.json.UrlSecurityJsonFile; +import com.devonfw.tools.ide.version.VersionRange; +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.az.Azure; +import com.devonfw.tools.ide.version.VersionIdentifier; + +import java.nio.file.Path; + +/*** + * Test of {@link ToolCommandlet}. + */ +public class ToolCommandletTest extends AbstractIdeContextTest { + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where the set version is the latest but + * vulnerable. + */ + @Test + public void testSecurityRiskInteractionCurrentIsLatest() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("2>5"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("7>9"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("*"))).isEqualTo(VersionIdentifier.of("9")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("*"))).isEqualTo(VersionIdentifier.of("6")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where there are no newer versions that + * are safe, but there is a previous version that is safe. + */ + @Test + public void testSecurityRiskInteractionNextSafeIsNull() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("3>3"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("6>7"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("8>"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("6"))).isEqualTo(VersionIdentifier.of("6")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("6"))).isEqualTo(VersionIdentifier.of("5")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where the next safe version is also the + * latest. + */ + @Test + public void testSecurityRiskInteractionNextSafeIsLatest() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("3>3"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("6>7"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("8>8"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("7"))).isEqualTo(VersionIdentifier.of("7")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("7"))).isEqualTo(VersionIdentifier.of("9")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where the next safe version is also the + * latest safe version, and the overall latest version is not safe. + */ + @Test + public void testSecurityRiskInteractionNextSafeIsLatestSafe() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("3>3"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("5>6"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("8>9"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("5"))).isEqualTo(VersionIdentifier.of("5")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("5"))).isEqualTo(VersionIdentifier.of("7")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where the next safe version differs from + * the latest safe, which is also the overall latest version. + */ + @Test + public void testSecurityRiskInteractionLatestSafeDiffersFromNextSafeButIsLatest() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2", "3" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("3>3"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("5>6"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("8>8"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("5"))).isEqualTo(VersionIdentifier.of("5")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("5"))).isEqualTo(VersionIdentifier.of("7")); + // answer to the interaction is 3 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("5"))).isEqualTo(VersionIdentifier.of("9")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where the next safe version differs from + * the latest safe, and the overall latest version is not safe. + */ + @Test + public void testSecurityRiskInteractionLatestSafeDiffersFromNextSafeAndLatest() { + + // arrange + Class dummyTool = Azure.class; + String[] answers = { "1", "2", "3" }; + IdeContext context = getContextForSecurityJsonTests(dummyTool, answers); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("3>3"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("6>6"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("8>9"), null, null, null, null, null, null); + + // act & assert + // answer to the interaction is 1 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("3"))).isEqualTo(VersionIdentifier.of("3")); + // answer to the interaction is 2 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("3"))).isEqualTo(VersionIdentifier.of("4")); + // answer to the interaction is 3 + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("3"))).isEqualTo(VersionIdentifier.of("7")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where set version is safe. + */ + @Test + public void testSecurityRiskInteractionCurrentVersionIsSafe() { + + // arrange + Class dummyTool = Azure.class; + IdeContext context = getContextForSecurityJsonTests(dummyTool); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("1>5"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("7>8"), null, null, null, null, null, null); + + // act & assert + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("6"))).isEqualTo(VersionIdentifier.of("6")); + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("9"))).isEqualTo(VersionIdentifier.of("9")); + } + + /*** + * Test of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)} where no safe version is available. + */ + @Test + public void testSecurityRiskInteractionNoSafeVersionFound() { + + // arrange + Class dummyTool = Azure.class; + IdeContext context = getContextForSecurityJsonTests(dummyTool); + ToolCommandlet tool = context.getCommandletManager().getCommandlet(dummyTool); + UrlSecurityJsonFile securityFile = context.getUrls().getEdition(tool.getName(), tool.getEdition()) + .getSecurityJsonFile(); + securityFile.addSecurityWarning(VersionRange.of("1>5"), null, null, null, null, null, null); + securityFile.addSecurityWarning(VersionRange.of("6>"), null, null, null, null, null, null); + + // act & assert + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("6"))).isEqualTo(VersionIdentifier.of("6")); + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("1"))).isEqualTo(VersionIdentifier.of("1")); + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("9"))).isEqualTo(VersionIdentifier.of("9")); + assertThat(tool.securityRiskInteraction(VersionIdentifier.of("*"))).isEqualTo(VersionIdentifier.of("*")); + } + + /*** + * Creates the context and data for the tests of {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)}. + * + * @param dummyTool the dummy tool to be used for the tests. The {@link com.devonfw.tools.ide.url.model.folder.UrlVersion folders} + * representing the versions of the dummy tool are created here. + * @param answers the answers to be used for the interaction in {@link ToolCommandlet#securityRiskInteraction(VersionIdentifier)}. + * @return the {@link IdeTestContext} to be used for the tests. + */ + private IdeContext getContextForSecurityJsonTests(Class dummyTool, String... answers) { + + String path = "workspaces/foo-test/my-git-repo"; + // if I don't pass answers here I get: End of answers reached! + IdeContext context = newContext("basic", path, true, answers); + ToolCommandlet toolCommandlet = context.getCommandletManager().getCommandlet(dummyTool); + Path eitionPath = context.getUrlsPath().resolve(toolCommandlet.getName()).resolve(toolCommandlet.getEdition()); + context.getFileAccess().delete(eitionPath); // I want to define my own versions for simplicity + for (int i = 1; i < 10; i++) { + context.getFileAccess().mkdirs(eitionPath.resolve(String.valueOf(i))); + } + return context; + } +} + + diff --git a/pom.xml b/pom.xml index a432552af..36ed794ef 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ documentation cli - security + security diff --git a/security/src/main/java/com/devonfw/tools/security/Main.java b/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFile.java similarity index 56% rename from security/src/main/java/com/devonfw/tools/security/Main.java rename to security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFile.java index d78fff2e5..345960e74 100644 --- a/security/src/main/java/com/devonfw/tools/security/Main.java +++ b/security/src/main/java/com/devonfw/tools/security/BuildSecurityJsonFile.java @@ -1,11 +1,15 @@ package com.devonfw.tools.security; +import java.math.BigDecimal; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; +import java.util.ArrayList; +import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.AnalysisPhase; import org.owasp.dependencycheck.analyzer.FileNameAnalyzer; @@ -27,15 +31,15 @@ import com.devonfw.tools.ide.version.VersionIdentifier; import com.devonfw.tools.ide.version.VersionRange; -public class Main { +public class BuildSecurityJsonFile { - private static final Logger logger = LoggerFactory.getLogger(Main.class); + private static final Logger logger = LoggerFactory.getLogger(BuildSecurityJsonFile.class); private static final String CVE_BASE_URL = "https://nvd.nist.gov/vuln/detail/"; - private static double minV2Severity; + private static BigDecimal minV2Severity; - private static double minV3Severity; + private static BigDecimal minV3Severity; public static void main(String[] args) { @@ -43,18 +47,19 @@ public static void main(String[] args) { throw new RuntimeException("Please provide 2 numbers: minV2Severity and minV3Severity"); } try { - minV2Severity = Double.parseDouble(args[0]); - minV3Severity = Double.parseDouble(args[1]); + minV2Severity = new BigDecimal(String.format(args[0])); + minV3Severity = new BigDecimal(String.format(args[1])); } catch (NumberFormatException e) { - throw new RuntimeException("These two args could not be parsed as double"); + throw new RuntimeException("These two args could not be parsed as BigDecimal"); } - run(minV2Severity, minV3Severity); + run(); } - private static void run(double minV2Severity, double minV3Severity) { + private static void run() { IdeContext ideContext = new IdeContextConsole(IdeLogLevel.INFO, null, false); + UpdateManager updateManager = new UpdateManager(ideContext.getUrlsPath(), null); // TODO edit dependency check properties file to switch off analysers, this file is currently read only // TODO maybe this can be done in pom.xml @@ -65,7 +70,7 @@ private static void run(double minV2Severity, double minV3Severity) { // TODO ~/.m2/repository/org/owasp/dependency-check-utils/8.4.2/data/7.0/odc.update.lock // why is this not in projects dir but in user dir? - Dependency[] dependencies = getDependenciesWithVulnerabilities(ideContext); + Dependency[] dependencies = getDependenciesWithVulnerabilities(updateManager); for (Dependency dependency : dependencies) { @@ -73,20 +78,50 @@ private static void run(double minV2Severity, double minV3Severity) { Path parent = Paths.get(filePath).getParent(); String tool = parent.getParent().getParent().getFileName().toString(); String edition = parent.getParent().getFileName().toString(); + AbstractUrlUpdater urlUpdater = updateManager.getUrlUpdater(tool); UrlSecurityJsonFile securityFile = ideContext.getUrls().getEdition(tool, edition).getSecurityJsonFile(); - securityFile.clearSecurityMatches(); + + // TODO maybe instead of clear check cve name and add only if cve name is not already present + // TODO if new min security is higher than the severity in the loaded file, then remove the old one? + + // TODO wenn dieses repo auch als nightly laufen soll, wo sollen dann die min severity werte herkommen? + securityFile.clearSecurityWarnings(); List sortedVersions = ideContext.getUrls().getSortedVersions(tool, edition); + List sortedCpeVersions = sortedVersions.stream().map(VersionIdentifier::toString) + .map(urlUpdater::mapUrlVersionToCpeVersion).map(VersionIdentifier::of) + .collect(Collectors.toCollection(ArrayList::new)); Set vulnerabilities = dependency.getVulnerabilities(true); for (Vulnerability vulnerability : vulnerabilities) { - addVulnerabilityToSecurityFile(vulnerability, securityFile, sortedVersions); + addVulnerabilityToSecurityFile(vulnerability, securityFile, sortedCpeVersions); } securityFile.save(); } } + private static Dependency[] getDependenciesWithVulnerabilities(UpdateManager updateManager) { + + Settings settings = new Settings(); + // Using "try with resource" or engine.close() at the end resulted in SEVERE warning by owasp + Engine engine = new Engine(settings); + FileTypeAnalyzer myAnalyzer = new UrlAnalyzer(updateManager); + engine.getFileTypeAnalyzers().add(myAnalyzer); + engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).add(myAnalyzer); + engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).removeIf(analyze -> analyze instanceof FileNameAnalyzer); + + // engine.scan(ideContext.getUrlsPath().toString()); + engine.scan("C:\\projects\\_ide\\myUrls"); + + try { + engine.analyzeDependencies(); + } catch (ExceptionCollection e) { + throw new RuntimeException(e); + } + return engine.getDependencies(); + } + private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, UrlSecurityJsonFile securityFile, List sortedVersions) { @@ -94,7 +129,11 @@ private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, throw new RuntimeException("Vulnerability without severity found: " + vulnerability.getName()); } boolean hasV3Severity = vulnerability.getCvssV3() != null; - double severity = hasV3Severity ? vulnerability.getCvssV3().getBaseScore() : vulnerability.getCvssV2().getScore(); + double severityDouble = hasV3Severity + ? vulnerability.getCvssV3().getBaseScore() + : vulnerability.getCvssV2().getScore(); + String formatted = String.format(Locale.US, "%.1f", severityDouble); + BigDecimal severity = new BigDecimal(formatted); String severityVersion = hasV3Severity ? "v3" : "v2"; String cveName = vulnerability.getName(); String description = vulnerability.getDescription(); @@ -104,7 +143,10 @@ private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, if (referenceUrls.isEmpty()) { referenceUrls.add("No references found, try searching for the CVE name (" + cveName + ") on the web."); } - boolean toLowSeverity = hasV3Severity ? severity < minV3Severity : severity < minV2Severity; + boolean toLowSeverity = hasV3Severity + ? severity.compareTo(minV3Severity) < 0 + : severity.compareTo(minV2Severity) < 0; + if (toLowSeverity) { return; } @@ -112,99 +154,120 @@ private static void addVulnerabilityToSecurityFile(Vulnerability vulnerability, if (versionRange == null) { logger.info( "Vulnerability {} is not relevant because its affected versions have no overlap with the versions available " - + "through IDEasy.", - vulnerability.getName()); + + "through IDEasy.", vulnerability.getName()); return; } - securityFile.addSecurityMatch(versionRange, severity, severityVersion, cveName, description, nistUrl, + securityFile.addSecurityWarning(versionRange, severity, severityVersion, cveName, description, nistUrl, referenceUrls); } - private static Dependency[] getDependenciesWithVulnerabilities(IdeContext ideContext) { - - Settings settings = new Settings(); - // Using try with resource or engine.close at the end resulted in SEVERE warning by owasp - Engine engine = new Engine(settings); - UpdateManager updateManager = new UpdateManager(ideContext.getUrlsPath(), null); - FileTypeAnalyzer myAnalyzer = new UrlAnalyzer(updateManager); - engine.getFileTypeAnalyzers().add(myAnalyzer); - engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).add(myAnalyzer); - engine.getAnalyzers(AnalysisPhase.INFORMATION_COLLECTION).removeIf(analyze -> analyze instanceof FileNameAnalyzer); + /*** + * From the vulnerability determine the {@link VersionRange versionRange} to which the vulnerability applies. + * + * @param sortedVersions sorted versions of the tool available through IDEasy. Must match the format of the versions + * in the vulnerability. See {@link AbstractUrlUpdater#mapUrlVersionToCpeVersion(String)}. + * @param vulnerability the vulnerability determined by OWASP dependency check. + * @return the {@link VersionRange versionRange} to which the vulnerability applies. + */ + static VersionRange getVersionRangeFromVulnerability(List sortedVersions, + Vulnerability vulnerability) { - // engine.scan(ideContext.getUrlsPath().toString()); - engine.scan("C:\\projects\\_ide\\myUrls"); + VulnerableSoftware matchedVulnerableSoftware = vulnerability.getMatchedVulnerableSoftware(); + String vEndExcluding = matchedVulnerableSoftware.getVersionEndExcluding(); + String vEndIncluding = matchedVulnerableSoftware.getVersionEndIncluding(); + String vStartExcluding = matchedVulnerableSoftware.getVersionStartExcluding(); + String vStartIncluding = matchedVulnerableSoftware.getVersionStartIncluding(); - try { - engine.analyzeDependencies(); - } catch (ExceptionCollection e) { - throw new RuntimeException(e); + if (vEndExcluding == null && vEndIncluding == null && vStartExcluding == null && vStartIncluding == null) { + return VersionRange.of(">"); } - return engine.getDependencies(); + + return getVersionRangeFromInterval(sortedVersions, vStartExcluding, vStartIncluding, vEndIncluding, vEndExcluding); } + /*** + * From the interval determine the {@link VersionRange versionRange} to which the vulnerability applies. Since the + * versions as specified in the vulnerability might not be in the {@code sortedVersions} list, the {@link VersionRange} + * is determined by finding the versions in the {@code sortedVersions} list that, when selected, cover all affected + * versions correctly. + */ static VersionRange getVersionRangeFromInterval(List sortedVersions, String vStartExcluding, String vStartIncluding, String vEndIncluding, String vEndExcluding) { - VersionIdentifier max = null; - if (vEndIncluding != null) { - max = VersionIdentifier.of(vEndIncluding); // this allows that max is not part of the available versions, this has - // no impact on the contains method but maybe confusing - } else if (vEndExcluding != null) { - VersionIdentifier end = VersionIdentifier.of(vEndExcluding); - for (VersionIdentifier version : sortedVersions) { - if (version.isLess(end)) { - - // TODO here the version from the name in url dir is v.2.7.0 for example and end is 2.7.2 which should be - // smaller but is not - // sinvce the v is there, i either have to map the sorted versions and remove the v or add the v to "end" - max = version; - break; - } + VersionIdentifier min = null; + if (vStartExcluding != null) { + min = findMinFromStartExcluding(sortedVersions, vStartExcluding); + if (min == null) { + return null; } - if (max == null) { // vEndExcluding is smaller or equal than all available versions -> this vulnerability is not - // relevant and just leaving max to be null could result in a version range like ">" meaning all versions are - // effected, which is wrong. + } else if (vStartIncluding != null) { + min = findMinFromStartIncluding(sortedVersions, vStartIncluding); + if (min == null) { return null; } } - VersionIdentifier min = null; - if (vStartIncluding != null) { - min = VersionIdentifier.of(vStartIncluding); - } else if (vStartExcluding != null) { - for (int i = sortedVersions.size() - 1; i >= 0; i--) { - VersionIdentifier version = sortedVersions.get(i); - if (version.isGreater(VersionIdentifier.of(vStartExcluding))) { - min = version; - break; - } + VersionIdentifier max = null; + if (vEndIncluding != null) { + max = findMaxFromEndIncluding(sortedVersions, vEndIncluding); + if (max == null) { + return null; } - if (min == null) { // vStartExcluding is greater or equal than all available versions -> this vulnerability is not - // relevant and just leaving min to be null could result in a version range like ">" meaning all versions are - // effected, which is wrong. + } else if (vEndExcluding != null) { + max = findMaxFromEndExcluding(sortedVersions, vEndExcluding); + if (max == null) { return null; } } return new VersionRange(min, max); } - static VersionRange getVersionRangeFromVulnerability(List sortedVersions, - Vulnerability vulnerability) { + private static VersionIdentifier findMinFromStartExcluding(List sortedVs, String vStartExcluding) { - VulnerableSoftware matchedVulnerableSoftware = vulnerability.getMatchedVulnerableSoftware(); - String vEndExcluding = matchedVulnerableSoftware.getVersionEndExcluding(); - String vEndIncluding = matchedVulnerableSoftware.getVersionEndIncluding(); - String vStartExcluding = matchedVulnerableSoftware.getVersionStartExcluding(); - String vStartIncluding = matchedVulnerableSoftware.getVersionStartIncluding(); + VersionIdentifier startExcl = VersionIdentifier.of(vStartExcluding); + for (int i = sortedVs.size() - 1; i >= 0; i--) { + VersionIdentifier version = sortedVs.get(i); + if (version.isGreater(startExcl)) { + return version; + } + } + return null; + } - if (vEndExcluding == null && vEndIncluding == null && vStartExcluding == null && vStartIncluding == null) { - // maybe instead all versions are vulnerable in this case - return VersionRange.of(">"); - // throw new RuntimeException("Vulnerability without version range found: " + vulnerability.getName()); + private static VersionIdentifier findMinFromStartIncluding(List sortedVs, String vStartIncluding) { + + VersionIdentifier startIncl = VersionIdentifier.of(vStartIncluding); + for (int i = sortedVs.size() - 1; i >= 0; i--) { + VersionIdentifier version = sortedVs.get(i); + if (version.compareTo(startIncl) >= 0) { + return version; + } } + return null; + } - return getVersionRangeFromInterval(sortedVersions, vStartExcluding, vStartIncluding, vEndIncluding, vEndExcluding); + private static VersionIdentifier findMaxFromEndIncluding(List sortedVs, String vEndIncluding) { + + VersionIdentifier endIncl = VersionIdentifier.of(vEndIncluding); + for (VersionIdentifier version : sortedVs) { + if (version.compareTo(endIncl) <= 0) { + return version; + } + } + return null; + } + + private static VersionIdentifier findMaxFromEndExcluding(List sortedVs, String vEndExcluding) { + + VersionIdentifier endExl = VersionIdentifier.of(vEndExcluding); + for (VersionIdentifier version : sortedVs) { + if (version.isLess(endExl)) { + return version; + } + } + return null; } + } diff --git a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java index a85985846..09ffca6a5 100644 --- a/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java +++ b/security/src/main/java/com/devonfw/tools/security/UrlAnalyzer.java @@ -4,6 +4,8 @@ import java.nio.file.Path; import java.nio.file.Paths; +import com.devonfw.tools.ide.url.updater.AbstractUrlUpdater; +import com.devonfw.tools.ide.url.updater.UrlUpdater; import org.owasp.dependencycheck.Engine; import org.owasp.dependencycheck.analyzer.AbstractFileTypeAnalyzer; import org.owasp.dependencycheck.analyzer.AnalysisPhase; @@ -39,10 +41,12 @@ protected void analyzeDependency(Dependency dependency, Engine engine) throws An String tool = parent.getParent().getParent().getFileName().toString(); String edition = parent.getParent().getFileName().toString(); + AbstractUrlUpdater urlUpdater = updateManager.getUrlUpdater(tool); + String source = "UrlAnalyzer"; // adding vendor evidence - String vendor = updateManager.getCpeVendor(tool); + String vendor = urlUpdater.getCpeVendor(); Evidence evidence; if (vendor == null) { vendor = tool; @@ -51,7 +55,7 @@ protected void analyzeDependency(Dependency dependency, Engine engine) throws An dependency.addEvidence(EvidenceType.VENDOR, evidence); // adding product evidence - String product = updateManager.getCpeProduct(tool); + String product = urlUpdater.getCpeProduct(); if (product == null) { // for the product it is reasonable to assume that "tool" is the product in most cases product = tool; } @@ -59,14 +63,14 @@ protected void analyzeDependency(Dependency dependency, Engine engine) throws An dependency.addEvidence(EvidenceType.PRODUCT, evidence); // adding edition evidence - String editionEvidence = updateManager.getCpeEdition(tool); + String editionEvidence = urlUpdater.getCpeEdition(); if (editionEvidence != null) { evidence = new Evidence(source, "CpeEdition", editionEvidence, Confidence.HIGH); dependency.addEvidence(EvidenceType.PRODUCT, evidence); } // adding version evidence - String version = updateManager.mapUrlVersionToCpeVersion(tool, parent.getFileName().toString()); + String version = urlUpdater.mapUrlVersionToCpeVersion(parent.getFileName().toString()); evidence = new Evidence(source, "CpeVersion", version, Confidence.HIGH); dependency.addEvidence(EvidenceType.VERSION, evidence); } diff --git a/security/src/test/java/com/devonfw/tools/security/BuildSecurityJsonFileTest.java b/security/src/test/java/com/devonfw/tools/security/BuildSecurityJsonFileTest.java new file mode 100644 index 000000000..d8f926483 --- /dev/null +++ b/security/src/test/java/com/devonfw/tools/security/BuildSecurityJsonFileTest.java @@ -0,0 +1,107 @@ +package com.devonfw.tools.security; + +import com.devonfw.tools.ide.version.VersionIdentifier; +import com.devonfw.tools.ide.version.VersionRange; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.devonfw.tools.security.BuildSecurityJsonFile.getVersionRangeFromInterval; + +public class BuildSecurityJsonFileTest extends Assertions { + + /*** + * Test of {@link BuildSecurityJsonFile#getVersionRangeFromInterval(List, String, String, String, String)} and passing + * vStartExcluding and null for the other parameters . + */ + @Test + public void testGetVersionRangeFromIntervalStartExcluding() { + + // arrange + List v = getSortedVersions(); + + // act & assert + assertThat(getVersionRangeFromInterval(v, null, null, null, null)).isEqualTo(VersionRange.of(">")); + assertThat(getVersionRangeFromInterval(v, "1", null, null, null)).isEqualTo(VersionRange.of("1.2.3>")); + assertThat(getVersionRangeFromInterval(v, "1.2.3", null, null, null)).isEqualTo(VersionRange.of("1.2.4>")); + assertThat(getVersionRangeFromInterval(v, "1.4", null, null, null)).isEqualTo(VersionRange.of("2.0>")); + assertThat(getVersionRangeFromInterval(v, "1.5", null, null, null)).isEqualTo(VersionRange.of("2.0>")); + assertThat(getVersionRangeFromInterval(v, "2.1", null, null, null)).isNull(); + assertThat(getVersionRangeFromInterval(v, "2.2", null, null, null)).isNull(); + + } + /*** + * Test of {@link BuildSecurityJsonFile#getVersionRangeFromInterval(List, String, String, String, String)} and passing + * vStartIncluding and null for the other parameters . + */ + @Test + public void testGetVersionRangeFromIntervalStartIncluding() { + + // arrange + List v = getSortedVersions(); + + // act & assert + assertThat(getVersionRangeFromInterval(v, null, "1", null, null)).isEqualTo(VersionRange.of("1.2.3>")); + assertThat(getVersionRangeFromInterval(v, null, "1.2.3", null, null)).isEqualTo(VersionRange.of("1.2.3>")); + assertThat(getVersionRangeFromInterval(v, null, "1.4", null, null)).isEqualTo(VersionRange.of("1.4>")); + assertThat(getVersionRangeFromInterval(v, null, "1.5", null, null)).isEqualTo(VersionRange.of("2.0>")); + assertThat(getVersionRangeFromInterval(v, null, "2.1", null, null)).isEqualTo(VersionRange.of("2.1>")); + assertThat(getVersionRangeFromInterval(v, null, "2.2", null, null)).isNull(); + + } + + /*** + * Test of {@link BuildSecurityJsonFile#getVersionRangeFromInterval(List, String, String, String, String)} and passing + * vEndIncluding and null for the other parameters . + */ + @Test + public void testGetVersionRangeFromIntervalEndIncluding() { + + // arrange + List v = getSortedVersions(); + + // act & assert + assertThat(getVersionRangeFromInterval(v, null, null, "1", null)).isNull(); + assertThat(getVersionRangeFromInterval(v, null, null, "1.2.3", null)).isEqualTo(VersionRange.of(">1.2.3")); + assertThat(getVersionRangeFromInterval(v, null, null, "1.4", null)).isEqualTo(VersionRange.of(">1.4")); + assertThat(getVersionRangeFromInterval(v, null, null, "1.5", null)).isEqualTo(VersionRange.of(">1.4")); + assertThat(getVersionRangeFromInterval(v, null, null, "2.1", null)).isEqualTo(VersionRange.of(">2.1")); + assertThat(getVersionRangeFromInterval(v, null, null, "2.2", null)).isEqualTo(VersionRange.of(">2.1")); + + } + + /*** + * Test of {@link BuildSecurityJsonFile#getVersionRangeFromInterval(List, String, String, String, String)} and passing + * vEndExcluding and null for the other parameters . + */ + @Test + public void testGetVersionRangeFromIntervalEndExcluding() { + + // arrange + List v = getSortedVersions(); + + // act & assert + assertThat(getVersionRangeFromInterval(v, null, null, null, " 1")).isNull(); + assertThat(getVersionRangeFromInterval(v, null, null, null, "1.2.3")).isNull(); + assertThat(getVersionRangeFromInterval(v, null, null, null, "1.4")).isEqualTo(VersionRange.of(">1.3")); + assertThat(getVersionRangeFromInterval(v, null, null, null, "1.5")).isEqualTo(VersionRange.of(">1.4")); + assertThat(getVersionRangeFromInterval(v, null, null, null, "2.1")).isEqualTo(VersionRange.of(">2.0")); + assertThat(getVersionRangeFromInterval(v, null, null, null, "2.2")).isEqualTo(VersionRange.of(">2.1")); + + } + + private static List getSortedVersions() { + + List sortedVersions = new ArrayList<>(); + sortedVersions.add(VersionIdentifier.of("2.1")); + sortedVersions.add(VersionIdentifier.of("2.0")); + sortedVersions.add(VersionIdentifier.of("1.4")); + sortedVersions.add(VersionIdentifier.of("1.3")); + sortedVersions.add(VersionIdentifier.of("1.2.5")); + sortedVersions.add(VersionIdentifier.of("1.2.4")); + sortedVersions.add(VersionIdentifier.of("1.2.3")); + return sortedVersions; + } +}