From 386bb709eb2814f6907bad689d344a59e4c39f31 Mon Sep 17 00:00:00 2001 From: Zack_Aayush <60972989+AayushSaini101@users.noreply.github.com> Date: Tue, 23 Apr 2024 21:04:47 +0530 Subject: [PATCH] Adds link to security advisory to security probe and scoring display (#446) Co-authored-by: aayushRedHat Co-authored-by: Adrien Lecharpentier --- .../model/updatecenter/SecurityWarning.java | 10 +- .../KnownSecurityVulnerabilityProbe.java | 31 ++- .../scores/SecurityWarningScoring.java | 57 +++-- .../KnownSecurityVulnerabilityProbeTest.java | 228 +++++++++--------- .../scores/SecurityWarningScoringTest.java | 61 ++++- .../resources/templates/scores/details.html | 13 +- 6 files changed, 232 insertions(+), 168 deletions(-) diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/model/updatecenter/SecurityWarning.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/model/updatecenter/SecurityWarning.java index 6922046d3..d597ae162 100644 --- a/core/src/main/java/io/jenkins/pluginhealth/scoring/model/updatecenter/SecurityWarning.java +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/model/updatecenter/SecurityWarning.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Jenkins Infra + * Copyright (c) 2023-2024 Jenkins Infra * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,14 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package io.jenkins.pluginhealth.scoring.model.updatecenter; import java.util.List; -public record SecurityWarning( - String id, - String name, - List versions -) { -} +public record SecurityWarning(String id, String name, String url, List versions) {} diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbe.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbe.java index a2e69eaf4..fbcf560f5 100644 --- a/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbe.java +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbe.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Jenkins Infra + * Copyright (c) 2023-2024 Jenkins Infra * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package io.jenkins.pluginhealth.scoring.probes; import java.util.List; @@ -46,21 +45,19 @@ public class KnownSecurityVulnerabilityProbe extends Probe { protected ProbeResult doApply(Plugin plugin, ProbeContext context) { final List warnings = context.getUpdateCenter().warnings(); final String issues = warnings.stream() - .filter(w -> w.name().equals(plugin.getName())) - .filter(w -> w.versions().stream().anyMatch(securityWarningVersion -> { - if (securityWarningVersion.lastVersion() != null) { - return plugin.getVersion().isOlderThanOrEqualTo(securityWarningVersion.lastVersion()); - } - final Pattern pattern = Pattern.compile(securityWarningVersion.pattern()); - final Matcher matcher = pattern.matcher(plugin.getVersion().toString()); - return matcher.find(); - })) - .map(SecurityWarning::id) - .collect(Collectors.joining(", ")); + .filter(w -> w.name().equals(plugin.getName())) + .filter(w -> w.versions().stream().anyMatch(securityWarningVersion -> { + if (securityWarningVersion.lastVersion() != null) { + return plugin.getVersion().isOlderThanOrEqualTo(securityWarningVersion.lastVersion()); + } + final Pattern pattern = Pattern.compile(securityWarningVersion.pattern()); + final Matcher matcher = pattern.matcher(plugin.getVersion().toString()); + return matcher.find(); + })) + .map(warning -> warning.id() + "|" + warning.url()) + .collect(Collectors.joining(", ")); - return !issues.isBlank() ? - this.success(issues) : - this.success("No known security vulnerabilities."); + return !issues.isBlank() ? this.success(issues) : this.success("No known security vulnerabilities."); } @Override @@ -75,6 +72,6 @@ public String getDescription() { @Override public long getVersion() { - return 1; + return 2; } } diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoring.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoring.java index b128eb174..31aa576d0 100644 --- a/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoring.java +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoring.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Jenkins Infra + * Copyright (c) 2023-2024 Jenkins Infra * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,14 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package io.jenkins.pluginhealth.scoring.scores; +import java.util.Arrays; import java.util.List; import java.util.Map; import io.jenkins.pluginhealth.scoring.model.Plugin; import io.jenkins.pluginhealth.scoring.model.ProbeResult; +import io.jenkins.pluginhealth.scoring.model.Resolution; import io.jenkins.pluginhealth.scoring.model.ScoringComponentResult; import io.jenkins.pluginhealth.scoring.probes.KnownSecurityVulnerabilityProbe; @@ -41,31 +42,39 @@ public class SecurityWarningScoring extends Scoring { @Override public List getComponents() { - return List.of( - new ScoringComponent() { - @Override - public String getDescription() { - return "The plugin must not have on-going security advisory."; - } + return List.of(new ScoringComponent() { + @Override + public String getDescription() { + return "The plugin must not have on-going security advisory."; + } - @Override - public ScoringComponentResult getScore(Plugin $, Map probeResults) { - final ProbeResult probeResult = probeResults.get(KnownSecurityVulnerabilityProbe.KEY); - if (probeResult == null || ProbeResult.Status.ERROR.equals(probeResult.status())) { - return new ScoringComponentResult(-100, 100, List.of("Cannot determine if plugin has on-going security advisory.")); - } - if ("No known security vulnerabilities.".equals(probeResult.message())) { - return new ScoringComponentResult(100, getWeight(), List.of("Plugin does not seem to have on-going security advisory.")); - } - return new ScoringComponentResult(0, getWeight(), List.of("Plugin seem to have on-going security advisory.", probeResult.message())); + @Override + public ScoringComponentResult getScore(Plugin $, Map probeResults) { + final ProbeResult probeResult = probeResults.get(KnownSecurityVulnerabilityProbe.KEY); + if (probeResult == null || ProbeResult.Status.ERROR.equals(probeResult.status())) { + return new ScoringComponentResult( + -100, 100, List.of("Cannot determine if plugin has on-going security advisory.")); } - - @Override - public int getWeight() { - return 1; + if ("No known security vulnerabilities.".equals(probeResult.message())) { + return new ScoringComponentResult( + 100, getWeight(), List.of("Plugin does not seem to have on-going security advisory.")); } + final List resolutions = Arrays.stream( + probeResult.message().split(",")) + .map(m -> { + final String[] parts = m.trim().split("\\|"); + return new Resolution(parts[0], parts[1]); + }) + .toList(); + return new ScoringComponentResult( + 0, getWeight(), List.of("Plugin seem to have on-going security advisory."), resolutions); + } + + @Override + public int getWeight() { + return 1; } - ); + }); } @Override @@ -85,6 +94,6 @@ public String description() { @Override public int version() { - return 1; + return 2; } } diff --git a/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbeTest.java b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbeTest.java index 36fb6ee5d..dab2bc66f 100644 --- a/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbeTest.java +++ b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/KnownSecurityVulnerabilityProbeTest.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Jenkins Infra + * Copyright (c) 2023-2024 Jenkins Infra * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package io.jenkins.pluginhealth.scoring.probes; import static org.assertj.core.api.Assertions.assertThat; @@ -60,47 +59,45 @@ void shouldBeOKWithNoSecurityWarning() { final ProbeContext ctx = mock(ProbeContext.class); final KnownSecurityVulnerabilityProbe probe = getSpy(); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyList() - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList())); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); } @Test void shouldBeOKWithWarningOnDifferentPlugin() { final String pluginName = "foo-bar"; final VersionNumber pluginVersion = new VersionNumber("1.0"); - final var pluginInUC = new Plugin(pluginName, pluginVersion, "scm", ZonedDateTime.now().minusHours(1), List.of(), 0, "", "main"); + final var pluginInUC = new Plugin( + pluginName, pluginVersion, "scm", ZonedDateTime.now().minusHours(1), List.of(), 0, "", "main"); final var plugin = mock(io.jenkins.pluginhealth.scoring.model.Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); final KnownSecurityVulnerabilityProbe probe = getSpy(); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Map.of(pluginName, pluginInUC), - Collections.emptyMap(), - List.of( - new SecurityWarning("SECURITY-1", "wiz", List.of(new SecurityWarningVersion(null, ".*"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Map.of(pluginName, pluginInUC), + Collections.emptyMap(), + List.of(new SecurityWarning( + "SECURITY-1", + "wiz", + "http://link-to-issue", + List.of(new SecurityWarningVersion(null, ".*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); } @Test @@ -114,24 +111,23 @@ void shouldBeOKWithWarningOnOlderVersion() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning("SECURITY-1", pluginName, List.of( - new SecurityWarningVersion(new VersionNumber("1.0"), "0\\.*") - )) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of(new SecurityWarning( + "SECURITY-1", + pluginName, + "http://link-to-issue", + List.of(new SecurityWarningVersion(new VersionNumber("1.0"), "0\\.*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); } @Test @@ -139,6 +135,7 @@ void shouldNotBeOKWithWarningOnCurrentVersion() { final String pluginName = "foo-bar"; final VersionNumber pluginVersion = new VersionNumber("1.1"); final String warningId = "SECURITY-1"; + final String url = "http://link-to-issue"; final var plugin = mock(io.jenkins.pluginhealth.scoring.model.Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); @@ -146,22 +143,23 @@ void shouldNotBeOKWithWarningOnCurrentVersion() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning(warningId, pluginName, List.of(new SecurityWarningVersion(pluginVersion, "1.0"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of(new SecurityWarning( + warningId, + pluginName, + url, + List.of(new SecurityWarningVersion(pluginVersion, "1.0")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, warningId, probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, warningId + "|" + url, probe.getVersion())); } @Test @@ -169,6 +167,7 @@ void shouldNotBeOKWithWarningWithoutLastVersion() { final String pluginName = "foo-bar"; final VersionNumber pluginVersion = new VersionNumber("1.1"); final String warningId = "SECURITY-1"; + final String url = "http://link-to-issue"; final var plugin = mock(io.jenkins.pluginhealth.scoring.model.Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); @@ -176,22 +175,20 @@ void shouldNotBeOKWithWarningWithoutLastVersion() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning(warningId, pluginName, List.of(new SecurityWarningVersion(null, ".*"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of(new SecurityWarning( + warningId, pluginName, url, List.of(new SecurityWarningVersion(null, ".*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, warningId, probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, warningId + "|" + url, probe.getVersion())); } @Test @@ -200,6 +197,8 @@ void shouldNotBeOKWithMultipleWarningsWithoutLastVersion() { final VersionNumber pluginVersion = new VersionNumber("1.1"); final String warningId1 = "SECURITY-1"; final String warningId2 = "SECURITY-2"; + final String url1 = "http://link-to-issue"; + final String url2 = "https://link-to-other-issue"; final var plugin = mock(io.jenkins.pluginhealth.scoring.model.Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); @@ -207,23 +206,28 @@ void shouldNotBeOKWithMultipleWarningsWithoutLastVersion() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning(warningId1, pluginName, List.of(new SecurityWarningVersion(null, ".*"))), - new SecurityWarning(warningId2, pluginName, List.of(new SecurityWarningVersion(null, ".*"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of( + new SecurityWarning( + warningId1, pluginName, url1, List.of(new SecurityWarningVersion(null, ".*"))), + new SecurityWarning( + warningId2, + pluginName, + url2, + List.of(new SecurityWarningVersion(null, ".*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "%s, %s".formatted(warningId1, warningId2), probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, + "%s, %s".formatted(warningId1 + "|" + url1, warningId2 + "|" + url2), + probe.getVersion())); } @Test @@ -232,6 +236,8 @@ void shouldNotBeOKWithWarningWithoutLastVersionAndOneResolved() { final VersionNumber pluginVersion = new VersionNumber("1.2"); final String warningId1 = "SECURITY-1"; final String warningId2 = "SECURITY-1"; + final String url1 = "http://link-to-issue"; + final String url2 = "https://link-to-other-issue"; final var plugin = mock(io.jenkins.pluginhealth.scoring.model.Plugin.class); final ProbeContext ctx = mock(ProbeContext.class); @@ -239,23 +245,26 @@ void shouldNotBeOKWithWarningWithoutLastVersionAndOneResolved() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning(warningId1, pluginName, List.of(new SecurityWarningVersion(null, ".*"))), - new SecurityWarning(warningId2, pluginName, List.of(new SecurityWarningVersion(new VersionNumber("1.1"), ".*"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of( + new SecurityWarning( + warningId1, pluginName, url1, List.of(new SecurityWarningVersion(null, ".*"))), + new SecurityWarning( + warningId2, + pluginName, + url2, + List.of(new SecurityWarningVersion(new VersionNumber("1.1"), ".*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, warningId1, probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, warningId1 + "|" + url1, probe.getVersion())); } @Test @@ -269,21 +278,22 @@ void shouldBeOKWithWarningWithoutLastVersionButOutOfPattern() { when(plugin.getName()).thenReturn(pluginName); when(plugin.getVersion()).thenReturn(pluginVersion); - when(ctx.getUpdateCenter()).thenReturn( - new UpdateCenter( - Collections.emptyMap(), - Collections.emptyMap(), - List.of( - new SecurityWarning("SECURITY-1", pluginName, List.of(new SecurityWarningVersion(null, "[0-1]\\..*"))) - ) - ) - ); + when(ctx.getUpdateCenter()) + .thenReturn(new UpdateCenter( + Collections.emptyMap(), + Collections.emptyMap(), + List.of(new SecurityWarning( + "SECURITY-1", + pluginName, + "http://link-to-issue", + List.of(new SecurityWarningVersion(null, "[0-1]\\..*")))))); final ProbeResult result = probe.apply(plugin, ctx); assertThat(result) - .isNotNull() - .usingRecursiveComparison() - .comparingOnlyFields("id", "status", "message") - .isEqualTo(ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); + .isNotNull() + .usingRecursiveComparison() + .comparingOnlyFields("id", "status", "message") + .isEqualTo(ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", probe.getVersion())); } } diff --git a/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoringTest.java b/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoringTest.java index 12b7a1efa..69b5b68f6 100644 --- a/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoringTest.java +++ b/core/src/test/java/io/jenkins/pluginhealth/scoring/scores/SecurityWarningScoringTest.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Jenkins Infra + * Copyright (c) 2023-2024 Jenkins Infra * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package io.jenkins.pluginhealth.scoring.scores; import static org.assertj.core.api.Assertions.assertThat; @@ -29,10 +28,12 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Map; import io.jenkins.pluginhealth.scoring.model.Plugin; import io.jenkins.pluginhealth.scoring.model.ProbeResult; +import io.jenkins.pluginhealth.scoring.model.Resolution; import io.jenkins.pluginhealth.scoring.model.ScoreResult; import io.jenkins.pluginhealth.scoring.probes.KnownSecurityVulnerabilityProbe; @@ -45,19 +46,59 @@ SecurityWarningScoring getSpy() { } @Test - void shouldBeAbleToDetectPluginWithSecurityWarning() { + void shouldBeAbleToDetectPluginWithSingleSecurityWarning() { final Plugin plugin = mock(Plugin.class); final SecurityWarningScoring scoring = getSpy(); - when(plugin.getDetails()).thenReturn(Map.of( - KnownSecurityVulnerabilityProbe.KEY, ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "SECURITY-123, link-to-security-advisory", 1) - )); + when(plugin.getDetails()) + .thenReturn(Map.of( + KnownSecurityVulnerabilityProbe.KEY, + ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, + "SECURITY-123|https://link-to-security-advisory", + 1))); + + final ScoreResult result = scoring.apply(plugin); + + assertThat(result.key()).isEqualTo("security"); + assertThat(result.weight()).isEqualTo(1f); + assertThat(result.value()).isEqualTo(0); + result.componentsResults().forEach(ScoringComponentResult -> { + ScoringComponentResult.resolutions().forEach(resolution -> { + assertThat(resolution.link()).isEqualTo("https://link-to-security-advisory"); + assertThat(resolution.text()).isEqualTo("SECURITY-123"); + }); + }); + } + + @Test + void shouldBeAbleToDetectPluginWithMultipleSecurityWarning() { + final Plugin plugin = mock(Plugin.class); + final SecurityWarningScoring scoring = getSpy(); + final List securityInfo = List.of( + "SECURITY-123|https://link-to-security-advisory", "SECURITY-123|https://link-to-security-advisory"); + when(plugin.getDetails()) + .thenReturn(Map.of( + KnownSecurityVulnerabilityProbe.KEY, + ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, String.join(", ", securityInfo), 1))); final ScoreResult result = scoring.apply(plugin); assertThat(result.key()).isEqualTo("security"); assertThat(result.weight()).isEqualTo(1f); assertThat(result.value()).isEqualTo(0); + final List resolutionList = securityInfo.stream() + .map(securityMessage -> { + String[] securityDetails = securityMessage.split("\\|"); + return new Resolution(securityDetails[0].trim(), securityDetails[1]); + }) + .toList(); + + result.componentsResults().forEach(componentResult -> { + componentResult.resolutions().forEach(resolution -> { + assert resolutionList.contains(resolution); + }); + }); } @Test @@ -79,9 +120,11 @@ void shouldBeAbleToDetectPluginWithNoSecurityWarning() { final Plugin plugin = mock(Plugin.class); final SecurityWarningScoring scoring = getSpy(); - when(plugin.getDetails()).thenReturn(Map.of( - KnownSecurityVulnerabilityProbe.KEY, ProbeResult.success(KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", 1) - )); + when(plugin.getDetails()) + .thenReturn(Map.of( + KnownSecurityVulnerabilityProbe.KEY, + ProbeResult.success( + KnownSecurityVulnerabilityProbe.KEY, "No known security vulnerabilities.", 2))); final ScoreResult result = scoring.apply(plugin); diff --git a/war/src/main/resources/templates/scores/details.html b/war/src/main/resources/templates/scores/details.html index c05e01a87..6660fed66 100644 --- a/war/src/main/resources/templates/scores/details.html +++ b/war/src/main/resources/templates/scores/details.html @@ -110,7 +110,18 @@

Probes results

- + + + + + + + + + + +