Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#103: security warning for CVEs in file tool/edition/security #119

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2237158
#103: security warning for CVEs in file tool/edition/security
MattesMrzik Oct 25, 2023
897b800
promting the user for vulnerabilities and start of Main for OWASP check
MattesMrzik Nov 8, 2023
59f6a1b
merged main into feature 103
MattesMrzik Nov 8, 2023
5f162e2
moved owasp dep from idecli pom to security pom, mor work in Main
MattesMrzik Nov 14, 2023
e5c70e7
#103: url analyzer works and vulnerabilities are retrieved
MattesMrzik Nov 15, 2023
34febf5
#103: Security Entry and determination of version Range for vulnerabi…
MattesMrzik Nov 16, 2023
ae0558b
#103: writing security json file
MattesMrzik Nov 24, 2023
ba87b95
#103: test interaction and getVersionRangeFromInterval
MattesMrzik Dec 5, 2023
ba694ab
#103: refinements
MattesMrzik Dec 6, 2023
81b8586
#103: more refinement
MattesMrzik Dec 14, 2023
9574f8d
#158: VersionRange with open boundaries
MattesMrzik Dec 18, 2023
7e2023e
Merge remote-tracking branch 'upstream/main' into feature/#103-implem…
MattesMrzik Dec 19, 2023
64c8454
Merge remote-tracking branch 'origin/feature/#158-version-range-with-…
MattesMrzik Dec 19, 2023
5518138
#103: removed duplicate VersionRange.equals
MattesMrzik Dec 19, 2023
4fbef6e
#103: versionRange with open interval
MattesMrzik Dec 19, 2023
1b9224b
#103: updated urlSecJsonFile.contains
MattesMrzik Dec 19, 2023
9a86e34
#103: rephrase interaction, mapUtil, LICENCE
MattesMrzik Dec 21, 2023
fe9109f
#103: moved urlSecurityJson to its own class
MattesMrzik Dec 22, 2023
b19b877
#103: fixed write json bug, and more
MattesMrzik Dec 22, 2023
fd64100
#103: some final cleanup
MattesMrzik Jan 2, 2024
312afdd
#103: updated to be in line with #158
MattesMrzik Jan 2, 2024
37122ff
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MattesMrzik Jan 20, 2024
9b28679
#103: fixed small bug due to merged main
MattesMrzik Jan 20, 2024
1389057
#103: fixed bugs
MattesMrzik Jan 20, 2024
98b3da3
#103: fixed bug
MattesMrzik Jan 20, 2024
06cc433
#103: added logging test
MattesMrzik Jan 21, 2024
80ab231
#103: refactored code
MattesMrzik Jan 21, 2024
a26df56
103: first half of team review
MattesMrzik Jan 21, 2024
dbee293
#103: more change requests from team review and bug fix
MattesMrzik Jan 21, 2024
ea1bb26
Update cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
MattesMrzik Jan 22, 2024
b02bfcf
#103: checkpoint to not accidentally lose progress
MattesMrzik Jan 23, 2024
fb6b842
Merge branch 'feature/#103-implement-version-security-checks' of http…
MattesMrzik Jan 23, 2024
0c54094
#103: fixed intellij updater test
MattesMrzik Jan 23, 2024
55f139c
#103: done with change requests
MattesMrzik Jan 24, 2024
d69bea7
Merge branch 'main' of https://www.github.com/devonfw/IDEasy into fea…
MattesMrzik Jan 25, 2024
f34fc22
Update security/src/main/java/com/devonfw/tools/security/BuildSecurit…
MattesMrzik Jan 25, 2024
1c66c77
#103: small fix
MattesMrzik Jan 25, 2024
c78aad4
Merge branch 'feature/#103-implement-version-security-checks' of http…
MattesMrzik Jan 25, 2024
6a20d3c
#103: added tests
MattesMrzik Jan 25, 2024
cbe086d
#103: test for UrlSecurityJson
MattesMrzik Jan 25, 2024
20fecc3
#103: last small changes
MattesMrzik Jan 26, 2024
47ae5b7
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 19, 2024
ae52292
#103: code reformat & cleanup
jan-vcapgemini Feb 19, 2024
f66c7ea
#103: implemented requested changes
jan-vcapgemini Feb 19, 2024
7628cc9
#103: applied reformat
jan-vcapgemini Feb 19, 2024
db6e276
#103: implemented requested changes
jan-vcapgemini Feb 19, 2024
6da9066
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 22, 2024
2862e6b
#103: fixed merge issues
jan-vcapgemini Feb 22, 2024
be3ec96
#103: some fixes
jan-vcapgemini Feb 22, 2024
097bbdc
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 23, 2024
a7d686c
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
a299504
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
0f3596f
#103: implemented requested changes
jan-vcapgemini Feb 23, 2024
30d5bf2
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 26, 2024
69e1fdd
#103: fixed intellij and vscode
jan-vcapgemini Feb 29, 2024
4d6766c
#103: fixed NPEs and other issues
jan-vcapgemini Feb 29, 2024
f162e09
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Feb 29, 2024
3834ce8
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Mar 28, 2024
ba4bc07
#103 fixed tests
jan-vcapgemini Apr 2, 2024
d794e67
Merge branch 'main' into feature/#103-implement-version-security-checks
jan-vcapgemini Apr 2, 2024
998387d
#103 implemented requested changes
jan-vcapgemini Apr 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
614 changes: 307 additions & 307 deletions cli/pom.xml

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwar
String tool = getTool(path, ideRoot);
if (tool == null) {
this.paths.add(path);
}
}
}
collectToolPath(softwarePath);
}

