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

feat: check checksums when downloading tools from json #230

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -96,19 +99,22 @@ public static DownloadHelper getInstance() {
* "silentMode": true, //if the download needs to be started automatically without user input
* "platforms": {
* "win": {
* "url": "https://tool.com/tool/v1.0.0/odo-windows-amd64.exe.tar.gz",
* "url": "https://tool.com/tool/v1.0.0/tool-windows-amd64.exe.tar.gz",
* "cmdFileName": "tool.exe",
* "dlFileName": "tool-windows-amd64.exe.gz"
* "sha256": "123456789"
* },
* "osx": {
* "url": "https://tool.com/tool/v1.0.0/odo-darwin-amd64.tar.gz",
* "url": "https://tool.com/tool/v1.0.0/tool-darwin-amd64.tar.gz",
* "cmdFileName": "tool",
* "dlFileName": "tool-darwin-amd64.gz"
* "sha256": "123456789"
* },
* "lnx": {
* "url": "https://tool.com/tool/v1.0.0/odo-linux-amd64.tar.gz",
* "cmdFileName": "odo",
* "dlFileName": "odo-linux-amd64.gz"
* "url": "https://tool.com/tool/v1.0.0/tool-linux-amd64.tar.gz",
* "cmdFileName": "tool",
* "dlFileName": "tool-linux-amd64.gz"
* "sha256": "123456789"
* }
* }
* }
Expand Down Expand Up @@ -138,7 +144,7 @@ private CompletableFuture<ToolInstance> downloadIfRequiredAsyncInner(String tool
Path path = Paths.get(tool.getBaseDir().replace("$HOME", CommonConstants.HOME_FOLDER), "cache", tool.getVersion(), command);
final String cmd = path.toString();
if (!Files.exists(path)) {
downloadInBackground(toolName, platform, path, cmd, tool, version, result);
result = downloadInBackground(toolName, platform, path, cmd, tool, version, platform.getSha256());
} else {
result.complete(new ToolInstance(cmd, false));
}
Expand All @@ -158,21 +164,23 @@ private ToolsConfig.Platform getPlatformBasedOnOs(ToolsConfig.Tool tool) {

}

private void downloadInBackground(String toolName, ToolsConfig.Platform platform, Path path, String cmd, ToolsConfig.Tool tool, String version, CompletableFuture<ToolInstance> result) {
private CompletableFuture<ToolInstance> downloadInBackground(String toolName, ToolsConfig.Platform platform, Path path, String cmd, ToolsConfig.Tool tool, String version, String checksum) {
CompletableFuture<ToolInstance> result = new CompletableFuture<>();
if (ApplicationManager.getApplication().isUnitTestMode()) {
downloadInBackgroundManager(toolName, platform, path, cmd, result);
downloadInBackgroundManager(toolName, platform, path, cmd, checksum, result);
} else {
ApplicationManager.getApplication().invokeLater(() -> {
if (tool.isSilentMode() || isDownloadAllowed(toolName, version, tool.getVersion())) {
downloadInBackgroundManager(toolName, platform, path, cmd, result);
downloadInBackgroundManager(toolName, platform, path, cmd, checksum, result);
} else {
result.complete(new ToolInstance(platform.getCmdFileName(), false));
}
});
}
return result;
}

private void downloadInBackgroundManager(String toolName, ToolsConfig.Platform platform, Path path, String cmd, CompletableFuture<ToolInstance> result) {
private void downloadInBackgroundManager(String toolName, ToolsConfig.Platform platform, Path path, String cmd, String checksum, CompletableFuture<ToolInstance> result) {
final Path dlFilePath = path.resolveSibling(platform.getDlFileName());
ProgressManager.getInstance().run(new Task.Backgroundable(null, "Downloading " + toolName, false) {
@Override
Expand All @@ -181,6 +189,9 @@ public void run(@NotNull ProgressIndicator progressIndicator) {
HttpRequests.request(platform.getUrl().toString()).useProxy(true).connect(request -> {
downloadFile(request.getInputStream(), dlFilePath, progressIndicator, request.getConnection().getContentLength());
uncompress(dlFilePath, path);
if (checksum != null && !verify(dlFilePath, checksum)){
throw new IOException("Failed to verify checksum for " + platform.getDlFileName());
}
return cmd;
});
} catch (IOException e) {
Expand Down Expand Up @@ -266,15 +277,6 @@ private static void downloadFile(InputStream input, Path dlFileName, ProgressInd
}
}

private InputStream checkTar(InputStream stream) throws IOException {
TarArchiveInputStream tarStream = new TarArchiveInputStream(stream);
if (tarStream.getNextTarEntry() != null) {
return tarStream;
} else {
throw new IOException("No TAR entry found in " + stream);
}
}

private InputStream mapStream(String filename, InputStream input) {
String extension;
while (((extension = FilenameUtils.getExtension(filename)) != null) && MAPPERS.containsKey(extension)) {
Expand Down Expand Up @@ -317,6 +319,16 @@ private void save(InputStream source, Path destination, long length) throws IOEx
destination.toFile().setExecutable(true);
}

private boolean verify(Path path, String checksum) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(Files.readAllBytes(path));
return MessageDigest.isEqual(hash, checksum.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new IOException("Could not verify checksum for file " + path.toString(), e);
}
}

public static class ToolInstance {
private final String command;
private final boolean isDownloaded;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,42 +34,22 @@ public Map<String, Platform> getPlatforms() {
return platforms;
}

public void setPlatforms(Map<String, Platform> platforms) {
this.platforms = platforms;
}

public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}

public String getVersionCmd() {
return versionCmd;
}

public void setVersionCmd(String versionCmd) {
this.versionCmd = versionCmd;
}

public String getVersionExtractRegExp() {
return versionExtractRegExp;
}

public void setVersionExtractRegExp(String versionExtractRegExp) {
this.versionExtractRegExp = versionExtractRegExp;
}

public String getVersionMatchRegExpr() {
return versionMatchRegExpr;
}

public void setVersionMatchRegExpr(String versionMatchRegExpr) {
this.versionMatchRegExpr = versionMatchRegExpr;
}

public String getBaseDir() {
return baseDir;
}
Expand All @@ -81,17 +61,14 @@ public void setBaseDir(String baseDir) {
public boolean isSilentMode() {
return silentMode;
}

public void setSilentMode(boolean silentMode) {
this.silentMode = silentMode;
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Platform {
private URL url;
private String cmdFileName;
private String dlFileName;
private String sha256;

public URL getUrl() {
return url;
Expand All @@ -105,17 +82,14 @@ public String getCmdFileName() {
return cmdFileName;
}

public void setCmdFileName(String cmdFileName) {
this.cmdFileName = cmdFileName;
}

public String getDlFileName() {
return dlFileName;
}

public void setDlFileName(String dlFileName) {
this.dlFileName = dlFileName;
public String getSha256() {
return sha256;
}

}

private Map<String, Tool> tools = new HashMap<>();
Expand All @@ -124,7 +98,4 @@ public Map<String, Tool> getTools() {
return tools;
}

public void setTools(Map<String, Tool> tools) {
this.tools = tools;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.common.utils;

import com.intellij.openapi.ui.TestDialog;
Expand All @@ -6,6 +16,7 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;

public class DownloadHelperTest extends LightPlatformTestCase {
private TestDialog previous;
Expand Down Expand Up @@ -39,11 +50,28 @@ public void testThatTarGZIsDownloaded() throws IOException {
assertEquals(17, new File(toolInstance.getCommand()).length());
}

public void testThatPlainFileDownloaded() throws IOException{
public void testThatPlainFileDownloaded() throws IOException {
DownloadHelper.ToolInstance toolInstance = DownloadHelper.getInstance().downloadIfRequired("kn", DownloadHelperTest.class.getResource("/knative-test.json"));
assertNotNull(toolInstance);
assertNotNull(toolInstance.getCommand());
assertEquals("." + File.separatorChar + "cache" + File.separatorChar + "0.5.0" + File.separatorChar + "tkn", toolInstance.getCommand());
assertEquals(17, new File(toolInstance.getCommand()).length());
}

public void testThatChecksumIsValidForDownloadedTool() throws IOException {
DownloadHelper.ToolInstance toolInstance = DownloadHelper.getInstance().downloadIfRequired("tkn", DownloadHelperTest.class.getResource("/tkn-test.json"));
assertNotNull(toolInstance);
assertNotNull(toolInstance.getCommand());
FileUtils.deleteDirectory(Paths.get(toolInstance.getCommand()).toFile().getParentFile());
}

public void testThatChecksumIsInValidForDownloadedTool() {
try {
DownloadHelper.ToolInstance toolInstance = DownloadHelper.getInstance().downloadIfRequired("tkn", DownloadHelperTest.class.getResource("/tkn-test-invalid-checksum.json"));
FileUtils.deleteDirectory(Paths.get(toolInstance.getCommand()).toFile().getParentFile());
fail("should raise exception");
} catch (IOException e){
assertTrue(e.getMessage().contains("Error while setting tool"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,59 +28,59 @@ public static void init() throws IOException {
}

@Test
public void verifyThatConfigCanLoad() throws IOException {
public void verifyThatConfigCanLoad() {
assertNotNull(config);
}

@Test
public void verifyThatConfigReturnsTools() throws IOException {
public void verifyThatConfigReturnsTools() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
}

@Test
public void verifyThatConfigReturnsVersion() throws IOException {
public void verifyThatConfigReturnsVersion() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertEquals("0.5.0", tool.getVersion());
}

@Test
public void verifyThatConfigReturnsVersionCmd() throws IOException {
public void verifyThatConfigReturnsVersionCmd() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertEquals("version", tool.getVersionCmd());
}

@Test
public void verifyThatConfigReturnsVersionExtractRegExp() throws IOException {
public void verifyThatConfigReturnsVersionExtractRegExp() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertEquals("Client version: (\\d+[\\.\\d+]*)\\s.*", tool.getVersionExtractRegExp());
}

@Test
public void verifyThatConfigReturnsVersionMatchRegExp() throws IOException {
public void verifyThatConfigReturnsVersionMatchRegExp() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertEquals("0\\..*", tool.getVersionMatchRegExpr());
}

@Test
public void verifyThatConfigReturnsBaseDir() throws IOException {
public void verifyThatConfigReturnsBaseDir() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertEquals("$HOME/.tekton", tool.getBaseDir());
}

@Test
public void verifyThatConfigReturnsPlatforms() throws IOException {
public void verifyThatConfigReturnsPlatforms() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
Expand All @@ -89,7 +89,7 @@ public void verifyThatConfigReturnsPlatforms() throws IOException {
}

@Test
public void verifyThatConfigReturnsWindowsPlatform() throws IOException {
public void verifyThatConfigReturnsWindowsPlatform() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
Expand All @@ -99,7 +99,7 @@ public void verifyThatConfigReturnsWindowsPlatform() throws IOException {
}

@Test
public void verifyThatConfigReturnsOSXPlatform() throws IOException {
public void verifyThatConfigReturnsOSXPlatform() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
Expand All @@ -109,12 +109,23 @@ public void verifyThatConfigReturnsOSXPlatform() throws IOException {
}

@Test
public void verifyThatConfigReturnsLinuxPlatform() throws IOException {
public void verifyThatConfigReturnsLinuxPlatform() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertNotNull(tool.getPlatforms());
assertFalse(tool.getPlatforms().isEmpty());
assertNotNull(tool.getPlatforms().get("lnx"));
}

@Test
public void verifyThatConfigReturnsCorrectChecksums() {
assertNotNull(config);
ToolsConfig.Tool tool = config.getTools().get("tkn");
assertNotNull(tool);
assertNotNull(tool.getPlatforms());
assertFalse(tool.getPlatforms().isEmpty());
assertNotNull(tool.getPlatforms().get("win"));
assertEquals("50dfa941ccdbe63c112cb28af521f74f3d972cf06ba0092844a20197ddf31de5", tool.getPlatforms().get("win").getSha256());
}
}
Loading
Loading