ON_IRRELEVANT_RESULT = Topic.create("Source code changed", AnnotationEvents.class);
/**
* Called when the selected file is modified.
*/
void update(String filePath);
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/events/ApplicationEvents.java
================================================
package com.jfrog.ide.idea.events;
import com.intellij.util.messages.Topic;
/**
* Application based events.
*
* Created by romang on 3/5/17.
*/
public interface ApplicationEvents {
// Scan started
Topic ON_SCAN_LOCAL_STARTED = Topic.create("Local scan started", ApplicationEvents.class);
Topic ON_SCAN_LOCAL_CANCELED = Topic.create("Local scan canceled", ApplicationEvents.class);
Topic ON_SCAN_CI_STARTED = Topic.create("CI scan started", ApplicationEvents.class);
// Configuration changed
Topic ON_CONFIGURATION_DETAILS_CHANGE = Topic.create("Configuration details changed", ApplicationEvents.class);
Topic ON_BUILDS_CONFIGURATION_CHANGE = Topic.create("Builds configuration changed", ApplicationEvents.class);
// Filter changed
Topic ON_CI_FILTER_CHANGE = Topic.create("CI issues changed", ApplicationEvents.class);
/**
* Called when a scan started, a configuration changed or a filter changed.
*/
void update();
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/events/BuildEvents.java
================================================
package com.jfrog.ide.idea.events;
import com.intellij.util.messages.Topic;
import com.jfrog.ide.common.ci.BuildGeneralInfo;
/**
* Project based events.
*
* @author yahavi
*/
public interface BuildEvents {
Topic ON_SELECTED_BUILD = Topic.create("Build selected", BuildEvents.class);
/**
* Called when the selected build is modified.
*/
void update(BuildGeneralInfo generalInfo);
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/events/ProjectEvents.java
================================================
package com.jfrog.ide.idea.events;
import com.intellij.util.messages.Topic;
import com.jfrog.ide.common.utils.ProjectsMap;
/**
* Project based events.
*
* @author yahavi
*/
public interface ProjectEvents {
Topic ON_SCAN_CI_CHANGE = Topic.create("CI changed", ProjectEvents.class);
/**
* Called when the store of issues in changed files is modified. It is modified only as a result of a user action to analyse all changed files.
*/
void update(ProjectsMap.ProjectKey projectKey);
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/exclusion/Excludable.java
================================================
package com.jfrog.ide.idea.exclusion;
import com.intellij.openapi.project.Project;
/**
* Created by Bar Belity on 28/05/2020.
*/
public interface Excludable {
/**
* Exclude from project-descriptor.
*/
void exclude(Project project);
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/exclusion/ExclusionUtils.java
================================================
package com.jfrog.ide.idea.exclusion;
import com.jfrog.ide.idea.navigation.NavigationTarget;
import org.jfrog.build.extractor.scan.DependencyTree;
/**
* Created by Bar Belity on 28/05/2020.
*/
public class ExclusionUtils {
/**
* Check if a specific node from the Dependencies-tree can be excluded from project-descriptor.
*
* @param nodeToExclude - The node in tree to exclude.
* @param affectedNode - Direct dependency's node in tree which will be affected by the exclusion.
* @return true if the provided nodeToExclude can be excluded from project-descriptor.
*/
public static boolean isExcludable(DependencyTree nodeToExclude, DependencyTree affectedNode) {
return MavenExclusion.isExcludable(nodeToExclude, affectedNode);
}
/**
* Get the corresponding Excludable object for the node to exclude.
*
* @param nodeToExclude - The node in tree to exclude.
* @param navigationTarget - The navigation-target of the node to exclude.
* @return the corresponding Excludable object, Null if exclusion is not supported for this node.
*/
public static Excludable getExcludable(DependencyTree nodeToExclude, DependencyTree affectedNode, NavigationTarget navigationTarget) {
if (MavenExclusion.isExcludable(nodeToExclude, affectedNode)) {
return new MavenExclusion(nodeToExclude, navigationTarget);
}
return null;
}
/**
* Find node's root project node.
* In single project tree - the root's parent is null.
* In multi project tree - the root-parent's general info is null.
* @param node - DependencyTree node to find its project's root.
* @return the project root node.
*/
public static DependencyTree getProjectRoot(DependencyTree node) {
if (node == null) {
return null;
}
while (node.getParent() != null && ((DependencyTree) node.getParent()).getGeneralInfo() != null) {
node = (DependencyTree) node.getParent();
}
return node;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/exclusion/MavenExclusion.java
================================================
package com.jfrog.ide.idea.exclusion;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.jfrog.ide.idea.inspections.MavenInspection;
import com.jfrog.ide.idea.navigation.NavigationTarget;
import org.jfrog.build.extractor.scan.DependencyTree;
/**
* Created by Bar Belity on 28/05/2020.
*/
public class MavenExclusion implements Excludable {
public static final String MAVEN_EXCLUSIONS_TAG = "exclusions";
public static final String MAVEN_EXCLUSION_TAG = "exclusion";
private final DependencyTree nodeToExclude;
private final NavigationTarget navigationTarget;
public MavenExclusion(DependencyTree nodeToExclude, NavigationTarget navigationTarget) {
this.nodeToExclude = nodeToExclude;
this.navigationTarget = navigationTarget;
}
/**
* Walk up the dependencies-tree to validate that the project's root is of type 'Maven'.
* This is required as dependency nodes in 'Gradle' projects are also of type 'Maven', and dependency nodes
* can have type 'null'.
* @param nodeToExclude - The node in tree to exclude.
* @param affectedNode - Direct dependency's node in tree which will be affected by the exclusion.
* @return true if nodeToExclude is a valid Maven node which can be excluded.
*/
public static boolean isExcludable(DependencyTree nodeToExclude, DependencyTree affectedNode) {
if (nodeToExclude == null || nodeToExclude.equals(affectedNode)) {
return false;
}
return isMavenPackageType(ExclusionUtils.getProjectRoot(nodeToExclude));
}
public static boolean isMavenPackageType(DependencyTree node) {
return node != null && node.getGeneralInfo() != null && "maven".equals(node.getGeneralInfo().getPkgType());
}
@Override
public void exclude(Project project) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(navigationTarget.getElement().getContainingFile().getVirtualFile());
if (!(psiFile instanceof XmlFile)) {
return;
}
XmlFile file = (XmlFile) psiFile;
WriteCommandAction.writeCommandAction(project, file).run(() -> {
String groupId = nodeToExclude.getGeneralInfo().getGroupId();
String artifactId = nodeToExclude.getGeneralInfo().getArtifactId();
if (!(navigationTarget.getElement() instanceof XmlTag)) {
return;
}
XmlTag xmlElement = (XmlTag) navigationTarget.getElement();
navigateToElement(xmlElement);
XmlTag exclusionsTag = xmlElement.findFirstSubTag(MAVEN_EXCLUSIONS_TAG);
if (exclusionsTag == null) {
exclusionsTag = xmlElement.createChildTag(MAVEN_EXCLUSIONS_TAG, "", "", false);
createAndAddExclusionTags(exclusionsTag, groupId, artifactId);
xmlElement.addSubTag(exclusionsTag, false);
return;
}
XmlTag[] allExclusions = exclusionsTag.findSubTags(MAVEN_EXCLUSION_TAG);
if (exclusionExists(allExclusions, groupId, artifactId)) {
// Don't create exclusion tag.
return;
}
createAndAddExclusionTags(exclusionsTag, groupId, artifactId);
});
}
boolean exclusionExists(XmlTag[] allExclusions, String groupId, String artifactId) {
for (XmlTag exclusionTag : allExclusions) {
XmlTag groupIdTag = exclusionTag.findFirstSubTag(MavenInspection.MAVEN_GROUP_ID_TAG);
if (groupIdTag == null || !groupId.equals(groupIdTag.getValue().getText())) {
continue;
}
XmlTag artifactIdTag = exclusionTag.findFirstSubTag(MavenInspection.MAVEN_ARTIFACT_ID_TAG);
if (artifactIdTag != null && artifactId.equals(artifactIdTag.getValue().getText())) {
return true;
}
}
return false;
}
void createAndAddExclusionTags(XmlTag exclusionsTag, String groupId, String artifactId) {
XmlTag exclusionTag = exclusionsTag.createChildTag(MAVEN_EXCLUSION_TAG, "", "", false);
XmlTag groupIdTag = exclusionTag.createChildTag(MavenInspection.MAVEN_GROUP_ID_TAG, "", groupId, false);
XmlTag artifactIdTag = exclusionTag.createChildTag(MavenInspection.MAVEN_ARTIFACT_ID_TAG, "", artifactId, false);
exclusionTag.addSubTag(groupIdTag, true);
exclusionTag.addSubTag(artifactIdTag, false);
exclusionsTag.addSubTag(exclusionTag, false);
}
private void navigateToElement(XmlTag xmlElement) {
PsiElement navigationTarget = xmlElement.getNavigationElement();
if (!(navigationTarget instanceof Navigatable)) {
return;
}
Navigatable navigatable = (Navigatable) navigationTarget;
if (navigatable.canNavigate()) {
navigatable.navigate(true);
}
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/AbstractInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiElement;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.DescriptorFileTreeNode;
import com.jfrog.ide.common.nodes.SortableChildrenTreeNode;
import com.jfrog.ide.common.nodes.VulnerabilityNode;
import com.jfrog.ide.common.nodes.subentities.ImpactTree;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.navigation.NavigationService;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import javax.swing.tree.TreeNode;
import java.util.*;
import java.util.stream.Collectors;
/**
* Parent class of all inspections and annotations.
* The inspections are the "Show in JFrog plugin" action.
* The annotations are the "Top issue" and "Licenses" labels.
*
* @author yahavi
*/
public abstract class AbstractInspection extends LocalInspectionTool implements Annotator {
private final String packageDescriptorName;
// True if the code inspection was automatically triggered after an Xray scan using InspectionEngine.runInspectionOnFile(...).
private boolean afterScan;
AbstractInspection(Descriptor descriptor) {
this.packageDescriptorName = descriptor.getFileName();
}
public void setAfterScan(boolean afterScan) {
this.afterScan = afterScan;
}
/**
* Get Psi element and decide whether to add "Show in JFrog plugin" option, and register a corresponding
* navigation from item in tree to item in project-descriptor.
*
* @param problemsHolder - The "Show in JFrog plugin" option will be registered in this container.
* @param element - The Psi element in the package descriptor
* @param isOnTheFly - True if the inspection was triggered by opening a package descriptor file.
* False if the inspection was triggered manually by clicking on "Code | Inspect Code".
*/
void visitElement(ProblemsHolder problemsHolder, PsiElement element, boolean isOnTheFly) {
if (!afterScan && !isOnTheFly) {
// Code inspection was triggered manually by clicking on "Code | Inspect Code".
return;
}
String componentName = createComponentName(element);
if (StringUtils.isBlank(componentName)) {
return; // Failed creating the component name
}
List dependencies = getDependencies(element, componentName);
if (CollectionUtils.isEmpty(dependencies)) {
return;
}
NavigationService navigationService = NavigationService.getInstance(element.getProject());
for (DependencyNode dependency : dependencies) {
if (isOnTheFly) {
registerProblem(problemsHolder, dependency, element, componentName);
}
navigationService.addNavigation(dependency, element, componentName);
}
}
/**
* Get Psi element and decide whether to add licenses and top issue annotations.
*
* @param annotationHolder - The annotations will be registered in this container
* @param element - The Psi element in the package descriptor
*/
void visitElement(AnnotationHolder annotationHolder, PsiElement element) {
String componentName = createComponentName(element);
if (componentName == null) {
return; // Failed creating the component name
}
List dependencies = getDependencies(element, componentName);
if (CollectionUtils.isNotEmpty(dependencies)) {
AnnotationUtils.registerAnnotation(annotationHolder, dependencies.get(0), element, showAnnotationIcon(element));
}
}
/**
* Get the relevant scan manager according to the project type and path.
*
* @param project - The Project
* @param path - Path to project
* @return ScanManager
*/
abstract ScannerBase getScanner(Project project, String path);
/**
* Return true if and only if the Psi element is a dependency.
*
* @param element - The Psi element in the package descriptor.
* @return true if and only if the Psi element is a dependency
*/
abstract boolean isDependency(PsiElement element);
/**
* Create a component name from the Psi element. Called when isDependency(element) == true.
*
* @param element - The Psi element in the package descriptor
* @return GeneralInfo
*/
abstract String createComponentName(PsiElement element);
/**
* Get the file descriptors nodes that containing the dependency in the Psi element.
*
* @param element - The Psi element in the package descriptor
* @return Set of file nodes containing the dependency or null if not found
*/
Set getFileDescriptors(PsiElement element) {
Project project = element.getProject();
ComponentsTree componentsTree = LocalComponentsTree.getInstance(project);
if (componentsTree == null || componentsTree.getModel() == null) {
return null;
}
Set fileDescriptors = new HashSet<>();
Enumeration roots = ((SortableChildrenTreeNode) componentsTree.getModel().getRoot()).children();
for (TreeNode root : Collections.list(roots)) {
if (root instanceof DescriptorFileTreeNode fileNode) {
if (fileNode.getFilePath().equals(element.getContainingFile().getVirtualFile().getPath())) {
fileDescriptors.add(fileNode);
}
}
}
return fileDescriptors;
}
/**
* Override this method to determine whether to display multiple annotation icons in the same line.
*
* @param element - The Psi element in the package descriptor
* @return true if it should show annotation icon.
*/
boolean showAnnotationIcon(PsiElement element) {
return true;
}
/**
* Determine whether to apply the inspection on the Psi element.
*
* @param element - The Psi element in the package descriptor
* @return true if and only if the element is a dependency and the plugin is ready to show inspection for it
*/
boolean isShowInspection(PsiElement element) {
Project project = element.getProject();
ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("JFrog");
if (toolWindow == null) {
return false; // Tool window not yet activated
}
VirtualFile editorFile = element.getContainingFile().getVirtualFile();
if (editorFile == null || editorFile.getParent() == null || !editorFile.getPath().endsWith(packageDescriptorName)) {
return false; // File is not a package descriptor file
}
ScannerBase scanner = getScanner(project, editorFile.getParent().getPath());
if (scanner == null) {
return false; // Scan manager for this project not yet created
}
return isDependency(element);
}
/**
* Get all dependencies in the tree that relevant to the element.
*
* @param element - The Psi element in the package descriptor
* @return all dependencies in the dependency tree that relevant to the element
*/
List getDependencies(PsiElement element, String componentName) {
if (!isShowInspection(element)) {
return null; // Inspection is not needed for this element
}
Set filesNodes = getFileDescriptors(element);
if (filesNodes == null) {
return null; // No files descriptors found for this element
}
return filesNodes.stream()
.map(descriptorFile -> getMatchDependencies(descriptorFile, componentName))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
/**
* Get the dependencies that match to the input componentName.
*
* @param file - The Descriptor file node in the tree
* @param componentName - Component name representing a dependency without version
* @return the dependencies node that match to the input general info
*/
private List getMatchDependencies(DescriptorFileTreeNode file, String componentName) {
List dependencies = new ArrayList<>();
for (DependencyNode dependency : file.getDependencies()) {
if (isNodeMatch(dependency, componentName)) {
dependencies.add(dependency);
}
}
return dependencies;
}
/**
* Compare the component name from the Psi element and the dependency node from the Dependency tree.
*
* @param node - the dependency node from the dependency tree
* @param componentName - Component name representing a dependency without version
* @return true if the node matches the component name
*/
boolean isNodeMatch(DependencyNode node, String componentName) {
String artifactID = node.getComponentIdWithoutPrefix();
ImpactTree impactTree = node.getImpactTree();
String versionPrefix = ":";
return StringUtils.equals(extractArtifactIdWithoutVersion(artifactID), componentName) || impactTree.contains(componentName+versionPrefix);
}
abstract UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issues, String descriptorPath);
void registerProblem(ProblemsHolder problemsHolder, DependencyNode dependency, PsiElement element, String componentName) {
boolean isTransitive = dependency.isIndirect() || !StringUtils.contains(dependency.getTitle(), componentName);
String dependencyDescription = getDependencyDescription(dependency.getTitle(), isTransitive);
List quickFixes = new ArrayList<>();
quickFixes.add(new ShowInDependencyTree(dependency, dependencyDescription));
if (!isTransitive) {
Multimap fixVersionToCves = ArrayListMultimap.create();
dependency.children().asIterator().forEachRemaining(issueNode -> {
List fixVersionStrings = ListUtils.emptyIfNull(((VulnerabilityNode) issueNode).getFixedVersions());
for (String fixVersionString : fixVersionStrings) {
String fixVersion = convertFixVersionStringToMinFixVersion(fixVersionString);
fixVersionToCves.put(fixVersion, issueNode.toString());
}
});
String descriptorPath = element.getContainingFile().getVirtualFile().getPath();
fixVersionToCves.asMap().forEach((fixedVersion, issues) -> {
UpgradeVersion upgradeVersion = getUpgradeVersion(dependency.getArtifactId(), fixedVersion, issues, descriptorPath);
quickFixes.add(upgradeVersion);
});
}
problemsHolder.registerProblem(
element,
"JFrog: " + dependencyDescription + " has security vulnerabilities",
ProblemHighlightType.WARNING,
quickFixes.toArray(LocalQuickFix[]::new)
);
}
private String getDependencyDescription(String depComponent, boolean isTransitive) {
String description = "dependency <" + depComponent + ">";
if (isTransitive) {
description = "transitive " + description;
}
return description;
}
protected static String convertFixVersionStringToMinFixVersion(String fixVersionString) {
// Possible fix version string formats:
// 1.0 >> 1.0
// (,1.0] >> N/A
// (,1.0) >> N/A
// [1.0] >> 1.0
// (1.0,) >> N/A
// (1.0, 2.0) >> N/A
// [1.0, 2.0] >> 1.0
String fixVersion = fixVersionString.trim().split(",")[0];
if (fixVersion.charAt(0) == '(') {
// If first character is '(' then we can't tell what's the minimal fix version
return "";
}
fixVersion = StringUtils.strip(fixVersion, "[");
fixVersion = StringUtils.strip(fixVersion, "]");
return fixVersion;
}
private String extractArtifactIdWithoutVersion(String artifact) {
int versionIndex = artifact.lastIndexOf(':');
if (versionIndex != -1) {
return artifact.substring(0, versionIndex);
} else {
return artifact;
}
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/AnnotationIconRenderer.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.project.Project;
import com.intellij.util.ui.tree.TreeUtil;
import com.jfrog.ide.common.nodes.subentities.Severity;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import com.jfrog.ide.idea.ui.utils.IconUtils;
import com.jfrog.ide.idea.utils.Utils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.Objects;
/**
* Represents the icon near the dependencies in the package descriptor file.
*
* @author yahavi
**/
public class AnnotationIconRenderer extends GutterIconRenderer {
private final DefaultMutableTreeNode node;
private final String tooltipText;
private final Icon icon;
private Project project;
public AnnotationIconRenderer(DefaultMutableTreeNode node, Severity severity, String tooltipText) {
this.node = node;
this.tooltipText = tooltipText;
this.icon = IconUtils.load(StringUtils.lowerCase(severity.toString()));
}
public void setProject(Project project) {
this.project = project;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AnnotationIconRenderer)) {
return false;
}
return Objects.equals(icon, ((AnnotationIconRenderer) obj).getIcon());
}
@Override
public int hashCode() {
return Objects.hashCode(icon);
}
@Override
public @NotNull Icon getIcon() {
return icon;
}
@Override
public @Nullable String getTooltipText() {
return tooltipText;
}
@Override
public @Nullable AnAction getClickAction() {
return new AnAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Utils.focusJFrogToolWindow(project);
TreeUtil.selectInTree(project, node, true, LocalComponentsTree.getInstance(project), true);
}
};
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/AnnotationUtils.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.lang.annotation.AnnotationBuilder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.psi.PsiElement;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.subentities.License;
import java.util.List;
import java.util.stream.Collectors;
import static com.intellij.lang.annotation.HighlightSeverity.INFORMATION;
/**
* @author yahavi
*/
public class AnnotationUtils {
/**
* Register "Top issue" and "Licenses" annotations.
*
* @param annotationHolder - The annotations will be registered in this container
* @param dependency - The dependency tree node correlated to the element
* @param element - The element to apply the annotations
* @param showIcon - True if should add annotation icon
*/
static void registerAnnotation(AnnotationHolder annotationHolder, DependencyNode dependency, PsiElement element, boolean showIcon) {
String licensesString = getLicensesString(dependency);
String topIssue = getTopIssueString(dependency);
AnnotationIconRenderer iconRenderer = showIcon ? new AnnotationIconRenderer(dependency, dependency.getSeverity(), topIssue) : null;
try {
AnnotationBuilder builder = annotationHolder.newAnnotation(INFORMATION, topIssue).range(element);
if (showIcon) {
iconRenderer.setProject(element.getProject());
builder = builder.gutterIconRenderer(iconRenderer);
}
builder.create();
annotationHolder.newAnnotation(INFORMATION, licensesString).range(element).create();
} catch (IllegalArgumentException e) {
// Exception is thrown when the element we register the annotation for is out of bound of the
// containing element exists in the provided annotationHolder.
// This scenario may occur during a gradle-inspections.
}
}
/**
* Get the top issue string.
*
* @param node - The dependency tree node
* @return the top issue string
*/
private static String getTopIssueString(DependencyNode node) {
return "Top issue severity: " + node.getSeverity();
}
/**
* Get licenses string
*
* @param node - The dependency tree node
* @return licenses string
*/
private static String getLicensesString(DependencyNode node) {
String results = "Licenses: ";
List licensesStrings = node.getLicenses().stream().map(License::getName).collect(Collectors.toList());
if (licensesStrings.isEmpty()) {
return results + "Unknown";
}
return results + String.join(", ", licensesStrings);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/GoInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.goide.vgo.mod.psi.VgoModuleSpec;
import com.goide.vgo.mod.psi.VgoRequireDirective;
import com.goide.vgo.mod.psi.VgoVisitor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jfrog.ide.idea.inspections.upgradeversion.GoUpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.scan.ScanManager;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Created by Bar Belity on 17/02/2020.
*/
public class GoInspection extends AbstractInspection {
public GoInspection() {
super(Descriptor.GO);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new VgoVisitor() {
@Override
public void visitModuleSpec(@NotNull VgoModuleSpec element) {
super.visitPsiElement(element);
GoInspection.this.visitElement(holder, element, isOnTheFly);
}
};
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof VgoModuleSpec) {
GoInspection.this.visitElement(holder, element);
}
}
@Override
boolean isDependency(PsiElement element) {
PsiElement parentElement = element.getParent();
return parentElement instanceof VgoRequireDirective;
}
@Override
ScannerBase getScanner(Project project, String path) {
return ScanManager.getScanners(project).stream()
.filter(manager -> StringUtils.equals(manager.getProjectPath(), path))
.findAny()
.orElse(null);
}
@Override
String createComponentName(PsiElement element) {
VgoModuleSpec goElement = ((VgoModuleSpec) element);
if (goElement.getModuleVersion() != null) {
String version = goElement.getModuleVersion().getText();
// String leading "v" from version
version = StringUtils.strip(version, "v");
return String.join(":", goElement.getIdentifier().getText(), version);
}
return "";
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new GoUpgradeVersion(componentName, fixVersion, issue, descriptorPath);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/GradleGroovyInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jfrog.ide.idea.inspections.upgradeversion.GradleGroovyUpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrNamedArgumentsOwner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* @author yahavi
*/
public class GradleGroovyInspection extends GradleInspection {
public static final String GRADLE_GROUP_KEY = "group";
public static final String GRADLE_NAME_KEY = "name";
public static final String GRADLE_VERSION_KEY = "version";
public GradleGroovyInspection() {
super(Descriptor.GRADLE_GROOVY);
}
/**
* Get the string value of the groovy literal.
*
* @param literal - The groovy literal
* @return the value of the literal
*/
public static String getLiteralValue(GrLiteral literal) {
String artifact = Objects.toString((literal).getValue(), "");
int versionIndex = artifact.lastIndexOf(':');
if (versionIndex == -1) {
return artifact;
}
return artifact.substring(0, versionIndex);
}
public static boolean isNamedArgumentComponent(PsiElement element) {
return (element instanceof GrNamedArgumentsOwner && ((GrNamedArgumentsOwner) element).getNamedArguments().length >= 3);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new GroovyPsiElementVisitor(new GroovyElementVisitor() {
@Override
public void visitArgumentList(@NotNull GrArgumentList list) {
super.visitArgumentList(list);
List elementsToVisit = parseComponentElements(list);
for (GroovyPsiElement elementToVisit : elementsToVisit) {
GradleGroovyInspection.this.visitElement(holder, elementToVisit, isOnTheFly);
}
}
});
}
@Override
boolean isDependency(PsiElement element) {
PsiElement parent = element.getParent();
for (int i = 0; i < 6; i++, parent = parent.getParent()) {
if (StringUtils.startsWith(parent.getText(), "dependencies")) {
return true;
}
}
return false;
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof GrArgumentList) {
List elementsToVisit = parseComponentElements((GrArgumentList) element);
for (GroovyPsiElement elementToVisit : elementsToVisit) {
GradleGroovyInspection.this.visitElement(holder, elementToVisit);
}
}
}
List parseComponentElements(GrArgumentList element) {
List elementsToVisit = new ArrayList<>();
if (isNamedArgumentComponent(element)) {
// Example: implementation group: 'j', name: 'k', version: 'l'
elementsToVisit.add(element);
} else {
// Example:
// implementation([group: 'net.lingala.zip4j', name: 'zip4j', version: '2.3.0'],
// [group: 'org.codehaus.groovy', name: 'groovy-all', version: '3.0.5'])
// OR
// implementation("org.codehaus.groovy:groovy-all:3.0.5")
// OR
// implementation 'net.lingala.zip4j:zip4j:2.3.0',
// 'org.codehaus.groovy:groovy-all:3.0.5'
for (GroovyPsiElement subElement : element.getAllArguments()) {
if (isNamedArgumentComponent(subElement) || (subElement instanceof GrLiteral)) {
elementsToVisit.add(subElement);
} else if (subElement.getChildren().length > 0 && subElement.getChildren()[0] instanceof GrLiteral) {
elementsToVisit.add((GrLiteral) subElement.getChildren()[0]);
}
}
}
return elementsToVisit;
}
@Override
String createComponentName(PsiElement element) {
if (isNamedArgumentComponent(element)) {
// implementation group: 'j', name: 'k', version: 'l'
return String.join(":",
extractExpression(element, GRADLE_GROUP_KEY),
extractExpression(element, GRADLE_NAME_KEY));
}
if (element instanceof GrLiteral) {
// implementation 'g:h:i'
return getLiteralValue((GrLiteral) element);
}
return "";
}
/**
* Extract expression from groovy arguments.
*
* @param argumentList - The arguments list
* @param name - The name of the argument to extract
* @return the value of the argument
*/
private String extractExpression(PsiElement argumentList, String name) {
GrNamedArgument argument = ((GrNamedArgumentsOwner) argumentList).findNamedArgument(name);
if (argument == null) {
return "";
}
GrExpression grExpression = argument.getExpression();
if (grExpression == null) {
return "";
}
if (!(grExpression instanceof GrLiteral)) {
return "";
}
return getLiteralValue((GrLiteral) grExpression);
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new GradleGroovyUpgradeVersion(componentName, fixVersion, issue);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/GradleInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jfrog.ide.idea.scan.GradleScanner;
import com.jfrog.ide.idea.scan.ScanManager;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
/**
* @author yahavi
*/
public abstract class GradleInspection extends AbstractInspection {
private int lastAnnotatedLine;
public GradleInspection(Descriptor descriptor) {
super(descriptor);
}
@Override
ScannerBase getScanner(Project project, String path) {
return ScanManager.getScanners(project).stream()
.filter(GradleScanner.class::isInstance)
.findAny()
.orElse(null);
}
@Override
boolean showAnnotationIcon(PsiElement element) {
Document document = element.getContainingFile().getViewProvider().getDocument();
boolean showAnnotationIcon = true;
if (document != null) {
int currentLine = document.getLineNumber(element.getTextOffset());
showAnnotationIcon = currentLine != lastAnnotatedLine;
lastAnnotatedLine = currentLine;
}
return showAnnotationIcon;
}
public static String stripVersion(String componentId) {
if (StringUtils.countMatches(componentId, ":") >= 2) {
// implementation('a:b:c')
String[] splitComponent = componentId.split(":");
componentId = splitComponent[0] + ":" + splitComponent[1];
}
return componentId;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/GradleKotlinInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jfrog.ide.idea.inspections.upgradeversion.GradleKotlinUpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.*;
import java.util.Collection;
import java.util.List;
/**
* Each dependency in Gradle-Kotlin is a function call with at least one argument.
* Examples:
* compile("a:b:c")
* testCompile("d", "e", "f")
* implementation(project(":project")) // Subproject dependencies start with ":"
*
* @author yahavi
*/
public class GradleKotlinInspection extends GradleInspection {
public GradleKotlinInspection() {
super(Descriptor.GRADLE_KOTLIN);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new KtVisitorVoid() {
@Override
public void visitValueArgumentList(@NotNull KtValueArgumentList list) {
// Verify that the visited file is a build.gradle.kts file
if (((KtFile) list.getContainingFile()).isScript()) {
GradleKotlinInspection.this.visitElement(holder, list, isOnTheFly);
}
}
};
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof KtValueArgumentList && ((KtFile) element.getContainingFile()).isScript()) {
GradleKotlinInspection.this.visitElement(holder, element);
}
}
@Override
boolean isDependency(PsiElement element) {
List argumentList = ((KtValueArgumentList) element).getArguments();
if (argumentList.isEmpty() || !(argumentList.get(0).getArgumentExpression() instanceof KtStringTemplateExpression)) {
return false;
}
// Make sure the element is under "dependencies" scope
for (PsiElement parent = element.getParent(); parent != null; parent = parent.getParent()) {
if (!(parent instanceof KtCallExpression)) {
continue;
}
KtExpression expression = ((KtCallExpression) parent).getCalleeExpression();
if (expression != null && "dependencies".equals(expression.getText())) {
return true;
}
}
return false;
}
@Override
String createComponentName(PsiElement element) {
if (!(element instanceof KtValueArgumentList)) {
return "";
}
List argumentList = ((KtValueArgumentList) element).getArguments();
if (argumentList.size() == 1) {
// "commons-collections:commons-collections:3.2.2"
String artifactId = extractArgument(argumentList.get(0));
return StringUtils.substringBeforeLast(artifactId, ":");
}
if (argumentList.size() >= 3) {
// "commons-collections", "commons-collections"
return String.join(":",
extractArgument(argumentList.get(0)),
extractArgument(argumentList.get(1))
);
}
return "";
}
/**
* Extract argument text from Kotlin argument.
*
* @param ktValueArgument - The arguments list
* @return the value of the argument
*/
private String extractArgument(KtValueArgument ktValueArgument) {
// Remove quotes
String value = ktValueArgument.getText().replaceAll("\"", "");
// Remove '@' suffix, for example commons-lang:commons-lang:2.4@jar
return StringUtils.substringBefore(value, "@");
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new GradleKotlinUpgradeVersion(componentName, fixVersion, issue);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/JFrogSecurityAnnotator.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.SortableChildrenTreeNode;
import com.jfrog.ide.idea.events.AnnotationEvents;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.TreeNode;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
/**
* General Annotator for JFrog security source code issues (for Example: applicable CVE)
* The annotator receives the JFrogSecurityWarning data from the most recent scan.
*
* @author Tal Arian
*/
public class JFrogSecurityAnnotator extends ExternalAnnotator> {
@NotNull
private static final HighlightSeverity HIGHLIGHT_TYPE = HighlightSeverity.WARNING;
@Nullable
@Override
public PsiFile collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) {
return file;
}
@Nullable
@Override
public List doAnnotate(PsiFile file) {
List issues = new ArrayList<>();
ComponentsTree componentsTree = LocalComponentsTree.getInstance(file.getProject());
if (componentsTree == null || componentsTree.getModel() == null) {
return null;
}
Enumeration roots = ((SortableChildrenTreeNode) componentsTree.getModel().getRoot()).children();
roots.asIterator().forEachRemaining(root -> {
FileTreeNode fileNode = (FileTreeNode) root;
if (fileNode.getFilePath().equals(file.getContainingFile().getVirtualFile().getPath())) {
fileNode.children().asIterator().forEachRemaining(issueNode -> {
if (issueNode instanceof FileIssueNode) {
issues.add((FileIssueNode) issueNode);
}
}
);
}
});
return issues;
}
@Override
public void apply(@NotNull PsiFile file, List warnings, @NotNull AnnotationHolder holder) {
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) {
return;
}
warnings.stream().filter(Objects::nonNull).forEach(warning -> {
int startOffset = StringUtil.lineColToOffset(file.getText(), warning.getRowStart(), warning.getColStart());
int endOffset = StringUtil.lineColToOffset(file.getText(), warning.getRowEnd(), warning.getColEnd());
AnnotationIconRenderer iconRenderer = new AnnotationIconRenderer(warning, warning.getSeverity(), "");
iconRenderer.setProject(file.getProject());
TextRange range = new TextRange(startOffset, endOffset);
String lineText = document.getText(range);
// If the file has been update after the scan and the relevant line is affected,
// no annotation will be added.
if (lineText.contains(warning.getLineSnippet())) {
holder.newAnnotation(HIGHLIGHT_TYPE, "\uD83D\uDC38 JFrog [" + warning.getTitle() + "]: " + warning.getReason())
.range(range)
.gutterIconRenderer(iconRenderer)
.create();
}
// Notify outdated scan result
else {
file.getProject().getMessageBus().syncPublisher(AnnotationEvents.ON_IRRELEVANT_RESULT).update(warning.getFilePath());
}
});
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/JFrogSecurityWarning.java
================================================
package com.jfrog.ide.idea.inspections;
import com.jfrog.ide.common.nodes.subentities.FindingInfo;
import com.jfrog.ide.common.nodes.subentities.Severity;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.scan.data.*;
import lombok.Getter;
import java.net.URI;
import java.nio.file.Paths;
import java.util.List;
@Getter
public class JFrogSecurityWarning {
private final int lineStart;
private final int colStart;
private final int lineEnd;
private final int colEnd;
private final String reason;
private final String filePath;
private final String lineSnippet;
private String scannerSearchTarget;
private final String ruleID;
private final SourceCodeScanType reporter;
private final Severity severity;
private final FindingInfo[][] codeFlows;
private final boolean isApplicable;
public JFrogSecurityWarning(
int lineStart,
int colStart, int lineEnd,
int colEnd, String reason,
String filePath,
String ruleID,
String lineSnippet,
SourceCodeScanType reporter,
boolean isApplicable,
Severity severity,
FindingInfo[][] codeFlows
) {
this.lineStart = lineStart;
this.colStart = colStart;
this.lineEnd = lineEnd;
this.colEnd = colEnd;
this.reason = reason;
this.filePath = filePath;
this.ruleID = ruleID;
this.lineSnippet = lineSnippet;
this.reporter = reporter;
this.isApplicable = isApplicable;
this.severity = severity;
this.codeFlows = codeFlows;
}
public JFrogSecurityWarning(SarifResult result, SourceCodeScanType reporter, Rule rule) {
this(getFirstRegion(result).getStartLine() - 1,
getFirstRegion(result).getStartColumn() - 1,
getFirstRegion(result).getEndLine() - 1,
getFirstRegion(result).getEndColumn() - 1,
determineReason(result.getMessage().getText(), rule.getShortDescription().getText(), reporter),
getFilePath(result),
result.getRuleId(),
getFirstRegion(result).getSnippet().getText(),
reporter,
isWarningApplicable(result, rule),
Severity.fromSarif(result.getSeverity()),
convertCodeFlowsToFindingInfo(result.getCodeFlows())
);
}
private static boolean isWarningApplicable(SarifResult result, Rule rule) {
return !result.getKind().equals("pass") && (rule.getRuleProperties().map(properties -> properties.getApplicability().equals("applicable")).orElse(true));
}
private static String getFilePath(SarifResult result) {
return !result.getLocations().isEmpty() ? uriToPath(result.getLocations().get(0).getPhysicalLocation().getArtifactLocation().getUri()) : "";
}
private static FindingInfo[][] convertCodeFlowsToFindingInfo(List codeFlows) {
if (codeFlows == null || codeFlows.isEmpty()) {
return null;
}
List flows = codeFlows.get(0).getThreadFlows();
if (flows == null || flows.isEmpty()) {
return null;
}
FindingInfo[][] results = new FindingInfo[flows.size()][];
for (int i = 0; i < flows.size(); i++) {
ThreadFlow flow = flows.get(i);
List locations = flow.getLocations();
results[i] = new FindingInfo[locations.size()];
for (int j = 0; j < locations.size(); j++) {
PhysicalLocation location = locations.get(j).getLocation().getPhysicalLocation();
results[i][j] = new FindingInfo(
uriToPath(location.getArtifactLocation().getUri()),
location.getRegion().getStartLine(),
location.getRegion().getStartColumn(),
location.getRegion().getEndLine(),
location.getRegion().getEndColumn(),
location.getRegion().getSnippet().getText()
);
}
}
return results;
}
public static JFrogSecurityWarning notApplicable(String ruleId, SourceCodeScanType reporter) {
return new JFrogSecurityWarning(0, 0, 0, 0, "", "", ruleId, "", reporter, false, Severity.Unknown, null);
}
public boolean isApplicable() {
return this.isApplicable;
}
private static Region getFirstRegion(SarifResult result) {
Region emptyRegion = new Region();
emptyRegion.setSnippet(new Message());
return !result.getLocations().isEmpty() ? result.getLocations().get(0).getPhysicalLocation().getRegion() : emptyRegion;
}
public void setScannerSearchTarget(String scannerSearchTarget) {
this.scannerSearchTarget = scannerSearchTarget;
}
private static String uriToPath(String path) {
return Paths.get(URI.create(path)).toString();
}
private static String determineReason(String resultMessage, String ruleMessage, SourceCodeScanType scannerType) {
return scannerType.equals(SourceCodeScanType.SAST) ? ruleMessage : resultMessage;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/JumpToCode.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilBase;
import org.jetbrains.annotations.NotNull;
/**
* The JumpToCode class is responsible for navigating to a specific location in a code file
* and highlighting the corresponding code.
*/
public class JumpToCode {
Project project;
FileEditorManager fileEditorManager;
/**
* Constructs a new {@code JumpToCode} with the provided project.
*
* @param project The current project.
*/
private JumpToCode(@NotNull Project project) {
this.project = project;
fileEditorManager = FileEditorManager.getInstance(project);
}
public static JumpToCode getInstance(@NotNull Project project) {
return project.getService(JumpToCode.class);
}
/**
* Executes the jump to code operation by opening the file in the editor and highlighting the specified code range.
*
* @param filePath The path of the file to navigate to.
* @param startRow The starting row of the code range.
* @param endRow The ending row of the code range.
* @param startColumn The starting column of the code range.
* @param endColumn The ending column of the code range.
*/
public void execute(String filePath, int startRow, int endRow, int startColumn, int endColumn) {
if (this.project == null || this.fileEditorManager == null) return;
VirtualFile file = getVirtualFile(filePath);
if (file == null) return;
ApplicationManager.getApplication().invokeLater(() -> {
openFileInEditor(file);
highlightCode(startRow, endRow, startColumn, endColumn);
});
}
private void openFileInEditor(VirtualFile file) {
fileEditorManager.openFile(file, true);
}
private void highlightCode(int startRow, int endRow, int startColumn, int endColumn) {
Editor editor = fileEditorManager.getSelectedTextEditor();
if (editor == null) return;
Document document = getDocument(editor);
if (document == null) return;
int startOffset = getOffset(document, startRow, startColumn);
int endOffset = getOffset(document, endRow, endColumn);
highlightCode(editor, startOffset, endOffset);
scrollToHighlightedCode(editor, startOffset);
}
private VirtualFile getVirtualFile(String path) {
return LocalFileSystem.getInstance().findFileByPath(path);
}
private Document getDocument(Editor editor) {
PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
if (psiFile == null) return null;
return psiFile.getViewProvider().getDocument();
}
private int getOffset(Document document, int row, int column) {
return StringUtil.lineColToOffset(document.getText(), row, column);
}
private void highlightCode(Editor editor, int startOffset, int endOffset) {
SelectionModel selectionModel = editor.getSelectionModel();
selectionModel.setSelection(startOffset, endOffset);
editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, 0, null, HighlighterTargetArea.EXACT_RANGE);
}
private void scrollToHighlightedCode(Editor editor, int startOffset) {
editor.getCaretModel().moveToOffset(startOffset);
editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/MavenInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.XmlElementVisitor;
import com.intellij.psi.impl.source.xml.XmlTagImpl;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import com.jfrog.ide.idea.inspections.upgradeversion.MavenUpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.scan.MavenScanner;
import com.jfrog.ide.idea.scan.ScanManager;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.dom.model.MavenDomArtifactCoordinates;
import java.util.Collection;
/**
* @author yahavi
*/
public class MavenInspection extends AbstractInspection {
public static final String MAVEN_DEPENDENCY_MANAGEMENT = "dependencyManagement";
public static final String MAVEN_DEPENDENCIES_TAG = "dependencies";
public static final String MAVEN_PLUGINS_TAG = "plugins";
public static final String MAVEN_DEPENDENCY_TAG = "dependency";
public static final String MAVEN_PLUGIN_TAG = "plugin";
public static final String MAVEN_ARTIFACT_ID_TAG = "artifactId";
public static final String MAVEN_GROUP_ID_TAG = "groupId";
public MavenInspection() {
super(Descriptor.MAVEN);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new XmlElementVisitor() {
@Override
public void visitXmlTag(@NotNull XmlTag element) {
if (isDependencyOrPlugin(element)) {
MavenInspection.this.visitElement(holder, element, isOnTheFly);
}
}
};
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof XmlTag && isDependencyOrPlugin((XmlTag) element)) {
MavenInspection.this.visitElement(holder, element);
}
}
boolean isDependencyOrPlugin(XmlTag xmlTag) {
return StringUtils.equalsAny(xmlTag.getName(), MAVEN_DEPENDENCY_TAG, MAVEN_PLUGIN_TAG);
}
@Override
boolean isDependency(PsiElement element) {
PsiElement parentElement = element.getParent();
if ((parentElement instanceof XmlTag) &&
StringUtils.equalsAny(((XmlTag) parentElement).getName(), MAVEN_DEPENDENCIES_TAG, MAVEN_PLUGINS_TAG)) {
return true;
}
PsiElement grandParentElement = parentElement.getParent();
return (grandParentElement instanceof XmlTag &&
StringUtils.equals(((XmlTag) grandParentElement).getName(), MAVEN_DEPENDENCY_MANAGEMENT));
}
@Override
ScannerBase getScanner(Project project, String path) {
return ScanManager.getScanners(project).stream()
.filter(MavenScanner.class::isInstance)
.findAny()
.orElse(null);
}
@Override
String createComponentName(PsiElement element) {
XmlTag groupId = ((XmlTagImpl) element).findFirstSubTag(MAVEN_GROUP_ID_TAG);
XmlTag artifactId = ((XmlTagImpl) element).findFirstSubTag(MAVEN_ARTIFACT_ID_TAG);
if (groupId == null || artifactId == null) {
return null;
}
DomElement domElement = DomManager.getDomManager(element.getProject()).getDomElement((XmlTag) element);
if (domElement instanceof MavenDomArtifactCoordinates) {
return String.join(":", groupId.getValue().getText(), artifactId.getValue().getText());
}
return null;
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new MavenUpgradeVersion(componentName, fixVersion, issue);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/NpmInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.json.psi.JsonElementVisitor;
import com.intellij.json.psi.JsonProperty;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jfrog.ide.idea.inspections.upgradeversion.NpmUpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.scan.NpmScanner;
import com.jfrog.ide.idea.scan.ScanManager;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.utils.Descriptor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* @author yahavi
*/
public class NpmInspection extends AbstractInspection {
public NpmInspection() {
super(Descriptor.NPM);
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JsonElementVisitor() {
@Override
public void visitProperty(@NotNull JsonProperty element) {
super.visitProperty(element);
NpmInspection.this.visitElement(holder, element, isOnTheFly);
}
};
}
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
if (element instanceof JsonProperty) {
NpmInspection.this.visitElement(holder, element);
}
}
@Override
boolean isDependency(PsiElement element) {
PsiElement parentElement = element.getParent().getParent();
return parentElement != null && StringUtils.equalsAny(parentElement.getFirstChild().getText(), "\"dependencies\"", "\"devDependencies\"");
}
@Override
ScannerBase getScanner(Project project, String path) {
return ScanManager.getScanners(project).stream()
.filter(manager -> StringUtils.equals(manager.getProjectPath(), path))
.filter(this::isMatchingScanner)
.findAny()
.orElse(null);
}
boolean isMatchingScanner(ScannerBase scanner) {
return scanner instanceof NpmScanner;
}
@Override
String createComponentName(PsiElement element) {
return StringUtils.unwrap(element.getFirstChild().getText(), "\"");
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new NpmUpgradeVersion(componentName, fixVersion, issue, descriptorPath);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/ShowInDependencyTree.java
================================================
package com.jfrog.ide.idea.inspections;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.util.ui.tree.TreeUtil;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import com.jfrog.ide.idea.ui.utils.IconUtils;
import com.jfrog.ide.idea.utils.Utils;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* Adds the yellow bulb action - "Show in JFrog plugin".
*
* @author yahavi
*/
public class ShowInDependencyTree implements LocalQuickFix, Iconable, HighPriorityAction {
final static String SHOW_IN_TREE_MESSAGE = "Show vulnerability info in JFrog plugin";
private final DependencyNode node;
private final String dependencyDescription;
public ShowInDependencyTree(DependencyNode node, String dependencyDescription) {
this.node = node;
this.dependencyDescription = dependencyDescription;
}
@Override
public Icon getIcon(int flags) {
return IconUtils.load("jfrog_icon");
}
@NotNull
@Override
public String getFamilyName() {
return SHOW_IN_TREE_MESSAGE + " - " + dependencyDescription;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
Utils.focusJFrogToolWindow(project);
TreeUtil.selectInTree(project, node, true, LocalComponentsTree.getInstance(project), true);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/YarnInspection.java
================================================
package com.jfrog.ide.idea.inspections;
import com.jfrog.ide.idea.inspections.upgradeversion.UpgradeVersion;
import com.jfrog.ide.idea.inspections.upgradeversion.YarnUpgradeVersion;
import com.jfrog.ide.idea.scan.ScannerBase;
import com.jfrog.ide.idea.scan.YarnScanner;
import java.util.Collection;
/**
* @author michaels
*/
public class YarnInspection extends NpmInspection {
@Override
boolean isMatchingScanner(ScannerBase scanner) {
return scanner instanceof YarnScanner;
}
@Override
UpgradeVersion getUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
return new YarnUpgradeVersion(componentName, fixVersion, issue, descriptorPath);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/GoUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.go.GoComponentUpdater;
import com.jfrog.ide.idea.utils.GoUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class GoUpgradeVersion extends UpgradeVersion {
private final String descriptorPath;
public GoUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
super(componentName, fixVersion, issue);
this.descriptorPath = descriptorPath;
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) throws IOException {
Path modulePath = Paths.get(descriptorPath).getParent();
String goExec = GoUtils.getGoExeAndSetEnv(env, project);
GoComponentUpdater goComponentUpdater = new GoComponentUpdater(modulePath, this.log, this.env, goExec);
goComponentUpdater.run(componentName, fixVersion);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/GradleGroovyUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrNamedArgumentsOwner;
import java.util.Collection;
import static com.jfrog.ide.idea.inspections.GradleGroovyInspection.GRADLE_VERSION_KEY;
import static com.jfrog.ide.idea.inspections.GradleGroovyInspection.getLiteralValue;
import static com.jfrog.ide.idea.inspections.GradleInspection.stripVersion;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class GradleGroovyUpgradeVersion extends UpgradeVersion {
public GradleGroovyUpgradeVersion(String componentName, String fixVersion, Collection issue) {
super(componentName, fixVersion, issue);
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
GroovyPsiElementFactory psiFactory = GroovyPsiElementFactory.getInstance(project);
if (element instanceof GrNamedArgumentsOwner) {
// group: 'com', name: 'guava', version: '1.1.1' >> group: 'com', name: 'guava', version: '2.2.2'
GrNamedArgument versionArg = ((GrNamedArgumentsOwner) element).findNamedArgument(GRADLE_VERSION_KEY);
if (versionArg != null && versionArg.getExpression() != null) {
versionArg.getExpression().replace(psiFactory.createExpressionFromText(StringUtils.wrap(fixVersion, "'")));
return;
}
}
if (element instanceof GrLiteral) {
// 'com:guava:1.1.1' >> 'com:guava:2.2.2'
String componentString = getLiteralValue((GrLiteral) element);
String fixedComponentString = String.join(":", stripVersion(componentString), fixVersion);
element.replace(psiFactory.createExpressionFromText(StringUtils.wrap(fixedComponentString, "'")));
}
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/GradleKotlinUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtStringTemplateExpression;
import org.jetbrains.kotlin.psi.KtValueArgument;
import org.jetbrains.kotlin.psi.KtValueArgumentList;
import java.util.Collection;
import java.util.List;
import static com.jfrog.ide.idea.inspections.GradleInspection.stripVersion;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class GradleKotlinUpgradeVersion extends UpgradeVersion {
public GradleKotlinUpgradeVersion(String componentName, String fixVersion, Collection issue) {
super(componentName, fixVersion, issue);
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
List argumentList = ((KtValueArgumentList) descriptor.getPsiElement()).getArguments();
String updateText = "";
KtExpression expressionToUpdate = null;
if (argumentList.size() == 1) {
// "commons-collections:commons-collections:3.2.2"
expressionToUpdate = argumentList.get(0).getArgumentExpression();
if (expressionToUpdate != null) {
String stripQuotes = StringUtils.unwrap(expressionToUpdate.getText(), "\"");
updateText = stripVersion(stripQuotes) + ":" + fixVersion;
}
} else if (argumentList.size() >= 3) {
// "commons-collections", "commons-collections", "3.2.2"
expressionToUpdate = argumentList.get(2).getArgumentExpression();
updateText = fixVersion;
}
if (expressionToUpdate instanceof KtStringTemplateExpression) {
((KtStringTemplateExpression) expressionToUpdate).updateText(StringUtils.wrap(updateText, "\""));
}
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/MavenUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.impl.source.xml.XmlTagImpl;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTagValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.dom.MavenDomUtil;
import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.idea.maven.dom.MavenDomProjectProcessorUtils.findProperty;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class MavenUpgradeVersion extends UpgradeVersion {
private static final Pattern POM_PROPERTY_REGEX = Pattern.compile("^\\$\\{(.*)}");
public MavenUpgradeVersion(String componentName, String fixVersion, Collection issue) {
super(componentName, fixVersion, issue);
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final XmlTag[] versions = ((XmlTagImpl) descriptor.getPsiElement()).findSubTags("version");
XmlTagValue versionTagValue = versions[0].getValue();
Matcher propMatcher = POM_PROPERTY_REGEX.matcher(versionTagValue.getText());
if (!propMatcher.find()) {
// Simple version tag. (example: '1.2.3')
versionTagValue.setText(fixVersion);
} else {
// Property version tag. (example: '${my.ver}')
MavenDomProjectModel domModel = MavenDomUtil.getMavenDomProjectModel(project, descriptor.getPsiElement().getContainingFile().getVirtualFile());
if (domModel != null) {
XmlTag prop = findProperty(domModel.getProperties(), propMatcher.group(1));
if (prop != null) {
prop.getValue().setText(fixVersion);
}
}
}
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/NpmUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.npm.NpmComponentUpdater;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class NpmUpgradeVersion extends UpgradeVersion {
private final String descriptorPath;
public NpmUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
super(componentName, fixVersion, issue);
this.descriptorPath = descriptorPath;
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) throws IOException {
Path modulePath = Paths.get(descriptorPath).getParent();
NpmComponentUpdater npmComponentUpdater = new NpmComponentUpdater(modulePath, this.log, this.env);
npmComponentUpdater.run(componentName, fixVersion);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/UpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.ui.utils.IconUtils;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public abstract class UpgradeVersion implements LocalQuickFix, Iconable, HighPriorityAction {
protected String componentName;
protected String fixVersion;
protected String issue;
protected Logger log;
protected Map env;
public UpgradeVersion(String componentName, String fixVersion, Collection issue) {
this.componentName = componentName;
this.fixVersion = fixVersion;
this.issue = issue.toString();
this.log = Logger.getInstance();
this.env = new HashMap<>(EnvironmentUtil.getEnvironmentMap());
}
@Override
public Icon getIcon(int flags) {
return IconUtils.load("jfrog_icon");
}
@NotNull
@Override
public String getFamilyName() {
return "Upgrade version to " + fixVersion + " to fix " + issue;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
Task.Backgroundable scanAndUpdateTask = new Task.Backgroundable(project, "Upgrading dependency...") {
@Override
public void run(@NotNull com.intellij.openapi.progress.ProgressIndicator indicator) {
ApplicationManager.getApplication().invokeAndWait(() -> {
WriteCommandAction.runWriteCommandAction(project, () -> {
try {
upgradeComponentVersion(project, descriptor);
log.info("Upgraded " + componentName + " to version " + fixVersion + " successfully.");
} catch (IOException e) {
log.error("Failed while trying to upgrade component " + componentName + " to version " + fixVersion + ". Error: " + e);
}
});
descriptor.getPsiElement().getContainingFile().getVirtualFile().refresh(false, false);
});
}
};
ProgressManager.getInstance().run(scanAndUpdateTask);
}
abstract public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) throws IOException;
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/inspections/upgradeversion/YarnUpgradeVersion.java
================================================
package com.jfrog.ide.idea.inspections.upgradeversion;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.yarn.YarnComponentUpdater;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
/**
* Adds the yellow bulb action - "Upgrade Version".
*
* @author michaels
*/
public class YarnUpgradeVersion extends UpgradeVersion {
private final String descriptorPath;
public YarnUpgradeVersion(String componentName, String fixVersion, Collection issue, String descriptorPath) {
super(componentName, fixVersion, issue);
this.descriptorPath = descriptorPath;
}
@Override
public void upgradeComponentVersion(@NotNull Project project, @NotNull ProblemDescriptor descriptor) throws IOException {
Path modulePath = Paths.get(descriptorPath).getParent();
YarnComponentUpdater yarnComponentUpdater = new YarnComponentUpdater(modulePath, this.log, this.env);
yarnComponentUpdater.run(componentName, fixVersion);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/log/Logger.java
================================================
package com.jfrog.ide.idea.log;
import com.intellij.notification.*;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.jfrog.ide.idea.ui.utils.IconUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.plexus.util.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import org.jfrog.build.api.util.Log;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
/**
* @author yahavi
*/
public class Logger implements Log {
private static final long serialVersionUID = 1L;
private static final NotificationGroup EVENT_LOG_NOTIFIER = NotificationGroupManager.getInstance().getNotificationGroup("JFrog Log");
private static final NotificationGroup BALLOON_NOTIFIER = NotificationGroupManager.getInstance().getNotificationGroup("JFrog Errors");
private static final com.intellij.openapi.diagnostic.Logger ideaLogger = com.intellij.openapi.diagnostic.Logger.getInstance(Logger.class);
private static Notification lastNotification;
private static final String INFORMATION_TITLE = "JFrog";
private static final String ERROR_TITLE = "JFrog scan failed";
public static Logger getInstance() {
return ApplicationManager.getApplication().getService(Logger.class);
}
private Logger() {
}
@Override
public void debug(String message) {
ideaLogger.debug(message);
}
@Override
public void info(String message) {
ideaLogger.info(message);
NotificationType notificationType = NotificationType.INFORMATION;
log(INFORMATION_TITLE, message, notificationType);
}
@Override
public void warn(String message) {
ideaLogger.warn(message);
NotificationType notificationType = NotificationType.WARNING;
log(INFORMATION_TITLE, message, notificationType);
}
/**
* Log an error.
* Notice - For the best user experience, make sure that only interactive actions should call this method.
*
* @param message - The message to log
*/
@Override
public void error(String message) {
// We log to IntelliJ log in "warn" log level to avoid popup annoying fatal errors
ideaLogger.warn(message);
NotificationType notificationType = NotificationType.ERROR;
popupBalloon(message, notificationType);
log(ERROR_TITLE, message, notificationType);
}
/**
* Log an error.
* Notice - For the best user experience, make sure that only interactive actions should call this method.
*
* @param message - The message to log
* @param t - The exception raised
*/
@Override
public void error(String message, Throwable t) {
// We log to IntelliJ log in "warn" log level to avoid popup annoying fatal errors
ideaLogger.warn(message, t);
NotificationType notificationType = NotificationType.ERROR;
popupBalloon(message, notificationType);
String title = StringUtils.defaultIfBlank(t.getMessage(), ERROR_TITLE);
log(title, message + System.lineSeparator() + ExceptionUtils.getStackTrace(t), notificationType);
}
private static void log(String title, String details, NotificationType notificationType) {
if (StringUtils.isBlank(details)) {
details = title;
}
Notifications.Bus.notify(EVENT_LOG_NOTIFIER.createNotification(title, prependPrefix(details, notificationType), notificationType));
}
private static void popupBalloon(String content, NotificationType notificationType) {
if (lastNotification != null) {
lastNotification.hideBalloon();
}
if (StringUtils.isBlank(content)) {
content = ERROR_TITLE;
}
Notification notification = BALLOON_NOTIFIER.createNotification(ERROR_TITLE, content, notificationType);
lastNotification = notification;
Notifications.Bus.notify(notification);
}
private static String prependPrefix(String message, NotificationType notificationType) {
return switch (notificationType) {
case WARNING -> "[WARN] " + message;
case ERROR -> "[ERROR] " + message;
default -> "[INFO] " + message;
};
}
/**
* Add a log message with an open settings link.
* Usage example:
* Logger.openSettings("It looks like Gradle home was not properly set in your project.
* Click here to set Gradle home.", project, GradleConfigurable.class);
*
* @param details - The log message
* @param project - IDEA project
* @param configurable - IDEA settings to open
*/
public static void addOpenSettingsLink(String details, Project project, Class extends Configurable> configurable) {
EVENT_LOG_NOTIFIER.createNotification(INFORMATION_TITLE, prependPrefix(details, NotificationType.INFORMATION), NotificationType.INFORMATION)
.addAction(new AnAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, configurable);
}
})
.notify(project);
}
/**
* Popup a balloon with an actionable link.
* Usage example:
* Logger.showActionableBalloon(project,
* "The scan results have expired. Click here to trigger a scan.",
* () -> ScanManager.getInstance(project).startScan());
*
* @param project - IDEA project
* @param htmlContent - The log message
* @param action - The action to perform
*/
public static void showActionableBalloon(Project project, String htmlContent, Runnable action) {
StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(htmlContent,
IconUtils.load("jfrog_icon"),
JBColor.foreground(),
JBColor.background(),
event -> {
if (event.getEventType() != ACTIVATED) {
return;
}
action.run();
})
.setCloseButtonEnabled(true)
.setHideOnAction(true)
.setHideOnClickOutside(true)
.setHideOnLinkClick(true)
.setHideOnKeyOutside(true)
.setDialogMode(true)
.createBalloon()
.show(RelativePoint.getNorthWestOf(statusBar.getComponent()), Balloon.Position.atRight);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/log/ProgressIndicatorImpl.java
================================================
package com.jfrog.ide.idea.log;
import com.intellij.openapi.progress.ProgressIndicator;
/**
* @author yahavi
*/
public class ProgressIndicatorImpl implements com.jfrog.ide.common.log.ProgressIndicator {
private final ProgressIndicator indicator;
public ProgressIndicatorImpl(ProgressIndicator indicator) {
this.indicator = indicator;
}
@Override
public void setFraction(double fraction) {
indicator.setIndeterminate(false);
indicator.setFraction(fraction);
}
@Override
public void setIndeterminate(boolean indeterminate) {
indicator.setIndeterminate(true);
}
@Override
public void setText(String text) {
indicator.setText(text);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/navigation/NavigationService.java
================================================
package com.jfrog.ide.idea.navigation;
import com.google.common.collect.Maps;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jfrog.ide.common.nodes.DependencyNode;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Created by Bar Belity on 27/04/2020.
* Manage navigation from node in Issues-tree to its corresponding item in the project descriptor.
*/
public class NavigationService {
private final Map> navigationMap = Maps.newHashMap();
public static NavigationService getInstance(@NotNull Project project) {
return project.getService(NavigationService.class);
}
/**
* Clear existing navigation map.
*/
public static void clearNavigationMap(@NotNull Project project) {
NavigationService navigationService = NavigationService.getInstance(project);
navigationService.navigationMap.clear();
}
/**
* Add a navigation element to the node in tree.
*
* @param treeNode The tree-node to register the navigation from.
* @param navigationTargetElement The PsiElement we register the navigation to.
*/
public void addNavigation(DependencyNode treeNode, PsiElement navigationTargetElement, String componentName) {
PsiFile containingFile = navigationTargetElement.getContainingFile();
FileViewProvider fileViewProvider = containingFile.getViewProvider();
Document document = fileViewProvider.getDocument();
if (document == null) {
return;
}
NavigationTarget navigationTarget = new NavigationTarget(navigationTargetElement, document.getLineNumber(navigationTargetElement.getTextOffset()), componentName);
Set navigationTargets = navigationMap.get(treeNode);
if (navigationTargets == null) {
navigationTargets = new HashSet<>(Collections.singletonList(navigationTarget));
navigationMap.put(treeNode, navigationTargets);
return;
}
navigationTargets.add(navigationTarget);
}
/**
* Get navigation targets for a specific node in tree.
*
* @param treeNode The tree-node to get its navigation.
* @return Set of candidates for navigation.
*/
public Set getNavigation(DependencyNode treeNode) {
return navigationMap.get(treeNode);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/navigation/NavigationTarget.java
================================================
package com.jfrog.ide.idea.navigation;
import com.intellij.psi.PsiElement;
import java.util.Objects;
/**
* Created by Bar Belity on 14/05/2020.
*/
public class NavigationTarget {
private final PsiElement element;
private final int lineNumber;
private final String componentName;
NavigationTarget(PsiElement element, int lineNumber, String componentName) {
this.element = element;
this.lineNumber = lineNumber;
this.componentName = componentName;
}
public PsiElement getElement() {
return element;
}
public int getLineNumber() {
return lineNumber;
}
public String getComponentName() {
return componentName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NavigationTarget)) return false;
NavigationTarget that = (NavigationTarget) o;
return lineNumber == that.lineNumber &&
element.isValid() && that.element.isValid() &&
Objects.equals(element.getContainingFile(), that.element.getContainingFile());
}
@Override
public int hashCode() {
return Objects.hash(element.getContainingFile(), lineNumber);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/ApplicabilityScannerExecutor.java
================================================
package com.jfrog.ide.idea.scan;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.ApplicableIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.VulnerabilityNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.scan.data.*;
import com.jfrog.xray.client.services.entitlements.Feature;
import org.apache.commons.lang3.StringUtils;
import org.jfrog.build.api.util.Log;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Tal Arian
*/
public class ApplicabilityScannerExecutor extends ScanBinaryExecutor {
private static final List SCANNER_ARGS = List.of("ca");
private static final List SUPPORTED_PACKAGE_TYPES = List.of(PackageManagerType.PYPI, PackageManagerType.NPM, PackageManagerType.YARN, PackageManagerType.GRADLE, PackageManagerType.MAVEN);
public ApplicabilityScannerExecutor(Log log) {
super(SourceCodeScanType.CONTEXTUAL, log);
supportedPackageTypes = SUPPORTED_PACKAGE_TYPES;
}
public List execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException {
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, indicator);
}
@Override
protected List parseOutputSarif(Path outputFile) throws IOException, IndexOutOfBoundsException {
Output output = getOutputObj(outputFile);
List warnings = new ArrayList<>();
for (Run run : output.getRuns()) {
List rules = run.getTool().getDriver().getRules();
Map> resultsByRule = run.getResults().stream()
.filter(SarifResult::isNotSuppressed)
.filter(r -> !"informational".equals(r.getKind()))
.collect(Collectors.groupingBy(SarifResult::getRuleId));
for (Rule rule : getUniqueRules(rules)) {
Optional props = rule.getRuleProperties();
if (props.isEmpty()) {
continue;
}
String applicability = props.get().getApplicability();
if (applicability == null) {
continue;
}
if ("applicable".equals(applicability)) {
List evidence = resultsByRule.getOrDefault(rule.getId(), List.of());
for (SarifResult result : evidence) {
if (!result.getLocations().isEmpty()) {
warnings.add(new JFrogSecurityWarning(result, scanType, rule));
}
}
} else if ("not_applicable".equals(applicability)) {
warnings.add(JFrogSecurityWarning.notApplicable(rule.getId(), scanType));
}
}
}
return warnings;
}
private List getUniqueRules(List rules) {
Map ruleMap = new LinkedHashMap<>();
for (Rule rule : rules) {
Rule existing = ruleMap.get(rule.getId());
if (existing == null) {
ruleMap.put(rule.getId(), rule);
} else {
Optional existingProps = existing.getRuleProperties();
if (existingProps.isPresent() && "not_applicable".equals(existingProps.get().getApplicability())) {
Optional currentProps = rule.getRuleProperties();
if (currentProps.isPresent() && !"not_applicable".equals(currentProps.get().getApplicability())) {
ruleMap.put(rule.getId(), rule);
}
}
}
}
return new ArrayList<>(ruleMap.values());
}
@Override
List createSpecificFileIssueNodes(List warnings) {
return createSpecificFileIssueNodes(warnings, new HashMap<>());
}
List createSpecificFileIssueNodes(List warnings, Map> issuesMap) {
HashMap results = new HashMap<>();
for (JFrogSecurityWarning warning : warnings) {
// Update all VulnerabilityNodes that have the warning's CVE
String cve = StringUtils.removeStart(warning.getRuleID(), "applic_");
List issues = issuesMap.get(cve);
if (issues != null) {
if (warning.isApplicable()) {
// Create FileTreeNodes for files with applicable issues
FileTreeNode fileNode = results.get(warning.getFilePath());
if (fileNode == null) {
fileNode = new FileTreeNode(warning.getFilePath());
results.put(warning.getFilePath(), fileNode);
}
ApplicableIssueNode applicableIssue = new ApplicableIssueNode(
cve, warning.getLineStart(), warning.getColStart(), warning.getLineEnd(), warning.getColEnd(),
warning.getFilePath(), warning.getReason(), warning.getLineSnippet(), warning.getScannerSearchTarget(),
issues.get(0), warning.getRuleID());
fileNode.addIssue(applicableIssue);
for (VulnerabilityNode issue : issues) {
issue.updateApplicableInfo(applicableIssue);
}
} else {
// Mark non-applicable vulnerabilities.
for (VulnerabilityNode issue : issues) {
issue.setNotApplicable();
}
}
}
}
return new ArrayList<>(results.values());
}
@Override
Feature getScannerFeatureName() {
return Feature.CONTEXTUAL_ANALYSIS;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/GoScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.google.common.collect.Maps;
import com.intellij.diagnostic.PluginException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.go.GoTreeBuilder;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.inspections.GoInspection;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import com.jfrog.ide.idea.utils.GoUtils;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.ExecutorService;
/**
* Created by Bar Belity on 06/02/2020.
*/
public class GoScanner extends SingleDescriptorScanner {
private final GoTreeBuilder goTreeBuilder;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param basePath the go.mod directory
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
GoScanner(Project project, String basePath, ExecutorService executor, ScanLogic scanLogic) {
super(project, basePath, ComponentPrefix.GO, executor, Paths.get(basePath, "go.mod").toString(), scanLogic);
getLog().info("Found Go project: " + getProjectPath());
Map env = Maps.newHashMap(EnvironmentUtil.getEnvironmentMap());
String goExec = null;
try {
goExec = GoUtils.getGoExeAndSetEnv(env, project);
} catch (NoClassDefFoundError error) {
getLog().warn("Go plugin is not installed. Install it to get a better experience.");
}
goTreeBuilder = new GoTreeBuilder(goExec, Paths.get(basePath), descriptorFilePath, env, getLog());
}
@Override
protected DepTree buildTree() throws IOException {
return goTreeBuilder.buildTree();
}
@Override
protected PsiFile[] getProjectDescriptors() {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(descriptorFilePath);
if (file == null) {
return null;
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
return new PsiFile[]{psiFile};
}
@Override
protected @Nullable AbstractInspection getInspectionTool() {
try {
return new GoInspection();
} catch (PluginException e) {
// Go plugin is disabled or not installed
getLog().warn("Inspections for Go projects require the Go language plugin to be installed and enabled. " +
"Please make sure the Go plugin is installed and enabled for a complete experience.");
return null;
}
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.GO;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/GradleScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.google.common.collect.Maps;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.gradle.GradleTreeBuilder;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.inspections.GradleGroovyInspection;
import com.jfrog.ide.idea.inspections.GradleKotlinInspection;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.jetbrains.plugins.gradle.service.GradleInstallationManager;
import org.jetbrains.plugins.gradle.service.settings.GradleConfigurable;
import org.jetbrains.plugins.gradle.settings.DistributionType;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import static com.jfrog.ide.common.log.Utils.logError;
/**
* Created by Yahav Itzhak on 9 Nov 2017.
*/
public class GradleScanner extends SingleDescriptorScanner {
private final GradleTreeBuilder gradleTreeBuilder;
private boolean kotlin;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param basePath the build.gradle or build.gradle.kts directory
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
GradleScanner(Project project, String basePath, ExecutorService executor, ScanLogic scanLogic) {
super(project, basePath, ComponentPrefix.GAV, executor, scanLogic);
getLog().info("Found Gradle project: " + getProjectPath());
Path dirPath = Paths.get(this.basePath);
Path buildGradleKotlinPath = dirPath.resolve("build.gradle.kts");
if (Files.exists(buildGradleKotlinPath)) {
descriptorFilePath = buildGradleKotlinPath.toString();
} else {
descriptorFilePath = dirPath.resolve("build.gradle").toString();
}
Map env = Maps.newHashMap(EnvironmentUtil.getEnvironmentMap());
Path pluginLibDir = PluginManagerCore.getPlugin(PluginId.findId("org.jfrog.idea")).getPluginPath().resolve("lib");
env.put("pluginLibDir", pluginLibDir.toAbsolutePath().toString());
gradleTreeBuilder = new GradleTreeBuilder(Paths.get(basePath), descriptorFilePath, env, getGradleExeAndJdk(env));
}
@Override
protected PsiFile[] getProjectDescriptors() {
LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
Path basePath = Paths.get(this.basePath);
VirtualFile file = localFileSystem.findFileByPath(basePath.resolve("build.gradle").toString());
if (file == null) {
file = localFileSystem.findFileByPath(basePath.resolve("build.gradle.kts").toString());
if (file == null) {
return null;
}
kotlin = true;
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
return new PsiFile[]{psiFile};
}
@Override
protected AbstractInspection getInspectionTool() {
return kotlin ? new GradleKotlinInspection() : new GradleGroovyInspection();
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.GRADLE;
}
@Override
protected DepTree buildTree() throws IOException {
return gradleTreeBuilder.buildTree(getLog());
}
/**
* Extract the chosen Gradle executable path from the Gradle plugin. If Gradle is not configured well, return null.
*
* @param env - The environment variables map to set the JAVA_HOME
* @return the chosen Gradle executable path or null
*/
String getGradleExeAndJdk(Map env) {
File gradleHome = resolveGradleAndSetJavaHome(env);
if (gradleHome == null) {
getLog().info("Using Gradle from system path.");
return null;
}
String gradleExe = gradleHome.toPath().resolve("bin").resolve(SystemUtils.IS_OS_WINDOWS ? "gradle.bat" : "gradle").toString();
getLog().info("Using Gradle executable " + gradleExe);
return gradleExe;
}
/**
* Resolve Gradle executable and Java home from Gradle settings.
*
* @param env - The environment variables to set the JAVA_HOME
* @return gradle executable
*/
private File resolveGradleAndSetJavaHome(Map env) {
GradleSettings gradleSettings = GradleSettings.getInstance(project);
GradleProjectSettings projectSettings = gradleSettings.getLinkedProjectSettings(basePath);
if (projectSettings == null && SystemUtils.IS_OS_WINDOWS) {
projectSettings = gradleSettings.getLinkedProjectSettings(basePath.replaceAll("\\\\", "/"));
}
if (projectSettings == null) {
logError(getLog(), "Couldn't retrieve Gradle project settings. Hint - make sure the Gradle project was properly imported.", false);
return null;
}
GradleInstallationManager gradleInstallationManager = ApplicationManager.getApplication().getService(GradleInstallationManager.class);
// Set JAVA_HOME
String javaHome = gradleInstallationManager.getGradleJvmPath(project, projectSettings.getExternalProjectPath());
if (StringUtils.isNotBlank(javaHome)) {
getLog().info("Using Java home: " + javaHome);
env.put("JAVA_HOME", javaHome);
}
File gradleHome = gradleInstallationManager.getGradleHome(project, projectSettings.getExternalProjectPath());
if (gradleHome != null) {
return gradleHome;
}
if (StringUtils.isNotBlank(projectSettings.getGradleHome())) {
return new File(projectSettings.getGradleHome());
}
// Gradle wasn't set properly
if (isMisconfigurationError(projectSettings.getExternalProjectPath())) {
Logger.addOpenSettingsLink("It looks like Gradle home was not properly set in your project. " +
"Click here to set Gradle home.", project, GradleConfigurable.class);
} else {
getLog().warn("Can't run Gradle from Gradle settings. Hint - try to reload Gradle project and then refresh the scan.");
}
return null;
}
private boolean isMisconfigurationError(String linkedProjectPath) {
GradleProjectSettings projectSettings = GradleSettings.getInstance(project).getLinkedProjectSettings(linkedProjectPath);
if (projectSettings != null) {
DistributionType distributionType = projectSettings.getDistributionType();
// Distribution type has not been chosen or the distribution type is not Gradle wrapper.
// If the distribution type is wrapped, it is probable that the Gradle wrapper is not yet created.
return distributionType == null || !distributionType.isWrapped();
}
// Gradle project settings are not set
return true;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/IACScannerExecutor.java
================================================
package com.jfrog.ide.idea.scan;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.data.ScanConfig;
import com.jfrog.xray.client.services.entitlements.Feature;
import org.jfrog.build.api.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author Tal Arian
*/
public class IACScannerExecutor extends ScanBinaryExecutor {
private static final List SCANNER_ARGS = List.of("iac");
private static final String ISSUE_TITLE = "Infrastructure as Code Vulnerability";
public IACScannerExecutor(Log log) {
super(SourceCodeScanType.IAC, log);
}
public List execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException {
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, indicator);
}
@Override
List createSpecificFileIssueNodes(List warnings) {
HashMap results = new HashMap<>();
for (JFrogSecurityWarning warning : warnings) {
// Create FileTreeNodes for files with found issues
FileTreeNode fileNode = results.get(warning.getFilePath());
if (fileNode == null) {
fileNode = new FileTreeNode(warning.getFilePath());
results.put(warning.getFilePath(), fileNode);
}
FileIssueNode issueNode = new FileIssueNode(ISSUE_TITLE,
warning.getFilePath(), warning.getLineStart(), warning.getColStart(), warning.getLineEnd(), warning.getColEnd(),
warning.getScannerSearchTarget(), warning.getLineSnippet(), warning.getReporter(), warning.getSeverity(), warning.getRuleID());
fileNode.addIssue(issueNode);
}
return new ArrayList<>(results.values());
}
@Override
public Feature getScannerFeatureName() {
return Feature.INFRASTRUCTURE_AS_CODE;
}
@Override
protected boolean isPackageTypeSupported(PackageManagerType packageType) {
return true;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/MavenScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.deptree.DepTreeNode;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.DescriptorFileTreeNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.inspections.MavenInspection;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import com.jfrog.ide.idea.utils.Utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.model.MavenArtifactNode;
import org.jetbrains.idea.maven.model.MavenArtifactState;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import static com.jfrog.ide.common.utils.Utils.createComponentId;
/**
* Created by romang on 3/2/17.
*/
public class MavenScanner extends ScannerBase {
private final String POM_FILE_NAME = "pom.xml";
/**
* @param project - Currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param executor - An executor that should limit the number of running tasks to 3
*/
MavenScanner(Project project, ExecutorService executor, ScanLogic scanLogic) {
super(project, Utils.getProjectBasePath(project).toString(), ComponentPrefix.GAV, executor, scanLogic);
getLog().info("Found Maven project: " + getProjectPath());
}
static boolean isApplicable(@NotNull Project project) {
return MavenProjectsManager.getInstance(project).hasProjects();
}
/**
* Returns all project modules locations as Paths.
* Other scanners such as npm will use this paths in order to find modules.
*
* @return all project modules locations as Paths
*/
public Set getProjectPaths() {
return MavenProjectsManager.getInstance(project).getProjects().stream()
.map(MavenProject::getDirectory)
.map(Paths::get)
.collect(Collectors.toSet());
}
@Override
protected DepTree buildTree() {
String rootId = project.getName();
DepTreeNode rootNode = new DepTreeNode();
Map nodes = new HashMap<>();
MavenProjectsManager.getInstance(project).getRootProjects().forEach(rootMavenProject -> populateMavenModule(nodes, rootNode, rootMavenProject));
if (rootNode.getChildren().size() == 1) {
return new DepTree(rootNode.getChildren().iterator().next(), nodes);
}
nodes.put(rootId, rootNode);
return new DepTree(rootId, nodes);
}
@Override
protected PsiFile[] getProjectDescriptors() {
// As project can contain subprojects, look for all 'pom.xml' files under it.
GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), XmlFileType.INSTANCE);
Collection allPoms = FilenameIndex.getVirtualFilesByName(POM_FILE_NAME, scope);
PsiManager psiManager = PsiManager.getInstance(project);
return allPoms.stream().map(psiManager::findFile).toArray(PsiFile[]::new);
}
@Override
protected AbstractInspection getInspectionTool() {
return new MavenInspection();
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.MAVEN;
}
/**
* Populate recursively the dependency tree with the maven module and its dependencies.
*
* @param nodes a map of {@link DepTreeNode}s by their component IDs to be filled with the module's components.
* @param parentModule the parent dependency node
* @param mavenProject the root Maven project
*/
private void populateMavenModule(Map nodes, DepTreeNode parentModule, MavenProject mavenProject) {
MavenId mavenId = mavenProject.getMavenId();
String compId = createComponentId(mavenId.getGroupId(), mavenId.getArtifactId(), mavenId.getVersion());
DepTreeNode mavenNode = getOrCreateMavenModuleNode(nodes, compId, mavenProject);
parentModule.getChildren().add(compId);
addMavenProjectDependencies(nodes, mavenNode, mavenProject);
mavenProject.getExistingModuleFiles().stream()
.map(this::getModuleByVirtualFile)
.filter(Objects::nonNull)
.forEach(mavenModule -> populateMavenModule(nodes, mavenNode, mavenModule));
}
private MavenProject getModuleByVirtualFile(VirtualFile virtualFile) {
return MavenProjectsManager.getInstance(project).getProjects()
.stream()
.filter(mavenModule -> Objects.equals(mavenModule.getFile().getCanonicalPath(), virtualFile.getCanonicalPath()))
.findAny()
.orElse(null);
}
private void addMavenProjectDependencies(Map nodes, DepTreeNode moduleNode, MavenProject mavenProject) {
mavenProject.getDependencyTree()
.stream()
.filter(mavenArtifactNode -> mavenArtifactNode.getState() == MavenArtifactState.ADDED)
.forEach(mavenArtifactNode -> updateChildrenNodes(nodes, moduleNode, mavenArtifactNode, true));
}
private DepTreeNode getOrCreateMavenModuleNode(Map nodes, String moduleCompId, MavenProject mavenProject) {
if (!nodes.containsKey(moduleCompId)) {
nodes.put(moduleCompId, new DepTreeNode());
}
return nodes.get(moduleCompId).descriptorFilePath(mavenProject.getPath());
}
private void updateChildrenNodes(Map nodes, DepTreeNode parentNode, MavenArtifactNode mavenArtifactNode, boolean setScopes) {
MavenArtifact mavenArtifact = mavenArtifactNode.getArtifact();
String compId = mavenArtifact.getDisplayStringSimple();
DepTreeNode currentNode;
if (nodes.containsKey(compId)) {
currentNode = nodes.get(compId);
} else {
currentNode = new DepTreeNode();
nodes.put(compId, currentNode);
}
if (setScopes) {
currentNode.getScopes().add(mavenArtifact.getScope());
}
mavenArtifactNode.getDependencies()
.stream()
.filter(mavenArtifactChild -> mavenArtifactChild.getState() == MavenArtifactState.ADDED)
.forEach(childrenArtifactNode -> updateChildrenNodes(nodes, currentNode, childrenArtifactNode, false));
parentNode.getChildren().add(compId);
}
/**
* Groups a collection of {@link DependencyNode}s by the descriptor files of the modules that depend on them.
* The returned DependencyNodes inside the {@link FileTreeNode}s are clones of the ones in depScanResults.
*
* @param depScanResults collection of DependencyNodes
* @param depTree the project's dependency tree
* @param parents a map of components by their IDs and their parents in the dependency tree
* @return a list of FileTreeNodes (that are all DescriptorFileTreeNodes) having the DependencyNodes as their children
*/
@Override
protected List groupDependenciesToDescriptorNodes(Collection depScanResults, DepTree depTree, Map> parents) {
Map descriptorMap = new HashMap<>();
Map> visitedComponents = new HashMap<>();
for (DependencyNode dependencyNode : depScanResults) {
String vulnerableDepId = dependencyNode.getComponentIdWithoutPrefix();
Set affectedModulesIds = getDependentModules(vulnerableDepId, depTree, parents, visitedComponents);
for (String descriptorId : affectedModulesIds) {
String descriptorPath = depTree.nodes().get(descriptorId).getDescriptorFilePath();
descriptorMap.putIfAbsent(descriptorPath, new DescriptorFileTreeNode(descriptorPath));
// Each dependency might be a child of more than one POM file, but Artifact is a tree node, so it can have only one parent.
// The solution for this is to clone the dependency before adding it as a child of the POM.
DependencyNode clonedDep = (DependencyNode) dependencyNode.clone();
clonedDep.setIndirect(!vulnerableDepId.equals(descriptorId) && !parents.get(vulnerableDepId).contains(descriptorId));
descriptorMap.get(descriptorPath).addDependency(clonedDep);
}
}
return new CopyOnWriteArrayList<>(descriptorMap.values());
}
/**
* Retrieve component IDs of all modules in the project that are dependent on the specified component.
*
* @param compId the component ID to identify modules depending on it
* @param depTree the project's dependency tree
* @param parents a map of components by their IDs and their parents in the dependency tree
* @param visitedComponents a map of components for which dependent modules have already been found
* @return a set of component IDs representing modules dependent on the specified component
*/
Set getDependentModules(String compId, DepTree depTree, Map> parents, Map> visitedComponents) {
if (visitedComponents.containsKey(compId)) {
return visitedComponents.get(compId);
}
Set modulesIds = new HashSet<>();
if (depTree.nodes().get(compId).getDescriptorFilePath() != null) {
modulesIds.add(compId);
}
if (parents.containsKey(compId)) {
for (String parentId : parents.get(compId)) {
modulesIds.addAll(getDependentModules(parentId, depTree, parents, visitedComponents));
}
}
visitedComponents.put(compId, modulesIds);
return modulesIds;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/NpmScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.npm.NpmTreeBuilder;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.inspections.NpmInspection;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
/**
* Created by Yahav Itzhak on 13 Dec 2017.
*/
public class NpmScanner extends SingleDescriptorScanner {
private final NpmTreeBuilder npmTreeBuilder;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param basePath the package.json directory
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
NpmScanner(Project project, String basePath, ExecutorService executor, ScanLogic scanLogic) {
super(project, basePath, ComponentPrefix.NPM, executor, Paths.get(basePath, "package.json").toString(), scanLogic);
getLog().info("Found npm project: " + getProjectPath());
npmTreeBuilder = new NpmTreeBuilder(Paths.get(basePath), descriptorFilePath, EnvironmentUtil.getEnvironmentMap());
}
@Override
protected DepTree buildTree() throws IOException {
return npmTreeBuilder.buildTree(getLog());
}
@Override
protected PsiFile[] getProjectDescriptors() {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(descriptorFilePath);
if (file == null) {
return null;
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
return new PsiFile[]{psiFile};
}
@Override
protected AbstractInspection getInspectionTool() {
return new NpmInspection();
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.NPM;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/PypiScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyRequirement;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.deptree.DepTreeNode;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import org.apache.commons.collections4.CollectionUtils;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import static com.jfrog.ide.common.utils.Utils.createComponentId;
/**
* @author yahavi
*/
public class PypiScanner extends SingleDescriptorScanner {
private final Sdk pythonSdk;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param pythonSdk the Python SDK
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
PypiScanner(Project project, Sdk pythonSdk, ExecutorService executor, ScanLogic scanLogic) {
super(project, pythonSdk.getHomePath(), ComponentPrefix.PYPI, executor, pythonSdk.getHomePath(), scanLogic);
this.pythonSdk = pythonSdk;
getLog().info("Found PyPI SDK: " + getProjectPath());
}
@Override
protected DepTree buildTree() throws IOException {
try {
return createSdkDependencyTree(pythonSdk);
} catch (ExecutionException e) {
throw new IOException(e);
}
}
/**
* Create a dependency tree for a given Python SDK.
*
* @param pythonSdk the Python SDK
* @return dependency tree created for a given Python SDK.
*/
private DepTree createSdkDependencyTree(Sdk pythonSdk) throws ExecutionException {
// Retrieve all PyPI packages
PyPackageManager packageManager = PyPackageManager.getInstance(pythonSdk);
List packages = packageManager.refreshAndGetPackages(true);
getLog().debug(CollectionUtils.size(packages) + " PyPI packages found in SDK " + pythonSdk.getName());
// Create dependency mapping
Map compIdByCompName = new HashMap<>();
Set dependencies = new HashSet<>();
for (PyPackage pyPackage : packages) {
String compId = createComponentId(pyPackage.getName(), pyPackage.getVersion());
compIdByCompName.put(pyPackage.getName().toLowerCase(), compId);
dependencies.add(compId);
}
// Populate each node's children
Map nodes = new HashMap<>();
for (PyPackage pyPackage : packages) {
String compId = createComponentId(pyPackage.getName(), pyPackage.getVersion());
DepTreeNode node = new DepTreeNode();
for (PyRequirement requirement : pyPackage.getRequirements()) {
String depId = compIdByCompName.get(requirement.getName().toLowerCase());
if (depId == null) {
getLog().warn("Dependency " + requirement.getName() + " is not installed.");
continue;
}
node.getChildren().add(depId);
}
nodes.put(compId, node);
}
// Create root SDK node
String rootCompId = pythonSdk.getName();
DepTreeNode sdkNode = new DepTreeNode().descriptorFilePath(pythonSdk.getHomePath());
sdkNode.children(dependencies);
nodes.put(rootCompId, sdkNode);
return new DepTree(rootCompId, nodes);
}
@Override
protected PsiFile[] getProjectDescriptors() {
return null;
}
@Override
protected AbstractInspection getInspectionTool() {
return null;
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.PYPI;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/SastScannerExecutor.java
================================================
package com.jfrog.ide.idea.scan;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.SastIssueNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.data.ScanConfig;
import com.jfrog.xray.client.services.entitlements.Feature;
import org.jfrog.build.api.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author Tal Arian
*/
public class SastScannerExecutor extends ScanBinaryExecutor {
private static final List SCANNER_ARGS = List.of("zd");
// This variable is used to indicate that this scanner supports only the new config (input) format.
// In the near future, when all scanners will use only the new input file structure this variable as well
// as the ScanConfig and ScanConfigs classes can be safely removed.
private static final boolean RUN_WITH_NEW_CONFIG_FILE = true;
private static final List SUPPORTED_PACKAGE_TYPES = List.of(PackageManagerType.PYPI, PackageManagerType.NPM, PackageManagerType.YARN, PackageManagerType.GRADLE, PackageManagerType.MAVEN);
public SastScannerExecutor(Log log) {
super(SourceCodeScanType.SAST, log);
}
public List execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException {
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, RUN_WITH_NEW_CONFIG_FILE, indicator);
}
@Override
List createSpecificFileIssueNodes(List warnings) {
HashMap results = new HashMap<>();
for (JFrogSecurityWarning warning : warnings) {
// Create FileTreeNodes for files with found issues
FileTreeNode fileNode = results.get(warning.getFilePath());
if (fileNode == null) {
fileNode = new FileTreeNode(warning.getFilePath());
results.put(warning.getFilePath(), fileNode);
}
FileIssueNode issueNode = new SastIssueNode(warning.getReason(),
warning.getFilePath(), warning.getLineStart(), warning.getColStart(), warning.getLineEnd(), warning.getColEnd(),
warning.getScannerSearchTarget(), warning.getLineSnippet(), warning.getCodeFlows(), warning.getSeverity(), warning.getRuleID());
fileNode.addIssue(issueNode);
}
return new ArrayList<>(results.values());
}
@Override
public Feature getScannerFeatureName() {
// TODO: change to SAST feature when Xray entitlement service supports it.
return Feature.CONTEXTUAL_ANALYSIS;
}
@Override
protected boolean isPackageTypeSupported(PackageManagerType packageType) {
return packageType != null && SUPPORTED_PACKAGE_TYPES.contains(packageType);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/ScanBinaryExecutor.java
================================================
package com.jfrog.ide.idea.scan;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.common.configuration.ServerConfig;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.configuration.ServerConfigImpl;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.scan.data.*;
import com.jfrog.xray.client.Xray;
import com.jfrog.xray.client.services.entitlements.Feature;
import lombok.Getter;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.UnzipParameters;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.Header;
import org.jfrog.build.api.util.Log;
import org.jfrog.build.api.util.NullLog;
import org.jfrog.build.client.ProxyConfiguration;
import org.jfrog.build.extractor.clientConfiguration.ArtifactoryManagerBuilder;
import org.jfrog.build.extractor.clientConfiguration.client.artifactory.ArtifactoryManager;
import org.jfrog.build.extractor.executor.CommandExecutor;
import org.jfrog.build.extractor.executor.CommandResults;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.jfrog.ide.common.utils.ArtifactoryConnectionUtils.createAnonymousAccessArtifactoryManagerBuilder;
import static com.jfrog.ide.common.utils.ArtifactoryConnectionUtils.createArtifactoryManagerBuilder;
import static com.jfrog.ide.common.utils.Utils.createMapper;
import static com.jfrog.ide.common.utils.Utils.createYAMLMapper;
import static com.jfrog.ide.common.utils.XrayConnectionUtils.createXrayClientBuilder;
import static com.jfrog.ide.idea.scan.utils.ScanUtils.getOSAndArc;
import static com.jfrog.ide.idea.utils.Utils.HOME_PATH;
import static java.lang.String.join;
/**
* @author Tal Arian
*/
public abstract class ScanBinaryExecutor {
public static final Path BINARIES_DIR = HOME_PATH.resolve("dependencies").resolve("jfrog-security");
private static final int UPDATE_INTERVAL = 1;
private static final int USER_NOT_ENTITLED = 31;
private static final int NOT_SUPPORTED = 13;
private static final String SCANNER_BINARY_NAME = "analyzerManager";
static final String DEFAULT_SCANNER_BINARY_VERSION = "1.30.1";
private static final String BINARY_DOWNLOAD_URL_PREFIX = "xsc-gen-exe-analyzer-manager-local/v1/";
private static final String DOWNLOAD_SCANNER_NAME = "analyzerManager.zip";
private static final String MINIMAL_XRAY_VERSION_SUPPORTED_FOR_ENTITLEMENT = "3.66.0";
private static final String ENV_PLATFORM = "JF_PLATFORM_URL";
private static final String ENV_USER = "JF_USER";
private static final String ENV_PASSWORD = "JF_PASS";
private static final String ENV_ACCESS_TOKEN = "JF_TOKEN";
private static final String ENV_HTTP_PROXY = "HTTP_PROXY";
private static final String JFROG_RELEASES = "https://releases.jfrog.io/artifactory/";
private static Path binaryTargetPath;
private static Path archiveTargetPath;
@Getter
private static String osDistribution;
private static LocalDateTime nextUpdateCheck;
private static String lastDownloadedVersion;
protected final SourceCodeScanType scanType;
protected Collection supportedPackageTypes;
private final Log log;
private boolean notSupported;
private final static Object downloadLock = new Object();
ScanBinaryExecutor(SourceCodeScanType scanType, Log log) {
this.scanType = scanType;
this.log = log;
String executable = SystemUtils.IS_OS_WINDOWS ? SCANNER_BINARY_NAME + ".exe" : SCANNER_BINARY_NAME;
binaryTargetPath = BINARIES_DIR.resolve(SCANNER_BINARY_NAME).resolve(executable);
archiveTargetPath = BINARIES_DIR.resolve(DOWNLOAD_SCANNER_NAME);
setOsDistribution();
}
private ArtifactoryManagerBuilder createManagerBuilder(boolean useJFrogReleases, ServerConfig server) {
if (useJFrogReleases) {
return createAnonymousAccessArtifactoryManagerBuilder(JFROG_RELEASES, server.getProxyConfForTargetUrl(JFROG_RELEASES), log);
}
try {
return createArtifactoryManagerBuilder(server, log);
} catch (Exception e) {
log.warn(e.getMessage());
notSupported = true;
}
return null;
}
protected void setOsDistribution() {
try {
osDistribution = getOSAndArc();
} catch (IOException e) {
log.warn(e.getMessage());
notSupported = true;
}
}
String getBinaryDownloadURL(String externalResourcesRepo) {
String downloadUrlPrefix = "";
if (!StringUtils.isEmpty(externalResourcesRepo)) {
downloadUrlPrefix = String.format("%s/artifactory/", externalResourcesRepo);
}
String binaryDownloadUrl = BINARY_DOWNLOAD_URL_PREFIX + getEffectiveScannerVersion();
return String.format("%s%s/%s/%s", downloadUrlPrefix, binaryDownloadUrl, getOsDistribution(), DOWNLOAD_SCANNER_NAME);
}
String getEffectiveScannerVersion() {
try {
ServerConfigImpl serverConfig = GlobalSettings.getInstance().getServerConfig();
if (serverConfig != null && StringUtils.isNotBlank(serverConfig.getScannerBinaryVersion())) {
return serverConfig.getScannerBinaryVersion();
}
} catch (Exception e) {
log.debug("Could not read scanner binary version from settings, using default.");
}
return DEFAULT_SCANNER_BINARY_VERSION;
}
abstract Feature getScannerFeatureName();
abstract List execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException, URISyntaxException;
protected List execute(ScanConfig.Builder inputFileBuilder, List args, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException {
return execute(inputFileBuilder, args, checkCanceled, false, indicator);
}
protected List execute(ScanConfig.Builder inputFileBuilder, List args, Runnable checkCanceled, boolean newConfigFormat, ProgressIndicator indicator) throws IOException, InterruptedException {
if (!shouldExecute()) {
return List.of();
}
checkCanceled.run();
updateBinaryIfNeeded();
Path outputTempDir = null;
Path inputFile = null;
try {
outputTempDir = Files.createTempDirectory("");
Path outputFilePath = Files.createTempFile(outputTempDir, "", ".sarif");
inputFileBuilder.output(outputFilePath.toString());
inputFileBuilder.scanType(scanType);
ScanConfig inputParams = inputFileBuilder.Build();
args = new ArrayList<>(args);
inputFile = newConfigFormat ? createTempRunInputFile(new NewScansConfig(new NewScanConfig(inputParams))) : createTempRunInputFile(new ScansConfig(List.of(inputParams)));
args.add(inputFile.toString());
if (newConfigFormat) {
args.add(outputFilePath.toString());
}
Logger log = Logger.getInstance();
// The following logging is done outside the commandExecutor because the commandExecutor log level is set to INFO.
// As it is an internal binary execution, the message should be printed for DEBUG use only.
indicator.setText(String.format("Running %s scan at %s", scanType.toString().toLowerCase(), String.join(" ", inputParams.getRoots())));
String cmd = String.format("%s %s", binaryTargetPath.toString(), join(" ", args));
log.info(String.format("Executing JAS scanner %s with config: %s", cmd, inputParams));
CommandExecutor commandExecutor = new CommandExecutor(binaryTargetPath.toString(), createEnvWithCredentials());
CommandResults commandResults = commandExecutor.exeCommand(binaryTargetPath.toFile().getParentFile(), args,
null, new NullLog(), Long.MAX_VALUE, TimeUnit.MINUTES);
checkCanceled.run();
if (commandResults.isOk()) {
log.info(String.format("Finished successfully to run command: %s", cmd));
log.debug(commandResults.getRes());
return parseOutputSarif(outputFilePath);
}
log.info(String.format("Failed to run command: %s", cmd));
switch (commandResults.getExitValue()) {
case USER_NOT_ENTITLED -> {
log.debug("User not entitled for advance security scan");
return List.of();
}
case NOT_SUPPORTED -> {
log.info(String.format("Scanner %s is not supported in the current Analyzer Manager version.", scanType));
return List.of();
}
default -> {
log.info(commandResults.getRes());
throw new IOException(commandResults.getErr());
}
}
} finally {
if (outputTempDir != null) {
FileUtils.deleteQuietly(outputTempDir.toFile());
}
if (inputFile != null) {
FileUtils.deleteQuietly(inputFile.toFile());
}
}
}
abstract List createSpecificFileIssueNodes(List warnings);
private void updateBinaryIfNeeded() throws IOException {
// Allow only one thread to check and update the binary at any time.
synchronized (downloadLock) {
LocalDateTime currentTime = LocalDateTime.now();
boolean targetExists = Files.exists(binaryTargetPath);
boolean versionChanged = !getEffectiveScannerVersion().equals(lastDownloadedVersion);
if (targetExists && !versionChanged && nextUpdateCheck != null && currentTime.isBefore(nextUpdateCheck)) {
return;
}
ServerConfig server = GlobalSettings.getInstance().getServerConfig();
String externalResourcesRepo = server.getExternalResourcesRepo();
ArtifactoryManagerBuilder artifactoryManagerBuilder = createManagerBuilder(StringUtils.isEmpty(externalResourcesRepo), server);
try (ArtifactoryManager artifactoryManager = artifactoryManagerBuilder.build()) {
if (targetExists) {
// Check for new version of the binary
try (FileInputStream archiveBinaryFile = new FileInputStream(archiveTargetPath.toFile())) {
String latestBinaryChecksum = getFileChecksumFromServer(artifactoryManager, externalResourcesRepo);
String currentBinaryCheckSum = DigestUtils.sha256Hex(archiveBinaryFile);
if (latestBinaryChecksum.equals(currentBinaryCheckSum)) {
lastDownloadedVersion = getEffectiveScannerVersion();
nextUpdateCheck = currentTime.plusDays(UPDATE_INTERVAL);
return;
}
log.debug(String.format("Resource %s is not up to date. Downloading it.", archiveTargetPath));
}
} else {
log.debug(String.format("Resource %s is not found. Downloading it.", binaryTargetPath));
}
downloadBinary(artifactoryManager, externalResourcesRepo);
lastDownloadedVersion = getEffectiveScannerVersion();
}
}
}
public String getFileChecksumFromServer(ArtifactoryManager artifactoryManager, String externalResourcesRepo) throws IOException {
String url = getBinaryDownloadURL(externalResourcesRepo);
Header[] headers = artifactoryManager.downloadHeaders(url);
for (Header header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), "x-checksum-sha256")) {
return header.getValue();
}
}
log.warn(String.format("Failed to retrieve file checksum from: %s/%s ", artifactoryManager.getUrl(), url));
return "";
}
protected boolean shouldExecute() {
if (notSupported) {
return false;
}
ServerConfig server = GlobalSettings.getInstance().getServerConfig();
try (Xray xrayClient = createXrayClientBuilder(server, log).build()) {
try {
if (!xrayClient.system().version().isAtLeast(MINIMAL_XRAY_VERSION_SUPPORTED_FOR_ENTITLEMENT)) {
return false;
}
return xrayClient.entitlements().isEntitled(getScannerFeatureName());
} catch (IOException e) {
log.error("Couldn't connect to JFrog Xray. Please check your credentials.", e);
return false;
}
}
}
protected boolean isPackageTypeSupported(PackageManagerType type) {
return type != null && supportedPackageTypes.contains(type);
}
protected List parseOutputSarif(Path outputFile) throws IOException,IndexOutOfBoundsException {
Output output = getOutputObj(outputFile);
List warnings = new ArrayList<>();
output.getRuns().forEach(run -> run.getResults().stream()
.filter(SarifResult::isNotSuppressed)
.filter(result -> !"informational".equals(result.getKind()))
.forEach(result -> warnings.add(new JFrogSecurityWarning(result, scanType, run.getRuleFromRunById(result.getRuleId())))));
Optional run = output.getRuns().stream().findFirst();
if (run.isPresent()) {
List scanners = run.get().getTool().getDriver().getRules();
// Adds the scanner search target data
for (JFrogSecurityWarning warning : warnings) {
String scannerSearchTarget = scanners.stream().filter(scanner -> scanner.getId().equals(warning.getRuleID())).findFirst().map(Rule::getFullDescription).map(Message::getText).orElse("");
warning.setScannerSearchTarget(scannerSearchTarget);
}
}
return warnings;
}
protected Output getOutputObj(Path outputFile) throws IOException {
ObjectMapper om = createMapper();
return om.readValue(outputFile.toFile(), Output.class);
}
protected void downloadBinary(ArtifactoryManager artifactoryManager, String externalResourcesRepo) throws IOException {
String downloadUrl = getBinaryDownloadURL(externalResourcesRepo);
File downloadArchive = artifactoryManager.downloadToFile(downloadUrl, archiveTargetPath.toString());
log.debug(String.format("Downloading: %s", downloadUrl));
if (downloadArchive == null) {
throw new IOException("An empty response received from Artifactory.");
}
// Delete current scanners
FileUtils.deleteDirectory(binaryTargetPath.toFile().getParentFile());
// Extract archive
UnzipParameters params = new UnzipParameters();
params.setExtractSymbolicLinks(false);
try (ZipFile zip = new ZipFile(archiveTargetPath.toFile())) {
zip.extractAll(binaryTargetPath.toFile().getParentFile().toString(), params);
} catch (ZipException exception) {
throw new IOException("An error occurred while trying to unarchived the JFrog executable:\n" + exception.getMessage());
}
// Set executable permissions to the downloaded scanner
if (!binaryTargetPath.toFile().setExecutable(true)) {
throw new IOException("An error occurred while trying to give access permissions to the JFrog executable.");
}
}
Path createTempRunInputFile(Object scanInput) throws IOException {
ObjectMapper om = createYAMLMapper();
Path tempDir = Files.createTempDirectory("");
Path inputPath = Files.createTempFile(tempDir, "", ".yaml");
om.writeValue(inputPath.toFile(), scanInput);
return inputPath;
}
private Map createEnvWithCredentials() {
Map env = new HashMap<>(EnvironmentUtil.getEnvironmentMap());
ServerConfigImpl serverConfig = GlobalSettings.getInstance().getServerConfig();
env.put(ENV_PLATFORM, serverConfig.getUrl());
if (StringUtils.isNotEmpty(serverConfig.getAccessToken())) {
env.put(ENV_ACCESS_TOKEN, serverConfig.getAccessToken());
} else {
env.put(ENV_USER, serverConfig.getUsername());
env.put(ENV_PASSWORD, serverConfig.getPassword());
}
ProxyConfiguration proxyConfiguration = serverConfig.getProxyConfForTargetUrl(serverConfig.getUrl());
if (proxyConfiguration != null) {
String proxyUrl = proxyConfiguration.host + ":" + proxyConfiguration.port;
if (StringUtils.isNoneBlank(proxyConfiguration.username, proxyConfiguration.password)) {
proxyUrl = proxyConfiguration.username + ":" + proxyConfiguration.password + "@" + proxyUrl;
}
//jfrog-ignore
env.put(ENV_HTTP_PROXY, "http://" + proxyUrl);
}
return env;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/ScanManager.java
================================================
package com.jfrog.ide.idea.scan;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.configuration.ServerConfig;
import com.jfrog.ide.common.scan.GraphScanLogic;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.events.ApplicationEvents;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.navigation.NavigationService;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import com.jfrog.xray.client.impl.XrayClient;
import com.jfrog.xray.client.impl.util.JFrogInactiveEnvironmentException;
import com.jfrog.xray.client.services.system.Version;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.jfrog.ide.common.log.Utils.logError;
import static com.jfrog.ide.common.utils.XrayConnectionUtils.createXrayClientBuilder;
public class ScanManager {
private final Project project;
private final ScannerFactory factory;
private final SourceCodeScannerManager sourceCodeScannerManager;
private Map scanners = Maps.newHashMap();
private ExecutorService executor;
private ScanManager(@NotNull Project project) {
this.project = project;
factory = new ScannerFactory(project);
sourceCodeScannerManager = new SourceCodeScannerManager(project);
}
public static ScanManager getInstance(@NotNull Project project) {
return project.getService(ScanManager.class);
}
public static Set getScanners(@NotNull Project project) {
ScanManager scanManager = getInstance(project);
return Sets.newHashSet(scanManager.scanners.values());
}
public void runInspections(Project project) {
try {
refreshScanners(null, null);
getScanners(project).forEach(ScannerBase::runInspections);
} catch (InterruptedException | IOException e) {
logError(Logger.getInstance(), "", e, false);
}
}
/**
* Start an Xray scan for all projects.
*/
public void startScan() {
if (DumbService.isDumb(project)) { // If intellij is still indexing the project
return;
}
if (isScanInProgress()) {
Logger.getInstance().info("Previous scan still running...");
return;
}
if (!GlobalSettings.getInstance().reloadMissingConfiguration()) {
Logger.getInstance().error("Xray server is not configured.");
return;
}
project.getMessageBus().syncPublisher(ApplicationEvents.ON_SCAN_LOCAL_STARTED).update();
LocalComponentsTree componentsTree = LocalComponentsTree.getInstance(project);
componentsTree.setScanningEmptyText();
Thread currScanThread = new Thread(() -> {
executor = Executors.newFixedThreadPool(3);
try {
// Source code scanners
sourceCodeScannerManager.asyncScanAndUpdateResults(executor, Logger.getInstance());
// Dependencies scanners
ScanLogic scanLogic = createScanLogic();
refreshScanners(scanLogic, executor);
NavigationService.clearNavigationMap(project);
for (ScannerBase scanner : scanners.values()) {
scanner.asyncScanAndUpdateResults();
}
executor.shutdown();
if (!executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES)) {
logError(Logger.getInstance(), "Scan timeout elapsed. The scan is being canceled.", true);
}
// Cache tree only if no errors occurred during scan.
if (scanners.values().stream().anyMatch(ScannerBase::isScanErrorOccurred)) {
componentsTree.deleteCachedTree();
componentsTree.setScanErrorEmptyText();
} else if (scanners.values().stream().anyMatch(ScannerBase::isScanCanceled)) {
project.getMessageBus().syncPublisher(ApplicationEvents.ON_SCAN_LOCAL_CANCELED).update();
} else {
componentsTree.cacheTree();
componentsTree.setNoIssuesEmptyText();
}
} catch (JFrogInactiveEnvironmentException e) {
handleJfrogInactiveEnvironment(e.getRedirectUrl());
componentsTree.setScanErrorEmptyText();
} catch (IOException | RuntimeException | InterruptedException e) {
logError(Logger.getInstance(), ExceptionUtils.getRootCauseMessage(e), e, true);
componentsTree.setScanErrorEmptyText();
} finally {
executor.shutdownNow();
}
});
currScanThread.start();
}
public void stopScan() {
executor.shutdown();
scanners.values().forEach(ScannerBase::stopScan);
sourceCodeScannerManager.stopScan();
}
/**
* Handle inactive JFrog platform (free-tier) by displaying a clear warning message and a reactivation link.
*
* @param reactivationUrl is an URL to reactivate the specific free-tier platform.
*/
private void handleJfrogInactiveEnvironment(String reactivationUrl) {
Logger.getInstance().warn("JFrog Platform is not active.");
Logger.showActionableBalloon(project, "JFrog Platform is not active.\nYou can activate it here. ", () -> BrowserUtil.browse(reactivationUrl));
}
/**
* Scan projects, create new Scanners and delete unnecessary ones.
*/
public void refreshScanners(ScanLogic scanLogic, @Nullable ExecutorService executor) throws IOException, InterruptedException {
scanners = factory.refreshScanners(scanners, scanLogic, executor);
}
public boolean isScanInProgress() {
return scanners.values().stream().anyMatch(ScannerBase::isScanInProgress) || sourceCodeScannerManager.isScanInProgress();
}
/**
* Create the scan logic according to Xray version.
*
* @return Xray scan logic
*/
private ScanLogic createScanLogic() throws IOException {
Logger logger = Logger.getInstance();
ServerConfig server = GlobalSettings.getInstance().getServerConfig();
try (XrayClient client = createXrayClientBuilder(server, logger).build()) {
Version xrayVersion = client.system().version();
GraphScanLogic.validateXraySupport(xrayVersion);
}
return new GraphScanLogic(logger);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/ScannerBase.java
================================================
package com.jfrog.ide.idea.scan;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInspection.GlobalInspectionContext;
import com.intellij.codeInspection.InspectionEngine;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.jfrog.ide.common.configuration.ServerConfig;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.deptree.DepTreeNode;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.log.ProgressIndicatorImpl;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.utils.ImpactTreeBuilder;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import com.jfrog.ide.idea.utils.Utils;
import com.jfrog.xray.client.services.summary.Components;
import lombok.Getter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import org.jfrog.build.api.util.Log;
import javax.annotation.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.jfrog.ide.common.log.Utils.logError;
/**
* Created by romang on 4/26/17.
*/
public abstract class ScannerBase {
private final ServerConfig serverConfig;
private final ComponentPrefix prefix;
@Getter
private final Log log;
// Lock to prevent multiple simultaneous scans
private final AtomicBoolean scanInProgress = new AtomicBoolean(false);
private final AtomicBoolean scanError = new AtomicBoolean(false);
private final AtomicBoolean scanCanceled = new AtomicBoolean(false);
private ScanLogic scanLogic;
protected Project project;
protected SourceCodeScannerManager sourceCodeScannerManager;
String basePath;
private ExecutorService executor;
private com.intellij.openapi.progress.ProgressIndicator progressIndicator;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}
* @param basePath project base path
* @param prefix components prefix for xray scan, e.g. gav:// or npm://
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
ScannerBase(@NotNull Project project, String basePath, ComponentPrefix prefix, ExecutorService executor, ScanLogic scanLogic) {
this.serverConfig = GlobalSettings.getInstance().getServerConfig();
this.prefix = prefix;
this.log = Logger.getInstance();
this.executor = executor;
this.basePath = basePath;
this.project = project;
this.sourceCodeScannerManager = new SourceCodeScannerManager(project, getPackageManagerType());
this.scanLogic = scanLogic;
}
void setExecutor(ExecutorService executor) {
this.executor = executor;
}
void setScanLogic(ScanLogic logic) {
this.scanLogic = logic;
}
/**
* Collect and return {@link Components} to be scanned by JFrog Xray.
* Implementation should be project type specific.
*/
protected abstract DepTree buildTree() throws IOException;
/**
* Return all project descriptors under the scan-manager project, which need to be inspected by the corresponding {@link LocalInspectionTool}.
*
* @return all project descriptors under the scan-manager project to be inspected.
*/
protected abstract PsiFile[] getProjectDescriptors();
/**
* Return the Inspection tool corresponding to the scan-manager type.
* The returned Inspection tool is used to perform the inspection on the project-descriptor files.
*
* @return the Inspection tool corresponding to the scan-manager type.
*/
@Nullable
protected abstract AbstractInspection getInspectionTool();
protected void sendUsageReport() {
ApplicationManager.getApplication().invokeLater(() -> Utils.sendUsageReport(getPackageManagerType().getName() + "-deps"));
}
protected abstract PackageManagerType getPackageManagerType();
/**
* Groups a collection of {@link DependencyNode}s by the descriptor files of the modules that depend on them.
* The returned DependencyNodes inside the {@link FileTreeNode}s might be clones of the ones in depScanResults, but
* it's not guaranteed.
*
* @param depScanResults collection of DependencyNodes
* @param depTree the project's dependency tree
* @param parents a map of components by their IDs and their parents in the dependency tree
* @return a list of FileTreeNodes (that are all DescriptorFileTreeNodes) having the DependencyNodes as their children
*/
protected abstract List groupDependenciesToDescriptorNodes(Collection depScanResults, DepTree depTree, Map> parents);
/**
* Scan and update dependency components.
*
* @param indicator - The progress indicator
*/
private void scanAndUpdate(ProgressIndicator indicator) {
try {
sendUsageReport();
// Building dependency tree
indicator.setText("1/3: Building dependency tree");
DepTree depTree = buildTree();
checkCanceled();
// Sending the dependency tree to Xray for scanning
indicator.setText("2/3: Xray scanning project dependencies");
log.debug("Start scan for '" + basePath + "'.");
Map results = scanLogic.scanArtifacts(depTree, serverConfig, indicator, prefix, this::checkCanceled);
checkCanceled();
indicator.setText("3/3: Finalizing");
if (results == null || results.isEmpty()) {
// No violations/vulnerabilities or no components to scan or an error was thrown
return;
}
List fileTreeNodes = buildImpactGraph(results, depTree);
addScanResults(fileTreeNodes);
// Contextual Analysis
List applicabilityScanResults = sourceCodeScannerManager.applicabilityScan(indicator, fileTreeNodes, this::checkCanceled);
addScanResults(applicabilityScanResults);
// Force inspections run due to changes in the displayed tree
runInspections();
} catch (ProcessCanceledException e) {
log.info("Xray scan was canceled");
scanCanceled.set(true);
} catch (Exception e) {
scanError.set(true);
logError(log, "Xray scan failed", e, true);
}
}
protected List buildImpactGraph(Map vulnerableDependencies, DepTree depTree) throws IOException {
Map> parents = getParents(depTree);
ImpactTreeBuilder.populateImpactTrees(vulnerableDependencies, parents, depTree.rootId());
return groupDependenciesToDescriptorNodes(vulnerableDependencies.values(), depTree, parents);
}
/**
* Find the parents of each node in the given {@link DepTree}.
* Nodes without parents (the root) don't appear in the returned map.
*
* @param depTree the {@link DepTree} to find the parents of its nodes
* @return a map of nodes from the {@link DepTree} amd each one's parents
*/
static Map> getParents(DepTree depTree) {
Map> parents = new HashMap<>();
for (Map.Entry node : depTree.nodes().entrySet()) {
String parentId = node.getKey();
for (String childId : node.getValue().getChildren()) {
parents.putIfAbsent(childId, new HashSet<>());
parents.get(childId).add(parentId);
}
}
return parents;
}
/**
* Launch async dependency scan.
*/
void asyncScanAndUpdateResults() {
if (DumbService.isDumb(project)) { // If intellij is still indexing the project
return;
}
// The tasks run asynchronously. To make sure no more than 3 tasks are running concurrently,
// we use a count-down latch that signals to that executor service that it can get more tasks.
CountDownLatch latch = new CountDownLatch(1);
Task.Backgroundable scanAndUpdateTask = new Task.Backgroundable(null, getTaskTitle()) {
@Override
public void run(@NotNull com.intellij.openapi.progress.ProgressIndicator indicator) {
if (project.isDisposed()) {
return;
}
// Prevent multiple simultaneous scans
if (!scanInProgress.compareAndSet(false, true)) {
log.info("Scan already in progress");
return;
}
progressIndicator = indicator;
scanAndUpdate(new ProgressIndicatorImpl(indicator));
}
@Override
public void onFinished() {
latch.countDown();
scanInProgress.set(false);
}
@Override
public void onThrowable(@NotNull Throwable error) {
log.error(ExceptionUtils.getRootCauseMessage(error));
scanError.set(true);
}
};
executor.submit(createRunnable(scanAndUpdateTask, latch, progressIndicator, log));
}
/**
* Stop the current scan.
*/
void stopScan() {
if (progressIndicator != null) {
progressIndicator.cancel();
}
}
/**
* Get text to display in the task progress.
*
* @return text to display in the task progress.
*/
private String getTaskTitle() {
Path projectBasePath = Utils.getProjectBasePath(project);
Path wsBasePath = Paths.get(basePath);
String relativePath = "";
if (projectBasePath.isAbsolute() == wsBasePath.isAbsolute()) {
// If one of the path is relative and the other one is absolute, the following exception is thrown:
// IllegalArgumentException: 'other' is different type of Path
relativePath = projectBasePath.relativize(wsBasePath).toString();
}
return "JFrog scanning " + StringUtils.defaultIfBlank(relativePath, project.getName());
}
/**
* Create a runnable to be submitted to the executor service, or run directly.
*
* @param task The task to submit
* @param latch The countdown latch, which makes sure the executor service doesn't get more than 3 tasks.
* If null, the scan was initiated by a change in the project descriptor and the executor
* service is terminated. In this case, there is no requirement to wait.
* @param progressIndicator The task's {@link com.intellij.openapi.progress.ProgressIndicator} object.
*/
public static Runnable createRunnable(Task.Backgroundable task, CountDownLatch latch, com.intellij.openapi.progress.ProgressIndicator progressIndicator, Log log) {
return () -> {
// The progress manager is only good for foreground threads.
if (SwingUtilities.isEventDispatchThread()) {
task.queue();
} else {
// Run the scan task when the thread is in the foreground.
ApplicationManager.getApplication().invokeLater(task::queue);
}
try {
// Wait for scan to finish, to make sure the thread pool remain full
if (latch != null) {
latch.await();
}
} catch (InterruptedException e) {
// This exception is thrown when this thread is interrupted (e.g. when the scan timeout has elapsed).
logError(log, ExceptionUtils.getRootCauseMessage(e), e, false);
progressIndicator.cancel();
}
};
}
/**
* Returns all project modules locations as Paths.
* Other scanners such as npm will use these paths in order to find modules.
*
* @return all project modules locations as Paths
*/
public Set getProjectPaths() {
Set paths = Sets.newHashSet();
paths.add(Paths.get(basePath));
return paths;
}
void runInspections() {
DumbService.getInstance(project).smartInvokeLater(() -> {
PsiFile[] projectDescriptors = getProjectDescriptors();
if (ArrayUtils.isEmpty(projectDescriptors)) {
return;
}
GlobalInspectionContext context = InspectionManager.getInstance(project).createNewGlobalContext();
AbstractInspection localInspectionTool = getInspectionTool();
if (localInspectionTool == null) {
return;
}
localInspectionTool.setAfterScan(true);
for (PsiFile descriptor : projectDescriptors) {
// Run inspection on descriptor.
InspectionEngine.runInspectionOnFile(descriptor, new LocalInspectionToolWrapper(localInspectionTool), context);
FileEditor[] editors = FileEditorManager.getInstance(project).getAllEditors(descriptor.getVirtualFile());
if (!ArrayUtils.isEmpty(editors)) {
// Refresh descriptor highlighting only if it is already opened.
DaemonCodeAnalyzer.getInstance(project).restart(descriptor);
}
}
});
}
private void addScanResults(List fileTreeNodes) {
if (fileTreeNodes.isEmpty()) {
return;
}
LocalComponentsTree componentsTree = LocalComponentsTree.getInstance(project);
componentsTree.addScanResults(fileTreeNodes);
}
protected void checkCanceled() {
if (project.isOpen()) {
// The project is closed if we are in test mode.
// In tests, we can't check if the user canceled the scan, since we don't have the ProgressManager service.
ProgressManager.checkCanceled();
}
}
boolean isScanInProgress() {
return this.scanInProgress.get();
}
boolean isScanErrorOccurred() {
return this.scanError.get();
}
boolean isScanCanceled() {
return this.scanCanceled.get();
}
public String getProjectPath() {
return this.basePath;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/ScannerFactory.java
================================================
package com.jfrog.ide.idea.scan;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.jetbrains.python.sdk.PythonSdkUtil;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.common.utils.PackageFileFinder;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.log.Logger;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import static com.jfrog.ide.idea.scan.utils.ScanUtils.createScanPaths;
import static com.jfrog.ide.idea.utils.Utils.getProjectBasePath;
/**
* Created by yahavi
*/
public class ScannerFactory {
private final Project project;
public ScannerFactory(Project project) {
this.project = project;
}
public static int getModuleIdentifier(String name, String path) {
return Objects.hashCode(name, path);
}
/**
* Scan projects, create new Scanners and delete unnecessary ones.
* Existing Scanners from previous scans, are not overridden.
*/
public Map refreshScanners(Map oldScanners, ScanLogic scanLogic,
@Nullable ExecutorService executor) throws IOException {
Map scanners = Maps.newHashMap();
refreshMavenScanner(scanners, oldScanners, executor, scanLogic);
refreshPypiScanners(scanners, oldScanners, executor, scanLogic);
Set scanPaths = createScanPaths(project);
oldScanners.values().stream().map(ScannerBase::getProjectPaths).flatMap(Collection::stream).forEach(scanPaths::add);
refreshGenericScanners(scanners, oldScanners, scanPaths, executor, scanLogic);
return scanners;
}
/**
* Create npm, Gradle, Go and Yarn Scanners.
*
* @param newScanners the new Scanners map to add the Scanners into
* @param oldScanners the Scanners map including the Scanner of the current project, or an empty map for a fresh start
* @param scanPaths potential paths for scanning for package descriptor files
* @param executor the thread pool
* @throws IOException in case of any I/O error during the search for the actual package descriptor files.
*/
private void refreshGenericScanners(Map newScanners, Map oldScanners,
Set scanPaths, ExecutorService executor, ScanLogic scanLogic) throws IOException {
Path basePath = getProjectBasePath(project);
PackageFileFinder packageFileFinder = new PackageFileFinder(scanPaths, basePath,
GlobalSettings.getInstance().getServerConfig().getExcludedPaths(), Logger.getInstance());
// Create Yarn scanners
Set yarnLockDirs = packageFileFinder.getYarnPackagesFilePairs();
refreshGenericScannersByType(yarnLockDirs, newScanners, oldScanners, GenericScannerType.YARN, executor, scanLogic);
// Create npm scanners
Set packageJsonDirs = packageFileFinder.getNpmPackagesFilePairs();
refreshGenericScannersByType(packageJsonDirs, newScanners, oldScanners, GenericScannerType.NPM, executor, scanLogic);
// Create Gradle scanners
Set buildGradleDirs = packageFileFinder.getBuildGradlePackagesFilePairs();
refreshGenericScannersByType(buildGradleDirs, newScanners, oldScanners, GenericScannerType.GRADLE, executor, scanLogic);
// Create Go scanners
Set goModDirs = packageFileFinder.getGoPackagesFilePairs();
refreshGenericScannersByType(goModDirs, newScanners, oldScanners, GenericScannerType.GO, executor, scanLogic);
}
/**
* Create a MavenScanner if this is a Maven project.
*
* @param newScanners new scanners map
* @param oldScanners existing scanners map
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
private void refreshMavenScanner(Map newScanners, Map oldScanners,
ExecutorService executor, ScanLogic scanLogic) {
int projectHash = getModuleIdentifier(project.getName(), project.getBasePath());
ScannerBase scanner = oldScanners.get(projectHash);
// Check if a ScanManager for this project already exists
if (scanner != null) {
// Set the new executor on the old scan manager
scanner.setExecutor(executor);
scanner.setScanLogic(scanLogic);
newScanners.put(projectHash, scanner);
} else {
// Unlike other scanners whereby we create them if the package descriptor exist, MavenScanner is created if
// the Maven plugin is installed and there are Maven projects loaded.
try {
if (MavenScanner.isApplicable(project)) {
scanner = new MavenScanner(project, executor, scanLogic);
newScanners.put(projectHash, scanner);
}
} catch (NoClassDefFoundError noClassDefFoundError) {
// The Maven plugin is not installed.
}
}
}
/**
* Create PypiScanner for each module with Python SDK configured.
*
* @param newScanners new scanners map
* @param oldScanners existing scanners map
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
private void refreshPypiScanners(Map newScanners, Map oldScanners,
ExecutorService executor, ScanLogic scanLogic) {
try {
for (Module module : ModuleManager.getInstance(project).getModules()) {
Sdk pythonSdk = PythonSdkUtil.findPythonSdk(module);
if (pythonSdk == null) {
continue;
}
int projectHash = getModuleIdentifier(pythonSdk.getName(), pythonSdk.getHomePath());
ScannerBase scanner = oldScanners.get(projectHash);
if (scanner == null) {
scanner = new PypiScanner(project, pythonSdk, executor, scanLogic);
}
scanner.setExecutor(executor);
scanner.setScanLogic(scanLogic);
newScanners.put(projectHash, scanner);
}
} catch (NoClassDefFoundError noClassDefFoundError) {
// The Python plugin is not installed.
}
}
private void refreshGenericScannersByType(Set packageDirs, Map newScanners, Map oldScanners,
GenericScannerType type, ExecutorService executor, ScanLogic scanLogic) {
for (String dir : packageDirs) {
int projectHash = getModuleIdentifier(dir, dir);
ScannerBase scanner = oldScanners.get(projectHash);
if (scanner == null) {
scanner = createGenericScanner(type, dir, executor, scanLogic);
}
if (scanner != null) {
scanner.setExecutor(executor);
scanner.setScanLogic(scanLogic);
newScanners.put(projectHash, scanner);
}
}
}
/**
* Create a new scanner according to the type. Add it to the scanners map.
* Supported types: Go, npm, gradle and Yarn.
*
* @param type project type
* @param dir project dir
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
private ScannerBase createGenericScanner(GenericScannerType type, String dir, ExecutorService executor, ScanLogic scanLogic) {
try {
switch (type) {
case GRADLE:
return new GradleScanner(project, dir, executor, scanLogic);
case YARN:
return new YarnScanner(project, dir, executor, scanLogic);
case NPM:
return new NpmScanner(project, dir, executor, scanLogic);
case GO:
return new GoScanner(project, dir, executor, scanLogic);
}
} catch (NoClassDefFoundError noClassDefFoundError) {
// The Gradle plugin is not installed.
}
return null;
}
private enum GenericScannerType {
GRADLE,
NPM,
GO,
YARN
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/SecretsScannerExecutor.java
================================================
package com.jfrog.ide.idea.scan;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.FileIssueNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.data.ScanConfig;
import com.jfrog.xray.client.services.entitlements.Feature;
import org.jfrog.build.api.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author Tal Arian
*/
public class SecretsScannerExecutor extends ScanBinaryExecutor {
private static final List SCANNER_ARGS = List.of("sec");
private static final String ISSUE_TITLE = "Potential Secret";
public SecretsScannerExecutor(Log log) {
super(SourceCodeScanType.SECRETS, log);
}
public List execute(ScanConfig.Builder inputFileBuilder, Runnable checkCanceled, ProgressIndicator indicator) throws IOException, InterruptedException {
return super.execute(inputFileBuilder, SCANNER_ARGS, checkCanceled, indicator);
}
@Override
List createSpecificFileIssueNodes(List warnings) {
HashMap results = new HashMap<>();
for (JFrogSecurityWarning warning : warnings) {
// Create FileTreeNodes for files with found issues
FileTreeNode fileNode = results.get(warning.getFilePath());
if (fileNode == null) {
fileNode = new FileTreeNode(warning.getFilePath());
results.put(warning.getFilePath(), fileNode);
}
FileIssueNode issueNode = new FileIssueNode(ISSUE_TITLE,
warning.getFilePath(), warning.getLineStart(), warning.getColStart(), warning.getLineEnd(), warning.getColEnd(),
warning.getScannerSearchTarget(), warning.getLineSnippet(), warning.getReporter(), warning.getSeverity(), warning.getRuleID());
fileNode.addIssue(issueNode);
}
return new ArrayList<>(results.values());
}
@Override
public Feature getScannerFeatureName() {
return Feature.SECRETS;
}
@Override
protected boolean isPackageTypeSupported(PackageManagerType packageType) {
return true;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/SingleDescriptorScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.deptree.DepTreeNode;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.DescriptorFileTreeNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import org.jetbrains.annotations.NotNull;
import org.jfrog.build.extractor.scan.DependencyTree;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
public abstract class SingleDescriptorScanner extends ScannerBase {
protected String descriptorFilePath;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}
* @param basePath project base path
* @param prefix components prefix for xray scan, e.g. gav:// or npm://
* @param executor an executor that should limit the number of running tasks to 3
* @param descriptorFilePath path to the project's descriptor file
* @param scanLogic the scan logic to use
*/
SingleDescriptorScanner(@NotNull Project project, String basePath, ComponentPrefix prefix, ExecutorService executor,
String descriptorFilePath, ScanLogic scanLogic) {
super(project, basePath, prefix, executor, scanLogic);
this.descriptorFilePath = descriptorFilePath;
}
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}
* @param basePath project base path
* @param prefix components prefix for xray scan, e.g. gav:// or npm://
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
SingleDescriptorScanner(@NotNull Project project, String basePath, ComponentPrefix prefix, ExecutorService executor,
ScanLogic scanLogic) {
this(project, basePath, prefix, executor, "", scanLogic);
}
/**
* Groups a collection of {@link DependencyNode}s by the descriptor files of the modules that depend on them.
* The returned DependencyNodes inside the {@link FileTreeNode}s are references of the ones in depScanResults.
*
* @param depScanResults collection of DependencyNodes
* @param depTree the project's dependency tree
* @param parents a map of components by their IDs and their parents in the dependency tree
* @return a list of FileTreeNodes (that are all DescriptorFileTreeNodes) having the DependencyNodes as their children
*/
@Override
protected List groupDependenciesToDescriptorNodes(Collection depScanResults, DepTree depTree, Map> parents) {
DescriptorFileTreeNode fileTreeNode = new DescriptorFileTreeNode(descriptorFilePath);
for (DependencyNode dependency : depScanResults) {
dependency.setIndirect(!isDirectDependency(dependency, depTree, parents));
fileTreeNode.addDependency(dependency);
}
return new CopyOnWriteArrayList<>(List.of(fileTreeNode));
}
private boolean isDirectDependency(DependencyNode dependency, DepTree depTree, Map> parents) {
// Check if the component is the root node
if (dependency.getComponentIdWithoutPrefix().equals(depTree.rootId())) {
return true;
}
// Check if any of the parent's descriptor file path matches the current descriptor file path
return parents.getOrDefault(dependency.getComponentIdWithoutPrefix(), Collections.emptySet())
.stream()
.map(depTree.nodes()::get)
.anyMatch(parent -> descriptorFilePath.equals(parent.getDescriptorFilePath()));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/SourceCodeScannerManager.java
================================================
package com.jfrog.ide.idea.scan;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.jfrog.ide.common.log.ProgressIndicator;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.nodes.VulnerabilityNode;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import com.jfrog.ide.idea.configuration.GlobalSettings;
import com.jfrog.ide.idea.inspections.JFrogSecurityWarning;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.log.ProgressIndicatorImpl;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.data.ScanConfig;
import com.jfrog.ide.idea.scan.data.applications.JFrogApplicationsConfig;
import com.jfrog.ide.idea.scan.data.applications.ModuleConfig;
import com.jfrog.ide.idea.scan.data.applications.ScannerConfig;
import com.jfrog.ide.idea.ui.LocalComponentsTree;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.NotNull;
import javax.swing.tree.TreeNode;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import static com.jfrog.ide.common.log.Utils.logError;
import static com.jfrog.ide.common.utils.Utils.createYAMLMapper;
import static com.jfrog.ide.idea.scan.ScannerBase.createRunnable;
import static com.jfrog.ide.idea.scan.data.applications.JFrogApplicationsConfig.createApplicationConfigWithDefaultModule;
import static com.jfrog.ide.idea.ui.configuration.ConfigVerificationUtils.EXCLUSIONS_PREFIX;
import static com.jfrog.ide.idea.ui.configuration.ConfigVerificationUtils.EXCLUSIONS_REGEX_PATTERN;
import static com.jfrog.ide.idea.ui.configuration.ConfigVerificationUtils.EXCLUSIONS_SUFFIX;
import static com.jfrog.ide.idea.utils.Utils.getProjectBasePath;
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
public class SourceCodeScannerManager {
private final Path jfrogApplictionsConfigPath;
private final AtomicBoolean scanInProgress = new AtomicBoolean(false);
private final ApplicabilityScannerExecutor applicability = new ApplicabilityScannerExecutor(Logger.getInstance());
private final Map scanners = initScannersCollection();
protected Project project;
protected PackageManagerType packageType;
private static final String SKIP_FOLDERS_SUFFIX = "*/**";
private com.intellij.openapi.progress.ProgressIndicator progressIndicator;
public SourceCodeScannerManager(Project project) {
this.project = project;
this.jfrogApplictionsConfigPath = getProjectBasePath(project).resolve(".jfrog").resolve("jfrog-apps-config.yml");
}
public SourceCodeScannerManager(Project project, PackageManagerType packageType) {
this(project);
this.packageType = packageType;
}
/**
* Applicability source code scanning (Contextual Analysis).
*
* @param indicator the progress indicator.
* @param fileTreeNodes collection of FileTreeNodes.
* @return A list of FileTreeNodes having the source code issues as their children.
*/
public List applicabilityScan(ProgressIndicator indicator, Collection fileTreeNodes, Runnable checkCanceled) {
if (project.isDisposed()) {
return Collections.emptyList();
}
// Prevent multiple simultaneous scans
if (!scanInProgress.compareAndSet(false, true)) {
return Collections.emptyList();
}
List scanResults = new ArrayList<>();
Map> issuesMap = mapDirectIssuesByCve(fileTreeNodes);
try {
if (applicability.isPackageTypeSupported(packageType)) {
indicator.setText("Running applicability scan");
indicator.setFraction(0.25);
Set directIssuesCVEs = issuesMap.keySet();
// If no direct dependencies with issues are found by Xray, the applicability scan is irrelevant.
if (!directIssuesCVEs.isEmpty()) {
List applicabilityResults = applicability.execute(createBasicScannerInput().cves(List.copyOf(directIssuesCVEs)), checkCanceled, indicator);
scanResults.addAll(applicabilityResults);
}
}
} catch (InterruptedException e) {
logError(Logger.getInstance(), "Scan canceled due to a user request or timeout.", false);
} catch (IOException | NullPointerException e) {
logError(Logger.getInstance(), "Failed to scan source code", e, true);
} finally {
scanInProgress.set(false);
indicator.setFraction(1);
}
return applicability.createSpecificFileIssueNodes(scanResults, issuesMap);
}
/**
* Launch async source code scans.
*/
void asyncScanAndUpdateResults(ExecutorService executor, Logger log) {
// If intellij is still indexing the project, do not scan.
if (DumbService.isDumb(project)) {
return;
}
// The tasks run asynchronously. To make sure no more than 3 tasks are running concurrently,
// we use a count-down latch that signals to that executor service that it can get more tasks.
CountDownLatch latch = new CountDownLatch(1);
Task.Backgroundable sourceCodeScanTask = new Task.Backgroundable(null, "Advanced source code scanning") {
@Override
public void run(@NotNull com.intellij.openapi.progress.ProgressIndicator indicator) {
if (project.isDisposed()) {
return;
}
// Prevent multiple simultaneous scans
if (!scanInProgress.compareAndSet(false, true)) {
log.info("Advanced source code scan is already in progress");
return;
}
try {
progressIndicator = indicator;
sourceCodeScanAndUpdate(new ProgressIndicatorImpl(indicator), ProgressManager::checkCanceled, log);
} catch (IOException e) {
logError(Logger.getInstance(), "Failed to run advanced source code scanning.", e, true);
}
}
@Override
public void onFinished() {
latch.countDown();
scanInProgress.set(false);
}
@Override
public void onThrowable(@NotNull Throwable error) {
log.error(ExceptionUtils.getRootCauseMessage(error));
}
};
executor.submit(createRunnable(sourceCodeScanTask, latch, progressIndicator, log));
}
public void stopScan() {
if (progressIndicator != null) {
progressIndicator.cancel();
}
}
private void sourceCodeScanAndUpdate(ProgressIndicator indicator, Runnable checkCanceled, Logger log) throws IOException {
indicator.setText("Running advanced source code scanning");
JFrogApplicationsConfig projectConfig = parseJFrogApplicationsConfig();
for (ModuleConfig moduleConfig : projectConfig.getModules()) {
scan(moduleConfig, indicator, checkCanceled, log);
}
}
private void scan(ModuleConfig moduleConfig, ProgressIndicator indicator, Runnable checkCanceled, Logger log) {
double fraction = 0;
for (SourceCodeScanType scannerType : scanners.keySet()) {
checkCanceled.run();
ScanBinaryExecutor scanner = scanners.get(scannerType);
ScannerConfig scannerConfig = null;
if (moduleConfig != null) {
// Skip the scanner If requested.
if (moduleConfig.getExcludeScanners() != null && moduleConfig.getExcludeScanners().contains(scannerType.toString().toLowerCase())) {
log.debug(String.format("Skipping %s scanning", scannerType.toString().toLowerCase()));
continue;
}
// Use specific scanner config if exists.
if (moduleConfig.getScanners() != null) {
scannerConfig = moduleConfig.getScanners().get(scannerType.toString().toLowerCase());
}
}
try {
List scanResults = scanner.execute(createBasicScannerInput(moduleConfig, scannerConfig), checkCanceled, indicator);
addSourceCodeScanResults(scanner.createSpecificFileIssueNodes(scanResults));
} catch (IOException | URISyntaxException | InterruptedException e) {
logError(log, "", e, true);
}
fraction += 1.0 / scanners.size();
indicator.setFraction(fraction);
}
}
private JFrogApplicationsConfig parseJFrogApplicationsConfig() throws IOException {
ObjectMapper mapper = createYAMLMapper();
File config = jfrogApplictionsConfigPath.toFile();
if (config.exists()) {
return mapper.readValue(config, JFrogApplicationsConfig.class);
}
return createApplicationConfigWithDefaultModule(project);
}
private void addSourceCodeScanResults(List fileTreeNodes) {
if (fileTreeNodes.isEmpty()) {
return;
}
LocalComponentsTree componentsTree = LocalComponentsTree.getInstance(project);
componentsTree.addScanResults(fileTreeNodes);
}
private ScanConfig.Builder createBasicScannerInput() {
String excludePattern = GlobalSettings.getInstance().getServerConfig().getExcludedPaths();
return new ScanConfig.Builder().roots(List.of(getProjectBasePath(project).toAbsolutePath().toString())).skippedFolders(convertToSkippedFolders(excludePattern));
}
private ScanConfig.Builder createBasicScannerInput(ModuleConfig config, ScannerConfig scannerConfig) {
if (config == null) {
return createBasicScannerInput();
}
// Scanner's working dirs (roots)
List workingDirs = new ArrayList<>();
String projectBasePath = defaultIfEmpty(config.getSourceRoot(), getProjectBasePath(project).toAbsolutePath().toString());
if (scannerConfig != null && !CollectionUtils.isEmpty(scannerConfig.getWorkingDirs())) {
for (String workingDir : scannerConfig.getWorkingDirs()) {
workingDirs.add(Paths.get(projectBasePath).resolve(workingDir).toString());
}
} else {
// Default: ".", the application's root directory.
workingDirs.add(projectBasePath);
}
// Module exclude patterns
List skippedFolders = new ArrayList<>();
if (config.getExcludePatterns() != null) {
skippedFolders.addAll(config.getExcludePatterns());
}
if (scannerConfig != null && scannerConfig.getExcludePatterns() != null) {
// Adds scanner specific exclude patterns if exists
skippedFolders.addAll(scannerConfig.getExcludePatterns());
}
String excludePattern = GlobalSettings.getInstance().getServerConfig().getExcludedPaths();
// If exclude patterns was not provided, use the configured IDE patterns.
skippedFolders = skippedFolders.isEmpty() ? convertToSkippedFolders(excludePattern) : skippedFolders;
// Extra scanners params
List excludeRules = null;
String language = null;
if (scannerConfig != null) {
excludeRules = scannerConfig.getExcludedRules();
language = scannerConfig.getLanguage();
}
return new ScanConfig.Builder().roots(workingDirs).skippedFolders(skippedFolders).excludedRules(excludeRules).language(language);
}
/**
* Splits the users' configured ExcludedPaths glob pattern to a list
* of simplified patterns by avoiding the use of "{}".
*
* @return a list of equivalent patterns without the use of "{}"
*/
public static List convertToSkippedFolders(String excludePattern) {
List skippedFoldersPatterns = new ArrayList<>();
if (StringUtils.isNotBlank(excludePattern)) {
Matcher matcher = EXCLUSIONS_REGEX_PATTERN.matcher(excludePattern);
if (!matcher.find()) {
// Convert pattern form shape "**/*a*" to "**/*a*/**"
return List.of(StringUtils.removeEnd(excludePattern, EXCLUSIONS_SUFFIX) + SKIP_FOLDERS_SUFFIX);
}
String[] dirsNames = matcher.group(1).split(",");
for (String dirName : dirsNames) {
skippedFoldersPatterns.add(EXCLUSIONS_PREFIX + dirName.strip() + SKIP_FOLDERS_SUFFIX);
}
}
return skippedFoldersPatterns;
}
/**
* Maps direct dependencies issues (vulnerabilities and security violations) by their CVE IDs.
* Issues without a CVE ID are ignored.
*
* @param fileTreeNodes collection of FileTreeNodes.
* @return a map of CVE IDs to lists of issues with them.
*/
private Map> mapDirectIssuesByCve(Collection fileTreeNodes) {
Map> issues = new HashMap<>();
for (FileTreeNode fileTreeNode : fileTreeNodes) {
for (TreeNode treeNode : fileTreeNode.getChildren()) {
DependencyNode dep = (DependencyNode) treeNode;
if (dep.isIndirect()) {
continue;
}
Enumeration treeNodeEnumeration = dep.children();
while (treeNodeEnumeration.hasMoreElements()) {
TreeNode node = treeNodeEnumeration.nextElement();
if (!(node instanceof VulnerabilityNode vulnerabilityNode)) {
continue;
}
if (vulnerabilityNode.getCve() == null || StringUtils.isBlank(vulnerabilityNode.getCve().getCveId())) {
continue;
}
String cveId = vulnerabilityNode.getCve().getCveId();
issues.putIfAbsent(cveId, new ArrayList<>());
issues.get(cveId).add(vulnerabilityNode);
}
}
}
return issues;
}
private Map initScannersCollection() {
Map scanners = new HashMap<>();
scanners.put(SourceCodeScanType.SECRETS, new SecretsScannerExecutor(Logger.getInstance()));
scanners.put(SourceCodeScanType.IAC, new IACScannerExecutor(Logger.getInstance()));
scanners.put(SourceCodeScanType.SAST, new SastScannerExecutor(Logger.getInstance()));
return scanners;
}
public boolean isScanInProgress() {
return this.scanInProgress.get();
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/YarnScanner.java
================================================
package com.jfrog.ide.idea.scan;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.EnvironmentUtil;
import com.jfrog.ide.common.deptree.DepTree;
import com.jfrog.ide.common.nodes.DependencyNode;
import com.jfrog.ide.common.nodes.DescriptorFileTreeNode;
import com.jfrog.ide.common.nodes.FileTreeNode;
import com.jfrog.ide.common.scan.ComponentPrefix;
import com.jfrog.ide.common.scan.ScanLogic;
import com.jfrog.ide.common.yarn.YarnTreeBuilder;
import com.jfrog.ide.idea.inspections.AbstractInspection;
import com.jfrog.ide.idea.inspections.YarnInspection;
import com.jfrog.ide.idea.scan.data.PackageManagerType;
import com.jfrog.ide.idea.scan.utils.ImpactTreeBuilder;
import com.jfrog.ide.idea.ui.ComponentsTree;
import com.jfrog.ide.idea.ui.menus.filtermanager.ConsistentFilterManager;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
/**
* Created by Yahav Itzhak on 13 Dec 2017.
*/
public class YarnScanner extends SingleDescriptorScanner {
private final YarnTreeBuilder yarnTreeBuilder;
/**
* @param project currently opened IntelliJ project. We'll use this project to retrieve project based services
* like {@link ConsistentFilterManager} and {@link ComponentsTree}.
* @param basePath the package.json directory
* @param executor an executor that should limit the number of running tasks to 3
* @param scanLogic the scan logic to use
*/
YarnScanner(Project project, String basePath, ExecutorService executor, ScanLogic scanLogic) {
super(project, basePath, ComponentPrefix.NPM, executor, Paths.get(basePath, "package.json").toString(), scanLogic);
getLog().info("Found yarn project: " + getProjectPath());
yarnTreeBuilder = new YarnTreeBuilder(Paths.get(basePath), descriptorFilePath, EnvironmentUtil.getEnvironmentMap(), getLog());
}
@Override
protected DepTree buildTree() throws IOException {
return yarnTreeBuilder.buildTree();
}
@Override
protected PsiFile[] getProjectDescriptors() {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(descriptorFilePath);
if (file == null) {
return null;
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
return new PsiFile[]{psiFile};
}
@Override
protected AbstractInspection getInspectionTool() {
return new YarnInspection();
}
@Override
protected PackageManagerType getPackageManagerType() {
return PackageManagerType.YARN;
}
/**
* Builds a map of package name to versions out of a set of : Strings.
*
* @param packages - A set of packages in the format of 'package-name:version'.
* @return - A map of package name to a set of versions.
*/
Map> getPackageNameToVersionsMap(Set packages) {
Map> packageNameToVersions = new HashMap<>();
for (String fullNamePackage : CollectionUtils.emptyIfNull(packages)) {
String[] packageSplit = StringUtils.split(fullNamePackage, ":");
if (packageSplit.length != 2) {
this.getLog().error("Illegal package name: " + fullNamePackage + ". Skipping package, the dependency tree may be incomplete.");
continue;
}
String packageName = packageSplit[0];
String packageVersion = packageSplit[1];
packageNameToVersions.putIfAbsent(packageName, new HashSet<>());
packageNameToVersions.get(packageName).add(packageVersion);
}
return packageNameToVersions;
}
private void buildImpactGraphFromPaths(DescriptorFileTreeNode descriptorNode, Map vulnerableDependencies, Map>> packageVersionsImpactPaths) {
for (Map.Entry>> aPackageVersionImpactPaths : packageVersionsImpactPaths.entrySet()) {
String packageFullName = aPackageVersionImpactPaths.getKey();
List> impactPaths = aPackageVersionImpactPaths.getValue();
DependencyNode dependencyNode = vulnerableDependencies.get(packageFullName);
// build the impact graph for each vulnerable dependency out of its impact paths
for (List impactPath : impactPaths) {
ImpactTreeBuilder.addImpactPathToDependencyNode(dependencyNode, impactPath);
}
boolean direct = impactPaths.stream().map(List::size).anyMatch(size -> size == 2);
dependencyNode.setIndirect(!direct);
descriptorNode.addDependency(dependencyNode);
}
}
/**
* Builds the impact graph for each given vulnerable dependencies.
* The impact graph is built by running 'yarn why ' command, making it different from other package managers.
*
* @param vulnerableDependencies - The vulnerable dependencies to build the impact graph for.
* The key is the package name and version, and the value is the dependency node.
* @param depTree - The whole dependency tree (not just vulnerable dependencies) that was generated earlier.
* @return - The impact graph attached to package.json DescriptorFileTreeNode
*/
@Override
protected List buildImpactGraph(Map vulnerableDependencies, DepTree depTree) throws IOException {
DescriptorFileTreeNode descriptorNode = new DescriptorFileTreeNode(depTree.getRootNodeDescriptorFilePath());
// Build a map of package name to versions, to avoid running 'yarn why' multiple times for the same package.
Map> packageNameToVersions = this.getPackageNameToVersionsMap(vulnerableDependencies.keySet());
for (Map.Entry> entry : packageNameToVersions.entrySet()) {
// Find the impact paths for each package for all its vulnerable versions
Map>> packageVersionsImpactPaths = yarnTreeBuilder.findDependencyImpactPaths(depTree.rootId(), entry.getKey(), entry.getValue());
// Build the impact graph for each vulnerable dependency out of its impact paths, set Indirect flag and add it to the descriptor node
buildImpactGraphFromPaths(descriptorNode, vulnerableDependencies, packageVersionsImpactPaths);
}
// Return a list of one element - the descriptor node for package.json
// COW list is used to avoid ConcurrentModificationException in SourceCodeScannerManager
return new CopyOnWriteArrayList<>(Collections.singletonList(descriptorNode));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/AnalyzeSuppression.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AnalyzeSuppression {
@JsonProperty("kind")
private String kind;
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/ArtifactLocation.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class ArtifactLocation {
@JsonProperty("uri")
private String uri;
public String getUri() {
return uri;
}
@SuppressWarnings("unused")
public void setUri(String uri) {
this.uri = uri;
}
@Override
public int hashCode() {
return Objects.hash(uri);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof ArtifactLocation)) {
return false;
}
ArtifactLocation rhs = ((ArtifactLocation) other);
return (Objects.equals(this.uri, rhs.uri));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/CodeFlow.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CodeFlow {
@JsonProperty("threadFlows")
private List threadFlows = new ArrayList<>();
@SuppressWarnings("unused")
public List getThreadFlows() {
return threadFlows;
}
@SuppressWarnings("unused")
public void setThreadFlows(List threadFlows) {
this.threadFlows = threadFlows;
}
@Override
public int hashCode() {
return Objects.hash(threadFlows);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof CodeFlow)) {
return false;
}
CodeFlow rhs = ((CodeFlow) other);
return (CollectionUtils.isEqualCollection(this.threadFlows, rhs.threadFlows));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Driver.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
import java.util.Objects;
public class Driver {
@JsonProperty("name")
private String name;
@JsonProperty("rules")
private List rules;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@SuppressWarnings({"unused"})
public List getRules() {
return rules;
}
@SuppressWarnings({"unused"})
public void setLocations( List rules) {
this.rules = rules;
}
@Override
public int hashCode() {
return Objects.hash(name, rules);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Driver)) {
return false;
}
Driver rhs = ((Driver) other);
return (Objects.equals(this.name, rhs.name) && (CollectionUtils.isEqualCollection(this.rules, rhs.rules)));
}
public Rule getRuleById(String ruleId) throws IndexOutOfBoundsException {
return rules.stream()
.filter(rule -> rule.getId().equals(ruleId))
.findFirst()
.orElseThrow(() -> new IndexOutOfBoundsException("Rule not found"));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Invocation.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@JsonPropertyOrder({"executionSuccessful", "arguments", "workingDirectory"})
public class Invocation {
@JsonProperty("executionSuccessful")
private Boolean executionSuccessful;
@JsonProperty("arguments")
private List arguments = new ArrayList<>();
@JsonProperty("workingDirectory")
private WorkingDirectory workingDirectory;
@SuppressWarnings("unused")
public Boolean getExecutionSuccessful() {
return executionSuccessful;
}
@SuppressWarnings("unused")
public void setExecutionSuccessful(Boolean executionSuccessful) {
this.executionSuccessful = executionSuccessful;
}
@SuppressWarnings("unused")
public List getArguments() {
return arguments;
}
@SuppressWarnings("unused")
public void setArguments(List arguments) {
this.arguments = arguments;
}
@SuppressWarnings("unused")
public WorkingDirectory getWorkingDirectory() {
return workingDirectory;
}
@SuppressWarnings("unused")
public void setWorkingDirectory(WorkingDirectory workingDirectory) {
this.workingDirectory = workingDirectory;
}
@Override
public int hashCode() {
return Objects.hash(arguments, executionSuccessful, workingDirectory);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Invocation)) {
return false;
}
Invocation rhs = ((Invocation) other);
return (((CollectionUtils.isEqualCollection(this.arguments, rhs.arguments)) && (Objects.equals(this.executionSuccessful, rhs.executionSuccessful))) && (Objects.equals(this.workingDirectory, rhs.workingDirectory)));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Location.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class Location {
@JsonProperty("physicalLocation")
private PhysicalLocation physicalLocation;
public PhysicalLocation getPhysicalLocation() {
return physicalLocation;
}
@SuppressWarnings("unused")
public void setPhysicalLocation(PhysicalLocation physicalLocation) {
this.physicalLocation = physicalLocation;
}
@Override
public int hashCode() {
return Objects.hash(physicalLocation);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Location)) {
return false;
}
Location rhs = ((Location) other);
return (Objects.equals(this.physicalLocation, rhs.physicalLocation));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Message.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class Message {
@JsonProperty("text")
private String text;
@JsonProperty("markdown")
private String markdown;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public int hashCode() {
return Objects.hash(text);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Message)) {
return false;
}
Message rhs = ((Message) other);
return (Objects.equals(this.text, rhs.text));
}
@SuppressWarnings({"unused"})
public String getMarkdown() {
return markdown;
}
@SuppressWarnings({"unused"})
public void setMarkdown(String markdown) {
this.markdown = markdown;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/NewScanConfig.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NewScanConfig {
@JsonProperty("type")
private SourceCodeScanType scanType;
@JsonProperty("roots")
private List roots;
@JsonProperty("language")
private String language;
@JsonProperty("output")
private String output;
@JsonProperty("exclude_patterns")
private List excludePatterns;
@JsonProperty("excluded-rules")
private List excludedRules;
public NewScanConfig(ScanConfig inputParams) {
this(inputParams.getScanType(),
inputParams.getRoots(),
inputParams.getLanguage(),
inputParams.getOutput(),
inputParams.getSkippedFolders(),
inputParams.getExcludedRules());
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/NewScansConfig.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@NoArgsConstructor
public class NewScansConfig {
@JsonProperty("scans")
private List scans;
public NewScansConfig(NewScanConfig scan) {
this.scans = List.of(scan);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Output.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class Output {
@JsonProperty("runs")
private List runs = new ArrayList<>();
@JsonProperty("version")
private String version;
public List getRuns() {
return runs;
}
@SuppressWarnings("unused")
public void setRuns(List runs) {
this.runs = runs;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
@Override
public int hashCode() {
return Objects.hash(runs, version);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Output)) {
return false;
}
Output rhs = ((Output) other);
return ((CollectionUtils.isEqualCollection(this.runs, rhs.runs))) && (Objects.equals(this.version, rhs.version));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/PackageManagerType.java
================================================
package com.jfrog.ide.idea.scan.data;
import lombok.Getter;
@Getter
public enum PackageManagerType {
PYPI("pypi"),
NPM("npm"),
YARN("yarn"),
MAVEN("maven"),
GRADLE("gradle"),
GO("go");
private final String name;
PackageManagerType(String name) {
this.name = name;
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/PhysicalLocation.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class PhysicalLocation {
@JsonProperty("artifactLocation")
private ArtifactLocation artifactLocation;
@JsonProperty("region")
private Region region;
public ArtifactLocation getArtifactLocation() {
return artifactLocation;
}
@SuppressWarnings("unused")
public void setArtifactLocation(ArtifactLocation artifactLocation) {
this.artifactLocation = artifactLocation;
}
public Region getRegion() {
return region;
}
@SuppressWarnings("unused")
public void setRegion(Region region) {
this.region = region;
}
@Override
public int hashCode() {
return Objects.hash(region, artifactLocation);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof PhysicalLocation)) {
return false;
}
PhysicalLocation rhs = ((PhysicalLocation) other);
return ((Objects.equals(this.region, rhs.region)) && (Objects.equals(this.artifactLocation, rhs.artifactLocation)));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Region.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
public class Region {
@JsonProperty("endColumn")
private int endColumn;
@JsonProperty("endLine")
private int endLine;
@JsonProperty("startColumn")
private int startColumn;
@JsonProperty("startLine")
private int startLine;
@JsonProperty("snippet")
private Message snippet;
public int getEndColumn() {
return endColumn;
}
@SuppressWarnings("unused")
public void setEndColumn(int endColumn) {
this.endColumn = endColumn;
}
public int getEndLine() {
return endLine;
}
@SuppressWarnings("unused")
public void setEndLine(int endLine) {
this.endLine = endLine;
}
public int getStartColumn() {
return startColumn;
}
@SuppressWarnings("unused")
public void setStartColumn(int startColumn) {
this.startColumn = startColumn;
}
public int getStartLine() {
return startLine;
}
@SuppressWarnings("unused")
public void setStartLine(int startLine) {
this.startLine = startLine;
}
public Message getSnippet() {
return snippet;
}
@SuppressWarnings("unused")
public void setSnippet(Message snippet) {
this.snippet = snippet;
}
@Override
public int hashCode() {
return Objects.hash(endLine, endColumn, startColumn, startLine, snippet);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Region)) {
return false;
}
Region rhs = ((Region) other);
return ((((Objects.equals(this.endLine, rhs.endLine)) && (Objects.equals(this.endColumn, rhs.endColumn))) && (Objects.equals(this.startColumn, rhs.startColumn))) && (Objects.equals(this.startLine, rhs.startLine)) && (Objects.equals(this.snippet, rhs.snippet)));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Rule.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
import java.util.Optional;
public class Rule {
@JsonProperty("id")
private String id;
@JsonProperty("name")
private String name;
@JsonProperty("shortDescription")
private Message shortDescription;
@JsonProperty("fullDescription")
private Message fullDescription;
@JsonProperty("properties")
private RuleProperties properties;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@SuppressWarnings({"unused"})
public Message getShortDescription() {
return shortDescription;
}
@SuppressWarnings({"unused"})
public void setShortDescription(Message shortDescription) {
this.shortDescription = shortDescription;
}
@SuppressWarnings({"unused"})
public Message getFullDescription() {
return fullDescription;
}
@SuppressWarnings({"unused"})
public void setFullDescription(Message fullDescription) {
this.fullDescription = fullDescription;
}
public Optional getRuleProperties() {
return Optional.ofNullable(properties);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Rule)) {
return false;
}
Rule rhs = ((Rule) other);
return Objects.equals(this.id, rhs.id);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/RuleProperties.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@Getter
public class RuleProperties {
@JsonProperty("conclusion")
private String conclusion;
@JsonProperty("applicability")
private String applicability;
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/Run.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class Run {
@JsonProperty("tool")
private Tool tool;
@JsonProperty("invocations")
private List invocations = new ArrayList<>();
@JsonProperty("results")
private List results = new ArrayList<>();
public Tool getTool() {
return tool;
}
@SuppressWarnings({"unused"})
public void setTool(Tool tool) {
this.tool = tool;
}
@SuppressWarnings({"unused"})
public List getInvocations() {
return invocations;
}
@SuppressWarnings({"unused"})
public void setInvocations(List invocations) {
this.invocations = invocations;
}
public List getResults() {
return results;
}
public Rule getRuleFromRunById(String ruleId) {
return this.getTool().getDriver().getRuleById(ruleId);
}
public void setResults(List results) {
this.results = results;
}
@Override
public int hashCode() {
return Objects.hash(results, tool, invocations);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Run)) {
return false;
}
Run rhs = ((Run) other);
return (((CollectionUtils.isEqualCollection(this.results, rhs.results)) && (Objects.equals(this.tool, rhs.tool))) && (CollectionUtils.isEqualCollection(this.invocations, rhs.invocations)));
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/SarifResult.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Getter
public class SarifResult {
@JsonProperty("message")
private Message message;
@JsonProperty("locations")
private List locations = new ArrayList<>();
@JsonProperty("ruleId")
private String ruleId;
@JsonProperty("codeFlows")
private List codeFlows = new ArrayList<>();
@JsonProperty("kind")
private String kind;
@JsonProperty("level")
private String severity;
@JsonProperty("suppressions")
private AnalyzeSuppression[] suppressions;
public void setKind(String kind) {
this.kind = kind;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public void setSuppressions(AnalyzeSuppression[] suppressions) {
this.suppressions = suppressions;
}
public String getKind() {
return StringUtils.defaultString(kind);
}
public String getSeverity() {
return StringUtils.defaultString(severity, "warning");
}
public void setMessage(Message message) {
this.message = message;
}
@SuppressWarnings({"unused"})
public void setLocations(List locations) {
this.locations = locations;
}
@SuppressWarnings({"unused"})
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
@SuppressWarnings({"unused"})
public List getCodeFlows() {
return codeFlows;
}
@SuppressWarnings({"unused"})
public void setCodeFlows(List codeFlows) {
this.codeFlows = codeFlows;
}
@Override
public int hashCode() {
return Objects.hash(locations, message, ruleId, codeFlows);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof SarifResult)) {
return false;
}
SarifResult rhs = ((SarifResult) other);
return ((((CollectionUtils.isEqualCollection(this.locations, rhs.locations)) && (Objects.equals(this.message, rhs.message))) && (Objects.equals(this.ruleId, rhs.ruleId))) && (CollectionUtils.isEqualCollection(this.codeFlows, rhs.codeFlows)));
}
public boolean isNotSuppressed() {
return ArrayUtils.isEmpty(suppressions);
}
}
================================================
FILE: src/main/java/com/jfrog/ide/idea/scan/data/ScanConfig.java
================================================
package com.jfrog.ide.idea.scan.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType;
import lombok.Getter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
@Getter
@ToString
public class ScanConfig {
@JsonProperty("type")
private SourceCodeScanType scanType;
@JsonProperty("language")
private String language;
@JsonProperty("roots")
private List roots;
@JsonProperty("output")
private String output;
@JsonProperty("grep-disable")
private Boolean grepDisable;
@JsonProperty("cve-whitelist")
private List cves;
@JsonProperty("skipped-folders")
private List skippedFolders;
@JsonProperty("excluded-rules")
private List excludedRules;
@SuppressWarnings("unused")
ScanConfig() {
}
ScanConfig(Builder builder) {
this.scanType = builder.scanType;
this.language = builder.language;
this.roots = builder.roots;
this.output = builder.output;
this.cves = builder.cves;
this.grepDisable = builder.grepDisable;
this.skippedFolders = builder.skippedFolders;
this.excludedRules = builder.excludedRules;
}
@SuppressWarnings("unused")
public SourceCodeScanType getScanType() {
return scanType;
}
@SuppressWarnings("unused")
public void setScanType(SourceCodeScanType scanType) {
this.scanType = scanType;
}
public void setLanguage(String language) {
this.language = language;
}
@SuppressWarnings("unused")
public void setRoots(List roots) {
this.roots = roots;
}
@SuppressWarnings("unused")
public void setOutput(String output) {
this.output = output;
}
@SuppressWarnings("unused")
public Boolean getGrepDisable() {
return grepDisable;
}
@SuppressWarnings("unused")
public void setGrepDisable(Boolean grepDisable) {
this.grepDisable = grepDisable;
}
@SuppressWarnings("unused")
public List getCves() {
return cves;
}
@SuppressWarnings("unused")
public void setCves(List cves) {
this.cves = cves;
}
@SuppressWarnings("unused")
public List getSkippedFolders() {
return skippedFolders;
}
@SuppressWarnings("unused")
public void setSkippedFolders(List skippedFolders) {
this.skippedFolders = skippedFolders;
}
public static class Builder {
private SourceCodeScanType scanType;
private String language;
private List roots;
private String output;
private Boolean grepDisable;
private List cves;
private List skippedFolders;
private List excludedRules;
public Builder() {
roots = new ArrayList<>();
cves = new ArrayList<>();
skippedFolders = new ArrayList<>();
excludedRules = new ArrayList<>();
}
@SuppressWarnings("UnusedReturnValue")
public Builder scanType(SourceCodeScanType scanType) {
this.scanType = scanType;
return this;
}
public Builder language(String language) {
this.language = language;
return this;
}
public Builder roots(List roots) {
this.roots = roots;
return this;
}
@SuppressWarnings("UnusedReturnValue")
public Builder output(String output) {
this.output = output;
return this;
}
@SuppressWarnings("unused")
public Builder grepDisable(Boolean grepDisable) {
this.grepDisable = grepDisable;
return this;
}
public Builder cves(List cves) {
this.cves = cves;
return this;
}
@SuppressWarnings("unused")
public Builder skippedFolders(List