Skip to content

Commit 20b080b

Browse files
Merge pull request #143 from zvigrinberg/feature/api-v4
feat: support multi-source Co-authored-by: Zvi Grinberg <[email protected]> Co-authored-by: Xieshen Zhang <[email protected]>
2 parents 845f7d2 + 2e15765 commit 20b080b

17 files changed

+769
-105
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies {
6363

6464
implementation 'org.kohsuke:github-api:1.314'
6565
implementation 'org.apache.commons:commons-compress:1.21'
66-
implementation 'com.redhat.exhort:exhort-java-api:0.0.3-SNAPSHOT'
66+
implementation 'com.redhat.exhort:exhort-java-api:0.0.4-SNAPSHOT'
6767
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
6868

6969
testImplementation('junit:junit:4.13.1')

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
ideaVersion = 2021.1
2-
projectVersion=0.7.1-SNAPSHOT
2+
projectVersion=0.9.0-SNAPSHOT
33
jetBrainsToken=invalid
44
jetBrainsChannel=stable

src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
import org.jetbrains.annotations.NotNull;
3232
import org.jetbrains.annotations.Nullable;
3333

34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Set;
34+
import java.util.*;
3735
import java.util.stream.Collectors;
3836

3937
public abstract class CAAnnotator extends ExternalAnnotator<CAAnnotator.Info, Map<Dependency, CAAnnotator.Result>> {
@@ -82,7 +80,7 @@ public abstract class CAAnnotator extends ExternalAnnotator<CAAnnotator.Info, Ma
8280
}
8381

8482
LOG.info("Get vulnerability report from cache");
85-
Map<Dependency, DependencyReport> reports = CAService.getReports(path);
83+
Map<Dependency, Map<VulnerabilitySource, DependencyReport>> reports = CAService.getReports(path);
8684
return this.matchDependencies(info.getDependencies(), reports);
8785
}
8886