Expand Down Expand Up @@ -165,7 +165,7 @@ private Path findBinaryInOrder(Path path, String tool) {
/**
* @param toolPath the {@link Path} to the tool installation.
* @return the {@link Path} to the binary executable of the tool. E.g. is "software/mvn" is given
* "software/mvn/bin/mvn" could be returned.
* "software/mvn/bin/mvn" could be returned.
*/
public Path findBinary(Path toolPath) {

Expand Down Expand Up @@ -200,7 +200,7 @@ public Path findBinary(Path toolPath) {
/**
* @param tool the name of the tool.
* @return the {@link Path} to the directory of the tool where the binaries can be found or {@code null} if the tool
* is not installed.
* is not installed.
*/
public Path getPath(String tool) {

Expand All @@ -224,7 +224,7 @@ public String toString() {

/**
* @param bash - {@code true} to convert the PATH to bash syntax (relevant for git-bash or cygwin on windows),
* {@code false} otherwise.
* {@code false} otherwise.
* @return this {@link SystemPath} as {@link String} for the PATH environment variable.
*/
public String toString(boolean bash) {
Expand Down Expand Up @@ -265,15 +265,15 @@ private static void appendPath(Path path, StringBuilder sb, char separator, bool
*/
public static String convertWindowsPathToUnixPath(String pathString) {

char slash = pathString.charAt(2);
if ((slash == '\\') || (slash == '/')) {
char drive = Character.toLowerCase(pathString.charAt(0));
if ((drive >= 'a') && (drive <= 'z')) {
pathString = "/" + drive + pathString.substring(2).replace('\\', '/');
char slash = pathString.charAt(2);
if ((slash == '\\') || (slash == '/')) {
char drive = Character.toLowerCase(pathString.charAt(0));
if ((drive >= 'a') && (drive <= 'z')) {
pathString = "/" + drive + pathString.substring(2).replace('\\', '/');
}
}
}
return pathString;
}
}

/**
* Method to validate if a given path string is a Windows path or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ 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);
setVersion(selectedVersion, silent);
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 @@ -61,9 +61,10 @@ protected boolean doInstall(boolean silent) {
VersionIdentifier configuredVersion = getConfiguredVersion();
// get installed version before installInRepo actually may install the software
VersionIdentifier installedVersion = getInstalledVersion();
VersionIdentifier selectedVersion = securityRiskInteraction(configuredVersion);
setVersion(selectedVersion, silent);
// install configured version of our tool in the software repository if not already installed
ToolInstallation installation = installInRepo(configuredVersion);

ToolInstallation installation = installInRepo(selectedVersion);
// check if we already have this version installed (linked) locally in IDE_HOME/software
VersionIdentifier resolvedVersion = installation.resolvedVersion();
if (resolvedVersion.equals(installedVersion) && !installation.newInstallation()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.devonfw.tools.ide.tool;

/**
* User interaction answers when a security risk was found and the user can f.e. choose to stay on the current unsafe
* version, use the latest safe version, use the latest version or use the next safe version.
*/
public enum SecurityRiskInteractionAnswer {

/**
* User answer to stay on the current unsafe version.
*/
STAY,

/**
* User answer to install the latest of all safe versions.
*/
LATEST_SAFE,

/**
* User answer to use the latest version.
*/
LATEST,

/**
* User answer to use the next safe version.
*/
NEXT_SAFE,

}
190 changes: 169 additions & 21 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
package com.devonfw.tools.ide.tool;

import static com.devonfw.tools.ide.tool.SecurityRiskInteractionAnswer.LATEST;
import static com.devonfw.tools.ide.tool.SecurityRiskInteractionAnswer.LATEST_SAFE;
import static com.devonfw.tools.ide.tool.SecurityRiskInteractionAnswer.NEXT_SAFE;
import static com.devonfw.tools.ide.tool.SecurityRiskInteractionAnswer.STAY;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.devonfw.tools.ide.commandlet.Commandlet;
import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.common.Tags;
Expand All @@ -11,14 +26,11 @@
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.process.ProcessMode;
import com.devonfw.tools.ide.property.StringListProperty;
import com.devonfw.tools.ide.url.model.file.UrlSecurityJsonFile;
import com.devonfw.tools.ide.url.model.file.json.UrlSecurityWarning;
import com.devonfw.tools.ide.util.Pair;
import com.devonfw.tools.ide.version.VersionIdentifier;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;

/**
* {@link Commandlet} for a tool integrated into the IDE.
*/
Expand All @@ -40,7 +52,7 @@ public abstract class ToolCommandlet extends Commandlet implements Tags {
* @param context the {@link IdeContext}.
* @param tool the {@link #getName() tool name}.
* @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of}
* method.
* method.
*/
public ToolCommandlet(IdeContext context, String tool, Set<Tag> tags) {

Expand Down Expand Up @@ -94,8 +106,8 @@ public void run() {
*
* @param processMode see {@link ProcessMode}
* @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
* without touching and IDE installation and used to run.
* 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 command-line arguments to run the tool.
*/
public void runTool(ProcessMode processMode, VersionIdentifier toolVersion, String... args) {
Expand Down Expand Up @@ -134,7 +146,7 @@ public String getEdition() {

/**
* @return the {@link #getName() tool} with its {@link #getEdition() edition}. The edition will be omitted if same as
* tool.
* tool.
* @see #getToolWithEdition(String, String)
*/
protected final String getToolWithEdition() {
Expand All @@ -146,7 +158,7 @@ protected final String getToolWithEdition() {
* @param tool the tool name.
* @param edition the edition.
* @return the {@link #getName() tool} with its {@link #getEdition() edition}. The edition will be omitted if same as
* tool.
* tool.
*/
protected final static String getToolWithEdition(String tool, String edition) {

Expand All @@ -169,7 +181,7 @@ public VersionIdentifier getConfiguredVersion() {
* {@link com.devonfw.tools.ide.commandlet.Commandlet}s.
*
* @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
* nothing has changed.
* nothing has changed.
*/
public boolean install() {

Expand All @@ -182,24 +194,161 @@ public boolean install() {
*
* @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
* @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
* nothing has changed.
* nothing has changed.
*/
public boolean install(boolean silent) {

return doInstall(silent);
}

/**
* 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) {
jan-vcapgemini marked this conversation as resolved.
Show resolved Hide resolved

UrlSecurityJsonFile securityFile = this.context.getUrls().getEdition(this.tool, this.getEdition())
.getSecurityJsonFile();

VersionIdentifier current = this.context.getUrls().getVersion(this.tool, this.getEdition(), configuredVersion);

if (!securityFile.contains(current, true, this.context, securityFile.getParent())) {
return configuredVersion;
}

List<VersionIdentifier> allVersions = this.context.getUrls().getSortedVersions(this.tool, this.getEdition());
VersionIdentifier latest = allVersions.get(0);

VersionIdentifier nextSafe = getNextSafeVersion(current, securityFile, allVersions);
VersionIdentifier latestSafe = getLatestSafe(allVersions, securityFile);
String cves = securityFile.getMatchingSecurityWarnings(current).stream().map(UrlSecurityWarning::getCveName)
.collect(Collectors.joining(", "));
String currentIsUnsafe = "Currently, version " + current + " of " + this.getName() + " is selected, "
+ "which has one or more vulnerabilities:\n\n" + cves + "\n\n(See also " + securityFile.getPath() + ")\n\n";

if (latestSafe == null) {
this.context.warning(currentIsUnsafe + "There is no safe version available.");
return configuredVersion;
}

return whichVersionDoYouWantToInstall(configuredVersion, current, nextSafe, latestSafe, latest, currentIsUnsafe);
}

/**
* Using all the safety information about the versions, this method asks the user which version to install.
*
* @param configuredVersion the version that was configured in the environment variables.
* @param current the current version that was resolved from the configured version.
* @param nextSafe the next safe version.
* @param latestSafe the latest safe version.
* @param latest the latest version.
* @param currentIsUnsafe the message that is printed if the current version is unsafe.
* @return the version that the user wants to install.
*/
private VersionIdentifier whichVersionDoYouWantToInstall(VersionIdentifier configuredVersion,
VersionIdentifier current, VersionIdentifier nextSafe, VersionIdentifier latestSafe, VersionIdentifier latest,
String currentIsUnsafe) {

String ask = "Which version do you want to install?";

// enum id, option message, version that is returned if this option is selected
Map<SecurityRiskInteractionAnswer, Pair<String, VersionIdentifier>> options = new HashMap<>();
options.put(STAY, Pair.of("Stay with the current unsafe version (" + current + ").", configuredVersion));
options.put(LATEST_SAFE, Pair.of("Install the latest of all safe versions (" + latestSafe + ").", latestSafe));
options.put(LATEST,
Pair.of("Install the latest version (" + latest + "). This version is save.", VersionIdentifier.LATEST));
options.put(NEXT_SAFE, Pair.of("Install the next safe version (" + nextSafe + ").", nextSafe));

if (current.equals(latest)) {
return getAnswer(options, currentIsUnsafe + "There are no updates available. " + ask, STAY, LATEST_SAFE);
} else if (nextSafe == null) { // install an older version that is safe or stay with the current unsafe version
return getAnswer(options, currentIsUnsafe + "All newer versions are also not safe. " + ask, STAY, LATEST_SAFE);
} else if (nextSafe.equals(latest)) {
return getAnswer(options, currentIsUnsafe + "Of the newer versions, only the latest is safe. " + ask, STAY,
LATEST);
} else if (nextSafe.equals(latestSafe)) {
return getAnswer(options, currentIsUnsafe + "Of the newer versions, only version " + nextSafe
+ " is safe, which is however not the latest. " + ask, STAY, NEXT_SAFE);
} else {
if (latestSafe.equals(latest)) {
return getAnswer(options, currentIsUnsafe + ask, STAY, NEXT_SAFE, LATEST);
} else {
return getAnswer(options, currentIsUnsafe + ask, STAY, NEXT_SAFE, LATEST_SAFE);
}
}
}

private VersionIdentifier getAnswer(Map<SecurityRiskInteractionAnswer, Pair<String, VersionIdentifier>> options,
String question, SecurityRiskInteractionAnswer... possibleAnswers) {

String[] availableOptions = Arrays.stream(possibleAnswers).map(options::get).map(Pair::getFirst)
.toArray(String[]::new);
String answer = this.context.question(question, availableOptions);
for (SecurityRiskInteractionAnswer possibleAnswer : possibleAnswers) {
if (options.get(possibleAnswer).getFirst().equals(answer)) {
return options.get(possibleAnswer).getSecond();
}
}
throw new IllegalStateException("Invalid answer " + answer);
}

/**
* Gets the latest safe version from the list of all versions.
*
* @param allVersions all versions of the tool.
* @param securityFile the {@link UrlSecurityJsonFile} to check if a version is safe or not.
* @return the latest safe version or {@code null} if there is no safe version.
*/
private VersionIdentifier getLatestSafe(List<VersionIdentifier> allVersions, UrlSecurityJsonFile securityFile) {

VersionIdentifier latestSafe = null;
for (int i = 0; i < allVersions.size(); i++) {
if (!securityFile.contains(allVersions.get(i), true, this.context, securityFile.getParent())) {
latestSafe = allVersions.get(i);
break;
}
}
return latestSafe;
}

/**
* Gets the next safe version from the list of all versions starting from the current version.
*
* @param current the current version.
* @param securityFile the {@link UrlSecurityJsonFile} to check if a version is safe or not.
* @param allVersions all versions of the tool.
* @return the next safe version or {@code null} if there is no safe version.
*/
private VersionIdentifier getNextSafeVersion(VersionIdentifier current, UrlSecurityJsonFile securityFile,
List<VersionIdentifier> allVersions) {

int currentVersionIndex = allVersions.indexOf(current);
VersionIdentifier nextSafe = null;
for (int i = currentVersionIndex - 1; i >= 0; i--) {
if (!securityFile.contains(allVersions.get(i), true, this.context, securityFile.getParent())) {
nextSafe = allVersions.get(i);
break;
}
}
return nextSafe;
}

/**
* Installs or updates the managed {@link #getName() tool}.
*
* @param silent - {@code true} if called recursively to suppress verbose logging, {@code false} otherwise.
* @return {@code true} if the tool was newly installed, {@code false} if the tool was already installed before and
* nothing has changed.
* nothing has changed.
*/
protected abstract boolean doInstall(boolean silent);

/**
* This method is called after the tool has been newly installed or updated to a new version.
* This method is called after the tool has been newly installed or updated to a new version. Override it to add
* custom post installation logic.
*/
protected void postInstall() {

Expand Down Expand Up @@ -271,7 +420,7 @@ public String getInstalledEdition() {

/**
* @param toolPath the installation {@link Path} where to find currently installed tool. The name of the parent
* directory of the real path corresponding to the passed {@link Path path} must be the name of the edition.
* directory of the real path corresponding to the passed {@link Path path} must be the name of the edition.
* @return the installed edition of this tool or {@code null} if not installed.
*/
public String getInstalledEdition(Path toolPath) {
Expand All @@ -287,11 +436,10 @@ public String getInstalledEdition(Path toolPath) {
}
return edition;
} catch (IOException e) {
throw new IllegalStateException(
"Couldn't determine the edition of " + getName() + " from the directory structure of its software path "
+ toolPath
+ ", assuming the name of the parent directory of the real path of the software path to be the edition "
+ "of the tool.", e);
throw new IllegalStateException("Couldn't determine the edition of " + getName()
+ " from the directory structure of its software path " + toolPath
+ ", assuming the name of the parent directory of the real path of the software path to be the edition "
+ "of the tool.", e);
}

}
Expand Down
Loading
Loading