Skip to content

Commit 4620018

Browse files
committed
feat: Return new matching vulnerability resolution definitions
Signed-off-by: Johanna Lamppu <[email protected]>
1 parent 5f2aafd commit 4620018

File tree

6 files changed

+150
-1
lines changed

6 files changed

+150
-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
@@ -877,6 +878,49 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
877878
}
878879
}
879880

881+
"return new matching definitions that have been created after the run" {
882+
integrationTestApplication {
883+
val run = dbExtension.fixtures.createOrtRun(repositoryId)
884+
val advisorJobId = dbExtension.fixtures.createAdvisorJob(run.id).id
885+
dbExtension.fixtures.createAdvisorRun(advisorJobId, generateAdvisorResult())
886+
887+
val definitionId = dbExtension.fixtures.createVulnerabilityResolutionDefinition(
888+
RepositoryId(repositoryId),
889+
run.id,
890+
listOf("CVE-2021-1234"),
891+
VulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY
892+
)
893+
894+
val response = superuserClient.get("/api/v1/runs/${run.id}/vulnerabilities")
895+
896+
response shouldHaveStatus HttpStatusCode.OK
897+
val vulnerabilities = response.body<PagedResponse<VulnerabilityWithDetails>>()
898+
899+
vulnerabilities.data shouldHaveSize 2
900+
901+
with(vulnerabilities.data.first()) {
902+
vulnerability.externalId shouldBe "CVE-2018-14721"
903+
resolutions.shouldBeEmpty()
904+
newMatchingResolutionDefinitions.shouldBeEmpty()
905+
}
906+
907+
with(vulnerabilities.data.last()) {
908+
vulnerability.externalId shouldBe "CVE-2021-1234"
909+
resolutions.shouldBeEmpty()
910+
newMatchingResolutionDefinitions shouldHaveSize 1
911+
912+
newMatchingResolutionDefinitions.first() shouldBe VulnerabilityResolutionDefinition(
913+
id = definitionId,
914+
idMatchers = listOf("CVE-2021-1234"),
915+
reason = ApiVulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY,
916+
comment = "Comment.",
917+
archived = false,
918+
changes = emptyList()
919+
)
920+
}
921+
}
922+
}
923+
880924
"require RepositoryPermission.READ_ORT_RUNS" {
881925
val run = ortRunRepository.create(
882926
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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.Res
4444
import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.ResolvedPackageCurationProvidersTable
4545
import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.ResolvedPackageCurationsTable
4646
import org.eclipse.apoapsis.ortserver.dao.tables.VulnerabilityResolutionDefinitionsTable
47+
import org.eclipse.apoapsis.ortserver.dao.tables.VulnerabilityResolutionDefinitionsTable.toVulnerabilityResolutionDefinition
4748
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifierDao
4849
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifiersTable
4950
import org.eclipse.apoapsis.ortserver.dao.utils.applyILike
@@ -85,6 +86,7 @@ import org.jetbrains.exposed.sql.stringLiteral
8586
import org.jetbrains.exposed.sql.wrapAsExpression
8687

8788
import org.ossreviewtoolkit.model.config.VulnerabilityResolution
89+
import org.ossreviewtoolkit.model.config.VulnerabilityResolutionReason
8890
import org.ossreviewtoolkit.model.utils.toPurl
8991

9092
/**
@@ -197,7 +199,27 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
197199
}
198200
}
199201

202+
val newVulnerabilityResolutionDefinitions = db.dbQuery {
203+
VulnerabilityResolutionDefinitionsTable
204+
.select(VulnerabilityResolutionDefinitionsTable.columns)
205+
.where {
206+
(VulnerabilityResolutionDefinitionsTable.repositoryId eq ortRun.repositoryId) and
207+
(VulnerabilityResolutionDefinitionsTable.contextRunId greaterEq ortRun.id) and
208+
(VulnerabilityResolutionDefinitionsTable.archived eq false)
209+
}
210+
.map { row -> row.toVulnerabilityResolutionDefinition() }
211+
}
212+
200213
val vulnerabilitiesWithResolutions = limitedVulnerabilities.map { vulnerabilityWithDetails ->
214+
val matchingNewDefinitions = newVulnerabilityResolutionDefinitions.filter { definition ->
215+
definition.idMatchers.any { idMatcher ->
216+
VulnerabilityResolution(
217+
idMatcher,
218+
VulnerabilityResolutionReason.valueOf(definition.reason.name),
219+
definition.comment
220+
).matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
221+
}
222+
}
201223
val matchingResolutions = resolutions.filter {
202224
it.matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
203225
}
@@ -218,7 +240,8 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
218240
}
219241
}
220242
)
221-
}
243+
},
244+
newMatchingResolutionDefinitions = matchingNewDefinitions
222245
)
223246
}
224247

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

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

693766
"countForOrtRunId" should {

0 commit comments

Comments
 (0)