Skip to content

Commit

Permalink
devonfw#103: test interaction and getVersionRangeFromInterval
Browse files Browse the repository at this point in the history
  • Loading branch information
MattesMrzik committed Dec 5, 2023
1 parent ae0558b commit ba87b95
Show file tree
Hide file tree
Showing 12 changed files with 606 additions and 228 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
74 changes: 39 additions & 35 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -68,7 +66,6 @@ public String getName() {
}

/**
*
* @return the name of the binary
*/
protected String getBinaryName() {
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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++) {
Expand All @@ -184,31 +181,36 @@ 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;
}

List<VersionIdentifier> 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))) {
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,7 +29,7 @@ public class UrlSecurityJsonFile extends AbstractUrlFile<UrlEdition> {

private static final Logger LOG = LoggerFactory.getLogger(UrlSecurityJsonFile.class);

List<UrlSecurityMatch> matches;
Set<UrlSecurityWarning> warnings;

/**
* The constructor.
Expand All @@ -39,63 +39,62 @@ public class UrlSecurityJsonFile extends AbstractUrlFile<UrlEdition> {
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<String> 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<UrlSecurityWarning> getSecurityWarnings(VersionIdentifier version) {
public Set<UrlSecurityWarning> getMatchingSecurityWarnings(VersionIdentifier version) {

Set<UrlSecurityWarning> warnings = new HashSet<>();
for (UrlSecurityMatch match : matches) {
if (match.getVersionRange().contains(version)) {
warnings.addAll(match.getWarnings());
Set<UrlSecurityWarning> 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
Expand All @@ -106,7 +105,7 @@ protected void doLoad() {
}
ObjectMapper mapper = JsonMapping.create();
try {
matches = mapper.readValue(getPath().toFile(), new TypeReference<List<UrlSecurityMatch>>() {
warnings = mapper.readValue(getPath().toFile(), new TypeReference<Set<UrlSecurityWarning>>() {
});
} catch (IOException e) {
throw new IllegalStateException("The UrlSecurityJsonFile " + getPath() + " could not be parsed.", e);
Expand All @@ -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);
}
Expand All @@ -139,43 +138,6 @@ protected void doSave() {
}
}

class UrlSecurityMatch {
private final VersionRange versionRange;

private final Set<UrlSecurityWarning> 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<UrlSecurityWarning> 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<String> referenceUrl) {
};
Loading

0 comments on commit ba87b95

Please sign in to comment.