|
40 | 40 | import org.jetbrains.annotations.NotNull; |
41 | 41 | import org.jetbrains.annotations.Nullable; |
42 | 42 |
|
| 43 | +import io.github.guacsec.trustifyda.api.PackageRef; |
| 44 | + |
43 | 45 | import java.util.Collection; |
44 | 46 | import java.util.HashSet; |
45 | 47 | import java.util.List; |
46 | 48 | import java.util.Map; |
47 | 49 | import java.util.Objects; |
48 | 50 | import java.util.Optional; |
| 51 | +import java.util.function.Function; |
49 | 52 | import java.util.stream.Collectors; |
| 53 | +import java.util.stream.Stream; |
50 | 54 |
|
51 | 55 | public class DockerfileAnnotator extends ExternalAnnotator<DockerfileAnnotator.Info, Map<BaseImage, DockerfileAnnotator.Result>> { |
52 | 56 |
|
@@ -243,57 +247,55 @@ static boolean hasIssue(AnalysisReport report) { |
243 | 247 |
|
244 | 248 | /** Returns the UBI image recommendation (from source-level dependencies), or null if none. */ |
245 | 249 | static String getRecommendation(AnalysisReport report, ImageRef imageRef) { |
246 | | - return Optional.ofNullable(report.getProviders()) |
247 | | - .flatMap(provider -> provider.values() |
248 | | - .stream() |
249 | | - .filter(Objects::nonNull) |
250 | | - .map(ProviderReport::getSources) |
251 | | - .filter(Objects::nonNull) |
252 | | - .map(Map::values) |
253 | | - .flatMap(Collection::stream) |
254 | | - .filter(Objects::nonNull) |
255 | | - .map(Source::getDependencies) |
256 | | - .filter(Objects::nonNull) |
257 | | - .flatMap(Collection::stream) |
258 | | - .filter(r -> r.getRef() != null) |
259 | | - .filter(r -> { |
260 | | - try { |
261 | | - return imageRef.getPackageURL().equals(r.getRef().purl()); |
262 | | - } catch (MalformedPackageURLException e) { |
263 | | - throw new RuntimeException(e); |
264 | | - } |
265 | | - }) |
266 | | - .map(DependencyReport::getRecommendation) |
267 | | - .filter(Objects::nonNull) |
268 | | - .findAny()) |
269 | | - .map(DockerfileAnnotator::toImageName) |
270 | | - .orElse(null); |
| 250 | + var deps = Optional.ofNullable(report.getProviders()) |
| 251 | + .stream() |
| 252 | + .flatMap(provider -> provider.values().stream()) |
| 253 | + .filter(Objects::nonNull) |
| 254 | + .map(ProviderReport::getSources) |
| 255 | + .filter(Objects::nonNull) |
| 256 | + .map(Map::values) |
| 257 | + .flatMap(Collection::stream) |
| 258 | + .filter(Objects::nonNull) |
| 259 | + .map(Source::getDependencies) |
| 260 | + .filter(Objects::nonNull) |
| 261 | + .flatMap(Collection::stream); |
| 262 | + return findMatchingRecommendation(deps, imageRef, DependencyReport::getRef, DependencyReport::getRecommendation); |
271 | 263 | } |
272 | 264 |
|
273 | 265 | /** Returns the hardened image recommendation (from provider-level recommendations), or null if none. */ |
274 | 266 | static String getHardenedRecommendation(AnalysisReport report, ImageRef imageRef) { |
275 | | - return Optional.ofNullable(report.getProviders()) |
276 | | - .flatMap(provider -> provider.values() |
277 | | - .stream() |
278 | | - .filter(Objects::nonNull) |
279 | | - .map(ProviderReport::getRecommendations) |
280 | | - .filter(Objects::nonNull) |
281 | | - .flatMap(recs -> recs.entrySet().stream()) |
282 | | - .filter(entry -> entry.getValue() != null) |
283 | | - .map(entry -> entry.getValue().getDependencies()) |
284 | | - .filter(Objects::nonNull) |
285 | | - .flatMap(Collection::stream) |
286 | | - .filter(r -> r.getRef() != null) |
287 | | - .filter(r -> { |
288 | | - try { |
289 | | - return imageRef.getPackageURL().equals(r.getRef().purl()); |
290 | | - } catch (MalformedPackageURLException e) { |
291 | | - throw new RuntimeException(e); |
292 | | - } |
293 | | - }) |
294 | | - .map(r -> r.getRecommendation()) |
295 | | - .filter(Objects::nonNull) |
296 | | - .findAny()) |
| 267 | + var deps = Optional.ofNullable(report.getProviders()) |
| 268 | + .stream() |
| 269 | + .flatMap(provider -> provider.values().stream()) |
| 270 | + .filter(Objects::nonNull) |
| 271 | + .map(ProviderReport::getRecommendations) |
| 272 | + .filter(Objects::nonNull) |
| 273 | + .flatMap(recs -> recs.entrySet().stream()) |
| 274 | + .filter(entry -> entry.getValue() != null) |
| 275 | + .map(entry -> entry.getValue().getDependencies()) |
| 276 | + .filter(Objects::nonNull) |
| 277 | + .flatMap(Collection::stream); |
| 278 | + return findMatchingRecommendation(deps, imageRef, |
| 279 | + io.github.guacsec.trustifyda.api.v5.RecommendationReport::getRef, |
| 280 | + io.github.guacsec.trustifyda.api.v5.RecommendationReport::getRecommendation); |
| 281 | + } |
| 282 | + |
| 283 | + private static <T> String findMatchingRecommendation(Stream<T> items, ImageRef imageRef, |
| 284 | + Function<T, PackageRef> refExtractor, |
| 285 | + Function<T, PackageRef> recommendationExtractor) { |
| 286 | + return items |
| 287 | + .filter(r -> refExtractor.apply(r) != null) |
| 288 | + .filter(r -> { |
| 289 | + try { |
| 290 | + return imageRef.getPackageURL().equals(refExtractor.apply(r).purl()); |
| 291 | + } catch (MalformedPackageURLException e) { |
| 292 | + LOG.warn("Skipping recommendation with malformed PURL", e); |
| 293 | + return false; |
| 294 | + } |
| 295 | + }) |
| 296 | + .map(recommendationExtractor) |
| 297 | + .filter(Objects::nonNull) |
| 298 | + .findAny() |
297 | 299 | .map(DockerfileAnnotator::toImageName) |
298 | 300 | .orElse(null); |
299 | 301 | } |
@@ -338,7 +340,7 @@ private static HighlightSeverity getHighlightSeverity(AnalysisReport report, Str |
338 | 340 | String hardenedRecommendation, boolean hasIssue, |
339 | 341 | @NotNull PsiElement context) { |
340 | 342 | // Recommendation-only (no vulnerabilities): use INFORMATION severity (blue) |
341 | | - if (!hasIssue && !hasIssue(report)) { |
| 343 | + if (!hasIssue) { |
342 | 344 | boolean hasAnyRecommendation = recommendation != null || hardenedRecommendation != null; |
343 | 345 | if (hasAnyRecommendation) { |
344 | 346 | return HighlightSeverity.INFORMATION; |
@@ -466,11 +468,9 @@ public void apply(@NotNull PsiFile file, Map<BaseImage, Result> annotationResult |
466 | 468 | builder = builder.enforcedTextAttributes(attrs); |
467 | 469 | } |
468 | 470 | builder = builder.withFix(new ImageReportIntentionAction()); |
469 | | - if (recommendation != null) { |
470 | | - builder.withFix(new UBIIntentionAction()); |
471 | | - } |
| 471 | + builder = builder.withFix(new UBIIntentionAction()); |
472 | 472 | if (hardenedRecommendation != null) { |
473 | | - builder.withFix(new HardenedImageIntentionAction()); |
| 473 | + builder = builder.withFix(new HardenedImageIntentionAction()); |
474 | 474 | } |
475 | 475 | builder.create(); |
476 | 476 | } |
|
0 commit comments