@@ -94,39 +92,79 @@ public void apply(@NotNull PsiFile file, Map<Dependency, Result> annotationResul
9492
LOG.info("Annotate dependencies");
9593
annotationResult.forEach((key, value) -> {
9694
if (value != null) {
97-
DependencyReport report = value.getReport();
95+
Map<VulnerabilitySource, DependencyReport> reports = value.getReports();
9896
List<PsiElement> elements = value.getElements();
99-
if (report.getIssues() != null && !report.getIssues().isEmpty()
97+
if (reports != null && !reports.isEmpty()
10098
&& elements != null && !elements.isEmpty()) {
101-
if (report.getRef() != null) {
102-
String d = getDependencyString(report.getRef().purl());
103-
int num = report.getIssues().size();
104-
String m = d + ", " + "Known security vulnerabilities: " + num + ", ";
105-
String t = "<html>" +
106-
"<p>" + d + "</p>" +
107-
"<p>Known security vulnerabilities: " + num + "</p>";
108-
109-
if (report.getHighestVulnerability() != null && report.getHighestVulnerability().getSeverity() != null) {
110-
String severity = report.getHighestVulnerability().getSeverity().getValue();
111-
m += "Highest severity: " + severity + ", ";
112-
t += "<p>Highest severity: " + severity + "</p>";
113-
}
99+
Optional<DependencyReport> reportOptional = reports.values().stream()
100+
.filter(Objects::nonNull).findAny();
101+
102+
if (reportOptional.isPresent() && reportOptional.get().getRef() != null) {
103+
String name = getDependencyString(reportOptional.get().getRef().purl());
104+
105+
StringBuilder messageBuilder = new StringBuilder(name);
106+
StringBuilder tooltipBuilder = new StringBuilder("<html>").append("<p>").append(name).append("</p>");
107+
Map<VulnerabilitySource, DependencyReport> quickfixes = new HashMap<>();
108+
109+
reports.forEach((source, report) -> {
110+
if (report.getIssues() != null && !report.getIssues().isEmpty()) {
111+
messageBuilder.append(System.lineSeparator());
112+
tooltipBuilder.append("<p/>");
114113

115-
m += "Dependency Analytics Plugin [Powered by Snyk]";
116-
t += "<p>Dependency Analytics Plugin [Powered by <a href='https://snyk.io/'>Snyk</a>]</p>" +
117-
"</html>";
114+
if (source.getProvider().equals(source.getSource())) {
115+
messageBuilder.append(source.getProvider())
116+
.append(" vulnerability info: ");
117+
tooltipBuilder.append("<p>")
118+
.append(source.getProvider())
119+
.append(" vulnerability info:</p>");
120+
} else {
121+
messageBuilder.append(source.getSource())
122+
.append(" (")
123+
.append(source.getProvider())
124+
.append(") vulnerability info: ");
125+
tooltipBuilder.append("<p>")
126+
.append(source.getSource())
127+
.append(" (")
128+
.append(source.getProvider())
129+
.append(") vulnerability info:</p>");
130+
}
118131

119-
String message = m;
120-
String tooltip = t;
132+
int num = report.getIssues().size();
133+
messageBuilder.append("Known security vulnerabilities: ")
134+
.append(num);
135+
tooltipBuilder.append("<p>Known security vulnerabilities: ")
136+
.append(num)
137+
.append("</p>");
138+
139+
if (report.getHighestVulnerability() != null && report.getHighestVulnerability().getSeverity() != null) {
140+
String severity = report.getHighestVulnerability().getSeverity().getValue();
141+
messageBuilder.append(", Highest severity: ")
142+
.append(severity);
143+
tooltipBuilder.append("<p>Highest severity: ")
144+
.append(severity)
145+
.append("</p>");
146+
}
147+
}
148+
149+
if (CAIntentionAction.isQuickFixAvailable(report)) {
150+
quickfixes.put(source, report);
151+
}
152+
});
121153

122154
elements.forEach(e -> {
123155
if (e != null) {
124-
AnnotationBuilder builder = holder
125-
.newAnnotation(HighlightSeverity.ERROR, message)
126-
.tooltip(tooltip)
127-
.range(e)
128-
.withFix(new CAIntentionAction());
129-
builder.create();
156+
if (!quickfixes.isEmpty() && this.isQuickFixApplicable(e)) {
157+
quickfixes.forEach((source, report) ->{
158+
AnnotationBuilder builder = holder
159+
.newAnnotation(getHighlightSeverity(report), messageBuilder.toString())
160+
.tooltip(tooltipBuilder.toString())
161+
.range(e);
162+
builder.withFix(new SAIntentionAction());
163+
builder.withFix(this.createQuickFix(e, source, report));
164+
builder.create();
165+
}
166+
);
167+
}
130168
}
131169
});
132170
}
@@ -135,12 +173,29 @@ public void apply(@NotNull PsiFile file, Map<Dependency, Result> annotationResul
135173
});
136174
}
137175

176+
@NotNull
177+
private static HighlightSeverity getHighlightSeverity(DependencyReport report) {
178+
if(CAIntentionAction.thereAreNoIssues(report) && CAIntentionAction.thereIsRecommendation(report))
179+
{
180+
return HighlightSeverity.WEAK_WARNING;
181+
}
182+
else
183+
{
184+
return HighlightSeverity.ERROR;
185+
}
186+
187+
}
188+
138189
abstract protected String getInspectionShortName();
139190

140191
abstract protected Map<Dependency, List<PsiElement>> getDependencies(PsiFile file);
141192

193+
abstract protected CAIntentionAction createQuickFix(PsiElement element, VulnerabilitySource source, DependencyReport report);
194+
195+
abstract protected boolean isQuickFixApplicable(PsiElement element);
196+
142197
private Map<Dependency, Result> matchDependencies(Map<Dependency, List<PsiElement>> dependencies,
143-
Map<Dependency, DependencyReport> reports) {
198+
Map<Dependency, Map<VulnerabilitySource, DependencyReport>> reports) {
144199
if (dependencies != null && !dependencies.isEmpty()
145200
&& reports != null && !reports.isEmpty()) {
146201
return dependencies.entrySet()
@@ -217,19 +272,19 @@ public Map<Dependency, List<PsiElement>> getDependencies() {
217272

218273
public static class Result {
219274
List<PsiElement> elements;
220-
DependencyReport report;
275+
Map<VulnerabilitySource, DependencyReport> reports;
221276

222-
public Result(List<PsiElement> elements, DependencyReport report) {
277+
public Result(List<PsiElement> elements, Map<VulnerabilitySource, DependencyReport> reports) {
223278
this.elements = elements;
224-
this.report = report;
279+
this.reports = reports;
225280
}
226281

227282
public List<PsiElement> getElements() {
228283
return elements;
229284
}
230285

231-
public DependencyReport getReport() {
232-
return report;
286+
public Map<VulnerabilitySource, DependencyReport> getReports() {
287+
return reports;
233288
}
234289
}
235290
}

src/main/java/org/jboss/tools/intellij/componentanalysis/CAIntentionAction.java

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,160 @@
1111

1212
package org.jboss.tools.intellij.componentanalysis;
1313

14-
import com.google.gson.JsonObject;
14+
import com.intellij.codeInsight.intention.FileModifier;
1515
import com.intellij.codeInsight.intention.IntentionAction;
1616
import com.intellij.codeInspection.util.IntentionFamilyName;
1717
import com.intellij.codeInspection.util.IntentionName;
1818
import com.intellij.openapi.editor.Editor;
19-
import com.intellij.openapi.fileEditor.FileEditorManager;
2019
import com.intellij.openapi.project.Project;
21-
import com.intellij.openapi.vfs.VirtualFile;
20+
import com.intellij.psi.PsiElement;
2221
import com.intellij.psi.PsiFile;
22+
import com.intellij.psi.util.PsiTreeUtil;
2323
import com.intellij.util.IncorrectOperationException;
24-
import org.jboss.tools.intellij.stackanalysis.SaUtils;
24+
import com.redhat.exhort.api.DependencyReport;
25+
import com.redhat.exhort.api.Issue;
2526
import org.jetbrains.annotations.NotNull;
27+
import org.jetbrains.annotations.Nullable;
2628

27-
import java.io.IOException;
29+
import java.util.Optional;
30+
31+
public abstract class CAIntentionAction implements IntentionAction {
32+
33+
protected @SafeFieldForPreview PsiElement element;
34+
protected @SafeFieldForPreview VulnerabilitySource source;
35+
protected @SafeFieldForPreview DependencyReport report;
36+
37+
protected CAIntentionAction(PsiElement element, VulnerabilitySource source, DependencyReport report) {
38+
this.element = element;
39+
this.source = source;
40+
this.report = report;
41+
}
2842

29-
public class CAIntentionAction implements IntentionAction {
3043
@Override
3144
public @IntentionName @NotNull String getText() {
32-
return "Detailed Vulnerability Report";
45+
return getQuickFixText(this.source, this.report);
3346
}
3447

3548
@Override
3649
public @NotNull @IntentionFamilyName String getFamilyName() {
37-
return "Maven";
50+
return "RHDA";
3851
}
3952

4053
@Override
41-
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
42-
if (file == null) {
43-
return false;
44-
}
45-
return "pom.xml".equals(file.getName())
46-
|| "package.json".equals(file.getName())
47-
|| "go.mod".equals(file.getName())
48-
|| "requirements.txt".equals(file.getName());
54+
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
55+
this.updateVersion(project, editor, file, getRecommendedVersion(this.report));
4956
}
5057

51-
@Override
52-
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
53-
SaUtils saUtils = new SaUtils();
54-
VirtualFile vf = file.getVirtualFile();
55-
56-
if (vf != null) {
57-
JsonObject manifestDetails = saUtils.performSA(vf);
58-
if (manifestDetails != null) {
59-
try {
60-
saUtils.openCustomEditor(FileEditorManager.getInstance(project), manifestDetails);
61-
} catch (IOException ex) {
62-
throw new RuntimeException(ex);
58+
private String getRecommendationsRepo(DependencyReport dependency) {
59+
String repo=null;
60+
if(thereAreNoIssues(dependency))
61+
{
62+
if(thereIsRecommendation(dependency))
63+
repo = dependency.getRecommendation().purl().getQualifiers().get("repository_url");
64+
}
65+
else
66+
{
67+
Optional<Issue> issue = dependency.getIssues().stream().findFirst();
68+
if(issue.isPresent())
69+
{
70+
if(thereIsTcRemediation(dependency)) {
71+
repo = issue.get().getRemediation().getTrustedContent().getRef().version();
6372
}
6473
}
74+
6575
}
76+
return repo;
6677
}
6778

6879
@Override
6980
public boolean startInWriteAction() {
70-
return false;
81+
return true;
82+
}
83+
84+
@Override
85+
public @Nullable FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
86+
return this.createCAIntentionActionInCopy(PsiTreeUtil.findSameElementInCopy(this.element, target));
87+
}
88+
89+
protected abstract void updateVersion(@NotNull Project project, Editor editor, PsiFile file, String version);
90+
91+
protected abstract @Nullable FileModifier createCAIntentionActionInCopy(PsiElement element);
92+
93+
//TODO
94+
private static @NotNull String getRecommendedVersion(DependencyReport dependency) {
95+
String version=null;
96+
if(thereAreNoIssues(dependency))
97+
{
98+
if(thereIsRecommendation(dependency))
99+
version = dependency.getRecommendation().version();
100+
}
101+
else
102+
{
103+
Optional<Issue> issue = dependency.getIssues().stream().findFirst();
104+
if(issue.isPresent())
105+
{
106+
if(thereIsTcRemediation(dependency)) {
107+
version = issue.get().getRemediation().getTrustedContent().getRef().version();
108+
}
109+
}
110+
111+
}
112+
return version;
113+
}
114+
115+
private static boolean thereIsTcRemediation(DependencyReport dependency) {
116+
Optional<Issue> issue = dependency.getIssues().stream().filter(iss -> iss.getRemediation().getTrustedContent() != null).findFirst();
117+
if(issue.isPresent()) {
118+
return issue.get().getRemediation().getTrustedContent() != null;
119+
}
120+
else
121+
{
122+
return false;
123+
}
124+
}
125+
126+
static boolean thereIsRecommendation(DependencyReport dependency) {
127+
return dependency.getRecommendation() != null && !dependency.getRecommendation().version().trim().equals("");
128+
}
129+
130+
static boolean thereAreNoIssues(DependencyReport dependency) {
131+
return dependency.getIssues() == null || dependency.getIssues().size() == 0;
132+
}
133+
134+
//TODO
135+
private static @NotNull String getQuickFixText(VulnerabilitySource source, DependencyReport dependency) {
136+
String text="";
137+
if(thereAreNoIssues(dependency) && thereIsRecommendation(dependency))
138+
{
139+
text = "Quick-Fix suggestion - apply redhat Recommended version";
140+
}
141+
else
142+
{
143+
if(thereIsTcRemediation(dependency))
144+
{
145+
text = "Quick-Fix suggestion - apply redhat remediation version";
146+
}
147+
}
148+
return text;
149+
}
150+
151+
//TODO
152+
static boolean isQuickFixAvailable(DependencyReport dependency) {
153+
boolean result=false;
154+
if(thereAreNoIssues(dependency))
155+
{
156+
if(thereIsRecommendation(dependency))
157+
{
158+
result = true;
159+
}
160+
}
161+
else
162+
{
163+
if(thereIsTcRemediation(dependency))
164+
{
165+
result = true;
166+
}
167+
}
168+
return result;
71169
}
72170
}

0 commit comments

Comments
 (0)