Skip to content

Commit 62cf249

Browse files
committed
feat: Return new matching vulnerability resolution definitions
If there have been new vulnerability resolution definitions made on the server since the run, add the matching definitions to the vulnerabilities response, so that it can be reflected on the UI that with a new run, the particular vulnerability would be resolved. Relates to #1009. Signed-off-by: Johanna Lamppu <[email protected]>
1 parent dbcd7d8 commit 62cf249

File tree

6 files changed

+151
-1
lines changed

6 files changed

+151
-1
lines changed

api/v1/mapping/src/commonMain/kotlin/ApiMappings.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ fun VulnerabilityWithDetails.mapToApi() =
612612
identifier = identifier.mapToApi(),
613613
rating = rating.mapToApi(),
614614
resolutions = resolutions.map { it.mapToApi() },
615+
newMatchingResolutionDefinitions = newMatchingResolutionDefinitions.map { it.mapToApi() },
615616
advisor = advisor.mapToApi(),
616617
purl = purl
617618
)

api/v1/model/src/commonMain/kotlin/VulnerabilityWithDetails.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ package org.eclipse.apoapsis.ortserver.api.v1.model
2121

2222
import kotlinx.serialization.Serializable
2323

24+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionDefinition
25+
2426
/**
2527
* A data class to gather information and related data about a [Vulnerability].
2628
*/
@@ -34,6 +36,9 @@ data class VulnerabilityWithDetails(
3436

3537
val resolutions: List<VulnerabilityResolution> = emptyList(),
3638

39+
/** The resolution definitions that match this vulnerability but were not yet available during the run. */
40+
val newMatchingResolutionDefinitions: List<VulnerabilityResolutionDefinition> = emptyList(),
41+
3742
/** Details of the used advisor. */
3843
val advisor: AdvisorDetails,
3944

core/src/test/kotlin/api/RunsRouteIntegrationTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.kotest.assertions.ktor.client.haveHeader
2626
import io.kotest.assertions.ktor.client.shouldHaveStatus
2727
import io.kotest.engine.spec.tempdir
2828
import io.kotest.inspectors.forAll
29+
import io.kotest.matchers.collections.shouldBeEmpty
2930
import io.kotest.matchers.collections.shouldBeSingleton
3031
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
3132
import io.kotest.matchers.collections.shouldHaveSize
@@ -875,6 +876,49 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
875876
}
876877
}
877878

