Skip to content
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
1 change: 1 addition & 0 deletions bundle/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ Export-Package: com.jfrog.ide.eclipse.configuration,
com.jfrog.ide.eclipse.ui,
com.jfrog.ide.eclipse.ui.actions,
com.jfrog.ide.eclipse.ui.issues,
com.jfrog.ide.eclipse.ui.webview,
com.jfrog.ide.eclipse.utils
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.jfrog.ide.eclipse.ui.webview;

import com.jfrog.ide.common.nodes.*;
import com.jfrog.ide.common.nodes.subentities.*;
import com.jfrog.ide.common.parse.Applicability;
import com.jfrog.ide.common.webview.ApplicableDetails;
import com.jfrog.ide.common.webview.Cve;
import com.jfrog.ide.common.webview.DependencyPage;
import com.jfrog.ide.common.webview.ImpactGraph;
import com.jfrog.ide.common.webview.ImpactGraphNode;
import com.jfrog.ide.common.webview.IssuePage;
import com.jfrog.ide.common.webview.Location;
import com.jfrog.ide.common.webview.SastIssuePage;

import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


public class WebviewObjectConverter {
public static final int IMPACT_PATHS_LIMIT = 20;
public static DependencyPage convertScaIssueToDepPage(ScaIssueNode scaNode) {
return new DependencyPage()
.cve(new Cve(scaNode.getTitle(), null, null, null, null, new ApplicableDetails(Applicability.getWebviewIconName(scaNode.getApplicability()), null, null)))
.component(scaNode.getComponentName())
.version(scaNode.getComponentVersion())
.severity(scaNode.getSeverity().getSeverityName())
.fixedVersion(scaNode.getFixedVersions())
.summary(scaNode.getFullDescription())
.impactGraph(toImpactGraph(scaNode.getImpactPaths()));
}

public static IssuePage convertFileIssueToIssuePage(FileIssueNode fileIssueNode) {
return new IssuePage()
.header(fileIssueNode.getTitle())
.type(ConvertPageType(fileIssueNode.getReporterType()))
.severity(fileIssueNode.getSeverity().name())
.description(fileIssueNode.getFullDescription())
.location(convertFileLocation(fileIssueNode));
}

public static SastIssuePage convertSastIssueToSastIssuePage(SastIssueNode sastIssueNode) {
return new SastIssuePage(convertFileIssueToIssuePage(sastIssueNode))
.setAnalysisSteps(convertCodeFlowsToLocations(sastIssueNode.getCodeFlows()))
.setRuleID(sastIssueNode.getRuleId());
}

private static Location[] convertCodeFlowsToLocations(FindingInfo[][] codeFlows) {
if (codeFlows != null && codeFlows.length > 0) {
FindingInfo[] codeFlowList = codeFlows[0];
int codeFlowListSize = codeFlowList.length;
Location[] locations = new Location[codeFlowListSize];
for (int i = 0; i < codeFlowListSize; i++) {
FindingInfo codeFlow = codeFlows[0][i];
locations[i] = new Location(
codeFlow.getFilePath(),
Paths.get(codeFlow.getFilePath()).getFileName().toString(),
codeFlow.getRowStart(),
codeFlow.getColStart(),
codeFlow.getRowEnd(),
codeFlow.getColEnd(),
codeFlow.getLineSnippet());
}
return locations;
}
return null;
}

private static String ConvertPageType(SourceCodeScanType reporterType) {
return switch (reporterType) {
case SECRETS -> "SECRETS";
case IAC -> "IAC";
case SAST -> "SAST";
default -> "EMPTY"; // any other value passed will result the load of the default WebView page
};
}

private static Location convertFileLocation(FileIssueNode fileIssueNodeNode) {
return new Location(
fileIssueNodeNode.getFilePath(),
Paths.get(fileIssueNodeNode.getFilePath()).getFileName().toString(),
fileIssueNodeNode.getRowStart() + 1,
fileIssueNodeNode.getColStart() + 1,
fileIssueNodeNode.getRowEnd() + 1,
fileIssueNodeNode.getColEnd() + 1,
fileIssueNodeNode.getLineSnippet());
}

/**
* Converts a list of impact paths to an ImpactGraph.
* Each path is a list of ImpactPath objects, representing a path from root to leaf.
* Node names are "name:version" (or just "name" if version is empty).
*/
public static ImpactGraph toImpactGraph(List<List<ImpactPath>> impactPaths) {
if (impactPaths == null || impactPaths.isEmpty()) {
return new ImpactGraph(new ImpactGraphNode("", new ImpactGraphNode[0]), 0);
}
// Use the first element in each path as the root for that path
Map<String, ImpactTreeNode> rootMap = new LinkedHashMap<>();
boolean isMaxLimitExceeded;
int pathsNumber = impactPaths.size();
int pathIndex = 0;

for (; pathIndex < pathsNumber && pathIndex < IMPACT_PATHS_LIMIT; pathIndex++) {
List<ImpactPath> currentPath = impactPaths.get(pathIndex);
if (currentPath == null || currentPath.isEmpty()) {
continue;
}

String rootName = getNodeName(currentPath.get(0));
ImpactTreeNode root = rootMap.computeIfAbsent(rootName, ImpactTreeNode::new);
ImpactTreeNode currentNode = root;
int currentPathSize = currentPath.size();

for (int nodeIndex = 1; nodeIndex < currentPathSize; nodeIndex++) {
String nodeName = getNodeName(currentPath.get(nodeIndex));
currentNode = getOrAddChild(currentNode, nodeName);
}
}

isMaxLimitExceeded = pathIndex >= IMPACT_PATHS_LIMIT ? true : false;

ImpactGraphNode rootGraphNode = toImpactGraphNode(rootMap.values().iterator().next());
// pass value for pathsLimit only if exceeded the defined IMPACT_PATHS_LIMIT, so a corresponding message will appear in the WebView UI
return new ImpactGraph(rootGraphNode, isMaxLimitExceeded ? IMPACT_PATHS_LIMIT : -1);
}

private static String getNodeName(ImpactPath ip) {
return ip.getName() + (ip.getVersion() != null && !ip.getVersion().isEmpty() ? ":" + ip.getVersion() : "");
}

// Helper to find or add a child node by name
private static ImpactTreeNode getOrAddChild(ImpactTreeNode parent, String nodeName) {
for (ImpactTreeNode child : parent.getChildren()) {
if (child.getName().equals(nodeName)) {
return child;
}
}
ImpactTreeNode newChild = new ImpactTreeNode(nodeName);
parent.getChildren().add(newChild);
return newChild;
}

// Convert ImpactTreeNode to ImpactGraphNode tree
private static ImpactGraphNode toImpactGraphNode(ImpactTreeNode impactTreeNode) {
ImpactGraphNode[] children = impactTreeNode.getChildren().stream().map(WebviewObjectConverter::toImpactGraphNode).toArray(ImpactGraphNode[]::new);
return new ImpactGraphNode(impactTreeNode.getName(), children);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package com.jfrog.ide.eclipse.ui.webview;

import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.ScaIssueNode;
import com.jfrog.ide.common.nodes.SastIssueNode;
import com.jfrog.ide.common.nodes.subentities.ImpactPath;
import com.jfrog.ide.common.nodes.subentities.Severity;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.common.parse.Applicability;
import com.jfrog.ide.common.webview.*;
import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.List;

public class WebviewObjectConverterTest extends TestCase{

private ScaIssueNode scaIssueNode;
private FileIssueNode secretIssueNode;
private SastIssueNode sastIssueNode;

// setup common data
String filePath = "/test/path/file.java";
int rowStart = 10;
int colStart = 5;
int rowEnd = 20;
int colEnd = 10;
String lineSnippet = "vulnerable code line";

public void testConvertScaIssueToDepPage() {
// Setup SCA test data
String scaTitle = "CVE-2023-1234";
String reason = "sca issue reason";
Severity severity = Severity.High;
String ruleID = "CVE-2023-1234_test-component_1.0.0";
Applicability applicability = Applicability.APPLICABLE;
List<List<ImpactPath>> impactPaths = createTestImpactPaths();
String[] fixedVersions = {"[1.0.1]", "[1.0.2]"};
String fullDescription = "Test vulnerability description";

scaIssueNode = new ScaIssueNode(scaTitle, reason, severity, ruleID, applicability, impactPaths, fixedVersions, fullDescription);
DependencyPage result = WebviewObjectConverter.convertScaIssueToDepPage(scaIssueNode);

assertNotNull(result);
assertEquals(scaIssueNode.getComponentName(), result.getComponent());
assertEquals(scaIssueNode.getComponentVersion(), result.getVersion());
assertEquals(scaIssueNode.getSeverity().getSeverityName(), result.getSeverity());
assertEquals(scaIssueNode.getFullDescription(), result.getSummary());
assertEquals(scaIssueNode.getFixedVersions(), result.getFixedVersion());
assertNotNull(result.getCve());
assertEquals(scaIssueNode.getTitle(), result.getCve().getId());
}

public void testConvertFileIssueToIssuePage() {
// setup secrets test data
String secretTitle = "Secret issue";
String secretReason = "Hard coded secrets were found";
Severity secretSeverity = Severity.Medium;
String secretRuleId = "SECRET-RULE";
String secretFullDescription = "Test Secret issue description";

secretIssueNode = new FileIssueNode(secretTitle, filePath, rowStart, colStart, rowEnd, colEnd, secretReason, lineSnippet, SourceCodeScanType.SECRETS, secretSeverity, secretRuleId, secretFullDescription);
IssuePage result = WebviewObjectConverter.convertFileIssueToIssuePage(secretIssueNode);

assertNotNull(result);
assertEquals(secretIssueNode.getTitle(), result.getHeader());
assertEquals(secretIssueNode.getSeverity().name(), result.getSeverity());
assertEquals(secretIssueNode.getFullDescription(), result.getDescription());
assertNotNull(result.getLocation());
assertEquals(secretIssueNode.getFilePath(), result.getLocation().getFile());
assertEquals(secretIssueNode.getRowStart() + 1, result.getLocation().getStartRow());
assertEquals(secretIssueNode.getColStart() + 1, result.getLocation().getStartColumn());
}

public void testConvertSastIssueToSastIssuePage() {
// setup SAST test data
String sastTitle = "SAST Issue";
String sastReason = "SAST issue reason";
Severity sastSeverity = Severity.Critical;
String sastRuleId = "SAST-RULE";
String sastFullDescription = "Test SAST issue description";

sastIssueNode = new SastIssueNode(sastTitle, filePath, rowStart, colStart, rowEnd, colEnd, sastReason, lineSnippet, null, sastSeverity, sastRuleId, sastFullDescription);

SastIssuePage result = WebviewObjectConverter.convertSastIssueToSastIssuePage(sastIssueNode);

assertNotNull(result);
assertEquals(sastIssueNode.getTitle(), result.getHeader());
assertEquals(sastIssueNode.getRuleId(), result.getRuleId());
assertEquals(sastIssueNode.getSeverity().name(), result.getSeverity());
assertEquals(sastIssueNode.getFullDescription(), result.getDescription());
}

public void testToImpactGraph_EmptyInput() {
ImpactGraph result = WebviewObjectConverter.toImpactGraph(null);

assertNotNull(result);
assertNotNull(result.getRoot());
assertEquals("", result.getRoot().getName());
assertEquals(0, result.getRoot().getChildren().length);
}

public void testToImpactGraph_SinglePath() {
List<List<ImpactPath>> impactPaths = new ArrayList<>();
List<ImpactPath> path = new ArrayList<>();
path.add(new ImpactPath("root", "1.0"));
path.add(new ImpactPath("child1", "2.0"));
path.add(new ImpactPath("child2", "3.0"));
impactPaths.add(path);

ImpactGraph result = WebviewObjectConverter.toImpactGraph(impactPaths);

assertNotNull(result);
assertNotNull(result.getRoot());
assertEquals("root:1.0", result.getRoot().getName());
assertEquals(1, result.getRoot().getChildren().length);
assertEquals("child1:2.0", result.getRoot().getChildren()[0].getName());
assertEquals(1, result.getRoot().getChildren()[0].getChildren().length);
assertEquals("child2:3.0", result.getRoot().getChildren()[0].getChildren()[0].getName());
// validate IMPACT_PATHS_LIMIT wasn't exceeded
assertEquals(-1, result.getPathsLimit());
}

public void testToImpactGraph_MultiplePaths() {
List<List<ImpactPath>> impactPaths = new ArrayList<>();

// First path
List<ImpactPath> path1 = new ArrayList<>();
path1.add(new ImpactPath("root", "1.0"));
path1.add(new ImpactPath("child1", "2.0"));
impactPaths.add(path1);

// Second path
List<ImpactPath> path2 = new ArrayList<>();
path2.add(new ImpactPath("root", "1.0"));
path2.add(new ImpactPath("child2", "3.0"));
impactPaths.add(path2);

ImpactGraph result = WebviewObjectConverter.toImpactGraph(impactPaths);

assertNotNull(result);
assertNotNull(result.getRoot());
assertEquals("root:1.0", result.getRoot().getName());
assertEquals(2, result.getRoot().getChildren().length);

// Verify both children exist
boolean hasChild1 = false;
boolean hasChild2 = false;
for (ImpactGraphNode child : result.getRoot().getChildren()) {
if (child.getName().equals("child1:2.0")) hasChild1 = true;
if (child.getName().equals("child2:3.0")) hasChild2 = true;
}
assertTrue(hasChild1 && hasChild2);
}

public void testToImpactGraph_ExceedsLimit() {
List<List<ImpactPath>> impactPaths = new ArrayList<>();
for (int i = 0; i < WebviewObjectConverter.IMPACT_PATHS_LIMIT + 5; i++) {
List<ImpactPath> path = new ArrayList<>();
path.add(new ImpactPath("root" + i, "1.0"));
path.add(new ImpactPath("child" + i, "2.0"));
impactPaths.add(path);
}

ImpactGraph result = WebviewObjectConverter.toImpactGraph(impactPaths);

assertNotNull(result);
assertEquals(WebviewObjectConverter.IMPACT_PATHS_LIMIT, result.getPathsLimit());
}

private List<List<ImpactPath>> createTestImpactPaths() {
List<List<ImpactPath>> impactPaths = new ArrayList<>();
List<ImpactPath> path = new ArrayList<>();
path.add(new ImpactPath("root", "1.0"));
path.add(new ImpactPath("child1", "2.0"));
path.add(new ImpactPath("child2", "3.0"));
impactPaths.add(path);
return impactPaths;
}
}
Loading