879+
"return new matching definitions that have been created after the run" {
880+
integrationTestApplication {
881+
val run = dbExtension.fixtures.createOrtRun(repositoryId)
882+
val advisorJobId = dbExtension.fixtures.createAdvisorJob(run.id).id
883+
dbExtension.fixtures.createAdvisorRun(advisorJobId, generateAdvisorResult())
884+
885+
val definitionId = dbExtension.fixtures.createVulnerabilityResolutionDefinition(
886+
RepositoryId(repositoryId),
887+
run.id,
888+
listOf("CVE-2021-1234"),
889+
VulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY
890+
)
891+
892+
val response = superuserClient.get("/api/v1/runs/${run.id}/vulnerabilities")
893+
894+
response shouldHaveStatus HttpStatusCode.OK
895+
val vulnerabilities = response.body<PagedResponse<VulnerabilityWithDetails>>()
896+
897+
vulnerabilities.data shouldHaveSize 2
898+
899+
with(vulnerabilities.data.first()) {
900+
vulnerability.externalId shouldBe "CVE-2018-14721"
901+
resolutions.shouldBeEmpty()
902+
newMatchingResolutionDefinitions.shouldBeEmpty()
903+
}
904+
905+
with(vulnerabilities.data.last()) {
906+
vulnerability.externalId shouldBe "CVE-2021-1234"
907+
resolutions.shouldBeEmpty()
908+
newMatchingResolutionDefinitions shouldHaveSize 1
909+
910+
newMatchingResolutionDefinitions.first() shouldBe VulnerabilityResolutionDefinition(
911+
id = definitionId,
912+
idMatchers = listOf("CVE-2021-1234"),
913+
reason = ApiVulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY,
914+
comment = "Comment.",
915+
archived = false,
916+
changes = emptyList()
917+
)
918+
}
919+
}
920+
}
921+
878922
"require RepositoryPermission.READ_ORT_RUNS" {
879923
val run = ortRunRepository.create(
880924
repositoryId,

model/src/commonMain/kotlin/VulnerabilityWithDetails.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ data class VulnerabilityWithDetails(
3535

3636
val resolutions: List<AppliedVulnerabilityResolution> = emptyList(),
3737

38+
/** The resolution definitions that match this vulnerability but were not yet available during the run. */
39+
val newMatchingResolutionDefinitions: List<VulnerabilityResolutionDefinition> = emptyList(),
40+
3841
/** Details about the used advisor. */
3942
val advisor: AdvisorDetails,
4043

services/ort-run/src/main/kotlin/VulnerabilityService.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import org.jetbrains.exposed.sql.stringLiteral
8686
import org.jetbrains.exposed.sql.wrapAsExpression
8787

8888
import org.ossreviewtoolkit.model.config.VulnerabilityResolution
89+
import org.ossreviewtoolkit.model.config.VulnerabilityResolutionReason
8990
import org.ossreviewtoolkit.model.utils.toPurl
9091

9192
/**
@@ -184,7 +185,27 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
184185
}
185186
}
186187

188+
val newVulnerabilityResolutionDefinitions = db.dbQuery {
189+
VulnerabilityResolutionDefinitionsTable
190+
.select(VulnerabilityResolutionDefinitionsTable.columns)
191+
.where {
192+
(VulnerabilityResolutionDefinitionsTable.repositoryId eq ortRun.repositoryId) and
193+
(VulnerabilityResolutionDefinitionsTable.contextRunId greaterEq ortRun.id) and
194+
(VulnerabilityResolutionDefinitionsTable.archived eq false)
195+
}
196+
.map { row -> row.toVulnerabilityResolutionDefinition() }
197+
}
198+
187199
val vulnerabilitiesWithResolutions = limitedVulnerabilities.map { vulnerabilityWithDetails ->
200+
val matchingNewDefinitions = newVulnerabilityResolutionDefinitions.filter { definition ->
201+
definition.idMatchers.any { idMatcher ->
202+
VulnerabilityResolution(
203+
idMatcher,
204+
VulnerabilityResolutionReason.valueOf(definition.reason.name),
205+
definition.comment
206+
).matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
207+
}
208+
}
188209
val matchingResolutions = resolutions.filter {
189210
it.matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
190211
}
@@ -201,7 +222,8 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
201222
resolution,
202223
definition
203224
)
204-
}
225+
},
226+
newMatchingResolutionDefinitions = matchingNewDefinitions
205227
)
206228
}
207229

services/ort-run/src/test/kotlin/VulnerabilityServiceTest.kt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,81 @@ class VulnerabilityServiceTest : WordSpec() {
689689
}
690690
}
691691
}
692+
693+
"return new matching definitions that have been created after the run" {
694+
val run = fixtures.createOrtRun()
695+
val advisorJobId = fixtures.createAdvisorJob(run.id).id
696+
697+
val vulnerabilities = createVulnerabilities(
698+
Triple(
699+
Identifier("Maven", "org.apache.logging.log4j", "log4j-core", "2.14.0"),
700+
listOf("CVE-2021-45046"),
701+
listOf(10.0)
702+
),
703+
Triple(
704+
Identifier("Maven", "com.fasterxml.jackson.core", "jackson-databind", "2.9.6"),
705+
listOf("CVE-2018-14721"),
706+
listOf(4.2)
707+
),
708+
Triple(
709+
Identifier("Maven", "junit", "junit", "1.0"),
710+
listOf("CVE-2024-24521"),
711+
listOf(5.0)
712+
)
713+
)
714+
715+
fixtures.createAdvisorRun(advisorJobId, createAdvisorResults(vulnerabilities))
716+
717+
val definitionId = fixtures.createVulnerabilityResolutionDefinition(
718+
contextRunId = run.id,
719+
idMatchers = listOf("CVE-2021-45046", "CVE-2024-24521"),
720+
reason = VulnerabilityResolutionReason.WORKAROUND_FOR_VULNERABILITY
721+
)
722+
723+
val results = service.listForOrtRunId(run.id)
724+
725+
results.totalCount shouldBe 3
726+
727+
with(results.data[0]) {
728+
vulnerability.externalId shouldBe "CVE-2018-14721"
729+
resolutions.shouldBeEmpty()
730+
newMatchingResolutionDefinitions.shouldBeEmpty()
731+
}
732+
733+
with(results.data[1]) {
734+
vulnerability.externalId shouldBe "CVE-2021-45046"
735+
resolutions.shouldBeEmpty()
736+
newMatchingResolutionDefinitions shouldHaveSize 1
737+
738+
newMatchingResolutionDefinitions.first() shouldBe VulnerabilityResolutionDefinition(
739+
id = definitionId,
740+
hierarchyId = RepositoryId(run.repositoryId),
741+
contextRunId = run.id,
742+
idMatchers = listOf("CVE-2021-45046", "CVE-2024-24521"),
743+
reason = VulnerabilityResolutionReason.WORKAROUND_FOR_VULNERABILITY,
744+
comment = "Comment.",
745+
archived = false,
746+
changes = emptyList()
747+
)
748+
}
749+
750+
with(results.data[2]) {
751+
vulnerability.externalId shouldBe "CVE-2024-24521"
752+
resolutions.shouldBeEmpty()
753+
newMatchingResolutionDefinitions shouldHaveSize 1
754+
755+
newMatchingResolutionDefinitions.first() shouldBe VulnerabilityResolutionDefinition(
756+
id = definitionId,
757+
hierarchyId = RepositoryId(run.repositoryId),
758+
contextRunId = run.id,
759+
idMatchers = listOf("CVE-2021-45046", "CVE-2024-24521"),
760+
reason = VulnerabilityResolutionReason.WORKAROUND_FOR_VULNERABILITY,
761+
comment = "Comment.",
762+
archived = false,
763+
changes = emptyList()
764+
)
765+
}
766+
}
692767
}
693768

694769
"countForOrtRunId" should {

0 commit comments

Comments
 (0)